Sobrevivendo à exaustão de conexões no MariaDB com CloudLinux sem downtime
Voltar para blog

Sobrevivendo à exaustão de conexões no MariaDB com CloudLinux sem downtime

25/10/2026 · 6 min · Banco de Dados

Em servidores compartilhados com dezenas ou centenas de contas, um único site desregulado pode comprometer a estabilidade de todos os demais. Um cenário clássico ocorre quando uma aplicação de atendimento em tempo real (como o LiveZilla) ou um plugin mal otimizado começa a abrir dezenas de conexões concorrentes no banco de dados.

Em uma situação recente, um único usuário (user_cliente) enfileirou 88 conexões simultâneas no MariaDB 10.11, levando o serviço ao teto global de max_connections e travando as consultas de todas as outras contas hospedadas na máquina.

O detalhe intrigante foi que o CloudLinux LVE Manager já estava ativado com limites restritivos para o usuário:

Mesmo com essas travas ativas, o comando mysqladmin proc stat continuava registrando 88 conexões ativas pertencentes ao mesmo usuário.

Neste artigo, vamos entender por que os limites do LVE no painel não contiveram a sobrecarga sozinhos, o que acontece no kernel durante esse tipo de travamento e como estabilizar o banco a quente, sem causar downtime em horário de pico.


1. Por que os limites do LVE pareceram não funcionar#

Para compreender a causa desse comportamento, precisamos olhar como o kernel Linux enxerga o agendamento de processos e a comunicação entre o servidor web e o banco de dados.

O LVE monitora e restringe processos que rodam sob o UID do usuário (user_cliente), como os workers do PHP-FPM ou processos CGI. No entanto, quando o script PHP chama mysqli_connect() ou instancia um objeto PDO, ele abre um descritor de arquivo de rede ou socket apontando para o binário do MariaDB (mysqld).

Assim que a conexão é aceita, o processamento interno da consulta ocorre no contexto do processo mysqld, que executa sob o UID de sistema mysql. Ou seja, a execução da query acontece fora da jaula do usuário, a menos que o MySQL Governor esteja totalmente acoplado ao binário do banco por meio de hooks específicos no kernel.

A armadilha do estrangulamento de CPU#

Nesse cenário, limitar a CPU do usuário pelo LVE para 200% produziu um efeito colateral inesperado:

  1. Com pouca CPU disponível, os scripts PHP do LiveZilla demoravam muito mais para processar cada requisição web.
  2. Como os scripts ficavam lentos ou atingiam limites de tempo de execução, eles nunca chegavam a invocar a função nativa de encerramento (mysql_close) para enviar o pacote de término (QUIT).
  3. No lado do MariaDB, as conexões permaneciam abertas em estado de espera (Sleep) ou executando subqueries pesadas, acumulando conexões zumbis na memória até bater no limite global do servidor.

O próprio estrangulamento da CPU no PHP alimentou o represamento de conexões no motor de banco de dados.


2. Diagnóstico no terminal e descarte de hipóteses#

Antes de aplicar qualquer medida corretiva, é fundamental inspecionar a lista de processos do MariaDB para verificar o tempo de atividade das threads:

mysqladmin proc v | grep user_cliente

A listagem expôs dezenas de consultas ativas marcando tempos entre 380 e 2.377 segundos (quase 40 minutos rodando em segundo plano).

Para isolar a causa exata, convém descartar os principais cenários de borda:

  1. Travamentos de tabela em engines legadas (MyISAM): se a tabela utilizasse MyISAM, uma instrução INSERT bloquearia todas as outras leituras na mesma tabela.
  1. Deadlocks no InnoDB: transações concorrentes disputando os mesmos registros.
  1. Esgotamento de portas TCP (TIME_WAIT): acúmulo de sockets pendentes de liberação na pilha de rede.
  1. Intervenção do OOM Killer: se as dezenas de conexões abertas estivessem consumindo memória acima do limite físico da máquina.

O resultado confirmou que a maioria das conexões estava presa em tabelas temporárias ou aguardando respostas de scripts PHP que haviam congelado.

Se quisermos observar como o MariaDB aceita novas conexões em nível de chamadas de sistema, podemos usar o strace:

strace -fp $(pidof mysqld) -e trace=accept,accept4

3. Investigando o MySQL governor#

A reação lógica seria usar o utilitário dbctl para aplicar um teto de conexões por usuário:

dbctl set user_cliente --max_user_connections 30

Porém, ao inspecionar os processos em execução no servidor:

ps faux | grep dbgovernor

O retorno mostrava apenas o coletor auxiliar sentry_daemon.py. O daemon principal dbgovernor não estava rodando.

