Solucionado (ver solução)
Solucionado
(ver solução)
14
respostas

JSON com muitos registros

Caros amigos,

  • Tenho uma rota realiza um SELECT em um banco de dados e cria um arquivo JSON em uma pasta (arquivo este que em média gera algo em torno de 100k a 150k de registros).

  • Em seguida é disparada outra rota que envia os dados deste arquivo para um endpoint utilizando o component HTTP4 e realizando um POST (o endpoint já esta preparado para realizar batch processing).

Notei algo curioso, que em determinados casos o Camel envia os dados, o Web Services esta processando corretamente, mas após alguns segundos o Camel me retorna uma DeadLetterChannel, com algumas exceptions:

  • "org.apache.http.wire" "http-outgoing-5" "end of stream"

  • "org.apache.http.nohttpresponseexception" "failed to respond"

  • "h.i.c.DefaultManagedHttpClientConnection" "http-outgoing-5" "Close connection"

    - "h.i.c.DefaultManagedHttpClientConnection" "http-outgoing-5" "Shutdown connection"

    - "o.a.http.impl.execchain.MainClientExec" "Connection discarded" "Connection released"

Porém o processo continua normalmente, ele tenta reenviar novamente, mas da o erro de novo, mas no web services na AWS esta processando normalmente e verifiquei que ele retorna os status code normal.

Pesquisando na documentação do Camel e encontrei httpClient. Fiz a configuração aumentando o timeout, mas nada adiantou, parece que ele ignora essas configurações (httpClient.socketTimeout=300000).

Estou pensando na possibilidade de dividir o arquivo JSON, com alguma regra como a cada 1000 registros gerar 1 arquivo JSON. Assim vou ter vários arquivos menores que vão ser processados mais rápido e com isso obter a resposta do servidor, evitando esse erro.

Sinceramente não sei se é a melhor solução para este caso, gostaria da opinião de vocês.

Caso essa seja a melhor alternativa, utilizando o EIP patterns - Splitter, tem algum options do split() que realiza essa divisão a cada 1000 registros?

Minha rota atual que busca os registros no banco de dados e cria um arquivo json é a seguinte:

from("timer://query?fixedRate=true&delay=5s&period=10800s")
    .routeId("route-produtos")
    .setBody(constant(new ProdutoQuery().getProdutoSelect()))
.to("jdbc:myDataSource")
    .marshal()
        .json(JsonLibrary.Gson)
        .setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
        .setHeader(Exchange.HTTP_CHARACTER_ENCODING, constant("UTF-8"))
        .setHeader("CamelFileName", constant("produtos.json"))
.to("file:produtos");
14 respostas

Ninguém tem nem uma ídeia desse caso?

Nem o Nico?

Ainda estou aguardando a reposta se é a melhor estratégia a ser adotada nesta situação.

Porém, pesquisando a documentação, consegui realizar o split:

from("timer://query?fixedRate=true&delay=5s&period=10800s")
    .routeId("route-produtos")
    .setBody(constant(new ProdutoQuery().getProdutoSelect()))
.to("jdbc:myDataSource?outputType=StreamList")
    .split(body()).streaming()
    .marshal()
        .json(JsonLibrary.Gson)
        .setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
        .setHeader("CamelFileName", simple("${id}.json"))
.to("file:produtos");

Dessa maneira cada registro cria um arquivo JSON, mas meu caso, quero agrupar esses registros, novamente na documentação é sugerido desa maneira:

.split().tokenize("\n", 1000).streaming()

Porém dessa maneira a saída dos registro mostra somente 1 arquivo com o seguinte conteúdo:

"org.apache.camel.component.jdbc.ResultSetIterator@7be7d81f"

Se eu retiro o option ?outputType=StreamList da URI do component JDBC, então é lançado a seguinte exception:

No converter found capable of converting from type [java.util.ArrayList<?>] to type [java.lang.String]

Sinceramente? Qual o motivo exatamente de retornar mais de 150k de registros em um json? Pensando em UX um usuário recebendo essa quantidade de registros é desnecessário, a não ser que para o seu contexto seja implicitamente necessário todos esses dados, divida-os.

Sinceramente ainda acho 1k de registros muita coisa, mas é o que digo, eu não sei qual o contexto da sua aplicação.

São várias filiais no Brasil, periodicamente (x horas por dia) preciso das informações dessas filiais.

Claro que depois da primeira integração, às próximas vou precisar apenas das informações adicionais, por isso vou realizar uma auditoria no banco de dados. Então ao disponibilizar o instalador para cada filial, todo esse processo vai ser executado, apenas o usuário avançando (várias aplicações rodando, outras sendo adicionadas como um serviço do S.O. e etc).

Porém a primeira será necessário que eu obtenha todas as informações. Por isso acredito que fragmentar este arquivo em lotes de 1k de registros cada pode resolver meu problema.

Mas você vai exibir de uma vez só todas estas? Por que não pagina? Ou será um sistema que vai consumir este json?

Tenho uma API Rest que vai consumir este JSON, a responsabilidade dela é única, somente consumir este JSON. Essa API é consumida por mais 4 outras APIs que precisam dessas informações das filiais.

Algumas precisam das informações X, outras precisam das informações Y e por ai vai.

