Autópsia de um timeout global: debugando gargalos de i/o, MySQL e Apache no cPanel
Voltar para blog

Autópsia de um timeout global: debugando gargalos de i/o, MySQL e Apache no cPanel

04/09/2026 · 7 min · Servidores

Quando dezenas de sites em um servidor começam a apresentar lentidão simultânea e o webmail passa a demorar minutos para carregar, o primeiro instinto costuma ser culpar ataques de rede (DDoS) ou picos anômalos de acessos. No entanto, em ambientes que combinam cPanel e CloudLinux, os sintomas visíveis quase sempre são apenas o estágio final de um efeito dominó que começou muito mais fundo.

Recentemente acompanhei um diagnóstico clássico onde múltiplos indicadores pareciam se contradizer: o processador estava ocioso, nenhum usuário estourava limites de recursos e as conexões do banco de dados estavam empilhadas em espera.

Abaixo está o roteiro forense passo a passo que desvendou a verdadeira causa raiz: desde a triagem do CloudLinux até a análise da latência de escrita em nível de bloco nos discos NVMe e o alívio do I/O através da memória RAM.


1. O alerta inicial e o descarte de limites individuais no CloudLinux#

O problema começou com clientes reportando timeouts intermitentes em sites WordPress e lentidão extrema ao tentar autenticar no Roundcube.

Em servidores com CloudLinux, a primeira verificação obrigatória é conferir se alguma conta específica está monopolizando a máquina e atingindo seu teto de processos de entrada (Entry Processes - EP) ou CPU (SPEED). O comando lveinfo (ou top-lve) mostra a fotografia em tempo real:

        ID     EP    PNO    TNO     SPEED      MEM         IO       IOPS
  cliente1      0      1      1        0%        8          0          0
  cliente2      0      1      1        0%        7          0          0
  cliente3      0      2      2        0%       18          0          0
  portal4       2      5      5        0%       14          0          0
  loja5         1      3      3        0%       11          3          0
  site6         3      7      7        2%       17          2          0

Todas as contas estavam com consumo de CPU praticamente nulo e a contagem de processos de entrada estava muito distante dos limites configurados (geralmente entre 20 e 40).

Nenhum usuário isolado estava sendo estrangulado pelo LVE. O problema era generalizado e afetava o servidor inteiro.


2. A avalanche de conexões em sleep no MySQL#

Quando a lentidão é coletiva, o motor de banco de dados costuma ser o suspeito número um. Ao verificar os processos ativos no MariaDB:

mysqladmin proc status

O terminal retornou uma lista com centenas de conexões travadas:

+---------+-----------------------+-----------+-----------------------+---------+------+----------+------------------+
| Id      | User                  | Host      | db                    | Command | Time | State    | Info             |
+---------+-----------------------+-----------+-----------------------+---------+------+----------+------------------+
| 2319092 | app_usuario1          | localhost | app_banco1            | Sleep   | 16   |          |                  |
| 2319246 | app_usuario2          | localhost | app_banco2            | Sleep   | 29   |          |                  |
| 2319268 | app_usuario3          | localhost | app_banco3            | Sleep   | 27   |          |                  |
| 2319394 | portal_blog           | localhost | portal_blog           | Sleep   | 18   |          |                  |
| 2319413 | loja_db               | localhost | loja_db               | Sleep   | 17   |          |                  |
| 2319681 | root                  | localhost |                       | Query   | 0    | starting | show processlist |
+---------+-----------------------+-----------+-----------------------+---------+------+----------+------------------+
Uptime: 51430  Threads: 144  Questions: 20479295  Slow queries: 266  Opens: 182740  Open tables: 2000  Queries per second avg: 398.197

O que essa saída revela:#

  1. Quase tudo em Sleep: O estado Sleep significa que a consulta SQL já terminou de ser executada pelo motor do banco. No entanto, o processo PHP que abriu a conexão ainda não a encerrou porque estava aguardando outros recursos (como I/O de disco ou buffers de rede do servidor web).
  2. Cache de tabelas no teto: O valor Open tables: 2000 estava rigidamente cravado no limite máximo definido na configuração (table_open_cache = 2000).
  3. Alto volume de requisições: Uma média de quase 400 consultas por segundo (QPS) operava sobre o motor.

O MySQL estava sendo bombardeado e acumulando filas, mas ele era apenas a primeira vítima visível do efeito dominó.


3. Investigando CPU e tráfego de rede#

Ao checar o uso geral de processamento com o top, o consumo de CPU estava relativamente baixo (mais de 90% ocioso), com apenas processos de auditoria e workers do Apache no topo:

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
2993897 nobody    20   0 2123948 387044  18404 S  28.6   0.3   1:05.26 /usr/sbin/httpd -k start
   2086 root      20   0 4204892 127552  17380 S  14.3   0.1  26:44.25 /usr/sbin/imunify-auditd-log-reader
