Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
概要
Cannylsのissue28を解決するために、リソースの解放を遅らせる「遅延解放」アルゴリズムを実装する。
後方互換性について
このPRでは、遅延解放アルゴリズムを
pending_portions
というオンメモリのベクタ構造の追加によって達成している。従って、既に存在するlusfファイルに対する破壊的な変更を行うことはない。よって
また、このPRを取り込んでもデフォルトでは遅延解放の効果はない。
このテストを参考にされたいが、
のように明示的に
enable_safe_release_mode
メソッドを呼ばない限りは、これまで通りの振る舞いをする。効果
issue28で0.9.3のmasterで問題が起こることを https://github.com/yuezato/get_unexpected_data/ で確認済みである。
一方で、このPRを使うと https://github.com/yuezato/get_unexpected_data/tree/use_PR34 「意図しないデータが読み込める問題」が解消することが確認できる。
備考
以前出していたパッチ #31 では、
GCのタイミングを持って永続化完了とみなしているため、ピーク時のメモリ使用量の見積もりが難しいといった問題があった。
Cannylsには明示的にジャーナルバッファをディスクに同期書き出しする機能があるため、ここで多くのことを行う実装に変更する。
PRの状態
アルゴリズムの説明
このPRでは、Lumpを安全に解放できるようになるまで解放しない(待つ)、という戦略をとることでissue28を解決する。安全な解放によって、解放した後に意図しないマシン再起動などが行われても、正常な状態に回復することを目指す。
(これ以前では、リソースを待たずに解放してしまうために、再起動後に異常な状態に入っていると言える。)
このPRのもとでは、あるLumpを削除したとしても、そのLumpが存在していたデータ領域を即座に再利用可能とすることはしない。これは「Lumpの使っていたデータ領域を安全に解放できるのは、そのLumpを削除する操作がジャーナル領域に永続化された時点」であると考えるからである。
現状の実装と問題点
即座に再利用可能とする場合の問題点は、issue28の問題そのものであるが、これを説明するために次の例を考える。これは
LumpId=123
のデータを読み出した後に削除し、別のLumpId=456
にデータを書き込んだところでマシンクラッシュしてしまい、再起動後にLumpId=123
からデータを読み出すと、意図しないデータを読み込んでしまう状況を表している。ジャーナル
ジャーナル
実行結果
Get(LumpId=123)
Put(123, 0xAA)
...
0xAA: "fuga"
...
fuga
Delete(Lumpid=123)
Put(123, 0xAA)
...
Put(Lumpid=456, Data="hoge")
Put(123, 0xAA)
...
0xAA: "hoge"
...
Put(123, 0xAA)
...
0xAA: "hoge"
...
Get(LumpId=123)
Put(123, 0xAA)
...
0xAA: "hoge"
...
注意: ジャーナル領域が個別の操作ごとに永続化されないのは高速化のためである。Cannylsにはジャーナルバッファと呼ばれる構造が存在し、まずこのバッファに書き込まれ、条件が満たされた時点でバッファを一括でディスクに永続化する工夫をしている。
クラッシュして再起動する際には、永続化済みのジャーナル領域から割当情報を復元するため、
LumpId=123
は削除されたにもかかわらず、削除コマンドが永続化される前に再起動したために存在しLumpid=123
が元いたアドレス0xAAには別のデータが書き込まれたため、それを読み込んでしまうという問題が生じている。
この問題を次のように整理する:
LumpId=123
が存在してみえるのは仕方がないLumpId=123
に書き込んでいないデータが読み込めてしまうこと。意図しないデータが読み込めてしまうのは、
LumpId=123
の占めていたアドレス0xAA
は解放してはならないにも関わらず、Delete
を受けてしまった段階で解放したことにあると考える。これを受けて、新しい実装では以下のように振る舞う。
新しい実装での振る舞い
Delete
操作が永続化されるまでは、データ領域から解放しないということを行う。すなわち、上のコマンド列に対して次のように振る舞う:ジャーナル
ジャーナル
実行結果
Get(LumpId=123)
Put(123, 0xAA)
...
0xAA: "fuga"
...
fuga
Delete(Lumpid=123)
Put(123, 0xAA)
...
0xAA: "fuga"
...
Put(Lumpid=456, Data="hoge")
Put(123, 0xAA)
...
0xAA: "fuga"
0xBB: "hoge"
...
Put(123, 0xAA)
...
0xAA: "fuga"
0xBB: "hoge"
...
Get(LumpId=123)
Put(123, 0xAA)
...
0xAA: "fuga"
0xBB: "hoge"
...
変更点は
Delete
しても0xAA
は即座に解放されないという点にある。その結果、再起動後に削除コマンドを発行した後の
LumpId=123
が存在しているとしデータが読み込めてしまうが、読み込めるデータは実際に書き込んだものになる。再起動後もデータが読み込めないことが理想的ではあるが、高速化のためにジャーナルバッファを導入したしわ寄せであり、それを達成することはできない。
アルゴリズムの実現・実装
pending_portions
の追加https://github.com/frugalos/cannyls/pull/34/files#diff-69698d1d35fde6060ec4d673a0c6f9b1R123
is_just_synced
の追加https://github.com/frugalos/cannyls/pull/34/files#diff-d6b5f96b4c7086aefad0a4e3a6a32176R185
release_pending_portions
メソッドを呼び出すことで、遅延している解放処理を実行する。 https://github.com/frugalos/cannyls/pull/34/files#diff-a2828e4f71806f3e4623d048057803d4R199pending_portions
のメンバを全てアロケータに伝えて解放する。DeleteRangeの特別性
このPRでは、delete rangeを特別視し、直後にsyncを呼び出してなるべく早くdata portionを解放している。これはDeleteRangeでは一度の操作で登録済みのLumpを全て削除することが可能であるから、その結果として
pending portions
が肥大化することを避けるためである。実行時間に関する議論
本質的に追加された処理は、DataPortionをlump indexから削除する際にそれを
pending_portions
に移動するだけのものである。従って大きな影響は与えないと考える。(必要であればベンチマークを行う)
リソース使用量に関する議論
pending_portions
に格納可能なDataPortionの個数はsync_interval
個である。例えばデフォルトの4096個である場合には、DataPortionが約8バイトであることを考えても非常に限られた追加メモリ使用量で済むことが分かる。