Alura > Cursos de Programação > Cursos de Java > Conteúdos de Java > Primeiras aulas do curso Persistência Poliglota: arquiteturas e otimização de desempenho

Persistência Poliglota: arquiteturas e otimização de desempenho

Persistência Poliglota em Arquiteturas Modernas - Introducao

Apresentando o instrutor e o curso

Olá! Meu nome é Maximillian Arruda e serei o instrutor neste curso de persistência poliglota com Java.

Audiodescrição: Maximillian é um homem pardo, com barba, olhos e cabelos castanhos. Ele usa óculos e veste uma camiseta do SouJava.

Introduzindo a persistência poliglota

Neste curso, nós vamos aprender a implementar a persistência poliglota em nossas soluções. Vamos desenvolver uma arquitetura, pois isso vai além de apenas integrar bancos de dados; é necessário compreender como isso funciona e entender os trade-offs envolvidos.

Vamos explorar um banco de dados relacional, suas capacidades, forças e fraquezas, além de um banco de dados de documentos, suas limitações e benefícios. Também abordaremos bancos de dados chave-valor, colunar e de grafos, compreendendo como eles se comunicam. Analisaremos uma arquitetura hexagonal para entender como ela pode ser integrada da melhor forma possível.

Explicando o projeto PolyAnalyzer

Faremos isso de maneira prática, seguindo um projeto chamado PolyAnalyzer, que será migrado para uma arquitetura poliglota. Este curso não termina aqui; é importante aproveitar a plataforma, participar dos fóruns, comunidades e atividades relacionadas para estar preparado para desenvolver sistemas no mundo moderno e em ambientes cloud (nuvem).

Concluindo e incentivando a participação

Esperamos que já sejamos pessoas desenvolvedoras plenas, capazes de progredir de forma autônoma no desenvolvimento, para acompanhar todo o processo, entender os trade-offs (compensações) e aplicar isso no dia a dia. Estamos prontos para implementar uma arquitetura poliglota? Nos vemos na próxima aula. Até mais!

Persistência Poliglota em Arquiteturas Modernas - Evolução da Persistência de Dados

Introduzindo o curso de persistência poliglota

Bem-vindos à nossa primeira aula do curso de Persistência Poliglota com Java. Neste curso, vamos explorar e implementar juntos uma arquitetura de persistência poliglota utilizando diferentes bancos de dados, atendendo assim aos diversos desafios do desenvolvimento de softwares modernos.

Na maioria das vezes, estamos envolvidos em projetos já existentes, onde a arquitetura está pronta, as decisões já foram tomadas e as tecnologias escolhidas, incluindo a persistência do banco de dados. Isso é comum no dia a dia de uma pessoa engenheira de software, especialmente em sistemas empresariais, onde decisões como essas foram feitas há anos, quando a opção por um banco de dados relacional era unanimidade. Naquela época, optar por um banco de dados relacional fazia todo sentido. Os data centers eram caros, o armazenamento também, e a internet ainda estava em desenvolvimento, não era como é hoje. Muitas empresas investiam fortemente nessas infraestruturas, então economizar espaço e garantir a integridade dos dados era uma prioridade.

Evolução dos modelos de banco de dados

Por volta de 1960, surgiu o modelo estruturado, que sucedeu os plain old files (arquivos antigos simples). Depois, veio o modelo de rede, que ligava dados usando ponteiros, mas ainda não era o ideal. Com o modelo relacional, as coisas mudaram, pois ele trouxe uma estrutura tabular, utilizando tabelas, colunas e tuplas, com uma estrutura bem definida de dados, permitindo consistência. Naquela época, também percebemos que era possível evoluir a aplicação separadamente dos dados, que até então estavam muito atrelados à aplicação. A integração entre aplicativos, utilizando arquivos, resultava em muitos problemas de truncamento de dados. O banco de dados relacional, com seu modelo, foi uma virada de chave, fazendo todo sentido a modificação.

O banco de dados relacional trouxe outras características, como as propriedades ACID (atomicidade, consistência, isolamento e durabilidade), que caracterizam todos os sistemas transacionais. Com esse modelo, criou-se também uma linguagem de consulta e manipulação de dados, o SQL. Diversos fornecedores começaram a desenvolver suas plataformas de persistência e sistemas gerenciadores de bancos de dados usando essa tecnologia.

