404 Not Found

404 Not Found


nginx

Otimização de Desempenho

Otimização de desempenho é um trabalho de engenharia de sistemas — envolve três dimensões: a JVM, o banco de dados e o pool de conexões. Somente identificando gargalos com precisão podemos tratar as causas raiz.

1. O Que Você Vai Aprender


2. Uma História Real de um Engenheiro de Desempenho

(1) Dor: A API é lenta como uma lesma

Charlie relatou que a latência P99 da API de lista de pedidos do OrderFlow era de 500 ms, resultando em uma experiência de usuário muito ruim. A análise de Alice revelou o seguinte: 1) Pausas frequentes de Full GC na JVM durando 200 ms; 2) Um problema N+1 ocorrendo quando JPA consulta a lista de pedidos (100 pedidos = 101 queries SQL); 3) O tamanho do pool de conexões HikariCP é insuficiente, causando timeout nas requisições enquanto aguardam uma conexão.

(2) Métodos de Solução para Otimização Sistêmica

Otimizar Camada por Camada para Abordar Gargalos com Precisão:

Nível Gargalo Solução de Otimização
JVM Pausa de Full GC G1GC + Ajuste do Tamanho do Heap
Pool de Conexões Conexões Insuficientes Otimização de Parâmetros HikariCP
ORM Queries N+1 JOIN FETCH / EntityGraph
Banco de Dados Scan Completo da Tabela Adicionar Índice

(3) Resultado

Depois que Alice otimizou cada componente um a um, a latência P99 caiu de 500 ms para 50 ms e a throughput aumentou dez vezes.


3. Ajuste de Parâmetros da JVM

(1) Comparação de Algoritmos GC

Algoritmo GC Pausa Máxima Cenários Adequados Parâmetros JVM
G1GC 10-200 ms Geral (padrão JDK 17) -XX:+UseG1GC
ZGC < 1 ms Baixa Latência (JDK 17+) -XX:+UseZGC
SerialGC Longa Heap Pequena (< 200MB) -XX:+UseSerialGC
ParallelGC Média Prioridade de Throughput -XX:+UseParallelGC
100%
graph LR
    A["Tuning JVM"] --> B["Tamanho do Heap<br/>-Xms = -Xmx"]
    A --> C["Algoritmo GC<br/>G1 / ZGC"]
    A --> D["Log do GC<br/>-Xlog:gc*"]
    B --> B1["Contêiner: MaxRAMPercentage"]
    C --> C1["Padrão: G1GC"]
    C --> C2["Baixa latência: ZGC"]

(1) ▶ Exemplo: Configuração JVM em Ambiente Docker

DOCKERFILE
ENTRYPOINT ["java", \
  "-XX:+UseG1GC", \
  "-XX:MaxRAMPercentage=75.0", \
  "-XX:InitialRAMPercentage=50.0", \
  "-XX:+UseStringDeduplication", \
  "-Xlog:gc*:file=/app/logs/gc.log:time,uptime,level,tags", \
  "-jar", "app.jar"]

Saída:

TEXT
// Execução bem-sucedida
Parâmetro Significado Valor Recomendado
MaxRAMPercentage Heap máximo como percentual da memória do contêiner 75.0
InitialRAMPercentage Percentual da memória do contêiner ocupado pelo heap inicial 50.0
+UseG1GC Usar o coletor de lixo G1 Habilitado por padrão
+UseStringDeduplication Desduplicação de Strings Economiza Memória Recomendado
MaxGCPauseMillis Tempo Alvo de Pausa do GC 200 (G1) / Nenhum (ZGC)

(2) ▶ Exemplo: Configuração ZGC de Baixa Latência

BASH
java -XX:+UseZGC \
     -XX:MaxRAMPercentage=75.0 \
     -XX:+ZGenerational \
     -Xlog:gc*:file=gc.log \
     -jar app.jar

Saída:

TEXT
// Comando executado com sucesso
📌 Ponto Chave: ZGC é um recurso experimental no JDK 17 (disponível oficialmente no JDK 21). Com tempo de pausa inferior a 1 ms, é adequado para cenários extremamente sensíveis à latência.


4. Otimização do Pool de Conexões HikariCP

(1) Parâmetros do Pool de Conexões

Parâmetro Valor Padrão Descrição Valor Recomendado
maximumPoolSize 10 Número máximo de conexões Núcleos de CPU x 2 + número de discos
minimumIdle = maxPool Conexões ociosas mínimas = maxPoolSize
connectionTimeout 30.000 ms Timeout de conexão 3.000 ms
idleTimeout 600.000 ms Timeout de conexão ociosa 600.000 ms
maxLifetime 1.800.000 ms Tempo de vida máximo da conexão 1.800.000 ms
leakDetectionThreshold 0 (Desabilitado) Detecção de Vazamento de Conexão 60.000 ms

