XtraBackupが更新のロックを取らずにホットバックアップをどのように実現しているのかについて興味があったので調べてまとめてみました。XtraBackupがどのようにホットバックアップを実現しているかを理解する上で大切なのはトランザクションログとそれを利用したクラッシュリカバリの仕組みです。
それぞれについて説明したあとにそれらがXtraBackupの中でどのように利用されているのかを見ていきます。
トランザクションログは、InnoDBがACIDモデルに準拠したトランザクションを提供するために重要な役割を担っています。例えばトランザクションの実行中に何らかの障害でMySQLがクラッシュした際に、不完全なままトランザクションが中断されてしまい、データが失われてしまったり、整合性が保障されなくなってしまってはDBMSとして使うのは信頼性の面からかなり厳しいでしょう。
トランザクションログがどのようなものかInnoDBのデータ更新のサイクルとともにより詳しく紹介します。
InnoDBはデータファイルに保存されるデータそれ自体とは別にトランザクションログと呼ばれるある種の”データ変更ログ”のようなもの持つことでACIDに準拠したトランザクションを保証しています。
InnoDBではデータの読み書きはメモリ(innodb_buffer_pool)上のページに対して行われます。ここでのページはInnoDBがデータを扱う単位だと思ってもらえれば良いです。OSが扱うページとは別のものです。もし対象のデータがbuffer pool上のページキャッシュになければディスクから読み込んでキャッシュに載せてから操作します。SQLステートメントによる一次的なデータ変更はメモリ上で完結するのでパフォーマンス的に有利です。
ここで、Linuxなどにはページキャッシュ機構があるのでわざわざMySQLでページをキャッシュする仕組みを別に持つのは2重キャッシュで無駄ではないかと思われたかもしれません。実際それはその通りで、InnoDBのページとOSのページは大きく異なるものなので置き換えることはできませんがOS側のキャッシュを無視することはできます。メモリ効率のためにOS側のページキャッシュをskipするDirect I/Oのような設定がMySQLにはあります。
innodb_buffer_pool上のページが更新された時点ではデータの永続化がされていないため、このままでは障害時にはデータが失われてしまいます。
普通に考えれば?ここで更新のあったページをデータファイルへ書き込んで永続化してしまうのが良いと思ってしまいます。しかし、実際にはMySQLが次にやるのはトランザクションログを記録してそのログレコードを永続化することです。トランザクションログへの記録をもって、トランザクションがCOMMITされたことになります。更新されたダーティーなページのデータファイルへの書き込みはチェックポイント処理(後述)によってトランザクションログへの書き込み後に非同期で行われます。
トランザクションログもメモリ上にbuffer領域があり、まずはそこに対して新しいログレコードが書かれてから実ファイルへと書き込まれます。トランザクションログのディスクへの書き込みには同期的にfsync(2)が呼び出されます。これによりメモリ上のbufferとディスクに永続化されたトランザクションログで差分が生まれず、トランザクションがCOMMITされた時点でトランザクションログが永続化されていることが保証されます。
ただし、fsync(2)の同期的な呼び出しはデータロストがないことの保証と引き換えにパフォーマンスを犠牲にしています。通常であればメモリ上のキャッシュに対しての変更に留まるところにディスクI/Oを発生させているからです。
よりパフォーマンスを向上させたい場合はinnodb_flush_log_at_trx_commit
の値を変更することでデフォルトの挙動を変えて1秒ごとにページキャッシュをディスクに同期するように挙動を変えることなどができます。システムのクラッシュによって1秒間隔の同期の間で永続化されなかった変更は失われてしまいますが、ここらへんはパフォーマンスとデータロストへの許容度とのトレードオフになります。
このようにInnoDBページへの変更を直接ディスクに永続化するのに先立ってトランザクションログへ記録するような仕組みをWAL(Write Ahead Log)と呼びます。データファイルへの書き込みはログの更新後に非同期的に行われます。なぜこのような方法を取っているのでしょうか。いくつか理由はありますが、ひとつはログへの書き込みはデータファイルへの書き込みに比べてシーケンシャルに行うことができるのでディスクへのランダムアクセスが発生せずヘッドのシークのオーバヘッドを抑えることができます(SSDはこの限りではない)。また、後述するクラッシュリカバリの仕組みもWALによって実現することができます。
トランザクションログは以下ような情報を含みます
LSN(Log Sequence Number)。 ログレコード毎に一意に割り当てられる識別番号
直前のLSNへの参照
更新したInnoDBのページに関する情報。ページのアドレス、
更新後のデータ(redo)
InnoDBでは更新前のデータを保持しておくUndoログはトランザクションログには含まれておらずInnoDBページと同じ様にbuffer pool上にキャッシュされていて非同期でデータファイルへの書き込みがされています。
LSNはログレコードを一意に識別するためのもので、LSNの値はその時点までにlog bufferに書き込まれたバイト数の合計の数値となっているようです。
先述の通り、InnoDBではbuffer pool上の変更されたダーティーなページのデータファイルへの書き込みをトランザクションログを記録したあとで非同期で行います。同時にLSNをログに記録して永続化しています。これらの処理をチェックポイント(処理)と言います。チェックポイントのログに記録されたLSNは、そのLSNまではデータファイルにページが永続化されたことを保障します。
どこまで書き込みが終了しているかを容易に判別可能なことは、クラッシュリカバリの速度を向上させる上で重要になってきます。
究極的にはチェックポイントによってその時点のLSNが記録されなくてもデータファイル上のInnoDBの各ページはそのページが最後に更新されたときのLSN持っているので、すべてのページを走査していけばリカバリの対象かどうかは判別可能だと思いますが非常に時間がかかります。
InnoDBではシステムのクラッシュからの復帰時にDurabilityを保障するためにトランザクションログを利用したクラッシュリカバリの仕組みを持っています。
InnoDBのクラッシュリカバリプロセスではまず最初にRedoログの適用がされます。Redoログはトランザクションによる更新後のデータのことでした。これにより、トランザクションログには記録されているが、データファイルへは反映されていないページへの変更がデータファイルに反映されることになります。
次に、クラッシュ時に実行中だったデータファイルへの反映が不完全なトランザクションによる変更をUndoログを元にロールバックします。この他にもクラッシュリカバリにはいくつかのプロセスがありますがここでは省略します。
このクラッシュリカバリプロセスにおけるRedo/Undoログ適用対象の判別には、LSNが使われます。先述の通り、チェックポイントで記録されたLSNはそこまでは永続化が完了していることを保障しています。ということは、トランザクションログ内のLSNがそれよりも進んでいた場合はログには存在するがデータファイルには存在していないページの変更の存在を示唆しており、Redoログの適用を持って未反映のトランザクションの復旧が行われます。逆にチェックポイントよりも進んだLSNを持っているが、完了していないトランザクションがトランザクションログ内に存在していればUndoログをもとにロールバックが行われます。
DBMS内部以外からもトランザクションログが利用されているケースがあります。XtraBackupによるバックアップです(本当はこれが本題)。
XtraBackupはPercona社が提供しているOSSのMySQLのデータバックアップツールです。他のバックアップ手段と比較したときの特長はなんと言ってもMySQLの更新をロックすることなくオンラインでのバックアップが可能なことです。通常であればバックアップ中にロックを取らなければデータファイルのバックアップ中に差分がうまれ整合性が取れなくなってしまいますがXtraBackupではInnoDBのトランザクションログとクラッシュリカバリの仕組みを利用することでホットバックアップを実現しています。
XtraBackupはバックアップ開始時にその時点のLSNを記録します。データファイルのコピーにはある程度の時間がかかり、更新をロックしていないのでその間にもデータは更新されていきます。当然コピー中にそのような変更が走るとコピー済みデータには変更が反映されず不整合が生じてしまいます。XtaraBackupはデータファイルのコピーと同時に、バックグランドでトランザクションログファイルへの変更も監視してコピーしています。トランザクションログはシーケンシャルに記録されていくので最新の変更を追っていけば不整合が生じません。バックアップ終了時のトランザクションログファイルを直接コピーするのではなくXtraBackup側でも変更を記録していくのは、トランザクションログファイルが規定の容量を超えるとファイルの先頭から既存のログレコードを上書いていくようになっているからです。
データファイルのコピーが終わる前に上書かれてしまうとトランザクションログが欠落してしまいます。これを防ぐためにXtraBackup側でトランザクションログファイルの変更のコピーを取り続ける必要があります。データファイルのコピーが終わるとトランザクションログをそれ以上追いかける必要がないのでこちらも終了します。
これで不完全な状態のデータファイルとトランザクションログを含んだバックアップが作成されます。この後XtraBackupではリストアをする前にRedoログの適用を行います。--prepare
や --apply-log-only
などによる操作がそれです。
このように、XtraBackupではInnoDBのクラッシュリカバリの仕組みを上手く利用して更新をロックすることなくリストア可能なバックアップを取得することに成功しています。
XtraBackupの無停止でのバックアップの仕組みを説明したかったんですが、あれもこれもと追加していったらわかりにくい文章になって前置きが長くなってしまいました。都度調べながら書いたので間違ったことを言ってしまっているかもしれませんがご了承ください。最後まで読んでいただきありがとうございました 😄
参考
https://www.percona.com/doc/percona-xtrabackup/2.4/how_xtrabackup_works.html https://dev.mysql.com/doc/refman/5.7/en/innodb-recovery.html https://dev.mysql.com/doc/refman/5.7/en/glossary.html https://tanishiking24.hatenablog.com/entry/innodb-durability など