Skip to content

vitorsalgado/rinha-2024-q1-nodejs

Repository files navigation

Rinha de Backend 2024 Q1 · Node.js · ci · GitHub License

Proposta de implementação da Rinha de Backend 2024 Q1.
Os resultados dos testes são publicados automaticamente neste site.
Submissão: aqui

Tech

  • Node.js (Javascript)
  • Postgres
  • Envoy
  • PgBouncer

Sobre

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.

Executando

Para executar o projeto completo em um docker compose local, execute no seu terminal:

make up

Testes de Carga

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