Fundamentos8 min

Performance em Sistemas Orientados a Eventos

Arquiteturas event-driven têm características de performance únicas. Entenda como medir, otimizar e testar sistemas assíncronos.

Sistemas orientados a eventos processam trabalho de forma assíncrona através de mensagens. Isso muda fundamentalmente como medimos e otimizamos performance. Métricas tradicionais como "tempo de resposta" precisam ser repensadas.

Em sistemas síncronos, você mede latência. Em sistemas assíncronos, você mede throughput e lag.

Características de Sistemas Event-Driven

Fluxo síncrono vs assíncrono

Síncrono:
Request → Processamento → Response
         [100ms total]

Assíncrono:
Request → Queue → ACK (5ms)
                    ↓
          Consumer processa (100ms)
                    ↓
          Resultado disponível

Métricas diferentes

Síncrono Assíncrono
Latência end-to-end End-to-end latency
Tempo de resposta Consumer lag
Requests/segundo Messages/segundo
Timeout TTL (Time to Live)

Métricas Essenciais

1. Consumer Lag

Diferença entre última mensagem publicada e última consumida.

Produtor publicou: mensagem #1000
Consumer processou: mensagem #800
Consumer lag: 200 mensagens

Em tempo:

Lag em mensagens: 200
Throughput do consumer: 50 msg/s
Lag em tempo: 200/50 = 4 segundos

Prometheus + Kafka:

# Lag por consumer group
kafka_consumergroup_lag{topic="orders"}

2. Throughput

Producer throughput: 1000 msg/s
Consumer throughput: 800 msg/s
Gap: 200 msg/s

→ Lag aumentará 200 msg/s continuamente
→ Sistema não é sustentável

Regra de ouro:

Consumer throughput ≥ Producer throughput
(com margem para picos)

3. Processing Time

Tempo para processar uma mensagem individual.

Mensagem recebida: 14:00:00.000
Processamento iniciou: 14:00:00.005
Processamento terminou: 14:00:00.105

Processing time: 100ms
Wait time: 5ms

4. End-to-End Latency

Tempo total desde produção até processamento completo.

Evento criado: 14:00:00.000
Publicado no broker: 14:00:00.010
Consumido: 14:00:00.050
Processado: 14:00:00.150

End-to-end: 150ms

Gargalos Comuns

1. Consumer lento

Producer: 1000 msg/s
Consumer: 100 msg/s

Lag após 1 hora: 3.24 milhões de mensagens

Soluções:

1. Mais consumers (partições paralelas)
2. Batching no processamento
3. Otimizar lógica do consumer
4. Async I/O dentro do consumer

2. Partições desbalanceadas

Partição 0: 500 msg/s → Consumer A (sobrecarregado)
Partição 1: 100 msg/s → Consumer B (ocioso)
Partição 2: 100 msg/s → Consumer C (ocioso)

Soluções:

1. Melhorar partition key
2. Mais partições
3. Rebalanceamento manual

3. Broker saturado

Broker CPU: 95%
Disco: 90% IOPS
Network: saturada

→ Latência de publicação aumenta
→ Consumers recebem atrasado

Soluções:

1. Mais brokers
2. Compression
3. Batching de producers
4. Retention menor

4. Reprocessamento infinito

Mensagem falha → Volta para fila
Falha novamente → Volta para fila
...infinitamente

→ Recursos consumidos por mensagens que nunca vão passar

Soluções:

1. Dead Letter Queue (DLQ)
2. Retry com limite
3. Exponential backoff
# Retry com DLQ
max_retries = 3
retry_count = message.headers.get('retry_count', 0)

if retry_count >= max_retries:
    send_to_dlq(message)
else:
    try:
        process(message)
    except Exception:
        message.headers['retry_count'] = retry_count + 1
        republish_with_delay(message)

Padrões de Otimização

1. Batching

# ❌ Commit por mensagem
for message in messages:
    process(message)
    consumer.commit()  # I/O por mensagem

# ✅ Batch commit
batch = []
for message in messages:
    batch.append(process(message))
    if len(batch) >= 100:
        save_batch(batch)
        consumer.commit()
        batch = []

