InnoDBのデータ領域はログファイルとテーブルスペースという、切っても切れない2種類のファイルから構成されている。ログファイルは名前からするとただのログだから削除しても平気かな?と思って削除してしまうという問題が後を絶たない。そこで、今日はログファイルとテーブルスペースの関係について説明しようと思う。
InnoDBのログファイルは、別名WAL - Write Ahead
Logと呼ばれるもので、名前を日本語に直すと「前もって書き込んでおくためのログ」とでも呼べるだろうか。InnoDBのテーブルに対して行われた更新は、全ていったんログに書き込まれるのである。トランザクションがコミットされると、innodb_flush_log_at_trx_commit=1が設定されていればログファイルに書き込みが行われる。0または2の場合には、ログバッファと呼ばれる領域にデータが保持される。その後、時間をおいてからテーブルスペースへ更新が反映されるのである。
なぜこのような仕組みになっているのだろうか?
コミットと同時にテーブルスペースを反映すればいいじゃないか?と思われるかも知れない。しかし、テーブルスペースへの更新には時間がかかるのである。なぜか?簡単に言うと、更新するべき箇所がたくさんあるからである。テーブルスペースに含まれているのはデータだけではなく、インデックス情報も含まれている。インデックスはBツリー構造になっているため、場合に因ってはインデックスを辿ったり、ページが分かれる場合にはリーフノード以外にも1つ以上のノンリーフノードを更新しなければいけない。そして、大抵の場合一つのテーブルに定義されているインデックスは複数あり、インデックスが増えればそれだけ更新するべきページも増えてしまう。さらに、一つのトランザクションにおいて更新するテーブルが複数ある場合も多い。更新するページはテーブルスペース内に離散して格納されているので、ディスクに対するランダムアクセスが発生してしまう。
つまり、テーブルスペースの更新はとてもコストが高いので、コミット時にこれら全ての更新を行っていたのでは書き込み性能が出ないのである。
そこで、まずはコミット時に全ての更新をWALに書き込み、時間が経ってからテーブルスペースを更新するという方法が採られている。この方法にはさらに次のメリットがある。
- WALに対する書き込みはシーケンシャルなので、シーク時間がなくとても速い。
- 急激にたくさんの書き込み要求が来た場合に耐えることができる。
- 複数のトランザクションが同じページを更新した場合、書き込み回数を減らすことができる。
即ち、WALにはテーブルスペースにまだ反映されていないデータが含まれていることになる。つまり、InnoDBのデータは、テーブルスペースとログを合わせて初めて、完全な情報を含んでいるのである。なのでみだりにログを削除してはいけない。
MySQLサーバを普通にシャットダウンした場合でも、WALにはテーブルスペースに反映されていないデータが残っている。シャットダウンと同時にテーブルスペースへデータを反映させるには、innodb_fast_shutdown=0を設定すれば良い。このオプションはMySQLサーバ稼働中に変更できるので、以下のようにしてからシャットダウンすると、WALを削除しても安全な状態になる。
mysql> SET GLOBAL innodb_fast_shutdown=0;
MySQLサーバ稼働中、WALにだけ書き込まれたデータはどこにあるのだろうか?答えは、InnoDBバッファプールである。バッファプールはテーブルスペースに対するキャッシュ+その他のデータを保持する領域であるが、対応するページがバッファプール内でだけ更新され、テーブルスペースは更新されていないという状態になる。このとき、そのページは「ダーティ」な状態であるという。
ダーティなページをテーブルスペースへ書き込む動作は、チェックポイント処理と呼ばれる。チェックポイントが行われると、「このページのチェックポイントしたよ!」という情報が、WALに書き込まれる。
InnoDBログの状態は、SHOW INNODB
STATUSコマンドで確認することが出来る。「LOG」セクションを見てみよう。
---
LOG
---
Log sequence number 0 531941548
Log flushed up to 0 531941548
Last checkpoint at 0 503180567
0 pending log writes, 0 pending chkp writes
116 log i/o's done, 0.00 log i/o's/second
Log sequence numberは、ログバッファへの更新が行われたトータルのバイト数、Log flushed up
toはWALへの書き込みが行われたバイト数、Last checkpoint
atは最後にチェックポイントが行われたバイト数である。innodb_flush_log_at_trx_commit=1ならば、Log
sequence numberとLog flushed up toは非常に近い値になる。
ではログファイルのサイズは一体いくつにすればいいのだろうか?
これは非常によく議論されるテーマである。いくら議論してもし足りないぐらいであるが、デフォルトの5MBでは大抵の場合不十分であることが多い。InnoDBのログファイルサイズは、innodb_log_file_sizeとinnodb_log_files_in_groupの2つのパラメータで調整することができる。ログファイルのサイズは、2つのパラメータの積である。そして、ログファイルにはまだチェックポイントが完了していない全ての更新データを記録しておく必要がある。つまり、以下の式が成り立つのである。
innodb_log_file_size × innodb_log_files_in_group
≧
Log sequence number − Last checkpoint at
実際にシステムが稼働している状態で、最大で(Log sequence number − Last checkpoint
at)がどのぐらいになるかを見てみよう。ログファイルサイズはそれより大きくする必要はない。ログファイルサイズが大きいと、クラッシュリカバリの時間が伸びてしまうというデメリットがある。InnoDBはクラッシュ後最初に再起動した際、ログファイルに含まれている全ての変更をテーブルスペースに反映する。なので、ログファイルサイズが1GBを超えるような場合には、クラッシュリカバリに長時間を要してしまう。特にMySQLサーバをHA化している場合には、切り替わりの所要時間に影響してしまうので、大きすぎるログファイルは禁物である。
個人的には、以下のような設定を推奨したい。
innodb_log_files_in_group=2(デフォルト固定)
innodb_log_file_size=Log sequence number − Last checkpoint
atの最大値
これは実際に必要な量の倍のログファイルサイズという意味である。ざっくりと2倍のマージンを取っているが、突発的な負荷に備えるという意味でこんなもんではないだろうか。もし今InnoDBのログファイルのサイズを大きくし過ぎている場合には、実際の負荷を見て少し減らしてみるといいだろう。計測するのが面倒だ!という人は、大抵の場合innodb_log_file_size=128M程度で十分であるので、このぐらいのサイズで使い始めよう。
InnoDBのログファイルのサイズを決める基準にはいろいろな流儀がある。MySQL Performance
Blogには以下のような記事が投稿されているので参考にして欲しい。
http://www.mysqlperformanceblog.com/2006/07/03/choosing-proper-innodb_log_file_size/