ハードウェアは強力なのにMySQLが「重い」理由
皮肉な光景です。サーバーは64GBのRAMと16コアのCPUを搭載しているのに、MySQLは「Too many connections」というエラーを出し続けます。htopで確認すると、メモリには十分な空きがある一方で、ディスクI/Oは「真っ赤」な状態。これは、あらゆる環境で起動できるように制限されたMySQLのデフォルト設定のまま運用していることが原因です。
私が管理しているMySQL 8.0(データ量50GB)のシステムでは、以下のチューニングを適用した結果、クエリのレスポンス速度が劇的に向上しました。また、トラフィックが通常の3倍に急増した際も、データベースは安定して稼働し続けました。盲目的にハードウェアをアップグレードするのはやめましょう。まずはMySQLがどのようにメモリを管理しているかを理解し、既存のリソースを最大限に活用する方法を学びましょう。
1. InnoDB Buffer Pool — 高速処理の要
もし一つだけパラメータを修正するとしたら、innodb_buffer_pool_sizeを選んでください。ここはInnoDBがテーブルのデータやインデックスをキャッシュする場所です。目標は、ハードディスク上のデータを可能な限りRAM上に載せ、即座にアクセスできるようにすることです。
最適な設定値は?
Buffer Poolに「RAMの80%」という数値を鵜鵜にしてはいけません。この計算式が成り立つのは、サーバーでMySQLのみが動作している場合だけです。NginxやRedisと共存している場合、メモリを割り当てすぎると「スワップ(Swap)」が発生します。そうなると、OSがディスクをメモリの代わりに使用するため、パフォーマンスは致命的に低下します。
私の実践的な経験に基づくと、以下のような配分が理想的です:
- DB専用サーバー: 全RAMの70%〜75%
- 複数サービス稼働サーバー: 全RAMの40%〜50%
例えば、16GBのRAMを搭載した専用サーバーであれば、約12GBに設定します:
[mysqld]
innodb_buffer_pool_size = 12G
設定が適切かどうかを確認するには、以下のコマンドを実行します:
SHOW ENGINE INNODB STATUS\G
Buffer pool hit rateの行に注目してください。もし「999/1000」であれば、99.9%のクエリがRAM上のデータを見つけられていることを意味します。この状態になれば、レイテンシ(遅延)は数十ミリ秒から1ミリ秒以下へと短縮されます。
2. Buffer Poolの分割による競合の削減
8GB以上のBuffer PoolをマルチコアCPU環境で運用する場合、単一のインスタンスでは「ミューテックス競合(mutex contention)」が発生しやすくなります。処理スレッドがメモリにアクセスするために、順番待ちをしてしまうのです。Buffer Poolを分割することで、MySQLの並列処理能力を効率的に引き出すことができます。
# 12GBのBuffer Poolを8つのインスタンスに分割(1つあたり1.5GB)
innodb_buffer_pool_instances = 8
各インスタンスは、効果を最大限に発揮させるために、少なくとも1GB以上の容量を確保するようにしてください。
3. Thread Poolによる数千の接続処理
MySQLはデフォルトで、1つの接続に対して1つのスレッドを作成します。1,000人のユーザーが一斉にアクセスしたと想像してみてください。CPUは実際のクエリ処理よりも、スレッドの切り替え(コンテキストスイッチ)だけで手一杯になってしまいます。
max_connectionsをむやみに増やさない
エラーを回避するためにmax_connections = 5000のように設定する人が多いですが、メモリが不足するとサーバーは即座にクラッシュします。代わりに、Thread Poolを使用して既存のスレッドを再利用しましょう。
注意:MySQL Community版には標準でThread Pool機能が含まれていません。Percona Serverを導入するか、前段にProxySQLを配置してユーザー管理を効率的に行い、接続を制御することをお勧めします。
max_connections = 500
max_used_connections = 400 # 実際の設定値は制限より20%低く抑えるのが理想的
4. Redo Logによる書き込み速度の向上
innodb_log_file_sizeパラメータはRedo Log의サイズを決定します。これはデータファイルに永続化される前に、変更内容を記録する場所です。このファイルが小さすぎると、MySQLはログのクリーンアップ(チェックポイント処理)のために頻繁に処理を停止させ、定期的な「ラグ」を引き起こします。
書き込み量が多いEコマースサイトなどのシステムでは、1GB以上に設定することが一般的です。
innodb_log_file_size = 1G
innodb_log_files_in_group = 2
5. 安全性と引き換えに書き込み速度を稼ぐ
innodb_flush_log_at_trx_commitパラメータでは、データの安全性レベルをカスタマイズできます:
- 値 1: 完全な安全性を保証しますが、毎回のコミット時にディスクへ書き込むため、ディスクI/O負荷が非常に高くなります。
- 値 2: 最もバランスが良い設定です。ログを1秒に1回キャッシュに書き込みます。デフォルトに比べて書き込み速度が3〜5倍向上することがあります。
通常のプロダクション環境では、SSDへの負荷を軽減するために私は常に「2」を選択しています。
innodb_flush_log_at_trx_commit = 2
6. O_DIRECTによるディスク書き込みの最適化
Linux環境では、O_DIRECTを使用してOSの中間キャッシュをバイパスするように設定しましょう。これにより、データが直接ディスクに書き込まれるようになり、メモリが重複して使用される「二重バッファリング(double buffering)」を防ぐことができます。
innodb_flush_method = O_DIRECT
安全な適用プロセス
my.cnfファイルを書き換えてすぐに再起動してはいけません。安全を確保するために、以下の3ステップを実行してください:
- 設定のバックアップ: トラブル発生時にすぐに復旧できるよう、常にバックアップを保持してください。
- ランタイムでの変更: 動的変更が可能なパラメータには
SET GLOBALコマンドを使用し、事前に影響を確認します。 - エラーログの確認:
/var/log/mysql/error.logを監視し、メモリ割り当てエラーなどが発生していないか早期に発見します。
まとめ
MySQLの最適化は、継続的な観察と調整のプロセスです。データが増大すれば、今日の最適設定が明日には通用しなくなることもあります。ハードウェアを限界まで追い込むことよりも、システムの安定性を優先しましょう。もしこれら全ての調整を行っても改善されない場合は、インデックス(索引)の見直しや、リードレプリカ(Read Replicas)の導入を検討すべき時期かもしれません。