2994423 nobody    20   0 2123952 376868  18368 S  14.3   0.3   1:09.60 /usr/sbin/httpd -k start

Para verificar se algum IP estava tentando saturar a porta 80 com requisições concorrentes:

netstat -anp | grep :80 | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -n | tail -10

O resultado mostrou que as conexões estavam amplamente distribuídas entre visitantes normais, com nenhum endereço isolado sustentando mais de 10 conexões simultâneas. Ataques externos de camada 7 estavam descartados.


4. O gargalo real: latência assimétrica no espelhamento RAID NVMe#

Se o processador está livre, a rede está calma e o banco está acumulando conexões esperando resposta, o próximo ponto de inspeção obrigatório é o subsistema de armazenamento.

Ao executar o utilitário iostat com intervalo de amostragem detalhado:

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           1.39    0.00    0.78    0.07    0.00   97.76

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   wrqm/s  %wrqm w_await wareq-sz  f_await  aqu-sz  %util
md127            0.00      0.00     0.00   0.00    0.00     0.00   41.00    776.50     0.00   0.00    9.68    18.94     0.00    0.40  14.00
nvme0n1          0.00      0.00     0.00   0.00    0.00     0.00   73.00    735.00     0.00   0.00    0.29    10.07     0.31    0.03   1.20
nvme1n1          0.00      0.00     0.00   0.00    0.00     0.00   76.00    807.00     0.00   0.00    9.16    10.62     8.70    0.98  25.50

A discrepância técnica entre os discos:#

O servidor utilizava um arranjo de espelhamento RAID 1 por software (md127) composto por dois discos NVMe (nvme0n1 e nvme1n1).

Repare na coluna w_await (tempo médio de espera para gravação de blocos):

Em um arranjo RAID 1 (espelhamento síncrono), o sistema operacional só considera a gravação de um bloco finalizada quando ambos os discos confirmam a operação. O servidor inteiro estava engasgando porque a velocidade máxima de I/O da máquina ficava limitada ao disco mais lento.

Inspecionando o modelo do disco problemático#

Ao rodar o smartctl sobre a unidade nvme1n1:

smartctl -a /dev/nvme1n1

O relatório exibiu:

Model Number:                       KINGSTON SNV3S4000G
Total NVM Capacity:                 4,000,787,030,016 [4.00 TB]
SMART overall-health self-assessment test result: PASSED
Percentage Used:                    2%

A integridade SMART estava perfeita e o disco tinha apenas 2% de vida útil consumida. O problema não era defeito de hardware, mas sim de arquitetura de componente:

O modelo Kingston NV3 é um SSD projetado para uso doméstico. Ele utiliza memórias do tipo QLC e arquitetura sem chip de memória dedicado (DRAMless), dependendo exclusivamente de um pequeno cache SLC dinâmico.

Quando submetido à carga de um servidor cPanel corporativo (com dezenas de bancos MySQL gravando logs transacionais e milhares de arquivos de sessão sendo criados por segundo (I/O randômico agressivo)), o cache SLC da unidade se esgota rapidamente. A velocidade de gravação cai para níveis inferiores aos de um disco mecânico antigo, gerando picos brutais de latência.


5. Contenção imediata: usando a memória RAM como escudo contra o disco#

A troca física do disco por uma unidade NVMe corporativa com DRAM dedicada (como linhas Enterprise com memória TLC) exige janela programada de manutenção em datacenter. Para normalizar a máquina em produção de imediato, a saída foi blindar o subsistema de I/O usando a memória RAM.

Verificando a memória disponível:

free -h
               total        used        free      shared  buff/cache   available
Mem:           125Gi        26Gi        18Gi       407Mi        81Gi        98Gi
Swap:           15Gi          0B        15Gi

O servidor contava com massivos 125 GB de memória RAM, dos quais 98 GB estavam completamente livres.

Ao checar o arquivo de configuração /etc/my.cnf que estava em vigor:

[mysqld]
innodb_buffer_pool_size = 4294967296
max_connections = 500
wait_timeout = 60

O MySQL estava configurado para alocar apenas 4 GB (4294967296 bytes) de buffer pool. Isso forçava o motor a ler e gravar constantemente nos NVMes lentos, mesmo com quase 100 GB de memória sobrando no sistema.

Aplicando a otimização no /etc/my.cnf:#

Multipliquei o buffer pool por 8 e aumentei o cache de descritores para manter todas as tabelas em memória:

[client-server]
!includedir /etc/my.cnf.d

[mysqld]
# --- OTIMIZAÇÃO DE MEMÓRIA (Bypass do Disco) ---
# Expandindo o Buffer Pool para manter bases ativas na RAM
innodb_buffer_pool_size = 32G
table_open_cache = 10000
table_definition_cache = 10000