Avanços tecnológicos e a mudança para a nuvem

Falando em tecnologia, várias linguagens e frameworks foram construídos utilizando essa tecnologia de banco de dados relacional. Temos ferramentas como clientes, drivers e assim por diante. A tecnologia pode não estar implementada de uma forma específica, mas se comunica sob o mesmo protocolo, que é o SQL. Hoje, temos o SQL 2023, se não me engano, com um padrão ANSI, mas cada fornecedor tem sua liberdade poética para ajustar e atender ao seu domínio.

Naquela época, fazia muito sentido ter servidores centralizados e uma escala vertical, em vez de horizontal. Para aumentar o processamento, era necessário investir em mais infraestrutura na máquina, como adicionar mais CPU, memória e armazenamento, além de contar com um datacenter. No entanto, com a chegada da cloud (nuvem) e da internet, essa dinâmica mudou. O número de acessos aumentou, os dados cresceram significativamente, e a forma de trabalhar com eles precisou ser repensada. Surgiu a dúvida se o modelo relacional ainda atendia à demanda.

Emergência dos bancos de dados NoSQL

Nesse contexto, a eventual consistência tornou-se uma possibilidade, pois a disponibilidade e a velocidade de receber e processar informações passaram a ser essenciais. Isso abriu espaço para o desenvolvimento de diversos engines de bancos de dados. Nos anos 2000, foram criados os bancos de dados NoSQL, que possuem focos especializados e são categorizados em quatro tipos: chave-valor, documentos, colunar e grafos. Atualmente, existem bancos NoSQL que tentam combinar o melhor dos dois mundos, como time series e SQL, para atender ao mercado. O crescimento da cloud impulsionou essa transformação.

Com a cloud, tudo se tornou um produto. Agora, pagamos pelo que consumimos, sem a necessidade de um datacenter próprio. Infraestrutura, plataforma, software e banco de dados se transformaram em serviços. Como pessoas engenheiras de software, precisamos saber lidar com isso, conectar-nos a esses bancos e utilizá-los da melhor maneira possível para resolver os problemas envolvidos.

Apresentando o projeto PolyAnalyzer

Neste curso, vamos abordar essas questões na prática. Temos um projeto chamado PolyAnalyzer, que inicialmente será implementado em um banco de dados SQL. A partir daí, vamos avançar e implementar a persistência poliglota. Será uma experiência interessante, e esperamos que todos gostem.

Na próxima aula, vamos entender melhor como isso funciona. O PolyAnalyzer começou a receber requisições e a sofrer degradação, pois, quanto mais requisições, mais contenção de recursos o servidor enfrenta. Ele precisa controlar a concorrência, já que é um sistema transacional, e isso traz desafios. Vamos reconhecer esses desafios e evoluir a aplicação.

Esperamos explorar essas questões ao longo do curso e aprender cada vez mais. Até a próxima aula!

Persistência Poliglota em Arquiteturas Modernas - Analisando Gargalos na Persistência

Explorando o projeto PolyAnalyzer

Agora, vamos explorar o projeto PolyAnalyzer e entender por que o seu ponto fraco atualmente é a utilização exclusiva do modelo relacional, tanto para escrita quanto para leitura. Vamos identificar onde podemos integrar nossos modelos de leitura, ou read models, utilizando outras tecnologias de persistência, como um banco de dados NoSQL, para alcançar a performance desejada e explorar a persistência poliglota.

O PolyAnalyzer é uma plataforma SaaS que tem como objetivo tratar feedbacks de usuários. Com base nesses feedbacks, realiza-se uma análise de sentimentos, detectando polaridade e pontuação. O processamento também inclui a classificação dos tópicos, identificando sobre o que cada tópico trata. Esses dados são agregados, armazenados no banco de dados e, em seguida, notificados no dashboard. Assim, nossos analistas e responsáveis pelo produto podem analisar e visualizar os feedbacks, utilizando tecnologia de server-side event para visualização em tempo real, além de outras informações, como IPs e agregações de dados, para auxiliar na análise do comportamento relacionado ao produto.

Configurando serviços com Docker Compose

