Sem categoria

Por que você deve se preocupar com o acesso reativo ao banco de dados

Desde que anunciamos que nosso novo Java SDK é totalmente baseado em componentes reativos e assíncronos, nossos usuários continuam perguntando quais são os benefícios em relação aos padrões de acesso síncronos testados e verdadeiros. Embora muitas vezes o "desempenho" seja um dos fatores determinantes, há muito mais a ser considerado. Além do tratamento de erros (que será abordado em um post separado), os seguintes recursos se destacam:

  1. Criação de fluxos de dados com fluxos de dados
  2. Melhor utilização de recursos sem dores de cabeça

O objetivo desta postagem do blog é converter padrões comuns de acesso a dados de síncronos para reativos e, como resultado, dar a você uma ideia do que é possível com essa nova abordagem. Não se preocupe se você nunca escreveu um código "reativo" - você aprenderá algumas noções básicas ao longo do caminho. Além disso, lembre-se de que ainda oferecemos suporte a uma API síncrona. A API síncrona é apenas um invólucro fino em torno da API reativa, permitindo que você migre gradualmente o seu aplicativo para uma abordagem mais avançada, sem precisar mergulhar de cabeça imediatamente.

Ah, e a propósito, temos um Webinar em breve no dia 22 de janeiro, quando Simon Basle, um de nossos engenheiros de SDK, apresentará o novo Java SDK e também mostrará algumas das partes reativas. Junte-se a ele e faça perguntas!

O padrão de pesquisa

Um padrão muito comum ao acessar um banco de dados ou um armazenamento de chave/valor é o padrão de pesquisa. Você carrega um documento e, com base no conteúdo do documento, carrega mais documentos. Vamos considerar o exemplo clássico de "postagem de blog e comentários". Imagine que você tenha o seguinte post de blog armazenado no Couchbase:

Ele contém uma lista de IDs de comentários que podem ser usados para carregar os detalhes completos de cada conteúdo, conforme necessário. Um comentário pode ter a seguinte aparência:

Agora, digamos que queremos carregar os dois primeiros comentários publicados quando a postagem do blog for carregada, para que sejam exibidos abaixo da postagem real. Esta é uma maneira de conseguir isso com acesso síncrono e o novo SDK:

Deixando de lado o tratamento de erros, essa abordagem tem algumas desvantagens:

  • Precisamos esperar pelo menos três vezes para que a rede responda, mantendo nosso thread ocioso enquanto ele poderia realizar um trabalho valioso.
  • É muito difícil aplicar um tempo limite global a todo o processo, pois cada operação tem um tempo limite individual.

 

Vamos converter isso em uma abordagem reativa, também usando lambdas do Java 8 (o Java 6/7 também é compatível, basta usar classes anônimas em vez de retornos de chamada):

Com base na nomenclatura dos operadores, fica bem claro o que está acontecendo. O código carrega a publicação do blog. Quando chega, ele extrai a lista de IDs de comentários e a passa para o próximo método, que de fato carrega os documentos de comentários. O método de filtro deixa passar apenas os comentários que foram publicados. Depois disso, pegamos apenas os dois primeiros comentários e cancelamos a assinatura para evitar mais trabalho (como carregar mais documentos, quando já temos o suficiente). Por fim, agregamos todos os comentários encontrados em uma lista, aplicamos um tempo limite global e, em seguida, bloqueamos.

Esse código "se expande" para carregar todos os comentários necessários em paralelo, atingindo mais nós no cluster ao mesmo tempo e, consequentemente, retornando os resultados desejados mais rapidamente. Além disso, o código aplica um tempo limite global a toda a "operação", o que é muito difícil de acertar com o código síncrono. Por fim, todo o código pode ser composto. Você pode encapsular a lógica em um método sem aplicar um tempo limite ou especificar a quantidade de comentários a serem feitos. A camada superior pode então encadear os operadores necessários para executar o trabalho conforme desejado.

Além disso, você pode ver que é muito fácil passar do conceito mais avançado (assíncrono, reativo) para o menos avançado (sincronização). O contrário não é possível.

Observe que ainda bloqueamos bem no final, e não há problema nisso. A maioria dos aplicativos bloqueará em algum momento (talvez até mesmo logo antes de retornar uma resposta ao usuário). Embora a adoção de uma pilha completa "reativa" proporcione o melhor desempenho e a melhor utilização de recursos, você ainda pode fazer com que partes maiores do seu código sejam executadas de forma assíncrona e se beneficiar enormemente.

Execução de consultas