Ao todo são 9 API Rest, cada uma com uma responsabilidade única. Onde vários outros sistemas consomem as informações dessas APIs.

Obs: Alias, me desculpa Márcio. Eu não vi que você já tinha respondido, enquanto eu estava editando o post acima.

Ahhh sim, entendi, achei que iria tudo para o usuário, desculpas.

Trabalho atualmente em uma empresa de telecom, temos em nossa menor tabela de 1997, 6 milhões de registros, tanto que não podemos simplesmente gettar todos os dados dela, até por quê daria timeout, porém a solução que nós encontramos para isso foi exatamente essa que você falou, porém temos uma tabela que chamamos de "pacotes" que armazena qual a informação base de cada transação, sim essa sua ideia de batch é se não a melhor, uma das melhores soluções para esse cenário, a longo prazo, sentimos vez ou outra pequenos gargalos no sistema, mas isso é proveniente as multiplos threads que invocamos quando lançamos o sistema. Espero ter ajudado, att

O gargalo mesmo vou ter na primeira integração, as próximas acredito que será de boa. Por isso estou pensando nessa possibilidade, de fragmentar. Realizando um teste manual, gerei 10 arquivos de 1k cada um, apenas p/ realizar os testes e ocorreu tudo bem, não aconteceu o problema novamente.

O foda agora esta sendo utilizar o Split, eu consigo fazer ele dividir, porém cada registro do banco de dados, o Camel gera um arquivo JSON. Não estou conseguindo agrupar determinados registros, como mostra na documentação:

.split().tokenize("\n", 1000).streaming()

Estou pesquisando no fórum do próprio Apache Camel e nada.

Boa tarde amigos

Ainda na batalha com este caso.....hehehehe

Vendo a documentação encontrei o component SQL Component:

http://camel.apache.org/sql-component.html

Nas opções que ele oferece, tem a seguinte:

  • maxMessagesPerPoll: Camel 2.11: SQL consumer only: An integer value to define the maximum number of messages to gather per poll. By default, no maximum is set.

Mas também com este componente ele ignora esta opção:

from("timer://query?fixedRate=true&delay=5s&period=10800s")
    .routeId("route-produtos")
to("sql:" + new ProdutoQuery().getProdutoSelect() + "?maxMessagesPerPoll=1000&dataSource=myDataSource")
    .marshal()
        .json(JsonLibrary.Gson)
        .setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
        .setHeader("CamelFileName", simple("${id}.json"))
.to("file:produtos");

Oi André,

estou chegando meio tarde na batalha de vcs nesse case interessante.

Permite me pergunta: vc está processando aquele json no webservice sincronamente? ou vc está recebendo o json no web service, grava em alguma lugar para depois processar?

abs

Em um POST no Web Service, envio e ele executa o batch. Cheguei a pensar em que o problema poderia ser esse, então realizei alguns testes utilizando o POSTMAN, e rodou normalmente.

Hoje eu pensei em testar o componente SEDA do Apache Camel e da o mesmo erro:

  • "org.apache.http.nohttpresponseexception" "failed to respond"

Então utilizei a opção waitForTaskToComplete=Never do componente SEDA e mesmo assim, deu o erro.

Então voltei a tentar novamente fragmentar o resultSet que vem do banco de dados, acredito que dessa maneira eu consiga resolver. Pois vou estar enviando lotes menores, onde vou obter o status code mais rápido.

Mas não estou conseguindo definir no split() para que a cada x registros crie um arquivo JSON.

Ainda nesta saga galera e estou correndo contra o tempo pois "Winter is coming..." kkkkkkk

Apenas para realizar testes eu deixei o split() criando 1 arquivo JSON para cada registro do banco de dados, claro que sei que isso não é ideal.

Mas para minha surpresa o processo funcionou normalmente e não lançou a exception "failed to respond".

Então cheguei a pensar na possibilidade de no lugar de fragmentar o resultset do banco de dados, optar por criar uma auditoria dos dados que foram enviados.

É possivel criar uma auditoria com o Apache Camel?

solução!

Boa noite amigos

Após algum tempo, consegui uma solução e parece que esta funcionando corretamente. Claro que ainda vai continuar em testes por mais algum tempo.

Eu fiz o próprio Apache Camel criar uma auditoria no banco de dados das filiais. Automaticamente quando ele se inicia junto com o sistema operacional, rodando em segundo plano, as primeiras rotas são para verificar o banco de dados se tem as triggers de auditoria, caso não tiver ele próprio ja cria.

A primeira vez realmente vai subir muitos registros, e o Camel vai identificar que é a primeira vez e vai utilizar uma rota com o componente SEDA.

Já as próximas vezes (a cada x horas), ele assume outras rotas, já nessas eu utilizo o componente DIRECT.

Criei um ambiente simulando várias filiais, para medir carga, erros e o que aparecer, esta rodando desde de quarta-feira 03/05/2017, sem desligar e sem parar.

Por enquanto o erro que estava aparecendo anteriormente, não voltou a ocorrer.

Achei alguns outros detalhes, fiz as devidas alterações. Porém, nem parei a versão que deixei rodando desde de semana passada.

Ai sexta-fera vou gerar essa nova versão e colocar p/ rodar em testes.

Abraços

Oi André,

Muito obrigado por compartilhar!

abs, Nico