(1) ▶ Exemplo: Configuração de Produção do HikariCP

YAML
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 20
      connection-timeout: 3000
      idle-timeout: 600000
      max-lifetime: 1800000
      leak-detection-threshold: 60000
      pool-name: OrderFlowHikariCP

Saída:

TEXT
A configuração entrou em vigor.

(2) ▶ Exemplo: Detecção de Vazamento de Conexão

YAML
# Habilitar detecção de vazamento (aviso no log se conexão mantida > 60s)
spring:
  datasource:
    hikari:
      leak-detection-threshold: 60000
💻 Saída (quando há vazamento):

TEXT
WARN  - Connection leak detection triggered for {conn-12345}
  Stack trace of the call site that acquired the connection:
    at com.orderflow.service.OrderService.getOrder(OrderService.java:45)
    ...

5. Otimização de Consultas JPA

(1) O Problema N+1

(1) ▶ Exemplo: Demonstração do Problema N+1

JAVA
// RUIM: problema N+1
// 1 SQL para buscar 100 pedidos + 100 SQLs para buscar items de cada pedido = 101 SQLs!
List<Order> orders = orderRepository.findAll();
for (Order order : orders) {
    order.getItems().size();  // Dispara carregamento lazy para cada pedido
}

Saída:

TEXT
// Execução bem-sucedida

(2) ▶ Exemplo: Usando JOIN FETCH para Resolver o Problema N+1

JAVA
// BOM: Query única com JOIN FETCH
@Query("SELECT DISTINCT o FROM Order o JOIN FETCH o.items i JOIN FETCH i.product WHERE o.status = :status")
List<Order> findByStatusWithItems(@Param("status") String status);

Saída:

TEXT
// Execução bem-sucedida

(3) ▶ Exemplo: Carregamento Declarativo com @EntityGraph

JAVA
@Entity
@NamedEntityGraph(
    name = "Order.withItemsAndProduct",
    attributeNodes = {
        @NamedAttributeNode("items"),
        @NamedAttributeNode(value = "items", subgraph = "item-product")
    },
    subgraphs = {
        @NamedSubgraph(name = "item-product", attributeNodes = @NamedAttributeNode("product"))
    }
)
public class Order { /* ... */ }

// Uso no Repository
@EntityGraph(value = "Order.withItemsAndProduct", type = EntityGraphType.LOAD)
List<Order> findByStatus(String status);

Saída:

TEXT
// Execução bem-sucedida
Solução Número de Statements SQL Cenários Aplicáveis Manutenibilidade
Lazy Loading Padrão N+1 Query de Objeto Único Alta
JOIN FETCH 1 Query Específica Média
@EntityGraph 1 Plano de carregamento reutilizável Alta
@BatchSize N/batchSize Carregamento em Lote Alta

6. Otimização de Índices do Banco de Dados

(1) Estratégia de Indexação

(1) ▶ Exemplo: Adicionando Índice no Banco de Dados

SQL
-- Índice para consultas por status de pedido
CREATE INDEX idx_order_status ON orders(status);

-- Índice composto para padrão de consulta comum
CREATE INDEX idx_order_status_created ON orders(status, created_at DESC);

-- Índice para busca de produtos
CREATE INDEX idx_product_name ON products(name);

-- Índice para lookup de produto nos items de pedido
CREATE INDEX idx_order_item_product ON order_items(product_id);

Saída:

TEXT
CREATE TABLE
Tipo de Índice Caso de Uso Exemplo
Índice de coluna única Query de condição única WHERE status = 'PENDING'
Índice Composto Query de Múltiplas Condições WHERE status = ? AND created_at > ?
Índice Único Restrição de Unicidade UNIQUE(sku)
Índice de Cobertura Índice contém todas as colunas da query SELECT status FROM orders WHERE id = ?

(2) ▶ Exemplo: Análise de Query Lenta

SQL
-- Configuração do log de queries lentas do MySQL
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;  -- Log queries > 1 segundo
SET GLOBAL log_queries_not_using_indexes = ON;

-- Analisar plano de execução da query
EXPLAIN SELECT o.*, i.*
FROM orders o
JOIN order_items i ON o.id = i.order_id
WHERE o.status = 'PENDING'
  AND o.created_at > '2024-01-01'
ORDER BY o.created_at DESC
LIMIT 20;

Saída:

TEXT
CREATE TABLE
Campo EXPLAIN Significado Pontos Chave
type Tipo de Acesso ALL (Scan Completo da Tabela) Precisa de Otimização
key Índice usado NULL indica que nenhum índice foi usado
rows Número de Linhas Escaneadas Quanto menor, melhor
Extra Informação Adicional Using filesort requer otimização

