Skip to content
bonybeat edited this page Sep 22, 2014 · 12 revisions

先行書き込みログ(以降ではWALと記載) ディスクキャッシュページ上で行われたオペレーションに関して、変更されたデータを記録するためのオペレーションログです。 デフォルトでは、WALは有効になっています。

不要な場合にはジャーナル (WAL) を無効にすることもできます。

-Dstorage.useWAL=false

このログは、レコードレベルのオペレーションのロギングのためのもので、高レベルなものではありません。各ページが変更されるたびに、以下のような内容を保管します。

  1. 変更されたチャンクのオフセットと長さ
  2. その長さ分のチャンクの変更前の値
  3. 同じく変更後の値(追加なら新しい値)

WALは論理的なデータではなく、raw形式のデータであり、ページ中に含まれていたそのままの形式のデータです。そのために、何度でも同じ変更を適用できるばかりでかく、各トランザクションの結果としてキャッシュの中身を必要に応じてフラッシュできるだけでなく、キャッシュ全体や一部分だけでもフラッシュできます。加えて、データをレストア中に、ストレージがクラッシュしても、何度でもデータをレストアできます。

データページに対して、次のような更新が行われたと仮定しましょう。

  1. 先頭の10バイトが変更された。
  2. 最後の10バイトが変更された。

データページの真ん中の部分をディスクにフラッシュしている間にストレージがクラッシュしたとして、最後の10バイトは書き込み済みで、最初の10バイトはページに書き込まれていないとします。

レストアにあたって、WALの内容を一個づつすべて適用していきます。つまり、最初の10バイトをまずセットして、それから最後の10バイトをセットしていきます。 変更されたページは、ディスクにフラッシュされていたかどうかを気にすることなく、正しく復元することができます。

WALファイルはページとセグメントに分割されます。それぞれのページは、謝り検査用のヘッダーCRC32と、検算用のマジックナンバーを含んでいます。 レコードに対する変更は、WALにロギングされる時にシリアライズドされたバイナリーデータとして追加されます。レコード全体のバイナリーデータを格納するのにスペースが不足している場合は、バイナリーデータのあふれた部分は次のレコードに格納されます。ページ中に空きスペースを作らないことが重要です。他のファイルと同じように、WALも破損する可能性があります。WALページ中のGAP検出は、データベースを破壊から守り、健全なWALページを維持する手法の一つです。これについては後で詳しく記載します。

どんなオペレーションでも複数のデータページを含む可能性があります。データの一貫性を損なうことを防ぐために、ひとつの論理的単位中のすべてのオペレーションは、単一のatomicなオペレーションとして実現する必要があります。これを機能的に実現するために、WALレコードには以下のような種類があります。

  1. 開始レコード。
  2. 終了レコード。
  3. 変更データレコード。

これらのレコードは以下のフィールドを含みます:

  1. WAL開始レコードは、以下のフィールドを含みます。
    1. AtomicオペレーションID(uuid)
    2. LSN (ログシーケンス番号) - WAL中のログレコードの物理的な位置
  2. WAL終了レコードは、以下のフィールドを含みます。
    1. AtomicオペレーションID(uuid)
    2. LSN (ログシーケンス番号) - WAL中のログレコードの物理的な位置
    3. ロールバックフラグ - Atomicなオペレーションがロールバックされたことを示す
  3. WAL変更データレコード。
    1. LSN (ログシーケンス番号) - WAL中のログレコードの物理的な位置
    2. 変更されたデータページのページインデックスとファイルID
    3. 変更されたデータページ自身の内容
    4. 今回の変更の以前に、現在のページに適用された変更のLSN - "prevLSN"

最後のレコードタイプ(変更ページのコンテナ)をもう少し説明します。データページキャッシュには、次のような"system"フィールドを含みます。

  1. 残りのデータのCRC32コード
  2. マジックナンバー
  3. そのページに適用された最新のWALのLSN - ページLSNという