Para começar, vamos configurar os serviços necessários para a aplicação utilizando o Docker Compose. Isso inclui o RabbitMQ como message broker e o banco de dados MySQL para armazenamento de dados. Veja a configuração abaixo:

services:

  rabbitmq:
    image: rabbitmq:3.8-management
    ports:
      - "5672:5672"
      - "15672:15672" # console
    environment:
      RABBITMQ_DEFAULT_USER: polyanalyzer
      RABBITMQ_DEFAULT_PASS: polyanalyzer
    volumes:
      - rabbitmq-data:/var/lib/rabbitmq

  mysql:
    image: mysql:8.0
    container_name: polyanalyzer-mysql
    environment:
      MYSQL_DATABASE: polyanalyzer
      MYSQL_USER: polyanalyzer
      MYSQL_PASSWORD: polyanalyzer
      MYSQL_ROOT_PASSWORD: root
      TZ: America/Sao_Paulo
      LANG: pt_BR:pt
      LANGUAGE: pt_BR:pt
      LC_ALL: pt_BR.UTF-8
    ports:
      - "3306:3306"
    command:
      - "--max_connections=5000"
      - "--character-set-server=utf8mb4"
      - "--collation-server=utf8mb4_0900_ai_ci"
    volumes:
      - mysql-large-data:/var/lib/mysql

volumes:
  rabbitmq-data:
    driver: local
  mysql-small-data:
    driver: local
  mysql-large-data:
    driver: local

Implementando a arquitetura hexagonal

O projeto segue uma arquitetura de portas e adaptadores, ou hexagonal, que separa os modelos da infraestrutura. No modelo, focamos nas interfaces que permitem que, na infraestrutura, nos adaptadores, implementemos os serviços externos. O modelo contém a implementação das regras de negócio, contadores que trazem métricas e quantidades relacionadas aos sentimentos processados, contagem de feedbacks e tópicos, além dos resolvers, que fornecem dados de forma abrangente, como feedbacks recentes, distribuição de sentimentos ao longo do tempo e tendências de sentimentos.

Na infraestrutura, temos os adaptadores com implementações específicas para cada serviço, atualmente utilizando um banco de dados relacional, como o MySQL. Utilizamos o Docker Compose para rodar e subir todos os serviços necessários para a aplicação, incluindo o RabbitMQ como message broker e o banco de dados MySQL para armazenamento de dados.

Processando feedbacks com o FeedbackController

Na parte da web, recebemos informações através de controles que processam requisições REST, utilizadas pelo nosso dashboard. Esses controles possuem todos os endpoints consumidos pelo dashboard web que a aplicação disponibiliza. Vamos ver como o FeedbackController é implementado para lidar com a criação de feedbacks:

package br.com.polyanalyzer.infrastructure.adapter.web.ingestion;

import ...

@RestController
@RequestMapping("/feedbacks")
public class FeedbackController {

    private final FeedbackRegistration feedbackRegistration;

    public FeedbackController(FeedbackRegistration feedbackRegistration) {
        this.feedbackRegistration = feedbackRegistration;
    }

    @PostMapping
    @Transactional
    public ResponseEntity<FeedbackResponse> create(@Valid @RequestBody FeedbackRequest request) {
        UUID id = feedbackRegistration.register(request.comment());
        return ResponseEntity.created(URI.create("/feedbacks/" + id)).body(new FeedbackResponse(id));
    }
}

Garantindo consistência com o Outbox Pattern

O processamento começa com o envio de feedback pelo usuário, que é interceptado pelo nosso controller. Este inicializa uma transação para registrar o feedback, que é salvo no banco de dados e gera um evento chamado Feedback Received. Utilizamos o Outbox Pattern, um padrão de microserviço que garante o envio dos eventos para o message broker, mesmo em caso de problemas de infraestrutura. A mensagem fica pronta para ser enviada ao broker, e um serviço chamado Outbox Event Publisher busca os dados no banco, publica os eventos no RabbitMQ e marca o evento como salvo.

O próximo passo é o processamento de análise de sentimentos. O RabbitMQ captura a mensagem enviada pelo Outbox Event Publisher e a envia para o consumer, que inicia uma transação e envia os dados para o serviço de detecção e análise de sentimentos. Após o processamento, os dados são enviados ao banco de dados e um novo evento é gerado para o próximo passo, que é a detecção de tópicos. Ao concluir a transação, a mensagem de confirmação é enviada de volta ao broker.

