Databázi MySQL můžete odstranit několika způsoby. Některé zřejmé způsoby jsou vypnout hostitele, vytáhnout napájecí kabel nebo tvrdě zabít proces mysqld pomocí SIGKILL, aby se simulovalo nečisté chování při vypínání MySQL. Existují však také méně rafinované způsoby, jak záměrně zhroucení serveru MySQL a poté zjistit, jaký druh řetězové reakce to spustí. Proč byste to chtěli udělat? Selhání a zotavení mohou mít mnoho rohových případů a jejich pochopení může pomoci snížit moment překvapení, když se ve výrobě něco stane. V ideálním případě byste chtěli simulovat selhání v kontrolovaném prostředí a poté navrhnout a otestovat procedury selhání databáze.
Existuje několik oblastí v MySQL, které můžeme řešit, v závislosti na tom, jak chcete, aby selhal nebo havaroval. Můžete poškodit tabulkový prostor, přeplnit vyrovnávací paměti a mezipaměti MySQL, omezit zdroje, aby server vyhladověl, a také si pohrávat s oprávněními. V tomto příspěvku na blogu vám ukážeme několik příkladů, jak zhroucení serveru MySQL v prostředí Linuxu. Některé z nich by byly vhodné např. Instance Amazon RDS, kde byste neměli přístup k základnímu hostiteli.
Zabít, zabít, zabít, zemřít, zemřít, zemřít
Nejjednodušší způsob, jak selhat server MySQL, je jednoduše zabít proces nebo hostitele a nedat MySQL šanci provést elegantní vypnutí. Chcete-li simulovat havárii mysqld, stačí odeslat signál 4, 6, 7, 8 nebo 11 procesu:
$ kill -11 $(pidof mysqld)
Při pohledu na chybový protokol MySQL můžete vidět následující řádky:
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...
Můžete také použít kill -9 (SIGKILL) k okamžitému ukončení procesu. Více podrobností o signálu Linux naleznete zde. Případně můžete po hardwarové stránce použít ostřejší způsob, jako je vytažení napájecího kabelu, stisknutí tlačítka pro tvrdý reset nebo použití šermířského zařízení ke STONITH.
Spouštění OOM
Populární MySQL v cloudových nabídkách, jako je Amazon RDS a Google Cloud SQL, nemají žádný přímý způsob, jak je zrušit. Za prvé proto, že nezískáte žádný přístup na úrovni operačního systému k instanci databáze, a za druhé proto, že poskytovatel používá proprietární opravený server MySQL. Jedním ze způsobů je přetečení některých vyrovnávacích pamětí a nechat správce out-of-memory (OOM) spustit proces MySQL.
Můžete zvětšit velikost vyrovnávací paměti pro třídění na něco většího, než co RAM dokáže zvládnout, a pořídit řadu třídicích dotazů mysql proti serveru MySQL. Pojďme vytvořit tabulku s 10 miliony řádků pomocí sysbench na naší instanci Amazon RDS, abychom mohli vytvořit obrovské řazení:
$ 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
Změňte sort_buffer_size na 5G (naše testovací instance je db.t2.micro - 1GB, 1vCPU) tak, že přejdete na Amazon RDS Dashboard -> Skupiny parametrů -> Vytvořit skupinu parametrů -> zadejte název skupiny -> Upravit parametry -> zvolte "sort_buffer_size" a zadejte hodnotu 5368709120.
Aplikujte změny skupiny parametrů tak, že přejdete na Instance -> Akce instance -> Upravit -> Možnosti databáze -> Skupina parametrů databáze -> a vyberete naši nově vytvořenou skupinu parametrů. Poté restartujte instanci RDS, abyste použili změny.
Až budete nahoře, ověřte novou hodnotu sort_buffer_size :
MySQL [(none)]> select @@sort_buffer_size;
+--------------------+
| @@sort_buffer_size |
+--------------------+
| 5368709120 |
+--------------------+
Poté spusťte 48 jednoduchých dotazů, které vyžadují třídění od klienta:
$ 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
Pokud výše uvedené spustíte na standardním hostiteli, všimnete si, že server MySQL bude ukončen a v syslog nebo dmesg operačního systému se zobrazí následující řádky:
[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
S systemd se MySQL nebo MariaDB automaticky restartují, stejně jako Amazon RDS. Můžete vidět, že doba provozuschopnosti naší instance RDS bude resetována zpět na 0 (ve stavu mysqladmin) a hodnota 'Nejnovější doba obnovení' (pod RDS Dashboard) bude aktualizována na okamžik, kdy selhal.
Poškození dat
InnoDB má svůj vlastní systémový tabulkový prostor pro ukládání datového slovníku, bufferů a rollback segmentů uvnitř souboru s názvem ibdata1. Ukládá také sdílený tabulkový prostor, pokud nenakonfigurujete innodb_file_per_table (ve výchozím nastavení povoleno v MySQL 5.6.6+). Můžeme jen vynulovat tento soubor, odeslat operaci zápisu a vyprázdnit tabulky, aby došlo ke zhroucení 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'
Po odeslání zápisu si v protokolu chyb všimnete:
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.
V tomto okamžiku se mysql zablokuje, protože nemůže provést žádnou operaci, a po propláchnutí získáte řádky „mysqld dostal signál 11“ a mysqld se vypne. Chcete-li vyčistit, musíte odstranit poškozený ibdata1 a také ib_logfile*, protože soubory redo log nelze použít s novým systémovým tabulkovým prostorem, který bude generován mysqld při příštím restartu. Očekává se ztráta dat.
U tabulek MyISAM si můžeme pohrát s .MYD (datový soubor MyISAM) a .MYI (index MyISAM) v datadir MySQL. Například následující příkaz nahradí jakýkoli výskyt řetězce "F" řetězcem "9" v souboru:
$ replace F 9 -- /var/lib/mysql/sbtest/sbtest1.MYD
Poté odešlete nějaké zápisy (např. pomocí sysbench) do cílové tabulky a proveďte vyprázdnění:
mysql> FLUSH TABLE sbtest.sbtest1;
V protokolu chyb MySQL by se mělo objevit následující:
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
Tabulka MyISAM bude označena jako havarovaná a pro její opětovné zpřístupnění je nutné spustit příkaz REPAIR TABLE.
Omezení zdrojů
Můžeme také použít limit prostředků operačního systému na náš proces mysqld, například počet otevřených deskriptorů souborů. Použití proměnné open_file_limit (výchozí hodnota je 5000) umožňuje mysqld rezervovat deskriptory souborů pomocí příkazu setrlimit(). Tuto proměnnou můžete nastavit relativně malou (tak akorát, aby se mysqld spustil) a poté odeslat více dotazů na server MySQL, dokud nedosáhne limitu.
Pokud mysqld běží na serveru systemd, můžeme jej nastavit v souboru systemd unit umístěném na /usr/lib/systemd/system/mysqld.service a změnit následující hodnotu na něco nižšího (výchozí hodnota systemd je 6000):
# Sets open_files_limit
LimitNOFILE = 30
Použijte změny na systemd a restartujte server MySQL:
$ systemctl daemon-reload
$ systemctl restart mysqld
Poté začněte odesílat nová připojení/dotazy, které se počítají v různých databázích a tabulkách, takže mysqld musí otevřít více souborů. Všimnete si následující chyby:
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!
V tomto okamžiku, když je dosaženo limitu, MySQL zamrzne a nebude moci provést žádnou operaci. Při pokusu o připojení byste po chvíli viděli následující:
$ mysql -uroot -p
ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet', system error: 104
Závada s oprávněními
Proces mysqld běží uživatelem "mysql", což znamená, že všechny soubory a adresáře, ke kterým potřebuje přístup, jsou ve vlastnictví uživatele/skupiny mysql. Tím, že si zahrajeme s oprávněním a vlastnictvím, můžeme učinit server MySQL nepoužitelným:
$ chown root:root /var/lib/mysql
$ chmod 600 /var/lib/mysql
Vygenerujte nějaké zatížení serveru a poté se připojte k serveru MySQL a vyprázdněte všechny tabulky na disk:
mysql> FLUSH TABLES WITH READ LOCK; UNLOCK TABLES;
V tuto chvíli mysqld stále běží, ale je tak trochu k ničemu. Můžete k němu přistupovat prostřednictvím klienta mysql, ale nemůžete provádět žádnou operaci:
mysql> SHOW DATABASES;
ERROR 1018 (HY000): Can't read dir of '.' (errno: 13 - Permission denied)
Chcete-li uklidit nepořádek, nastavte správná oprávnění:
$ chown mysql:mysql /var/lib/mysql
$ chmod 750 /var/lib/mysql
$ systemctl restart mysqld
Zamknout
FLUSH TABLE S READ LOCK (FTWRL) může být destruktivní za mnoha podmínek. Jako například v clusteru Galera, kde všechny uzly mohou zpracovávat zápisy, můžete tento příkaz použít k uzamčení clusteru z jednoho z uzlů. Tento příkaz jednoduše zastaví další dotazy, které má mysqld zpracovat během vyprázdnění, dokud se zámek neuvolní, což je velmi užitečné pro procesy zálohování (tabulky MyISAM) a snímky systému souborů.
Přestože tato akce během zamykání nezhroutí ani nespustí váš databázový server, následky mohou být obrovské, pokud relace, která drží zámek, jej neuvolní. Chcete-li to vyzkoušet, jednoduše:
mysql> FLUSH TABLES WITH READ LOCK;
mysql> exit
Poté odešlete na mysqld spoustu nových dotazů, dokud nedosáhne max_connections hodnota. Jakmile jste mimo, nemůžete se samozřejmě vrátit ke stejné relaci jako na předchozí. Zámek tedy poběží nekonečně a jediný způsob, jak zámek uvolnit, je zabít dotaz jiným uživatelem SUPER oprávnění (pomocí jiné relace). Nebo ukončete samotný proces mysqld nebo proveďte tvrdý restart.
Odmítnutí odpovědnosti
Tento blog je napsán proto, aby poskytoval alternativy systémovým správcům a správcům databází k simulaci scénářů selhání s MySQL. Nezkoušejte je na svém produkčním serveru :-)