Desafio de Performance - Rinha de Backend 2025 - Insights da Minha Versão em C# + PostgreSQL + Redis

Mostrar/Ocultar
Introdução
Este artigo traz uma análise dos principais insights técnicos que tive ao construir minha solução para a Rinha de Backend 2025. O foco é mostrar decisões que impactaram diretamente no desempenho, estabilidade e integridade do sistema, especialmente considerando as restrições severas do desafio.
Repositórios
- Código Fonte da Minha Versão em C# + PostgreSQL + Redis
- Repositório oficial do desafio da Rinha de Backend 2025
Insights principais
1. Coordenação reativa entre instâncias com ReactiveLock - rastreamento de estado e sincronização distribuída via Redis
ReactiveLock é uma biblioteca que desenvolvi para .NET 9, voltada para coordenação reativa de locks distribuídos.
Em vez de um lock tradicional que apenas trava ou libera um recurso, o ReactiveLock permite que múltiplas instâncias da aplicação monitorem e reajam ao estado ocupado/ocioso de recursos ou processos, usando handlers assíncronos reativos.
Isso significa que cada instância pode:
Incrementar ou decrementar um contador representando, por exemplo, requisições HTTP ativas ou processos em execução.
Consultar o estado atual, para saber se há alguma atividade em andamento.
Aguardar de forma assíncrona até que o sistema fique ocioso (todos os processos terminem).
O backend distribuído padrão é o Redis, que permite compartilhar esse estado entre múltiplas instâncias da API.
No contexto da Rinha de Backend 2025, usei o ReactiveLock para rastrear o estado das operações de processamento, garantindo que, por exemplo, o flush dos lotes de pagamentos no PostgreSQL só ocorra quando todas as operações ativas forem concluídas.
Isso evita inconsistências e permite sincronização eficiente sem bloqueios pesados que degradariam a performance.
O modelo reativo e assíncrono do ReactiveLock é fundamental para garantir alta concorrência e responsividade da aplicação, mesmo em ambientes distribuídos.
2. Performance real com execução nativa AOT no .NET 9
O modo AOT (Ahead-of-Time) do .NET 9 gera código nativo estático, eliminando o custo da compilação JIT e de reflexão dinâmica em tempo de execução.
Isso trouxe dois benefícios fundamentais:
Startup instantânea, que é crucial em benchmarks onde inicialização rápida é cobrada.
Uso reduzido de memória e CPU, importante dentro do limite apertado de 1.5 CPUs e 350 MB de RAM da Rinha.
Claro, houve desafios para manter compatibilidade, por exemplo evitando bibliotecas ou padrões que não suportam AOT, mas o ganho compensou.
3. Throughput otimizado com bulk insert no PostgreSQL
Inserções no PostgreSQL são custosas, principalmente por abrir e fechar transações e conexões. Para mitigar isso, agrupei os pagamentos em lotes de até 100 registros antes de inserir.
Isso:
Reduziu a sobrecarga por conexão.
Diminuiu o número de commits.
Aumentou muito o throughput da aplicação sem perder consistência.
Combinei isso com o lock reativo para garantir que o flush desses lotes ocorresse de forma sincronizada entre as instâncias.
4. Balanceamento de carga real com enfileiramento via Redis - somente distribuição de tráfego por round robin não é adequado para cenários dinâmicos
O desafio exige que múltiplas instâncias da API compartilhem a carga de trabalho de forma eficiente e equilibrada. No entanto, o balanceamento via round robin, apesar de simples e comum, não é adequado para cenários onde a duração e a complexidade das requisições variam significativamente.
Isso acontece porque o round robin distribui as requisições de forma cega, sem considerar o tempo real que cada instância leva para processá-las. Como resultado, uma instância pode acabar sobrecarregada enquanto outras ficam ociosas, causando gargalos e aumentando a latência geral.
Para contornar essa limitação, usei o Redis como uma fila distribuída, onde os pagamentos são enfileirados e consumidos por workers conforme sua real disponibilidade e capacidade de processamento. Dessa forma, o sistema adapta automaticamente a distribuição da carga, equilibrando o trabalho de acordo com a performance atual de cada instância.
Essa abordagem melhora a eficiência e a resiliência da aplicação, garantindo maior estabilidade mesmo em cenários altamente concorrentes e com variabilidade no tempo de execução das requisições.
5. Separação entre enfileiramento e processamento assíncrono para alta escalabilidade e responsividade
No projeto, o recebimento das requisições de pagamento foi desacoplado da persistência e processamento dos dados, por meio do uso de uma fila Redis e workers dedicados.
A API atua apenas como produtor, enfileirando mensagens de pagamento de forma rápida, evitando bloqueios síncronos que prejudicariam a latência da aplicação sob alta carga.
Os workers consomem essas mensagens concorrente e assíncronamente, processando lotes e realizando a persistência no PostgreSQL.
Essa arquitetura desacoplada:
Reduz o acoplamento entre recepção e processamento, permitindo escalabilidade independente dos componentes.
Melhora o throughput geral do sistema, otimizando o uso de threads e recursos entre múltiplas instâncias.
Aumenta a resiliência, pois falhas temporárias no processamento não impactam diretamente a disponibilidade da API.
Conclusão
No fim, esses insights mostram como as escolhas certas, como AOT, locks reativos, bulk inserts e enfileiramento inteligente, permitiram criar uma solução rápida, estável e consistente para o desafio da Rinha de Backend 2025, mesmo sob limitações severas de hardware.
Conecte-se para transformar sua tecnologia!
Saiba mais e entre em contato: