Skip to content

Benchmarking e Performance

Para analisar a performance da aplicação e identificar gargalos em endpoints, utilizamos a gem derailed_benchmarks. Este guia explica como configurar e executar medições de performance, além de como analisar os resultados utilizando flamegraphs.

1. Preparação do Ambiente

  1. Configuração de Banco de Dados: Utilize um dump de produção (se seguro/possível) ou uma base com volume de dados significativo.
  2. Ambiente e Credenciais:
    • É possível configurar credenciais no config/application.yml e passar RAILS_ENV=development nos comandos do derailed.
    • Se quiser uma análise mais detalhada simulando a aplicação com configurações de produção, você terá que garantir (seja editando config/credentials/production.yml.enc ou config/database.yml) que está sendo usada uma conexão com um banco de dados local no ambiente de produção.
    • Como os testes geralmente visam comparar resultados rodados no mesmo ambiente, o uso de RAILS_ENV=development é suficiente na maioria das vezes. Utilize o ambiente de produção apenas quando desejar extrair um resultado que reflita fielmente como o endpoint responderá em produção.

2. Autenticação nos Endpoints

A aplicação possui um hook no ApplicationController que facilita o bypass de autenticação durante os benchmarks. Você pode usar duas variáveis de ambiente para isso:

  • MOCK_AUTH=true: Essa variável de ambiente pode ser usada para testar endpoints que necessitem de um usuário autenticado, mas não importa qual o usuário. Autentica automaticamente como o primeiro usuário do banco (User.first).
  • HTTP_AUTHORIZATION='Bearer <user_id_or_username>': Autentica como um usuário específico, passando o seu ID ou username através da env, em formato Bearer <user_id_or_username>.

Exemplo:

bash
RAILS_ENV=development MOCK_AUTH=true PATH_TO_HIT='/api/v1/some_endpoint' bundle exec derailed exec perf:ips

3. Comandos de Benchmarking

Exame Geral (IPS)

O comando perf:ips (Iterations Per Second) fornece uma visão geral de quantas vezes o endpoint consegue ser processado por segundo. É útil para comparações rápidas antes e depois de uma mudança. Quanto maior o valor do ips, melhor a performance do endpoint.

bash
RAILS_ENV=development MOCK_AUTH=true PATH_TO_HIT='/some_endpoint' IPS_TIME=10 bundle exec derailed exec perf:ips
  • IPS_TIME: Tempo em segundos para a execução do teste (default: 5).

Análise Detalhada (StackProf)

O comando perf:stackprof examina a call stack do endpoint para identificar exatamente onde o tempo está sendo gasto (bottlenecks).

bash
RAILS_ENV=development TEST_COUNT=100 MOCK_AUTH=true PATH_TO_HIT='/some_endpoint' bundle exec derailed exec perf:stackprof

Para entender o resultado do StackProf, é necessário primeiro entender como ele é calculado. O StackProf é um profiler que examina a pilha de funções do programa (call stack) a partir de amostragens periódicas (Sampling Call-Stack Profiler). Ele tira snapshots periódicos da pilha de funções do programa, vendo as funções presentes na pilha naquele momento. Ele executa o teste uma quantidade iterativa de vezes (valor da env TEST_COUNT), e então o output do StackProf no terminal é gerado apresentando uma tabela com colunas como:

  • TOTAL: Representa o número total de samples em que essa função estava na pilha, ou seja, é a quantidade de samples em que esta função ou qualquer uma das funções que são chamadas a partir dela estava presente.
  • SAMPLES: Representa o número de samples em que essa função estava no topo da pilha, ou seja, representa quantas vezes essa função específica estava sendo executada no momento da amostragem (sem contar o tempo gasto em suas funções filhas).

Funções com um alto valor de SAMPLES são geralmente os melhores alvos para otimização, pois indicam que o tempo está sendo gasto diretamente nelas.

4. Gerando Flamegraphs

Para uma visualização mais intuitiva da call stack, podemos gerar um Flamegraph. Para isso, utilizamos a variável de ambiente STACKPROF_RAW (implementada via patch no projeto) para capturar dados brutos.

Passo 1: Gerar o Dump

Execute o benchmark com STACKPROF_RAW=true. Isso criará um arquivo .dump na pasta tmp/.

bash
RAILS_ENV=development TEST_COUNT=100 STACKPROF_RAW=true MOCK_AUTH=true PATH_TO_HIT='/some_endpoint' bundle exec derailed exec perf:stackprof

Passo 2: Gerar o HTML do Flamegraph

Use o comando stackprof para converter o dump gerado em um gráfico interativo:

bash
# Substitua pelo nome do arquivo gerado em tmp/
stackprof tmp/2026-05-16-stackprof-cpu-myapp.dump --d3-flamegraph > tmp/flamegraph.html

Abra o arquivo tmp/flamegraph.html no seu navegador para explorar a árvore de chamadas.

5. Como Analisar os Resultados

  • IPS: Procure por números maiores. Se uma refatoração reduziu o IPS significativamente, houve regressão.
  • StackProf (Terminal): Atente-se às colunas TOTAL e SAMPLES para identificar gargalos. O que está no topo geralmente é o que consome mais CPU.
  • Flamegraph:
    • Estrutura: As funções que iniciam a chamada estão na base (embaixo), e o topo da pilha são as últimas funções chamadas.
    • Interatividade: É possível usar o hover do mouse para pegar o nome completo e detalhes da função. Há também uma barra de pesquisa no topo da página para buscar por uma função específica.
    • Largura: Representa o tempo gasto. Quanto mais largo o bloco, mais tempo aquela função (e suas filhas) demorou. A porcentagem de samples apresentada equivale aproximadamente ao tempo gasto de cada função no endpoint, considerando como o samples é calculado.
    • Pilha (Vertical): Representa a hierarquia de chamadas (call stack).
    • Identificando Gargalos: Procure por "platôs" largos na pilha. Eles indicam funções que demoram muito para executar mas não chamam muitas outras funções (o tempo é gasto diretamente nelas).

Dicas Extras

  • Consistência: Sempre execute o benchmark múltiplas vezes para garantir consistência.
  • Isolamento: Desligue outros processos pesados na sua máquina durante o teste.
  • Parâmetros na URL: Se o endpoint exigir parâmetros, inclua-os no PATH_TO_HIT: PATH_TO_HIT='/api/search?q=teste'.
  • Customização da Request:
    • É possível escolher o método HTTP usado por meio da env REQUEST_METHOD (ex: POST). O default é o método GET
    • Passar body por meio da env REQUEST_BODY. Necessita que o content-type seja passado também.
    • Passar content-type por meio da env CONTENT_TYPE. Dica: geramente é CONTENT_TYPE="application/json".
    • Passar headers extras por meio de envs no formato HTTP_<UPPER_HEADER_NAME> (ex: HTTP_X_REQUEST_ID='123').
  • Identificando Problemas Típicos:
    • N+1 Queries: Se você vir muitas chamadas repetitivas de métodos do ActiveRecord (como load, find) de forma sequencial, pode indicar um problema de N+1. No Flamegraph, isso se manifesta como muitos blocos pequenos e repetidos.
    • Slow Regex: Se métodos de String ou Regexp aparecerem com alto SAMPLES, verifique se há regexes complexas sendo executadas em loops.
    • Alocação de Objetos: Um alto tempo em métodos como map, each ou inicializadores de classes pode indicar alocação excessiva de memória/objetos, o que impacta o Garbage Collector.
    • JSON Parsing/Rendering: Se o tempo estiver concentrado em bibliotecas de JSON, considere otimizar os serializers ou usar uma biblioteca mais rápida (como oj).