Otimizando Processos de Trabalho do Nginx para Máximo Desempenho: Um Guia Prático

Otimize seu servidor Nginx para tráfego de alto volume usando este guia prático para configurar diretivas de desempenho essenciais. Aprenda as melhores práticas para definir `worker_processes` de acordo com os núcleos da CPU, maximizar a concorrência com `worker_connections` e garantir conformidade com os limites de descritores de arquivo do sistema operacional (`ulimit`). Este artigo fornece exemplos de configuração acionáveis e dicas essenciais de ajuste para minimizar a latência e aumentar drasticamente a taxa de transferência do seu servidor.

Otimizando Processos de Trabalho do Nginx para Máximo Desempenho: Um Guia Prático

O Nginx pode lidar com muitas conexões simultâneas com uma pequena pegada de processos, mas apenas se seus limites de trabalho estiverem alinhados com a máquina subjacente. As duas configurações que as pessoas procuram primeiro são worker_processes e worker_connections. Elas são úteis, mas também são fáceis de ajustar em excesso. Definir ambas para números enormes não cria capacidade gratuita. Isso pode apenas mover o gargalo para descritores de arquivo, memória, servidores upstream ou a pilha de rede.

O objetivo prático é dar ao Nginx trabalhadores suficientes para usar os núcleos da CPU que ele tem, slots de conexão suficientes para tráfego real e limites de sistema operacional suficientes para evitar atingir o teto durante picos normais.

Entendendo a Arquitetura de Trabalho do Nginx

O Nginx opera usando um modelo mestre-trabalhador. O Processo Mestre é responsável por ler e validar a configuração, vincular-se a portas e gerenciar os processos de trabalho. Ele executa tarefas não críticas como monitorar recursos do sistema e reiniciar trabalhadores se necessário.

Processos de Trabalho são onde o trabalho pesado ocorre. Esses processos são single-threaded (na compilação padrão do Nginx) e usam chamadas de sistema não bloqueantes. Cada trabalhador lida com milhares de conexões simultâneas eficientemente usando um loop de eventos, permitindo que um processo gerencie múltiplas requisições sem bloquear, o que é chave para o desempenho do Nginx.

A otimização adequada envolve equilibrar o número de trabalhadores (vinculando-os aos recursos da CPU) e definir o número máximo de conexões que cada trabalhador pode manipular.

Configurando worker_processes: O Fator Núcleo da CPU

A diretiva worker_processes determina quantos processos de trabalho o Nginx deve criar. Esta configuração afeta diretamente como o Nginx utiliza os recursos de CPU do seu servidor.

Melhor Prática: Correspondendo Trabalhadores aos Núcleos

A prática mais comum e altamente recomendada é definir o número de processos de trabalho igual ao número de núcleos de CPU disponíveis no seu servidor. Isso garante que cada núcleo seja utilizado eficientemente sem incorrer em sobrecarga excessiva de troca de contexto.

Se o número de trabalhadores exceder o número de núcleos, o sistema operacional deve frequentemente alternar o foco da CPU entre processos concorrentes do Nginx (troca de contexto), o que introduz latência e reduz o desempenho geral.

Usando a Diretiva auto

Para versões modernas do Nginx (1.3.8 e posteriores), a configuração mais simples e eficaz é usar o parâmetro auto. O Nginx detectará automaticamente o número de núcleos de CPU disponíveis e definirá os processos de trabalho de acordo.

# Configuração recomendada para a maioria das implantações
worker_processes auto;

Configuração Manual

Se você precisar de controle manual ou estiver usando uma versão mais antiga, pode especificar o número exato de trabalhadores. Você pode encontrar o número de núcleos usando utilitários do sistema:

# Encontre o número de núcleos de CPU
grep processor /proc/cpuinfo | wc -l

Se o sistema tiver 8 núcleos, a configuração seria assim:

# Definindo manualmente processos de trabalho para 8
worker_processes 8;

Dica: Corresponder ao número de núcleos disponíveis é o ponto de partida mais seguro. Em cargas de trabalho incomuns com muita E/S, você pode testar um valor diferente, mas avalie-o sob tráfego realista antes de mantê-lo. Para servir estático típico, proxy e terminação TLS, auto é geralmente a escolha menos surpreendente.

Configurando worker_connections: O Fator Concorrência

A diretiva worker_connections é configurada dentro do bloco events e define o número máximo de conexões simultâneas que um único processo de trabalho pode manipular. Isso inclui conexões com clientes, conexões com servidores proxy upstream e conexões internas de verificação de saúde.

Calculando Clientes Máximos

O número máximo teórico de conexões de clientes simultâneas que seu servidor Nginx pode manipular é calculado da seguinte forma:

$$\text{Max Clients} = \text{worker_processes} \times \text{worker_connections}$$

Se você tem 4 processos de trabalho e 10.000 conexões de trabalho por processo, o Nginx poderia teoricamente manipular 40.000 conexões simultâneas.

