Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue28のための新しいパッチ #34

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

yuezato
Copy link
Member

@yuezato yuezato commented May 10, 2019

概要

Cannylsのissue28を解決するために、リソースの解放を遅らせる「遅延解放」アルゴリズムを実装する。

後方互換性について

このPRでは、遅延解放アルゴリズムをpending_portionsというオンメモリのベクタ構造の追加によって達成している。

従って、既に存在するlusfファイルに対する破壊的な変更を行うことはない。よって

  1. これ以前のCannylsで使っていたlusfファイルを、このPRを入れたCannylsで開くことができる。
  2. このPRを入れたCannylsを使って作られたlusfファイルは、これ以前のCannylsで開くことができる。

また、このPRを取り込んでもデフォルトでは遅延解放の効果はない。
このテストを参考にされたいが、

        let mut storage = track!(StorageBuilder::new()
            .journal_region_ratio(0.5)
            .enable_safe_release_mode()
            .create(nvm))?;

のように明示的に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)
...
... Delete(123) ... N/A
Put(Lumpid=456, Data="hoge") ...
Put(123, 0xAA)
...
... Delete(123) Put(456, 0xAA) ...
0xAA: "hoge"
...
N/A
クラッシュして再起動 ...
Put(123, 0xAA)
...
...
0xAA: "hoge"
...
N/A
Get(LumpId=123) ...
Put(123, 0xAA)
...
...
0xAA: "hoge"
...
"hoge"

注意: ジャーナル領域が個別の操作ごとに永続化されないのは高速化のためである。Cannylsにはジャーナルバッファと呼ばれる構造が存在し、まずこのバッファに書き込まれ、条件が満たされた時点でバッファを一括でディスクに永続化する工夫をしている。

クラッシュして再起動する際には、永続化済みのジャーナル領域から割当情報を復元するため、

  1. LumpId=123は削除されたにもかかわらず、削除コマンドが永続化される前に再起動したために存在し
  2. 加えてLumpid=123が元いたアドレス0xAAには別のデータが書き込まれたため、それを読み込んでしまう
    という問題が生じている。

この問題を次のように整理する:

  1. 永続化前ジャーナル(後述だが、これはCannylsの高速化のための仕組みで、正確にはジャーナルバッファと呼ばれている)が必要不可欠である => 再起動後にLumpId=123が存在してみえるのは仕方がない
  2. 問題なのは実際にLumpId=123に書き込んでいないデータが読み込めてしまうこと。

意図しないデータが読み込めてしまうのは、LumpId=123の占めていたアドレス0xAAは解放してはならないにも関わらず、Deleteを受けてしまった段階で解放したことにあると考える。

これを受けて、新しい実装では以下のように振る舞う。

新しい実装での振る舞い

Delete操作が永続化されるまでは、データ領域から解放しないということを行う。すなわち、上のコマンド列に対して次のように振る舞う:

コマンド 永続化済
ジャーナル
永続化前
ジャーナル
データ領域 コマンド
実行結果
Get(LumpId=123) ...
Put(123, 0xAA)
...
... ...
0xAA: "fuga"
...
fuga
Delete(Lumpid=123) ...
Put(123, 0xAA)
...
... Delete(123) ...
0xAA: "fuga"
...
N/A
Put(Lumpid=456, Data="hoge") ...
Put(123, 0xAA)
...
... Delete(123) Put(456, 0xBB) ...
0xAA: "fuga"
0xBB: "hoge"
...
N/A
クラッシュして再起動 ...
Put(123, 0xAA)
...
...
0xAA: "fuga"
0xBB: "hoge"
...
N/A
Get(LumpId=123) ...
Put(123, 0xAA)
...
...
0xAA: "fuga"
0xBB: "hoge"
...
"fuga"

変更点はDeleteしても0xAAは即座に解放されないという点にある。
その結果、再起動後に削除コマンドを発行した後のLumpId=123が存在しているとしデータが読み込めてしまうが、読み込めるデータは実際に書き込んだものになる。

再起動後もデータが読み込めないことが理想的ではあるが、高速化のためにジャーナルバッファを導入したしわ寄せであり、それを達成することはできない。

アルゴリズムの実現・実装

  1. lump indexから外されたが直ちには削除できないDataPortionたちを覚えておくためのエントリ pending_portionsの追加
    https://github.com/frugalos/cannyls/pull/34/files#diff-69698d1d35fde6060ec4d673a0c6f9b1R123
  2. ジャーナルバッファがディスクに永続化されたことを確認するメソッド is_just_synced の追加
    https://github.com/frugalos/cannyls/pull/34/files#diff-d6b5f96b4c7086aefad0a4e3a6a32176R185
    • ジャーナルバッファ中のエントリの永続化判定は、sync_interval(デフォルトは4096)回のジャーナルバッファへのエントリ追加が行われた後に実行される sync に依存する。
  3. 永続化直後にrelease_pending_portionsメソッドを呼び出すことで、遅延している解放処理を実行する。 https://github.com/frugalos/cannyls/pull/34/files#diff-a2828e4f71806f3e4623d048057803d4R199

DeleteRangeの特別性

このPRでは、delete rangeを特別視し、直後にsyncを呼び出してなるべく早くdata portionを解放している。これはDeleteRangeでは一度の操作で登録済みのLumpを全て削除することが可能であるから、その結果としてpending portionsが肥大化することを避けるためである。

実行時間に関する議論

本質的に追加された処理は、DataPortionをlump indexから削除する際にそれをpending_portionsに移動するだけのものである。従って大きな影響は与えないと考える。

(必要であればベンチマークを行う)

リソース使用量に関する議論

pending_portionsに格納可能なDataPortionの個数は sync_interval 個である。
例えばデフォルトの4096個である場合には、DataPortionが約8バイトであることを考えても非常に限られた追加メモリ使用量で済むことが分かる。

@yuezato yuezato changed the title issue28のための新しいパッチ [Draft] issue28のための新しいパッチ May 10, 2019
sile
sile previously approved these changes May 27, 2019
Copy link
Member

@sile sile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

性能的に問題がないなら、適当な(e.g., v1.0.0リリース)タイミングで safe_release_mode = false 時の挙動は削除してしまいたいな、と思いました。

@yuezato yuezato changed the title [Draft] issue28のための新しいパッチ issue28のための新しいパッチ Jun 17, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants