O trabalho que o Puma não deveria fazer

Prévia grátis 3 min de leitura

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.
Aula 1 de 7 · grátis

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.

Digite para buscar...