Esse número é apenas um limite superior aproximado. Uma requisição com proxy pode usar uma conexão de cliente e uma conexão upstream ao mesmo tempo. Tráfego WebSocket e de long-polling pode manter slots por muito mais tempo que uma requisição de página normal. Conexões keep-alive também podem permanecer abertas enquanto fazem muito pouco trabalho. Se o Nginx está principalmente servindo arquivos estáticos, a matemática está mais próxima da fórmula simples. Se está atuando como um proxy reverso, deixe margem.

Definindo o Limite de Conexão

É comum definir worker_connections para alguns milhares ou mais em servidores ocupados, assumindo que a memória e os limites de descritores de arquivo possam suportar. Não copie um valor grande cegamente; escolha um valor que corresponda à concorrência esperada mais espaço para picos.

# Exemplo de configuração para o bloco events

events {
    # Máximo de conexões simultâneas por processo de trabalho
    worker_connections 16384;

    # Pode ajudar durante picos, mas teste a justiça sob carga.
    multi_accept on;
}

Restrição de Limites do Sistema (ulimit)

Crucialmente, a configuração worker_connections é limitada pelo limite do sistema operacional no número de descritores de arquivo (FDs) abertos permitidos por processo, muitas vezes controlado pela configuração ulimit -n.

O Nginx não pode abrir mais conexões do que o sistema operacional permite descritores de arquivo. Como cada conexão (socket de cliente, arquivo de log, socket de proxy) requer um descritor de arquivo, é vital que o limite do sistema seja definido alto o suficiente.

Verificando e Aumentando Limites de Descritores de Arquivo

  1. Verifique o limite atual:

    ulimit -n
    
  2. Aumente temporariamente o limite (para a sessão atual):

    ulimit -n 65536
    
  3. Aumente permanentemente o limite (via /etc/security/limits.conf):

    Adicione as seguintes linhas, substituindo nginx_user pelo usuário que o Nginx executa (frequentemente www-data ou nginx):

    # /etc/security/limits.conf
    nginx_user soft nofile 65536
    nginx_user hard nofile 65536
    

Aviso: Certifique-se de que o limite de descritores de arquivo por processo para o usuário do trabalhador Nginx seja maior que worker_connections, com espaço extra para logs, sockets upstream, arquivos de cache e outros arquivos abertos. Limites de todo o sistema também importam, mas o limite por processo é o que mais frequentemente surpreende as pessoas.

Se o Nginx é gerenciado pelo systemd, /etc/security/limits.conf pode não ser suficiente. Muitas distribuições iniciam serviços com limites do arquivo de unidade. Verifique o limite ativo com:

cat /proc/$(pgrep -o nginx)/limits | grep "open files"

Para uma substituição do systemd, use:

sudo systemctl edit nginx

Em seguida, adicione:

[Service]
LimitNOFILE=65536

Recarregue o systemd e reinicie o Nginx durante uma janela de manutenção:

sudo systemctl daemon-reload
sudo systemctl restart nginx

Ajuste Avançado e Monitoramento

Além das diretivas principais, algumas considerações adicionais podem ajudar a ajustar o desempenho:

1. Fixando Processos de Trabalho

Em ambientes de alto desempenho, especialmente em sistemas com múltiplos sockets de CPU (arquiteturas NUMA), você pode querer usar a diretiva worker_cpu_affinity. Isso diz ao sistema operacional para restringir processos de trabalho específicos a CPUs específicas, o que pode melhorar o desempenho garantindo que os caches da CPU permaneçam quentes e evitando problemas de localidade de memória.

Exemplo para um sistema de 8 núcleos:

worker_processes 8;
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;

Esta configuração é complexa e geralmente benéfica apenas para situações de carga extrema; worker_processes auto é suficiente para a maioria das implantações.

2. Monitorando Métricas de Desempenho

Após aplicar otimizações, é crucial monitorar o impacto. Use o módulo Nginx Stub Status (ou uma ferramenta como Prometheus/Grafana) para rastrear métricas chave:

Métrica Descrição Verificação de Otimização
Conexões Ativas Total de conexões atualmente manipuladas. Deve estar abaixo do máximo teórico.
Lendo/Escrevendo/Aguardando Conexões em diferentes estados. Contagens altas de Aguardando frequentemente indicam Keep-Alives HTTP de longa duração (bom) ou recursos de processamento insuficientes (ruim).
Taxa de Requisições Requisições por segundo. Usado para medir a melhoria real de desempenho após mudanças de configuração.

Se você observar alta utilização de CPU em todos os núcleos e altas taxas de requisição, seus worker_processes provavelmente estão configurados corretamente. Se você tem núcleos de CPU ociosos durante picos de tráfego, considere revisar sua configuração ou verificar operações de E/S bloqueantes fora do Nginx.

3. Estratégia de Sobrecarga de Conexão

Se o servidor atingir o limite máximo de conexão (worker_processes * worker_connections), novas conexões podem falhar ou ficar em filas até expirarem. Aumentar worker_connections só pode ajudar quando o Nginx é o gargalo real. Se servidores de aplicação upstream estão saturados, aumentar o limite pode piorar a sensação de falha porque mais requisições se acumulam atrás de backends lentos.