Trabalhando com consistência eventual

Trabalhamos com envio de eventos e, nesse modelo, lidamos com consistência eventual, pois todos esses processos são executados de forma independente, não de maneira serial ou controlada.

Quando trabalhamos em modo assíncrono, estamos lidando com uma eventual consistência, mesmo utilizando um banco de dados relacional. O processo envolve o envio de dados para o broker, que utiliza o outbox pattern. O outbox event publisher captura o evento, envia-o novamente para a fila e processa o próximo consumer. Este, por sua vez, processa os tópicos, abre uma transação, detecta e salva os tópicos, e envia um evento confirmando que tudo está concluído, finalizando a transação e o processamento.

Notificando o dashboard e clientes

Após o processamento, o consumer envia o evento processado para o dashboard e para o controller, que aguarda um evento de domínio. Este evento indica que o processamento foi concluído e é enviado para os clientes conectados através da tecnologia de server-side event, evitando o uso de polling. No final, um acknowledgement é enviado para o broker. Não há transação envolvida nesse processo, pois não há alteração de dados, apenas notificação, o que permite uma boa escalabilidade. A comunicação ocorre apenas entre os serviços, sem impactar a infraestrutura do banco de dados.

Executando a aplicação e analisando performance

No cenário apresentado, entendemos como todo o processo funciona. O processo ACID oferece grandes benefícios para sistemas transacionais, mas também possui suas particularidades. Não há "almoço grátis", e a transação ACID pode impactar sistemas de alta demanda, tanto para leitura quanto para escrita.

Vamos executar a aplicação para entender melhor seu funcionamento. No terminal, subimos a infraestrutura da aplicação com o comando:

docker compose up -d

E em seguida, executamos a aplicação com:

mvn spring-boot:run

Após subir a infraestrutura, podemos verificar o dashboard. Nele, já temos alguns dados persistidos dos testes realizados, como números, QRPs, feedbacks, tópicos e sentimentos, todos capturados para visualização.

Simulando acesso e identificando gargalos

Agora, vamos simular o acesso a esses dados. Primeiro, simulamos o dashboard para metrificar o que ocorre nos bastidores. Subimos a infraestrutura com 100 administradores simulando acesso ao dashboard por 2 minutos. Com o dashboard metrificado, conseguimos observar a latência das métricas, que está em torno de 426 milissegundos, o que é considerado alto. A latência de sentimentos está próxima de 1 segundo, e a dos tópicos, quase meio segundo. Mesmo sem carga, apenas com leitura de 10 administradores, já observamos esses tempos.

Vamos simular um cliente executando o processo. Em um cenário real, um produto poderia ter milhões de clientes acessando e enviando feedbacks. Para simplificar, simulamos mil clientes acessando o aplicativo e enviando feedbacks. Ao executar, observamos que o tempo de resposta aumentou para 3 segundos, e a distribuição para quase 200 milissegundos. Os QIPS globais variam, e com mil usuários, já percebemos o impacto na execução. Para envio, a aplicação atende bem, com menos de 100 milissegundos por mensagem.

Considerando melhorias na persistência de dados

O problema ocorre porque o banco de dados relacional precisa gerenciar transações, comprometendo a retenção ao controlar o acesso concorrente nas tabelas. Isso é um ponto fraco para sistemas de alta demanda. Empresas costumam ter várias réplicas de escrita e leitura de bancos de dados relacionais, separando as conexões entre os nós, pois o modelo relacional foi projetado para escala vertical, não horizontal.

Para melhorar a pesquisa de dados, podemos pensar em reproduzir e delegar a responsabilidade do modelo relacional para outro mecanismo de persistência, uma vez que o dado já está salvo. Assim, não precisamos alterar o dado constantemente. Na próxima aula, começaremos a implementar um read model e isolar esse processamento da aplicação. Nos vemos lá.

Sobre o curso Persistência Poliglota: arquiteturas e otimização de desempenho

O curso Persistência Poliglota: arquiteturas e otimização de desempenho possui 308 minutos de vídeos, em um total de 60 atividades. Gostou? Conheça nossos outros cursos de Java em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Java acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas