Skip to content

Consistency Model

Takeru Ohta edited this page Oct 17, 2018 · 1 revision

CannyLSの特徴

以降の議論を進めるために知っておく必要がある特徴:

  • 各操作では、原則として一回のディスクI/Oしか発行されない:
    • PUT/DELETE/DELETE_RANGE操作であれば、対応するレコードのジャーナル領域への書き込み(追記)のみ
      • 正確に言えば、PUTでは最大で二回の書き込みが発行されるが、今回の観点では一回と見做しても問題ない(詳細は後述)
    • GET操作であれば、対象データの読み込みのみ
    • HEAD/LIST/LIST_RANGE操作では、そもそもディスクI/Oが不要
    • Journal Region GCPrecise Number of Disk Accessesに記載のように、各操作に付随していくつか補助的なI/Oが発行されることはあるが、各操作との依存性はないため、今回の話には影響を与えない
  • 一つのストレージに対する操作要求群は、直列的に処理される
    • 並列処理に纏わるタイミング問題やレースコンディションは発生しない
  • ジャーナルのオンメモリバッファが存在する:
    • 操作が完了しても、対応するジャーナルのレコードがディスクには同期されていない(書き込まれていない)ことがある
    • ジャーナルの内容のディスクへの同期タイミングは、ストレージ単位ないし操作単位でのオプションで指定可能
      • 前者はStorageBuilder::journal_sync_intervalオプションで変更可能で、デフォルト値は4096

PUT時のディスクI/O回数に関する補足

データをジャーナル領域に埋め込まない場合には、PUT操作を処理するためには、以下の二回のディスク書き込みが必要となる:

  1. データ領域に、対象lumpのデータを書き込む
  2. ジャーナル領域に、対象lumpデータのデータ領域内での格納位置を記したPUTレコードを追記する
    • これが完了したら呼び出し元に「格納が成功した」旨を返す

仮にこの12の間の地点で、プロセスがクラッシュしたとして、以下の理由により、ストレージの動作としては問題は生じないことになる:

  • まだ呼び出し元に成功応答は返していない
  • ジャーナル領域には、対応するPUTレコードは未保存:
    • ストレージは、再起動時にはジャーナル領域のレコード群から、以前の状態を復元する
    • 「ジャーナル領域に対応するPUTレコードが存在しない」ということは「データ領域にも対応するデータが存在しない」ものとして扱われるので、そもそも(再起動時には)1の処理がなかったことになる

つまり1の前と後ろのどちらでクラッシュしようが、ストレージの内容には影響を与えることは無いので、実質的には1の書き込み操作は考慮しなくても構わないことになる(換言するなら「データ領域への書き込みは考慮せずに、ジャーナル領域のみへの書き込みを気にすれば良い」となる)。 そのため、以降の話を簡単にするために、PUT操作に関しても「(整合性に影響を与える可能性がある)I/O操作は2の一回のみ」として話を進めることとする。

クラッシュ整合性

この節では「各操作要求の実行中にプロセスがクラッシュしたら、ストレージの整合性にどのような影響を与えるか」を考えてみる。 なお、ここで取り上げているのは「プロセスのクラッシュ」のケースではあるが、それを「ディスクに発行されたI/O要求がエラーとなったら」と読み替えることも可能である。

ジャーナルメモリバッファが無効な場合

まずは「ジャーナルメモリバッファが存在せず、各I/O要求は即座にディスクに反映される」という条件のもとでのクラッシュ整合性について考えてみる(StorageBuilder::journal_sync_interval0を指定することで、この条件を実際に満たすことが可能)。

取得系操作(e.g., GET)に関しては、ディスクの状態を変更することはないため、クラッシュ整合性の観点では無視してしまって構わない(整合性を崩すことは無い)。

