InnoDB ( 3 )

August 14, 2018

REDO ログ

InnoDB は全ての変更をバッファープールで行うが、トランザクションの COMMIT が完了した時点ではログ上への永続化が完了している。InnoDB REDO ログにはデータに何かの操作をするたびにデータが書き込まれる形となる。
InnoDB のログへ書き込まれるないようは、全て MTR (ミニトランザクション) という単位で記録され、再実行することで同じ操作を再現できるものになっている。MTR が生成される操作の単位は小さく、例えば 1 つの UPDATE 文を処理するあいだにも、何度も MTR が実行されることになるが、通常のトランザクションとは異なり、ルールバックという概念はない。

MTR により生成された REDO ログのデータは InnoDB ログバッファを通じ、最終的に InnoDB ログファイルへと書き込まれることになる。InnoDB ログバッファは MySQL サーバ内に1つしか存在せず、全てのトランザクションで共有されるオブジェクトであり、そのサイズは innodb_log_buffer_size で設定される。
MTR が開始されてから完了するまでの間、ここの MTR により生成されたログデータは MTR ごとにローカルなバッファに蓄えられる。この時点では、変更の内容は InnoDB ログバッファにすら書き込まれない。MTR が COMMIT して初めてローカルな MTR のバッファの内ようが InnoDB ログバッファにコピーされ、少し遅れてログファイルへ書き込まれることになる。InnoDB ログバッファがディスクへ書き込まれるタイミングは、トランザクションが COMMIT するか (innodb_flush_log_at_trx_commit=1 のとき)、ログバッファを使い果たし、新たに MTR を COMMIT するための空きスペースを確保する必要が出たときである。

このように InnoDB ログファイル (REDO ログ) には、分割することのできない個々の操作が連続的に記録されており、クラッシュリカバリ時にはフラッシュが完了したテーブルスペース (ステーブルデータベース) 上のデータに対して MTR を連続的に再生することで、InnoDB のデータ操作を時系列に沿って再現できる仕組みになっている。

REDO ログは 512 バイトのブロックが連続しているものとしてフォーマットされているが、個々の MTR は 512 バイトのブロックに収まる場合もあれば、ブロックをまたいで記録される場合もある。ブロックをまたいでいる場合、マシンがクラッシュすると先のブロックだけが永続化に成功する可能性があるが、このようなケースでは MTR は中途半端に記録されているため、クラッシュリカバリ時に切り捨てられる形となる。

なお、バイナリログと REDO ログの違いについては以下の通り。

バイナリログ:

  • レプリケーションに使用される。マスターはスレーブ側にバイナリログ内のイベントを送信し、スレーブはこれらのイベントを実行することでマスター上で実行されたものと同じデータ変更を実行する
  • 障害が発生した際に障害発生前の特定時点までデータを戻すために使用する (ポイントインタイムリカバリ)

REDO ログ:

  • REDO (再実行) を実行するためのログファイル
  • クラッシュリカバリに使用される

ダーティページのフラッシュ

MTR によるくっラッシュリカバリが成り立つためには 2 つ制約が存在する。

Write-Ahead-Log

ログファイルがデータファイルに先行してディスクへ書き込まれなければいけないという点となる。クラッシュリカバリ実行時にはデータファイルを読み取り、それからログを再生することになるため、ログファイルよりデータファイルが先行するとログファイルに含まれる変更は再生できなくなる。このようなログファイルは WAL (Write-Ahead-Log) と呼ばれる。
以上より、データファイルへのフラッシュはログファイルのフラッシュよりもあとで実行される。データファイルへのフラッシュが完了されるまでの間、InnoDB バッファープール上には変更は行われたが、ディスクへのフラッシュが完了していないデータページ (ダーティページ) が存在する。ダーティページのフラッシュはログファイルのフラッシュの後に行われる。

InnoDB ログファイルの上書き