Use o log de erros como um sinal. Mensagens como worker_connections are not enough apontam diretamente para limites do Nginx. Um aumento em upstream timed out, connect() failed, ou respostas 502/504 aponta mais para capacidade do backend, problemas de rede ou configurações de timeout.

Uma Configuração Inicial Razoável

Para um proxy reverso pequeno ou médio, esta é uma linha de base sensata:

worker_processes auto;
worker_rlimit_nofile 65536;

events {
    worker_connections 8192;
    multi_accept off;
}

Por que multi_accept off aqui? É o padrão conservador em muitos sistemas. Ativá-lo pode ajudar um trabalhador a drenar rapidamente uma fila de accept pendente, mas sob alguns padrões de tráfego pode permitir que um trabalhador pegue um lote grande enquanto outros ficam ociosos. Se você tem tráfego irregular e uma razão testada para ativá-lo, faça isso. Se você está ajustando um servidor web de propósito geral, mantenha a linha de base simples e meça primeiro.

Se o servidor lida com muitas conexões WebSocket, Server-Sent Events, ou streams de API de longa duração, aumente o limite de conexão mais agressivamente e preste muita atenção à memória. Um servidor com 20.000 clientes WebSocket principalmente ociosos tem um perfil diferente de um servidor fazendo 20.000 requisições curtas de arquivos estáticos.

Como Validar a Mudança

Antes de mudar a produção, capture uma pequena linha de base:

nginx -T | grep -E 'worker_processes|worker_connections|worker_rlimit_nofile'
ss -s
ulimit -n

Após a mudança, verifique se o Nginx realmente a carregou:

sudo nginx -t
sudo systemctl reload nginx
ps -o pid,comm,nlwp,pcpu,pmem -C nginx
cat /proc/$(pgrep -n nginx)/limits | grep "open files"

Em seguida, observe o comportamento durante o tráfego real. Se todos os núcleos da CPU estão ocupados e a latência aumenta, o Nginx pode estar fazendo trabalho útil e atingindo a capacidade da CPU. Se a CPU está baixa mas as conexões estão enfileirando ou expirando, olhe para descritores de arquivo, saturação upstream, resolução DNS, E/S de disco, ou limites de firewall. O ajuste de trabalhadores é uma alavanca, não toda a história de desempenho.

Lendo os Números no Contexto

Um erro comum é tratar "conexões ativas" como a mesma coisa que "usuários ativos". Não é. Um navegador pode abrir várias conexões para ativos. Um cliente de API pode manter uma conexão viva entre requisições. Um cliente WebSocket pode manter uma conexão por horas enquanto envia quase nenhum tráfego. Quando você dimensiona worker_connections, pense em termos de sockets simultâneos, não pessoas.

Para um proxy reverso, lembre-se também do lado upstream. Se 4.000 clientes estão esperando por respostas com proxy, o Nginx também pode estar segurando milhares de sockets upstream. É por isso que um servidor pode ficar sem descritores de arquivo antes que o cálculo simples do lado do cliente diga que deveria. Isso é especialmente visível quando a aplicação upstream desacelera: as requisições ficam abertas por mais tempo, a concorrência aumenta, e o Nginx começa a consumir mais sockets mesmo que a taxa de requisições recebidas não tenha mudado.

As configurações de keep-alive também influenciam isso. Timeouts keep-alive longos reduzem a rotatividade de conexões, o que pode ajudar sites ocupados, mas também mantêm sockets ociosos por mais tempo. Timeouts keep-alive muito curtos liberam sockets mais rápido, mas podem aumentar handshakes TLS e sobrecarga de estabelecimento de conexão. Não há valor perfeito; use a forma do tráfego como guia. Um site público com muitas visitas curtas pode precisar de um equilíbrio diferente de uma API interna com um pequeno número de clientes persistentes.

Se você está ajustando dentro de um contêiner, verifique os limites dentro do contêiner e no nível do host ou orquestrador. Um pod Kubernetes, contêiner Docker, ou serviço systemd pode ter um limite nofile menor que o shell do host que você usou para testar. Sempre verifique o processo Nginx em execução, não apenas sua sessão de login.

Resumo das Melhores Práticas

Diretiva Valor Recomendado Justificativa
worker_processes auto (ou contagem de núcleos) Garante utilização ótima da CPU e minimiza sobrecarga de troca de contexto.
worker_connections Comece com alguns milhares; aumente com base na concorrência medida Fornece margem de conexão sem esconder outros gargalos.
Limite do SO (ulimit -n) Maior que as necessidades de conexão por trabalhador, com espaço extra Fornece descritores de arquivo para sockets de cliente, sockets upstream, logs e arquivos de cache.
multi_accept Teste antes de ativar Pode ajudar com picos, mas não é automaticamente melhor para toda carga de trabalho.

A melhor configuração de trabalhador do Nginx é geralmente simples: worker_processes auto, um limite de conexão que reflete a concorrência real, e limites de descritores de arquivo que são altos o suficiente para a carga de trabalho. Ajuste, verifique os limites do processo ativo, e continue observando o log de erros. Se os sintomas apontarem para upstream, corrija o upstream em vez de fazer o Nginx aceitar mais trabalho do que a aplicação pode terminar.