CannyLSが提供する更新系操作は、相互に独立するもののみであり、また各操作要求の処理に必要なディスク回数も一回のみであるため、クラッシュ整合性を考える際に複数のディスクI/Oの実行タイミングを考慮する必要はない。
個々のディスクI/O(書き込み)に焦点を当てた場合には、以下の状況が発生し得ることになる:

  • (a) ディスクI/Oの完了後にクラッシュ
    • OK: 操作適用後のストレージの状態は正常に永続化された
  • (b) ディスクI/Oの発行前にクラッシュ
    • OK: ディスクの状態は未更新であり、ストレージは対象操作を提供する以前の整合性のある状態のまま
  • (c) ディスクI/Oの発行中にクラッシュ
    • NG: ディスクの状態(対象データの書き込みがどこまで完了しているか)は未定義
    • この場合には、永続化されたストレージの状態に不整合が発生している可能性がある
    • ※ なお「ジャーナルメモリバッファが無効化されており」かつ「ジャーナル領域にlumpを埋め込んでいない」のであれば、ジャーナル領域に追記されるレコードのサイズは100Bにも満たないため、通常はディスクへの書き込みはアトミックに処理されるはずである(i.e., (C)のケースが発生しない)

CannyLSでは(C)のケースを完全に保護するような仕組みは存在しないため、プロセスクラッシュによりストレージの整合性が崩れた(壊れた)場合には、使用を再開するには一度その全データをクリアする必要がある。ただし、整合性が崩れた場合でもあっても、たいていは(ジャーナル領域内のレコード群を格納するための)リングバッファの末尾が若干欠損しているだけであるため、将来的には何らかのツールを提供し、直近に処理された操作群以外は容易に復旧可能となるようにするかもしれない。

なお、上述のいずれのケースであっても、各操作要求が完全に実行されるまでの要求発行元に結果応答が返されることもないため、利用者の観点からすれば「成功応答が返って来たのであれば、要求した操作は正常に処理され、結果のストレージの状態も永続化されている」と判断することが可能となる。

ジャーナルメモリバッファが有効な場合

ここではストレージのジャーナルメモリバッファが無効化されており、全てのジャーナル領域へのレコード追記が即座にディスクに反映されるケースについてを考えてきた。 ただし、(主に性能上の理由から)ジャーナルメモリバッファが有効になっていると「ジャーナル領域へのレコード追記タイミング(メモリ上のバッファへの追記)」と「そのディスクへの反映タイミング(メモリ上のバッファのフラッシュ)」の間でラグが発生してしまうことになる。 各操作要求の発行元に対して応答を返す条件は、正確に書くなら「ジャーナル領域へのレコード追記完了後」となり実際にディスクに追記レコード群が永続化されているかどうかは考慮されていない。そのため、ジャーナルメモリバッファが有効になっている場合、例え成功応答が要求元に返されたとしても、その後メモリバッファの内容がディスクに同期(フラッシュ)される前に、プロセスがクラッシュしてしまった場合には、バッファに保持されているレコード群に対応する操作群に関しては、(成功応答を返したにも関わらず)ストレージに永続化されず、再起動時のストレージの状態には反映されないこととなる。

整合性を最大限に重視したい場合には(上述の通り)StorageBuilder::journal_sync_interval0を指定してストレージを開くと良い。また、特定の操作でのみ整合性を高めたい場合には、該当操作をjournal_syncフラグをセットして実行すれば、要求発行元に応答が返る前に確実にメモリバッファの内容がディスクに同期されるようになる。

ジャーナル領域のGC

ジャーナル領域にはGC機構が備わっているが、それがプログラムのクラッシュによる影響を受けることはない。詳細はJournal Region GCを参照のこと。

ジャーナル領域の整合性保証

ジャーナル領域内の各レコードには、その内容から算出されたチェックサム(Adler32)が付与されている。 レコードの読み込み時(典型的にはストレージ再起動時)には、その内容とチャックサムの照会が行われ、両者が一致しなかった場合にはエラーとなる。

データ領域の整合性保証

データ領域内のlumpデータ群に関しては、CannyLSは整合性保証の仕組みを提供していない。 そのため、データ領域内のデータが破損したり改変されたりした場合でも、GET要求に対しては、指定されたlumpに対応する範囲に格納されているデータをそのまま読み込み、要求元に返すだけである。 もしlumpの内容の検証が必要な場合には、それを実施するのはCannyLSの利用者側の責務となる。