「サイトが遅い」はほぼ必ずデータベースへ行き着きます。そして正しい順序で確認すれば数分で済みます。まず遅くなっている最中にしか見えないもの、それから何週間もかけて溜まってきたものです。
問題が起きている最中に見る
基本の原則:最も価値ある情報は、まさにすべてが遅くなっているときにしか手に入りません。MySQL の最初のコマンド:
sudo mysqladmin processlist
mysql -e 'SHOW FULL PROCESSLIST'
PostgreSQL では:
sudo -u postgres psql -c "SELECT pid, state, wait_event, now()-query_start AS dur, query
FROM pg_stat_activity WHERE state != 'idle' ORDER BY dur DESC;"
いま何が動いていて、どれだけの時間そうしているかが見えます。そして答えはたいていすぐに出ます。重いクエリが一つ、残りすべてを塞いでいるか、あるいは同じようなクエリが百件、ロックが外れるのを待っているかです。
接続
mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_connected'"
mysql -e "SHOW GLOBAL STATUS LIKE 'Max_used_connections'"
mysql -e "SHOW VARIABLES LIKE 'max_connections'"
Max_used_connections が max_connections に近づいているなら、アプリケーションは時折「too many connections」の拒否を受け取っています。そして上限を上げるのは最初にやることではありません。接続は一つ一つメモリを使いますし、もともとメモリが不足しているサーバーでそれを上げるのは swap への道を早めるだけです。まずなぜ接続が解放されないのかを突き止めてください。たいていは長いクエリか、アプリケーション側に接続プールが無いことです。
そして反対側からも見てください。接続の急増は人気の上昇からではなく、重いページを叩き続けるボットから来ることもあります——検索、一覧の絞り込み、レポート生成などです。
遅いクエリ
遅いクエリのログ無しには先へ進めません。MySQL:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
一日後:
sudo mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log
PostgreSQL は postgresql.conf に:
log_min_duration_statement = 1000
そして拡張 pg_stat_statements。クエリごとの合計時間を直接示してくれます。
SELECT calls, round(total_exec_time) AS total_ms, query
FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 10;
注意:重要なのは最も長いクエリではなく、合計時間が最も大きいクエリです。一分に千回走る 50 ミリ秒のクエリは、一日一回の十秒のレポートより害が大きいのです。
最も忘れられている設定
MySQL と MariaDB では、それは InnoDB のバッファプールの大きさです。既定値は 128 メガバイトで、何十年も変わっていません。データベースが数ギガバイトを占めるサーバーでは、それはメモリではなくディスクから絶えず読むことを意味します。
mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size'"
専用のデータベースサーバーなら妥当な目安は RAM の半分。Web サーバーと PHP も動くサーバーなら四分の一で、swap に落ちない範囲でという計算です。そしてこの一つの設定は、たいてい一週間かけてクエリを改善するより多くをもたらします。
静かにディスクを食べる二つのもの
MySQL のバイナリログ。レプリケーションとある時点への復旧に必要で、絶えず増え、既定ではときに一度も消されません。確認するには:
sudo du -sh /var/lib/mysql/*bin.*
mysql -e "SHOW VARIABLES LIKE 'binlog_expire_logs_seconds'"
レプリケーションが無く、ある時点への復旧も使わないなら、保持期間を妥当な数日に設定してください。
忘れられたレプリケーションスロットを伴う PostgreSQL の WAL ファイル。あまり知られておらず、より危険な状況です。レプリケーションのスロットが存在するのにその利用者がもう接続してこないなら、PostgreSQL は切断の瞬間からのすべてのログを保持する義務を負い——そしてディスクが尽きるまで保持し続けます。
sudo -u postgres psql -c "SELECT slot_name, active, restart_lsn FROM pg_replication_slots;"
誰にも必要とされていないそうした非活動のスロットは削除すべきです。そしてこれは、データベースが確実にディスクをゼロまで持っていく数少ない状況の一つです。
大きさと成長
SELECT table_schema, round(sum(data_length+index_length)/1024/1024) AS mb
FROM information_schema.tables GROUP BY table_schema ORDER BY mb DESC;
そして役に立つのは現在の数字ではなく、その変化の速さです。トラフィックが比例して増えていないのに一か月で倍になったデータベースは、たいてい何かの記録テーブルが書かれ続け、一度も掃除されていないことを意味します——セッション、ジョブの履歴、何かのプラグインの出来事のログなどです。
二行でできる安全確認
そしてデータベースのコンソールが開いているついでに、確認する価値のあることが二つあります。第一に、データベースが外から届くかどうか(ss -tulpn | grep 3306)——それはほとんど常に外を向くべきではありません。第二に、どのアドレスからでも接続できるアカウントです。
mysql -e "SELECT user, host FROM mysql.user"
host = '%' の項目は「どこからでも接続可」を意味します。そしてポートも開いているなら、データベースとインターネットの間に立っているのはパスワード一つだけです。
バックアップ
最後に。バックアップのファイルがあること自体は何も保証しません。保証するのは試された復旧だけです。一度も復旧されたことのない控えは、途中で切れている、ロックのエラーを含んだまま取られている、あるいは別のデータベースが入っている——そのどれである可能性も同じくらいあります。そしてそれは、控えが必要になった当日ではなく、前もって別のマシンで確かめるべきものです。
日々の見張りには三つの数字で足ります。接続数、一日あたりの遅いクエリの件数、そしてデータベースの大きさ。三つともゆっくりと予測可能に変わり、そのどれかの急な動きは目をやる価値があります。それらが一つのページでどう見えるかは下のデモで。