ダーティページが残っているあいだはそのページを更新した MTR がログファイルから失われてはいけない。InnoDB のログファイルはサイズが固定であり、innoDB_log_file_sizeinnodb_log_files_in_group の積で決まる。ログファイルのサイズが固定ということは、ログファイルは何度も上書きされ、使い回されるということになる。
バイナリログの場合は max_binlog_size を超えると新しいファイルが作成され、更新は新しいログファイルの末尾に行われることになるため、上書きは発生しないが、InnoDB ログは同じログファイルを使い回すため、不要になったログエントリが解放されたあとに、同じ領域が別のエントリによって上書きされることになる。
そのため、もしログファイルの空き領域がなくなった場合は、空きができるまでログファイルへの書き込みはできないようになっている。上書きされてしまったログはクラッシュリカバリが利用できず、その状態でクラッシュするとデータ復旧に失敗するためとなる。

チェックポイント

InnoDB はファジーチェックポイントという方式のチェックポイントを実装している。ファジーチェックポイントとは、ダーティページを少しずつディスクへフラッシュし、どこまでフラッシュが進んだか記録する方式となる。InnoDB ではどこまでフラッシュが進んだかを LSN (Log Sequence Number) を用いてログのヘッダに記録するようになっている。LSN は個々のログエントリが、ログの記録を開始してから何バイト目かを示す値であり、ログファイルが使い回されても LSN はリセットされることなく増加し続ける。
電源が喪失してしまったような場合においても確実に片方が残るようにするため、チェックポイントの領域は 2 つあり、512 バイトの異なるブロック上に交互に書き込まれるようになっている。チェックポイントに記録された LSN より古い REDO ログに含まれた更新はデータファイルへのフラッシュ完了が保証されているので、クラッシュリカバリにおいて再実行する必要は無い。
チェックポイントが完了すると、それより古い REDO ログに含まれる更新はすでにデータファイル上に存在するため、解放できるようになる。つまりチェックポイントよりも古いダーティページは構造上存在しないことになる。

なお、チェックポイントは、基本的に一定期間ごとに記録されるため、必ずしもデータファイルの更新と同期していない。チェックポイントよりもデータファイルの方が新しい更新を含んでいるという可能性もある。クラッシュリカバリは、最後に実行されたチェックポイントから REDO ログの適用を行うように実装されているため、実はすでに適用されている REDO ログが 2 度適用されることがあるように思えるが、これは問題ない。データファイル上の各ページにはそのページを最後に更新した際の LSN が記録されており、すでに REDO ログがそのページに適用されたかどうかは REDO ログの LSN とページの LSN を比較すればわかるようになっている。

ダブルライト

InnoDB ではデフォルトでデータファイルへの書き込みを 2 度行うようになっている。1 回目はダブルライトバッファと呼ばれるデータファイル上の連続した 2 MB の領域にデータを書き込み、その次に実際のデータページを書き込むようになっている(「バッファ」という名前だが、実際はファイル上)。同じ内容を別の領域に 2 回書き込むことにより、ダブルライトバッファもしくは実際のデータページのいずれかは確実に更新が完了しているため、データファイルの更新中にマシンがクラッシュしてもデータファイルが壊れることはない。もしダブルライトバッファが壊れていればその内容を吐きすればよく、データば壊れていればダブルライトバッファから復元すれば良い。

ダブルライトはデータを保護するという意味合いからはシンプルかつ確実な方法だが、データファイルへ書き込む量は単純に 2 倍になってしまう。ダブルライトバッファは連続領域なのでシーケンシャルのため、HDD の場合ランダムアクセスよりは書き込みのオーバーヘッドは少ないものの、フラッシュの速度に与える影響は少なく無い。もし write がアトミックになることが保証されているファイルシステムではダブルライトは意味をなさないため無効にすることもできる (skip_innodb_doublewrite, MySQL 5.7 より)。


 © 2023, Dealing with Ambiguity