/apis-benchmark

Benchmark entre APIs equivalentes em diferentes runtimes

Primary LanguagePython

APIs Benchmark

Comparação de performance entre APIs HTTP equivalentes em 4 runtimes diferentes.

Vale destacar que todos os resultados apresentados foram obtidos através de testes informais, sem rigor científico e de propósito educacional.

APIs

Ao todo foram comparas 4 APIs diferentes:

  • Node.js + Fastify + pg
  • Python + FastAPI + psycopg
  • Python + FastAPI + asyncpg
  • Go + Gin + sqlx

Em todos os casos foram utilizadas pools de conexões com o banco de dados com um limite de até 100 conexões. Além disso, foram utilizados os serializadores padrão de cada linguagem ou framework (quando disponível), a fim de simular um cenário comum e explorar o desempenho out of the box de cada ferramenta.

Cenários de teste

Foram conduzidos testes em dois cenários diferentes, a fim de explorar as performance das APIs em situações IO-bound e CPU-bound.

  1. Serialização de JSON (endpoint GET /cache): gera 100 objetos em memória e os retorna serializados em um JSON de aproximadamente 386.6Kb
  2. Interação com o banco de dados (endpoint GET /db): busca 20 registros em um banco PostgreSQL e os retorna serializados em um JSON de aproximadamente 6Kb

Metodologia

Para executar o benchmark foram utilizadas duas instâncias do Cloud Compute Engine: uma no papel de cliente e a outra no papel de servidor. A máquina no papel de servidor irá executar as 4 APIs a serem testadas. Já a máquina no papel de cliente será responsável por fazer as requisições para o servidor, simulando alto tráfego.

A máquina no papel de servidor é uma instância N1 Series com 1vCPU dedicado e 3.75Gb de memória RAM. Já a máquina no papel de cliente é também uma instância N1 Series, porém conta com 4vCPUs dedicados e 3.6Gb de memória RAM. Além disso, a base de dados foi executada numa instância do Cloud SQL com 4vCPU, 8GB de RAM e 10GB de SSD.

A ideia foi fazer com que o hardware do servidor fosse um fator limitante para a performance, e não o da máquina no papel de cliente ou o banco de dados. Outro ponto a ser levado em consideração é que o servidor possui apenas um core, o que inibe possíveis ganhos de performance trazidos com paralelismo. Isso é especialmente relevante quando comparamos runtimes single-threaded que só conseguem fazer uso de uma CPU como Node.js com linguagens como Go, que são capazes de fazer bom uso de ambientes computacionais com múltiplas CPUs.

A simulação de carga feita com a ferramenta bombardier rodando na instância no papel de cliente. Foram feitas rodadas de teste com 1, 10, 50 e 100 conexões concorrentes. Cada rodada teve duração de 5 minutos. O resultado de cada rodada é uma média de quantas requisições por segundo cada API foi capaz de processar.

Resultados

No cenário de interação com o banco de dados foi possível verificar um ganho de performance bastante expressivo com o uso de drivers de banco de dados assíncronos na API com FastAPI. Além disso, o que mais surpreende é a diferença de performance entre a API em Fastify das outras implementações. Talvez o Node.js realmente seja um runtime bastante otimizado para aplicações single-threaded realizando operações IO-bound.

assets/db.png

Já no cenário focado em serialização de JSON é possível ver uma diferença significativamente maior entre as implementações em Fastify e Go quando comparadas às implementações em Python. Como era de se esperar, tal operação não sofreu impacto dos diferentes drivers de banco de dados utilizados para as implementações em Python, que tiveram desempenho estatisticamente equivalente. Apesar disso, ainda impressiona o fato de que a implementação em Node.js utilizando Fastify performou melhor do que a implementação em Go para uma tarefa CPU-bound.

assets/cache.png