ページへの変更を実行する度に、WALへロギングしてから、その変更をデータページキャッシュに書き込みます。 その変更をデータページキャッシュにリリースした直後に、WALレコードのLSNを"page LSN"として、記録します。

WALは、バッファ上のカレントページが一杯になったらすぐにそれをフラッシュするのではなく、他のバッファされたページと一緒にバックグラウンドにて、フラッシュします。 フラッシュは、一秒に一回、バックグラウンドスレッドにて実行されます(その間隔はパフォーマンスと永続性のトレードオフと言えます)。しかし、WALにレコードをputするスレッドがフラッシュを実行する際に、二つの例外があります。

  1. WALのバッファが一杯になった時
  2. データページキャッシュがフラッシュされた際に、その"page LSN"が、最後にフラッシュされたWALレコードのLSNの比較されて、"page LSN"のほうが大きければ、WALページのフラッシュはトリガーされる。LSNはWALレコードの物理的な位置を指す。WALは、フラッシュされたレコードのLSNより"page LSN"が大きい場合のみログに追加される。これは、データページの変更は、ロギングされているが、それがバッファからフラッシュされていないことを意味します。しかし、データレストアを可能にするためには、すべてのページの変更がWALにも含まれている必要があるからです。

データのレストア処理は以下のようになる:

begin

すべてのWALレコードを一件づつ見る

すべてのatomicなオペレーションのレコードを一回のバッチにまとめる

  "Atomicなオペレーションの終了を示すレコード"があった場合
     if コミットが実行されている
        該当する"Atomicなオペレーションレコード"を「最初から最後まで順に」
        すべてのレコードの変更を適用し、"page LSN"を適用終了LSNにセットする。


     else
        該当する"Atomicなオペレーションレコード"を「最後から最初まで順に」
        すべてのレコードの更新前の内容を適用し、"page LSN"をWALRecord.prevLSN
        にセットする。

     endif       
end 

以前に記載したようにWALファイルは通常ファイルであり、WALのキャッシュをフラッシュ中に電源をオフした場合に部分的にしかフラッシュされていない可能性があります。WALページが破損するケースとしては以下の場合があります。

  1. ページが部分的にだけフラッシュされた
  2. いくつかのページは完全にフラッシュされたが、フラッシュされていないものもある。

最初のケースは検出も解決もとても簡単です。

  1. データベース起動時にWALをオープンし、WALのサイズを計算して検証する。
  2. ページを順に読み込み際に、ページ単位で、CRC32コードとマジックナンバーをベリファイする。もしページが破損していたらレストア処理を中止する。

2番目のケースは少しトリッキーです。WALは追加専用ログなので二つのパターンがあります。3ページ存在して、2回フラッシュがあったとし、2回目のフラッシュが失敗したと仮定しましょう。1ページ目と2番目のページの前半部分は、1回目にフラッシュされ、2番目のページの後半部分と3ページ目が、2回目にフラッシュされたとします。2回目のフラッシュは、電源オフによって中断された場合、状態としては二つのケースが考えられます。

  1. 2ページ目後半はフラッシュされ、3ページ目はフラッシュされていない。これはCRC32コードとマジックナンバーで検出できます。
  2. 2ページ目後半はフラッシュされず、3ページ目はフラッシュされた。この場合、CRC32コードとマジックナンバーは正しくなってしまっています。In such case CRC and magic number values will be correct and we can not use them instead of this when we read WAL page we check if this page has free space if it has then we check if this is last page if it is not we mark this WAL page as broken.
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script> <script> $(function() { $("*").contents().filter(function() { return this.nodeType==8 && this.nodeValue.match(/^original/); }).each(function(i, e) { var tooltips = e.nodeValue.replace(/^original *[\n\r]|[\n\r]$/g, ''); $(this).prev().attr('title', tooltips); }); }); </script>
Clone this wiki locally