Sistema de processamento de pagamentos de alta performance para a Rinha de Backend, usando Java 21 + Spring Boot 3.3, Redis para estado compartilhado e Nginx como load balancer.
-
Camada de Apresentação (Controller Layer)
PaymentController: Endpoints RESTPOST /payments— recebe e roteia pagamentosGET /payments-summary— resumo por processador (com filtro de data)POST /purge-payments— limpa dados
-
Camada de Serviço (Service Layer)
PaymentService: registra otimisticamente no Redis e dispara chamada assíncrona ao processador (fire-and-forget)HealthCheckService: monitora saúde dos processadores a cada 5s e implementa routing inteligente por scorePaymentProcessorService: HTTP reativo com retry e fallback entre processadores
-
Camada de Dados (Repository Layer)
PaymentRepository: Redis viaStringRedisTemplatecom pipelining nas escritas para menor latência- Contadores atômicos:
INCR/INCRBYno Redis - Registros individuais: sorted set
ZADDcom score = epoch ms
-
Camada de Configuração
HttpClientConfig: WebClient com pool de 200 conexões, keep-aliveAsyncConfig: thread pools para processamento assíncrono
- Expõe endpoints REST
- Filtro de data (
from/to) repassado ao serviço comoInstant
- Optimistic recording: pagamento registrado no Redis antes da chamada ao processador → resposta rápida
- Fire-and-forget:
processorService.processPayment()executado de forma assíncrona, sem bloquear o cliente - Routing via
HealthCheckService.getBestProcessor()
- Health checks a cada 5 segundos (reduzido de 10s)
- Score baseado em
minResponseTimeretornado pelo processador - Prefere DEFAULT quando seu score ≤ 1.5× o do FALLBACK
- Fallback automático se DEFAULT falhar
- Retry com delay fixo (1 retry, 50ms)
- Fallback automático para o outro processador em caso de erro
- Timeout de 600ms por chamada
| Operação | Comando Redis |
|---|---|
| Incrementar contador | INCR pay:d:req |
| Somar valor | INCRBY pay:d:amt <cents> |
| Gravar registro | ZADD pay:rec <epoch_ms> "<uuid>|<cents>|<1/0>" |
| Summary geral | 4× GET em pipeline |
| Summary filtrado | ZRANGEBYSCORE + agregação client-side |
| Purge | DEL das 5 chaves |
Escritas usam executePipelined — 3 comandos em 1 round-trip.
| Container | CPU | RAM | Função |
|---|---|---|---|
| Redis 7 Alpine | 0.1 | 30MB | Estado compartilhado |
| backend-1 | 0.6 | 150MB | Instância Spring Boot |
| backend-2 | 0.6 | 150MB | Instância Spring Boot |
| load-balancer | 0.2 | 20MB | Nginx least_conn |
| Total | 1.5 | 350MB |
- backend: comunicação interna LB ↔ Backends ↔ Redis
- payment-processor: rede externa para os processadores
Cliente → Nginx (least_conn)
→ Backend (qualquer instância)
→ HealthCheckService.getBestProcessor() ← cache em memória
→ PaymentRepository.record*Payment() → Redis (pipeline)
→ PaymentProcessorService.processPayment() → Processador externo (async)
→ 200 OK
Cliente → Nginx → Backend
→ PaymentRepository.getSummary(from, to)
→ Redis ZRANGEBYSCORE (filtro) ou 4× GET (total)
→ JSON response
- Registro no Redis em pipeline: 1 round-trip para 3 comandos
- Chamada ao processador assíncrona: não bloqueia resposta
- Virtual Threads (Java 21): I/O eficiente sem overhead de threads OS
- Summary total: 4 GETs em pipeline — sem iteração
- Summary filtrado: ZRANGEBYSCORE + loop no cliente
- G1GC com pausa máxima de 5ms
- Heap fixo em 100MB (
-Xms100m -Xmx100m) - TieredCompilation nível 1 para startup rápido (~3s)
-XX:+AlwaysPreTouchpara pré-alocar heap
- Undertow (substitui Tomcat)
- Connection pool: 200 conexões, keep-alive
- Nginx: least_conn, keepalive 256, epoll
Com 2 instâncias backend em memória isolada, o GET /payments-summary
retornaria valores inconsistentes dependendo de qual instância
respondesse. Redis resolve isso com operações atômicas sem necessidade
de sincronização entre instâncias.
Gravar primeiro e chamar o processador depois garante que o P99 não seja afetado pela latência do processador externo. A chamada assíncrona ainda chega ao processador, mas o cliente recebe 200 OK imediatamente.
ZADD com score = timestamp permite queries eficientes de range
(ZRANGEBYSCORE) sem varrer todos os dados. Complexidade O(log N + M)
onde M é o número de resultados no intervalo.