Meu projeto para rinha de backend 2024 Q1 feito em C.
Feito usando facil.io, uma biblioteca C que traz um web server performante + mini http framework. Special thanks to boazsegev for the lib!
Lib do próprio postgres para conexão com a db. Sem caching externo, como Redis por exemplo.
- Rinha de gal... backend
- Sumário
- Performance
- Funcionamento
- Takeaways
- Resultados
- Execução
- Utilização
- Gatling
- TODO
Muito boa aperformance 👍
- Nginx
- Workers: 1024 (testado até com 512 sem problemas)
- epoll
- Access log off
- Keepalive
- Caching
- Postgresql
- Docker compose health check
- Campo de busca (
generated
nome, apelido, stack) e indexação com trigrama
- Api
- Docker compose
- Network mode como
host
para todos os serviços
- Network mode como
- Recursos:
serviço | cpu (cores) | memória ram(gb) |
---|---|---|
api 1 | 0.1 | 100 mb |
api 2 | 0.1 | 100 mb |
nginx | 0.1 | 50 mb |
postgresql | 1.2 | 300 mb |
Este projeto foi feito em C utilizando postgres como banco de dados.
O elemento web foi feito utilizando a biblioteca facil.io, que já dá algumas funcionalidades básicas para um webserver http como: http parsing, multi threading, multi process/worker behavior, json parsing.
Para comunicação com o postgres utilizei a biblioteca do postgres mesmo.
Para ajudar na abstração e habilitar a criação de uma connection pool, eu mesmo fiz uma layer de abstração genérica para base dados em C, que engloba diferentes tipos de conexão, veja src/db.c, src/db.h, src/db_priv.h e src/db_postgres.h.
Temos um util varenv.h para carregar arquivos .env
e settar as váriavéis de ambiente no Linux:
// try load env var from env file
loadEnvVars(NULL);
- Containers docker em modo
network_mode: host
são mais performantes. Ao que tudo indica, a network padrão modo bridge possuí processamente extra sobre ele que afeta o desempenho, enquanto que quando se usa o host não há essa limitação - Webservers performantes usam uma thread para cada conexão, utilizando uma thread pool como mecanismo para tal
- Similarmente, queries para banco de dados usam uma conexão para cada thread, utilizando uma connection pool
- Base de dados gastam bastante cpu e memória se usadas muitas conexões, gargala demais, usar menos conexões se possível
- Desativar logging desnecessário
- Kit preguiçoso:
CREATE UNLOGGED TABLE
fsync = 0
- PgTune para 'tunar' a performance do postgres para as restrições de memória
Progresso feito através dos testes, documentando alterações, resultados e descobertas se encontra em RESULTADOS.md.
Dependencies:
- make
- gcc
- docker
- docker-compose
- java 8
Compilar projeto em modo release local
$ make release
Copiar o .env.template
para .env
e colocar sua config.
.env
:
SERVER_PORT=5000 # porta que o servidor vai escutar
SERVER_DB_CONNS=10 # quantidade de conexões simultâneas com o db
SERVER_THREADS=25 # quantidade de threads a serem usadas para o servidor
SERVER_WORKERS=5 # quantidade de processos a serem usado para o servidor
DB_HOST= # endereço do db
DB_PORT= # porta do db
DB_DATABASE= # nome da db
DB_USER= # usuário da db
DB_PASSWORD= # senha do usuário da db
(Opcional): Subir o banco postgres com docker compose:
$ sudo docker-compose up db -d
Para usar a db do compose, configurar o .env
como:
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=capi
DB_USER=capi
DB_PASSWORD=rinhadegalo
Executar:
$ ./webserver
Já configurado para rinha com: docker-compose.yml.
Atenção a senha, pois sudo
será usado por causa do docker
!
$ make up
- Utiliza
port=9999
.
HTTP Endpoints conforme definido nas intruções da rinha.
Para realizar o stress test com o gatling, basta executar:
$ make gatling
Obs:
- O gatling será instalado em gatling/gatling/.
- Os dados em gatling/resources; e o cenário scala gatling/simulations/RinhaBackendSimulation.scala são os mesmos definidos no repositório da rinha.
- Resultados estarão em gatling/results/.
- Contagem final será salva em
count.txt
.
- Usar unix sockets entre nginx e as apis