Ao checar a instalação:

yum install governor-mysql -y

O pacote básico já constava instalado, mas o binário do MariaDB em execução no sistema era a versão padrão da distribuição, e não o binário compilado pelo CloudLinux (cl-mariadb1011):

mysql -V

Para que o CloudLinux consiga interceptar chamadas internas do banco e aplicar regras automáticas de estrangulamento, é necessário substituir o pacote oficial pelos pacotes específicos do CloudLinux:

/usr/share/lve/dbgovernor/mysqlgovernor.py --set-mysql-version=mariadb1011
/usr/share/lve/dbgovernor/mysqlgovernor.py --install

O comando --install remove os pacotes do MariaDB padrão e instala a versão com suporte a LVE. No entanto, fazer essa troca durante o dia derruba o banco de dados temporariamente, inviabilizando a operação em horário comercial.


4. Plano de contenção a quente (sem reiniciar o MariaDB)#

Para restaurar a estabilidade imediata do servidor sem derrubar as conexões dos demais clientes, aplicamos três medidas defensivas em tempo de execução.

Passo 1: Limite rígido de conexões por usuário via SQL#

Em vez de depender de ferramentas externas, configuramos o próprio motor de autenticação do MariaDB para rejeitar novas conexões desse usuário assim que ele atingir 20 conexões simultâneas:

ALTER USER 'user_cliente'@'localhost' WITH MAX_USER_CONNECTIONS 20;
ALTER USER 'user_cliente'@'127.0.0.1' WITH MAX_USER_CONNECTIONS 20;
FLUSH PRIVILEGES;

Com essa diretiva ativa na memória, qualquer tentativa da aplicação de abrir a 21ª conexão é rejeitada com o erro 1226 (User has exceeded the 'max_user_connections' resource). O site do cliente passa a conter suas próprias requisições sem afetar nenhum outro inquilino do servidor.

Passo 2: Redução dinâmica dos tempos limite de inatividade#

Para encerrar conexões que ficaram órfãs após o encerramento dos scripts PHP, reduzimos os parâmetros de timeout globalmente sem reiniciar o daemon:

SET GLOBAL wait_timeout = 60;
SET GLOBAL interactive_timeout = 60;

Para garantir que a regra persista após futuros reinícios, incluímos as diretivas no /etc/my.cnf:

[mysqld]
wait_timeout = 60
interactive_timeout = 60
max_statement_time = 120

A flag max_statement_time = 120 define um teto máximo em segundos para consultas SELECT, cancelando automaticamente buscas que demorem mais de dois minutos para concluir.

Passo 3: Watchdog defensivo via crontab#

Como camada complementar de segurança, criamos um script leve para monitorar e encerrar consultas anormalmente longas do usuário problemático:

Crie o arquivo /usr/local/bin/mysql_limit_user.sh:

#!/bin/bash
# Localiza threads do usuario especifico rodando ha mais de 120 segundos
THREADS=$(mysql -Ne "SELECT id FROM information_schema.processlist WHERE user='user_cliente' AND time > 120")

if [ -n "$THREADS" ]; then
    for ID in $THREADS; do
        mysql -e "KILL $ID"
        logger -t MYSQL_WATCHDOG "Encerrada thread $ID do usuario user_cliente por excesso de tempo"
    done
fi

Dê permissão de execução ao script:

chmod +x /usr/local/bin/mysql_limit_user.sh

E configure a execução a cada dois minutos no cron do root:

(crontab -l 2>/dev/null; echo "*/2 * * * * /usr/local/bin/mysql_limit_user.sh >/dev/null 2>&1") | crontab -

5. Boas práticas para estabilidade contínua do banco de dados#

Após controlar o incidente sem paradas não planejadas, algumas providências definitivas garantem a tranquilidade do ambiente:

  1. Agende a instalação do MySQL Governor para uma janela de manutenção: em horário de baixo tráfego (geralmente de madrugada), execute a troca para a versão cl-mariadb1011 e inicie o serviço dbgovernor para que o CloudLinux passe a monitorar consultas no kernel de forma nativa.
  2. Revise as consultas da aplicação ofensora: em casos com LiveZilla ou fóruns legados, consultas de contagem (SELECT COUNT(*)) sobre tabelas históricas sem índices adequados são as principais vilãs. Criar índices em colunas de busca e arquivar conversas antigas alivia a carga de I/O em disco.
  3. Mantenha o teto de MAX_USER_CONNECTIONS em contas com histórico de abuso: estabelecer um limite individual de 20 ou 30 conexões para aplicações de chat ou e-commerce movimentados evita que surtos inesperados de tráfego consumam todo o pool global de conexões do servidor.

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