7. Exemplo Completo: P99 do OrderFlow Reduzido de 500 ms para 50 ms

YAML
# application-prod.yml (otimizado)
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 20
      connection-timeout: 3000
      leak-detection-threshold: 60000
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
    properties:
      hibernate:
        default_batch_fetch_size: 100
        jdbc:
          batch_size: 50
        order_inserts: true
        order_updates: true
BASH
# Parâmetros JVM (Docker ENTRYPOINT)
java -XX:+UseG1GC \
     -XX:MaxRAMPercentage=75.0 \
     -XX:InitialRAMPercentage=50.0 \
     -XX:+UseStringDeduplication \
     -XX:MaxGCPauseMillis=100 \
     -Xlog:gc*:file=/app/logs/gc.log:time,uptime,level,tags \
     -jar app.jar
JAVA
// OrderRepository otimizado com EntityGraph e busca em lote
public interface OrderRepository extends JpaRepository<Order, Long> {

    @EntityGraph(value = "Order.withItemsAndProduct", type = EntityGraphType.LOAD)
    @Query("SELECT o FROM Order o WHERE o.status = :status ORDER BY o.createdAt DESC")
    Page<Order> findByStatusPaged(@Param("status") String status, Pageable pageable);

    @EntityGraph(value = "Order.withItemsAndProduct", type = EntityGraphType.LOAD)
    Optional<Order> findById(Long id);
}

// Configuração de batch size do Hibernate elimina N+1 para coleções
// application.yml: hibernate.default_batch_fetch_size=100
Antes da Otimização Depois da Otimização Fator de Melhoria
P99: 500 ms P99: 50 ms 10x
SQL/Requisição: 101 SQL/Requisição: 1 100x
Pausa GC: 200 ms Pausa GC: 50 ms 4x
Espera de conexão: 100 ms Espera de conexão: 2 ms 50x

❓ Perguntas Frequentes

P Qual escolher, G1GC ou ZGC?
R G1GC é o padrão no JDK 17 e é adequado para a grande maioria dos cenários, com tempos de pausa de 10-200 ms. ZGC tem tempo de pausa < 1 ms e é adequado para cenários de baixa latência como transações financeiras, mas sua throughput é ligeiramente menor. Comece com G1GC e mude para ZGC apenas se encontrar problemas de latência.
P Um pool de conexões maior é sempre melhor?
R Não. Um pool de conexões muito grande resulta em: 1) aumento da carga no banco de dados; 2) overhead de troca de contexto; 3) aumento do uso de memória. Fórmula recomendada: conexões = (núcleos de CPU x 2) + effective_spindle_count.
P Qual a diferença entre @EntityGraph e JOIN FETCH?
R @EntityGraph é declarativo e pode ser definido na Entity ou Repository para reutilização; JOIN FETCH é imperativo e é escrito dentro de cada @Query. EntityGraph é mais fácil de manter em cenários complexos.
P O que o batch_size do Hibernate faz?
R Quando hibernate.jdbc.batch_size=50 é configurado, statements INSERT/UPDATE são executados em lotes (50 registros por lote), reduzindo o número de round trips ao banco de dados. Funciona ainda melhor quando usado em conjunto com order_inserts=true.
P Como identificar gargalos de desempenho?
R 1) Use Actuator /metrics para verificar durações de requisições HTTP; 2) Analise logs de GC para identificar pausas de GC; 3) Métricas HikariCP para verificar o status do pool de conexões; 4) Log de queries lentas do MySQL para identificar queries lentas; 5) Ferramentas APM (SkyWalking/Zipkin) para rastreamento end-to-end.
P Como realizar testes de desempenho?
R Use JMeter, Gatling ou k6. Aumente gradualmente a carga (ramp-up) e observe os pontos de inflexão no tempo de resposta e na taxa de erro. Cenários de teste devem cobrir: endpoints de API individuais, cenários mistos e cenários de pico.

📖 Resumo


📝 Exercícios

  1. Problema Básico (Dificuldade: ⭐): Configure os parâmetros G1GC da JVM e do pool de conexões HikariCP para o OrderFlow, e habilite o log de GC e a detecção de vazamento de conexão.

  2. Problema Avançado (Dificuldade ⭐⭐): Use @EntityGraph e JOIN FETCH para resolver o problema N+1 na consulta da lista de pedidos e compare o número de statements SQL e tempos de resposta antes e depois da otimização.

  3. Desafio (Dificuldade: ⭐⭐⭐): Use JMeter para realizar testes de carga na API de lista de pedidos do OrderFlow, estabeleça uma baseline de desempenho e otimize gradualmente (JVM + pool de conexões + N+1 + índices). Registre as mudanças em P50 e P99 para cada etapa de otimização e, por fim, otimize o P99 para abaixo de 50 ms.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%