# --- MITIGAÇÃO DE LATÊNCIA DE I/O ---
# Reduz a frequência de fsync síncrono para o disco em transações corriqueiras
innodb_flush_log_at_trx_commit = 2

# --- CAPACIDADE DE CONEXÕES ---
max_allowed_packet = 268435456
open_files_limit = 69000
max_connections = 1000
wait_timeout = 60
interactive_timeout = 60
long_query_time = 5

innodb_file_per_table = 1
sql_mode = ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

Ao reiniciar o MariaDB (systemctl restart mariadb), o banco passou a responder quase 100% das leituras direto da memória RAM, aliviando de imediato a pressão sobre o array NVMe.


6. O gargalo no Apache: broken pipe e travas no splitlogs#

Embora as consultas SQL tenham acelerado brutalmente, o log de erros do Apache ainda acusava falhas:

[log_config:warn] [pid 3922139:tid 3922562] (32)Broken pipe: [client 198.51.100.39:37482] AH00646: Error writing to |/usr/local/cpanel/bin/splitlogs --dir=/etc/apache2/logs/domlogs...
[lsapi:warn] [pid 4030908:tid 4031167] Backend log: PHP Warning: session_start(): open(/opt/alt/php56/var/lib/php/session/sess_..., O_RDWR) failed: Disk quota exceeded (122)

Dois problemas estavam ocorrendo aqui:

  1. Travamento no splitlogs: O utilitário do cPanel responsável por separar o log geral do Apache nos logs individuais de cada domínio (domlogs) não conseguia processar a rajada de conexões pendentes que foram liberadas pelo MySQL. Quando o buffer do pipe estoura, o Apache trava a resposta ao cliente e cospe o erro Broken pipe.
  2. Quota estourada em conta legada: A mensagem Disk quota exceeded não era da partição do sistema (que possuía mais de 1.2 TB livres com apenas 7% de inodes usados), mas sim de uma conta cPanel específica que atingiu o limite de armazenamento do plano e não conseguia salvar sessões do PHP.

Para destravar os pipes e recarregar a configuração do Apache de forma limpa:

# Finaliza eventuais processos zumbis do splitlogs
killall -9 splitlogs 2>/dev/null

# Reconstrói a configuração virtual do Apache
/scripts/rebuildhttpdconf

# Executa o restart forçado do serviço
/scripts/restartsrv_httpd --hard

O Apache voltou a responder imediatamente com a fila zerada. A conta que estava sem espaço teve sua cota ampliada no WHM, eliminando o erro de gravação de sessões.


7. Validando a ponta final com cURL#

Com os serviços restabelecidos, era hora de conferir se a resposta na ponta do usuário final estava normalizada.

Para inspecionar detalhadamente cada etapa da conexão HTTP e do aperto de mão criptográfico (TLS), usei o curl em modo detalhado (-Iv):

curl -Iv https://webmail.meudominio.com.br

O retorno confirmou a recuperação completa da velocidade:

* Host webmail.meudominio.com.br:443 was resolved.
* Connected to webmail.meudominio.com.br (198.51.100.246) port 443
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
* ALPN: server accepted http/1.1
< HTTP/1.1 200 OK
< Content-Length: 37940
< Set-Cookie: webmailsession=...; HttpOnly; path=/; port=443; secure

O handshake SSL em TLSv1.3 foi completado em milissegundos e a página de login do webmail respondeu com HTTP 200 sem nenhum atraso.


Roteiro de sobrevivência para evitar o efeito dominó em servidores#

Este incidente reforça lições técnicas valiosas para quem gerencia servidores de alta demanda:

  1. Hardware doméstico não aguenta I/O randômico de servidor: SSDs DRAMless com memória QLC (como o Kingston NV3) esgotam o cache SLC rapidamente sob gravações contínuas de bancos de dados. Em espelhamento RAID 1, um único disco lento obriga o disco rápido a operar na velocidade do pior membro.
  2. Não economize RAM no buffer pool do MySQL: Se o servidor tem 64 GB ou 128 GB de memória livre, manter o innodb_buffer_pool_size em 2 GB ou 4 GB é um desperdício grave de recursos e uma sobrecarga desnecessária sobre os discos.
  3. Monitore w_await no iostat: Olhar apenas %util ou taxas de transferência em MB/s pode esconder gargalos severos de latência por bloco.
  4. Pipes de log também causam timeout: Daemons auxiliares de log como o splitlogs podem congelar workers do Apache se a taxa de escrita for superior à capacidade de esvaziamento do buffer.

Com as camadas de I/O, memória e servidor web devidamente afinadas, a infraestrutura opera com folga e sem surpresas desagradáveis de timeout global.

Este artigo foi útil?

Deixe uma reação rápida para apoiar o conteúdo:

CC BY-NC

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários