Um platô não é uma query lenta
Existem dois problemas de desempenho bem diferentes. De fora, eles parecem iguais, e a maior parte da otimização desperdiçada vem de tratar um como se fosse o outro.
Os dois formatos
Um endpoint lento é lento a uma requisição por segundo e igualmente lento a cinquenta. O trabalho em si demora demais: um índice faltando, uma query num laço, uma imagem redimensionada no caminho da requisição. A latência fica plana conforme a carga sobe, e a correção é local: diminua aquele trabalho.
Um platô é rápido até deixar de ser. Os tempos de resposta ficam estáveis, até que, em algum nível de concorrência, eles sobem e continuam subindo, e nada no profile de uma requisição isolada parece diferente de antes.
Essa é a assinatura: a requisição individual não ficou mais lenta. Ela começou a esperar.
flowchart TB
A["o tempo de resposta está ruim"] --> Q{"está ruim em carga baixa também?"}
Q -->|sim| S["um caminho lento. perfile uma requisição"]
Q -->|não| P["um platô. conte os servidores, não os milissegundos"]
S --> S1["correção: faça o trabalho menor"]
P --> P1["correção: ache o que é finito e está se esgotando"]
P1 --> P2["perfilar uma requisição não vai mostrar isso"]
Por que o profiler falha num platô
Um profiler mede onde uma requisição gasta o tempo dela. Ele não consegue mostrar a requisição que não teve permissão para começar.
Quando um sistema chega a um platô, a requisição está esperando por um recurso que outra requisição está segurando: uma thread, uma conexão, uma trava. A espera costuma aparecer como tempo dentro de algo inocente, ou no intervalo entre a requisição chegar ao servidor e qualquer código da aplicação rodar. A maioria das ferramentas não mede esse intervalo por padrão.
Então a ferramenta depende do formato do problema. Para um caminho lento, use o profiler. Para um platô, conte: quantas coisas podem rodar ao mesmo tempo, e quantas estão tentando.
O que é finito aqui
Um deploy declara os próprios limites na configuração, e são números pequenos e específicos. Pegue um deploy que define:
WEB_CONCURRENCY: 2
RAILS_MAX_THREADS: 3
O Puma lê o WEB_CONCURRENCY como o número de processos worker, e o puma.rb desta aplicação define as threads por processo a partir do outro valor:
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count
Dois processos com três threads cada quer dizer que seis requisições podem estar em andamento ao mesmo tempo. A sétima requisição não roda devagar. Ela nem roda até uma das seis terminar.
Esse número é a primeira coisa a descobrir sobre qualquer sistema que pedem para você acelerar. Ele costuma estar escrito em algum lugar óbvio, num arquivo que ninguém enxerga como documento de desempenho.
A conta que vem daí
Seis vagas concorrentes com uma requisição média de 100 milissegundos dão sessenta requisições por segundo. Isso não é uma estimativa. É o que a configuração permite, antes de qualquer linha de código da aplicação entrar na conta.
Se as requisições levam 500 milissegundos em média, as mesmas seis vagas dão doze requisições por segundo. O teto mudou por um fator de cinco e nada no hardware mudou.
A relação que vale aprender é que a vazão é a concorrência dividida pela latência. Isso quer dizer que um caminho lento e um platô não são independentes, no fim das contas: deixar a requisição média mais rápida levanta o teto, e é por isso que os dois problemas se confundem tão fácil.
Mesmo assim, eles se corrigem de jeitos diferentes. Cortar pela metade a duração de um endpoint raro não faz nada pelo teto. Cortar a média pela metade faz.
Pontos-chave
- Um caminho lento é lento com qualquer carga. Um platô é rápido até a concorrência chegar a um limite, e aí degrada.
- Num platô, a requisição não ficou mais lenta. Ela começou a esperar, e profilers mostram isso mal.
- Para um caminho lento, use o profiler. Para um platô, conte o que pode rodar ao mesmo tempo e o que está tentando.
WEB_CONCURRENCY: 2vezesRAILS_MAX_THREADS: 3permite seis requisições concorrentes.- A vazão é a concorrência dividida pela latência, então baixar a latência média levanta o teto.
Continue com a aula 2
Tudo é um banco só
Mostra que primary, cache, queue e cable ficam num só servidor Postgres, dividindo tetos e um piso de polling.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.