Proposta de implementação da Rinha de Backend 2024 Q1.
Os resultados dos testes são publicados automaticamente neste site.
Submissão: aqui
- Node.js (Javascript)
- Postgres
- Envoy
- PgBouncer
A idéia era criar um projeto bem simples, com o mínimo possível de libs e frameworks e que também fosse fácil de replicar em outras linguagens.
Em relação a performance, aqui algumas idéias que guiaram o projeto:
-
menos round trips possíveis ao banco de dados. para isso, usei uma function no Postgres para as transações e uma query única para o obter o extrato bancário.
-
gestão eficiente de conexões com o banco. esse ponto é um complemente do anterior, conexões com o banco de dados são "caras" e aqui demorei para achar o setup ideal. desde o início a solução contava com um pool de conexões e no começo esse pool girou em torno de ~100 - ~300 de máx. conexões. depois de vários experimentos, encontrei uma ferramente interessante para o pool de conexões, PgBouncer. com o PgBouncer integrado, o setup ideal acabou sendo: pool=5 nas apis e pool=20 no PgBouncer, um número muito menor do que os experimentos inicias sem esse componente.
-
experimentei usar o nginx como load balancer inicialmente, mas após alguns experimentos com envoy, acabei optando pelo último.
-
threads: dadas as limitações do ambiente em relação a CPU e memória, experimentei diferentes setups de threads para as aplicações. após vários testes, o "sweet spot" para as apis foi definir UV_THREADPOOL_SIZE=1 para fazer o libuv usar uma thread apenas.
-
usar pg e pg-native para conexão com o Postgres. parecia ser a opção mais rápida, mas não cheguei a testar outras formas.
-
fast-json-stringify para serialização rápida de JSON.
-
busquei pré-alocar memória sempre que possível no caso de arrays e buffers. para a leitura do request body, usei um Buffer pré-alocado com o content-length da requisição, ao invés de concatenar strings ou arrays com Buffer.concat. essa forma me pareceu mais eficiente e se saiu melhor em alguns benchmarks locais. implementação aqui.
Para executar o projeto completo em um docker compose local, execute no seu terminal:
make up
Para executar os testes de carga contidos no repositório original da rinha, primeiro execute o comando de preparação:
make prepare
O comando make prepare
clona o repositório da rinha e instala a ferramente Gatling.
Ele deve ser executado apenas uma vez.
Para rodar os testes, execute o comando:
make test