Naturalmente, todo banco de dados permite que você consulte seus documentos armazenados com base em alguns critérios. Na maioria das vezes, mais de um registro será retornado, às vezes até milhares em um único lote. Depois que os dados são retornados, muitas vezes você também precisa modificar, combinar ou filtrar o conteúdo com base nos requisitos do seu aplicativo.

Vamos considerar o seguinte exemplo: você é um provedor de telecomunicações e está armazenando registros de usuários em seu bucket. No final do mês, você quer ter certeza de que todos os seus novos clientes que se inscreveram neste mês realmente receberam o novo telefone. Sua empresa de transporte de encomendas contratada fornece um serviço da Web em que você pode consultar o status da entrega.

Aqui está a implementação simulada do provedor:

Dado um UUID que representa o ID do pacote, retornamos aleatoriamente se um pacote já foi entregue ou não.

Veja como pode ser um registro de usuário:

Usamos o N1QL para obter todos os usuários no último mês e, em seguida, passamos o ID do pacote para o nosso provedor. Aqui está a versão síncrona:

Esse código não parece tão ruim, certo? E se eu lhe disser que seu provedor de encomendas tem um servidor da Web incrivelmente ruim que, às vezes, retorna consultas em 10 segundos em vez de 500 milissegundos? Além disso, perdemos a possibilidade de transmitir nossos resultados e executar ações assim que eles chegam.

Imagine que tivemos sorte e 2000 novos clientes se inscreveram. Primeiro, precisamos esperar até que os 2.000 usuários sejam consultados e, em seguida, precisamos executar 2.000 consultas em série no servidor da Web do nosso provedor de encomendas. É claro que podemos distribuí-las para um serviço executor, mas depois precisamos fazer a orquestração e a agregação por conta própria.

Podemos fazer muito melhor com a versão reativa:

Aqui, estamos transmitindo os resultados à medida que eles chegam do servidor. Quando temos um resultado, extraímos o ID do pacote e o enviamos para o servidor de pacotes. Quando a resposta chega, nós a pegamos e a agrupamos pelo estado de envio. Por fim, aplicamos uma boa formatação e imprimimos o resultado. Como nunca bloqueamos, enviamos todas as solicitações para o servidor de encomendas e as agrupamos assim que elas retornam (em qualquer ordem). Se algumas das solicitações demorarem mais, não nos importamos, pois podemos terminar de processar as outras primeiro e não ficaremos presos. Também podemos aplicar um tempo limite geral.

Resumo

Esses dois exemplos mostram claramente como seu aplicativo pode se beneficiar da execução assíncrona e reativa. Ele oferece um caminho claro para evitar o bloqueio e os drivers de banco de dados que consomem muitos recursos e deixam os servidores de aplicativos e os tempos de resposta mais lentos.

Nós mal arranhamos a superfície do que é possível. Estamos trabalhando em projetos de exemplo e em documentação ampliada para oferecer a você um caminho claro de orientação nessa nova maneira de escrever aplicativos de acesso a bancos de dados. Informe-nos se deseja ver seu caso de uso abordado e/ou como ele pode ser transformado.

Por fim, mais uma vez um lembrete rápido de que, em alguns dias, haverá um webinar sobre o novo Java SDK. Participe, ouça a introdução e depois não hesite em fazer suas perguntas!

Compartilhe este artigo
Receba atualizações do blog do Couchbase em sua caixa de entrada
Esse campo é obrigatório.

Autor

Postado por Michael Nitschinger

Michael Nitschinger trabalha como engenheiro de software principal na Couchbase. Ele é o arquiteto e mantenedor do Couchbase Java SDK, um dos primeiros drivers de banco de dados totalmente reativos na JVM. Ele também é o autor e mantenedor do Couchbase Spark Connector. Michael participa ativamente da comunidade de código aberto e contribui para vários outros projetos, como RxJava e Netty.

Um comentário

  1. Jacek Laskowski março 10, 2015 em 8:23 pm

    Uma postagem de blog muito útil. Gostaria que houvesse mais informações sobre como os respectivos métodos estão se comportando por baixo dos panos para que todo o fluxo de dados seja assíncrono e sem bloqueio. Como é possível que, quando uma linha/registro chega, ela passe para a chamada do método seguinte? Mal consigo imaginar isso e, por isso, as perguntas sobre os aspectos internos do SDK.

Deixe um comentário

Pronto para começar a usar o Couchbase Capella?

Iniciar a construção

Confira nosso portal do desenvolvedor para explorar o NoSQL, procurar recursos e começar a usar os tutoriais.

Use o Capella gratuitamente

Comece a trabalhar com o Couchbase em apenas alguns cliques. O Capella DBaaS é a maneira mais fácil e rápida de começar.

Entre em contato

Deseja saber mais sobre as ofertas do Couchbase? Deixe-nos ajudar.