Você pode derrubar um banco de dados MySQL de várias maneiras. Algumas maneiras óbvias são desligar o host, retirar o cabo de alimentação ou encerrar o processo mysqld com SIGKILL para simular um comportamento de desligamento sujo do MySQL. Mas também existem maneiras menos sutis de travar deliberadamente seu servidor MySQL e, em seguida, ver que tipo de reação em cadeia ele desencadeia. Por que você quer fazer isso? A falha e a recuperação podem ter muitos casos de canto, e entendê-los pode ajudar a reduzir o elemento surpresa quando as coisas acontecem na produção. Idealmente, você gostaria de simular falhas em um ambiente controlado e, em seguida, projetar e testar procedimentos de failover de banco de dados.
Existem várias áreas no MySQL que podemos abordar, dependendo de como você deseja que ele falhe ou falhe. Você pode corromper o tablespace, estourar os buffers e caches do MySQL, limitar os recursos para privar o servidor e também mexer nas permissões. Nesta postagem do blog, mostraremos alguns exemplos de como travar um servidor MySQL em um ambiente Linux. Alguns deles seriam adequados para e. Instâncias do Amazon RDS, nas quais você não teria acesso ao host subjacente.
Matar, matar, matar, morrer, morrer, morrer
A maneira mais fácil de falhar em um servidor MySQL é simplesmente matar o processo ou host, e não dar ao MySQL a chance de fazer um desligamento normal. Para simular uma falha do mysqld, basta enviar o sinal 4, 6, 7, 8 ou 11 para o processo:
$ kill -11 $(pidof mysqld)
Ao olhar para o log de erros do MySQL, você pode ver as seguintes linhas:
11:06:09 UTC - mysqld got signal 11 ;
This could be because you hit a bug. It is also possible that this binary
or one of the libraries it was linked against is corrupt, improperly built,
or misconfigured. This error can also be caused by malfunctioning hardware.
Attempting to collect some information that could help diagnose the problem.
As this is a crash and something is definitely wrong, the information
collection process might fail.
..
Attempting backtrace. You can use the following information to find out
where mysqld died. If you see no messages after this, something went
terribly wrong...
Você também pode usar kill -9 (SIGKILL) para matar o processo imediatamente. Mais detalhes sobre o sinal do Linux podem ser encontrados aqui. Alternativamente, você pode usar uma maneira mais cruel no lado do hardware, como puxar o cabo de alimentação, pressionar o botão de reinicialização forçada ou usar um dispositivo de esgrima para STONITH.
Acionando OOM
As ofertas populares do MySQL na nuvem, como Amazon RDS e Google Cloud SQL, não têm uma maneira direta de travá-las. Em primeiro lugar, porque você não terá nenhum acesso no nível do sistema operacional à instância do banco de dados e, em segundo lugar, porque o provedor usa um servidor MySQL com patch proprietário. Uma maneira é estourar alguns buffers e deixar o gerenciador de memória insuficiente (OOM) expulsar o processo do MySQL.
Você pode aumentar o tamanho do buffer de classificação para algo maior do que a RAM pode manipular e disparar várias consultas de classificação do mysql no servidor MySQL. Vamos criar uma tabela de 10 milhões de linhas usando o sysbench em nossa instância do Amazon RDS, para que possamos criar uma classificação enorme:
$ sysbench \
--db-driver=mysql \
--oltp-table-size=10000000 \
--oltp-tables-count=1 \
--threads=1 \
--mysql-host=dbtest.cdw9q2wnb00s.ap-tokyo-1.rds.amazonaws.com \
--mysql-port=3306 \
--mysql-user=rdsroot \
--mysql-password=password \
/usr/share/sysbench/tests/include/oltp_legacy/parallel_prepare.lua \
run
Altere o sort_buffer_size para 5G (nossa instância de teste é db.t2.micro - 1 GB, 1vCPU) acessando Amazon RDS Dashboard -> Parameter Groups -> Create Parameter Group -> especifique o nome do grupo -> Edit Parameters -> escolha "sort_buffer_size" e especifique o valor como 5368709120.
Aplique as alterações do grupo de parâmetros acessando Instances -> Instance Action -> Modify -> Database Options -> Database Parameter Group -> e escolha nosso grupo de parâmetros recém-criado. Em seguida, reinicie a instância do RDS para aplicar as alterações.
Uma vez ativado, verifique o novo valor de sort_buffer_size :
MySQL [(none)]> select @@sort_buffer_size;
+--------------------+
| @@sort_buffer_size |
+--------------------+
| 5368709120 |
+--------------------+
Em seguida, dispare 48 consultas simples que exigem classificação de um cliente:
$ for i in {1..48}; do (mysql -urdsroot -ppassword -h dbtest.cdw9q2wnb00s.ap-tokyo-1.rds.amazonaws.com -e 'SELECT * FROM sbtest.sbtest1 ORDER BY c DESC >/dev/null &); done
Se você executar o acima em um host padrão, você notará que o servidor MySQL será encerrado e você poderá ver as seguintes linhas aparecerem no syslog ou dmesg do SO:
[164199.868060] Out of memory: Kill process 47060 (mysqld) score 847 or sacrifice child
[164199.868109] Killed process 47060 (mysqld) total-vm:265264964kB, anon-rss:3257400kB, file-rss:0kB
Com o systemd, o MySQL ou o MariaDB serão reiniciados automaticamente, assim como o Amazon RDS. Você pode ver que o tempo de atividade da nossa instância do RDS será redefinido para 0 (sob o status mysqladmin) e o valor 'Latest restore time' (em RDS Dashboard) será atualizado no momento em que caiu.
Corrompendo os dados
O InnoDB tem seu próprio tablespace de sistema para armazenar dicionário de dados, buffers e segmentos de rollback dentro de um arquivo chamado ibdata1. Ele também armazena o tablespace compartilhado se você não configurar innodb_file_per_table (habilitado por padrão no MySQL 5.6.6+). Podemos apenas zerar este arquivo, enviar uma operação de gravação e liberar tabelas para travar o mysqld:
# empty ibdata1
$ cat /dev/null > /var/lib/mysql/ibdata1
# send a write
$ mysql -uroot -p -e 'CREATE TABLE sbtest.test (id INT)'
# flush tables
$ mysql -uroot -p -e 'FLUSH TABLES WITH READ LOCK; UNLOCK TABLES'
Depois de enviar uma gravação, no log de erros, você notará:
2017-11-15T06:01:59.345316Z 0 [ERROR] InnoDB: Tried to read 16384 bytes at offset 98304, but was only able to read 0
2017-11-15T06:01:59.345332Z 0 [ERROR] InnoDB: File (unknown): 'read' returned OS error 0. Cannot continue operation
2017-11-15T06:01:59.345343Z 0 [ERROR] InnoDB: Cannot continue operation.
Neste ponto, o mysql irá travar porque não pode executar nenhuma operação, e após a limpeza, você obterá as linhas "mysqld got signal 11" e o mysqld será desligado. Para limpar, você deve remover o ibdata1 corrompido, bem como o ib_logfile* porque os arquivos de log redo não podem ser usados com um novo tablespace do sistema que será gerado pelo mysqld na próxima reinicialização. A perda de dados é esperada.
Para tabelas MyISAM, podemos mexer com .MYD (arquivo de dados MyISAM) e .MYI (índice MyISAM) no diretório de dados MySQL. Por exemplo, o comando a seguir substitui qualquer ocorrência da string "F" por "9" dentro de um arquivo:
$ replace F 9 -- /var/lib/mysql/sbtest/sbtest1.MYD
Em seguida, envie algumas gravações (por exemplo, usando sysbench) para a tabela de destino e execute a limpeza:
mysql> FLUSH TABLE sbtest.sbtest1;
O seguinte deve aparecer no log de erros do MySQL:
2017-11-15T06:56:15.021564Z 448 [ERROR] /usr/sbin/mysqld: Incorrect key file for table './sbtest/sbtest1.MYI'; try to repair it
2017-11-15T06:56:15.021572Z 448 [ERROR] Got an error from thread_id=448, /export/home/pb2/build/sb_0-24964902-1505318733.42/rpm/BUILD/mysql-5.7.20/mysql-5.7.20/storage/myisam/mi_update.c:227
A tabela MyISAM será marcada como travada e é necessário executar a instrução REPAIR TABLE para torná-la acessível novamente.
Limitação dos recursos
Também podemos aplicar o limite de recursos do sistema operacional ao nosso processo mysqld, por exemplo, número de descritores de arquivos abertos. Usar a variável open_file_limit (o padrão é 5000) permite que o mysqld reserve descritores de arquivo usando o comando setrlimit(). Você pode definir esta variável relativamente pequena (apenas o suficiente para o mysqld iniciar) e então enviar várias consultas para o servidor MySQL até atingir o limite.
Se o mysqld estiver sendo executado em um servidor systemd, podemos configurá-lo no arquivo de unidade systemd localizado em /usr/lib/systemd/system/mysqld.service e alterar o seguinte valor para algo menor (o padrão do systemd é 6000):
# Sets open_files_limit
LimitNOFILE = 30
Aplique as alterações ao systemd e reinicie o servidor MySQL:
$ systemctl daemon-reload
$ systemctl restart mysqld
Então, comece a enviar novas conexões/consultas que contam em diferentes bancos de dados e tabelas para que o mysqld tenha que abrir vários arquivos. Você notará o seguinte erro:
2017-11-16T04:43:26.179295Z 4 [ERROR] InnoDB: Operating system error number 24 in a file operation.
2017-11-16T04:43:26.179342Z 4 [ERROR] InnoDB: Error number 24 means 'Too many open files'
2017-11-16T04:43:26.179354Z 4 [Note] InnoDB: Some operating system error numbers are described at http://dev.mysql.com/doc/refman/5.7/en/operating-system-error-codes.html
2017-11-16T04:43:26.179363Z 4 [ERROR] InnoDB: File ./sbtest/sbtest9.ibd: 'open' returned OS error 124. Cannot continue operation
2017-11-16T04:43:26.179371Z 4 [ERROR] InnoDB: Cannot continue operation.
2017-11-16T04:43:26.372605Z 0 [Note] InnoDB: FTS optimize thread exiting.
2017-11-16T04:45:06.816056Z 4 [Warning] InnoDB: 3 threads created by InnoDB had not exited at shutdown!
Neste ponto, quando o limite for atingido, o MySQL irá congelar e não poderá realizar nenhuma operação. Ao tentar se conectar, você verá o seguinte depois de um tempo:
$ mysql -uroot -p
ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet', system error: 104
Mexer nas permissões
O processo mysqld é executado pelo usuário "mysql", o que significa que todos os arquivos e diretórios que ele precisa acessar são de propriedade do usuário/grupo mysql. Ao mexer com a permissão e propriedade, podemos tornar o servidor MySQL inútil:
$ chown root:root /var/lib/mysql
$ chmod 600 /var/lib/mysql
Gere algumas cargas para o servidor e, em seguida, conecte-se ao servidor MySQL e libere todas as tabelas no disco:
mysql> FLUSH TABLES WITH READ LOCK; UNLOCK TABLES;
Neste momento, o mysqld ainda está em execução, mas é meio inútil. Você pode acessá-lo através de um cliente mysql, mas não pode fazer nenhuma operação:
mysql> SHOW DATABASES;
ERROR 1018 (HY000): Can't read dir of '.' (errno: 13 - Permission denied)
Para limpar a bagunça, defina as permissões corretas:
$ chown mysql:mysql /var/lib/mysql
$ chmod 750 /var/lib/mysql
$ systemctl restart mysqld
Bloquear
FLUSH TABLE WITH READ LOCK (FTWRL) pode ser destrutivo em várias condições. Como, por exemplo, em um cluster Galera em que todos os nós podem processar gravações, você pode usar essa instrução para bloquear o cluster de dentro de um dos nós. Esta instrução simplesmente interrompe outras consultas a serem processadas pelo mysqld durante a liberação até que o bloqueio seja liberado, o que é muito útil para processos de backup (tabelas MyISAM) e instantâneos do sistema de arquivos.
Embora esta ação não falhe ou derrube seu servidor de banco de dados durante o bloqueio, a consequência pode ser enorme se a sessão que mantém o bloqueio não o liberar. Para tentar isso, basta:
mysql> FLUSH TABLES WITH READ LOCK;
mysql> exit
Em seguida, envie um monte de novas consultas para o mysqld até atingir o max_connections valor. Obviamente, você não pode voltar a mesma sessão que a anterior quando estiver fora. Portanto, o bloqueio estará rodando infinitamente e a única maneira de liberar o bloqueio é matando a consulta, por outro usuário com privilégio SUPER (usando outra sessão). Ou mate o próprio processo mysqld, ou execute uma reinicialização forçada.
Isenção de responsabilidade
Este blog foi escrito para fornecer alternativas aos administradores de sistema e DBAs para simular cenários de falha com o MySQL. Não tente isso em seu servidor de produção :-)