O trabalho que o Puma não deveria fazer
O Puma é um servidor web Ruby, e é muito bom em rodar Ruby. Outra coisa costuma ficar na frente dele porque servir um arquivo estático não é rodar Ruby, e fazer isso numa thread Ruby é caro de um jeito específico.
Quanto custa uma thread
Pense num Puma dimensionado para um servidor pequeno: dois workers com três threads cada, o que dá seis vagas de requisição na máquina inteira. Os números exatos variam de uma aplicação para outra; o raciocínio não.
Uma requisição por /assets/application-a1b2c3.css ocupa uma dessas vagas pelo tempo que levar para ler o arquivo e escrevê-lo num socket.
Numa conexão rápida, isso são milissegundos. Numa conexão móvel lenta, são segundos, e a thread fica presa o tempo todo, porque o Puma escreve a resposta antes de a thread ficar livre.
Seis vagas é pouco. Uma página que puxa uma folha de estilo, um mapa de importação e trinta módulos pré-carregados pode ocupar todas elas com trabalho que não envolve Ruby nenhum.
flowchart TB
R["uma requisição por um arquivo CSS"] --> P["o Puma toma uma das seis threads"]
P --> F["ler o arquivo. escrever no socket"]
F --> S{"quão rápido é o cliente?"}
S -->|rápido| A["milissegundos. quase não se nota"]
S -->|lento| B["segundos. a thread fica indisponível"]
B --> C["seis vagas, e uma se foi"]
A resposta tradicional
Coloque o nginx na frente. Ele serve arquivos com um laço de eventos em vez de uma thread por conexão, então dez mil clientes lentos custam muito pouco para ele.
O custo é um segundo software, com arquivo de configuração próprio, comportamento de recarga próprio e modos de falha próprios, e, num deploy com contêineres, um contêiner próprio.
Onde fica a questão da compressão
Uma resposta com gzip é menor e mais lenta de produzir. Comprimir em Ruby gasta CPU nessas mesmas vagas de requisição.
Comprimir num proxy gasta CPU fora delas. É o mesmo argumento de servir arquivos, aplicado a outro tipo de trabalho.
Os dois custos são reais, e nenhum é um problema grande no tráfego desta aplicação. O que faz a correção valer a pena é que ela quase não custa nada.
Para que serve o Thruster
O Thruster é um binário Go que envolve o Puma e tira dele esse tipo de trabalho. Ele guarda os assets públicos em cache na memória, repassa a transferência de arquivos via X-Sendfile e comprime as respostas. Ele também fala HTTP/2 e sabe gerenciar certificados TLS, algo que este deploy deixa para o kamal-proxy.
Não há arquivo de configuração, segundo contêiner nem regras de roteamento, e esta aplicação já o roda.
A próxima lição mostra onde, e a resposta é uma linha do Dockerfile que quase ninguém lê duas vezes.
Pontos-chave
- Um processo Puma tem um número fixo de vagas de requisição, workers vezes threads, e no exemplo da lição são seis.
- Servir um arquivo estático ocupa uma dessas vagas pelo tempo que o cliente levar para recebê-lo.
- Um cliente lento segura uma thread Ruby fazendo um trabalho que não envolve Ruby.
- O nginx resolve com um laço de eventos, ao custo de um segundo software para operar.
- O Thruster assume o mesmo trabalho sem configuração e sem segundo contêiner.
Continue com a aula 2
A linha no Dockerfile
Encontra o Thruster no Gemfile e no Dockerfile, segue a árvore de processos e explica a verificação do entrypoint.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.