2. Prefetch

# Kafka consumer config
fetch.min.bytes = 1024        # Espera acumular dados
fetch.max.wait.ms = 500       # Ou até 500ms
max.poll.records = 500        # Até 500 registros por poll

3. Paralelismo

# Consumer com thread pool
from concurrent.futures import ThreadPoolExecutor

executor = ThreadPoolExecutor(max_workers=10)

for message in consumer:
    executor.submit(process, message)

Cuidado: Ordem não garantida com paralelismo!

4. Compression

# Producer config
compression.type = 'lz4'  # Rápido
# ou 'zstd'               # Melhor compressão
Sem compressão: 100MB/s bandwidth
Com LZ4: 25MB/s bandwidth (4x redução)

Testando Sistemas Event-Driven

Desafios

Síncrono:
- Request → Response
- Tempo medido diretamente

Assíncrono:
- Publish → ??? → Resultado em algum lugar
- Como medir end-to-end?

Técnicas

1. Correlation IDs

# Producer
event = {
    'correlation_id': uuid4(),
    'timestamp': time.time(),
    'data': {...}
}
publish(event)
store_correlation(event['correlation_id'], event['timestamp'])

# Consumer
def process(event):
    result = do_work(event)
    publish_result({
        'correlation_id': event['correlation_id'],
        'completed_at': time.time()
    })

# Collector
def collect_result(result):
    start = get_stored_timestamp(result['correlation_id'])
    end_to_end = result['completed_at'] - start
    record_metric('e2e_latency', end_to_end)

2. Synthetic events

# Injeta eventos de teste periodicamente
@every(minute=1)
def inject_synthetic():
    publish({
        'type': 'synthetic_test',
        'timestamp': time.time()
    })

# Consumer detecta e mede
def process(event):
    if event['type'] == 'synthetic_test':
        latency = time.time() - event['timestamp']
        record_metric('synthetic_e2e', latency)
        return  # Não processa

    # Processamento normal

3. Load testing assíncrono

// k6 para sistemas assíncronos
import { check } from 'k6';
import kafka from 'k6/x/kafka';

export default function() {
    // Publica
    kafka.produce({
        topic: 'orders',
        messages: [{ value: JSON.stringify({order_id: __VU}) }]
    });

    // Verifica resultado (eventual)
    // Polling em outro tópico ou banco
}

Métricas de teste

1. Throughput máximo sustentável
   - Aumenta carga até lag começar a crescer
   - Ponto antes = throughput máximo

2. Latência sob carga
   - Mede end-to-end em diferentes níveis de carga
   - Identifica ponto de inflexão

3. Recovery time
   - Injeta pico, para
   - Mede tempo até lag zerar

Monitoramento em Produção

Dashboard essencial

1. Consumer Lag (absoluto e em tempo)
2. Throughput: produced vs consumed
3. Processing time (p50, p95, p99)
4. Error rate por consumer
5. Partition distribution
6. Broker health

Alertas

# Consumer lag crescendo
- alert: ConsumerLagIncreasing
  expr: |
    delta(kafka_consumergroup_lag[5m]) > 1000
  for: 5m

# Lag absoluto alto
- alert: ConsumerLagHigh
  expr: |
    kafka_consumergroup_lag > 10000
  for: 2m

# Consumer parado
- alert: ConsumerStalled
  expr: |
    rate(kafka_consumer_records_consumed_total[5m]) == 0
  for: 5m

Conclusão

Performance em sistemas event-driven requer mindset diferente:

  1. Foco em throughput, não só latência
  2. Consumer lag é a métrica mais importante
  3. Balanceamento de partições é crítico
  4. Retry e DLQ previnem cascatas de falha
  5. Testes end-to-end requerem correlation IDs

A regra fundamental:

Consumer throughput > Producer throughput × margem de segurança

Se essa equação não fecha, lag só vai crescer.

Sistema event-driven saudável tem lag próximo de zero. Lag crescente é dívida técnica acumulando juros.

eventosassíncronokafkamensageria
Compartilhar:
Read in English

Quer entender os limites da sua plataforma?

Entre em contato para uma avaliação de performance.

Fale Conosco