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.
PRの状態
このPRの狙い
Cannylsのissue #28 を解決する。
アルゴリズム
アイディア
リソースの解放要求を、現時点の実装(Storageでdeleteを行ったタイミングでアロケータに解放要求を送る)より遅らせる。
より正確には、Storageでdeleteを行いdata portion pをlump indexから取り外した際に、アロケータにpの解放要求を送らず、それを貯めておき、対応するDeleteレコードがストレージに永続化されたことが確認できたタイミングでアロケータにpの解放要求を送る。同様のことをdelete_rangeに対しても行う。
制約
制約を考えない場合の最も素朴な実装は
といったものが考えられる。ただしこの方法ではストレージフォーマットを変更しなくてはならず、一時記憶が避けられないdata portionに加えてid達も追加で覚えなくてはならず、最適とは言い難い。
実装
DelayedReleaseInfo
構造体のinsert_data_portions
メソッドを用いる。DelayedReleaseInfo::detect_new_synced_delete_records
で記録する。insert_data_portions
でバッファしているdata portion達について、detect_new_synced_delete_records
される度に先頭からその分だけ解放可能な領域としてアロケータに解放要求を送る。正当性
このPRの実装では、Deleteレコード及びDeleteRangeレコードに個別にidを割り振っていない。
ジャーナル領域がring bufferすなわちFIFO構造をなすので、先にジャーナル領域に追加されたDeleteレコードまたはDeleteRangeレコードでは、先にデキューされたGCキューに登録されるからである。
これは
DelayedReleaseInfo
構造体にバッファする場合でも同様であるから、幾つのレコードが新たに安全に解放可能になったかという個数の情報だけが必要になる。挙動の図式的な説明
最後に簡単な例を用いて実際の挙動を説明する。以下では簡単のために削除レコードだけを記載する。
起動時に判明する情報として、まだGCキューに入っていない永続化済み削除レコードの数として「3」を記憶する。適当な操作のあと、以下の状態に遷移したとする:
二つの削除操作がジャーナル領域open後に行われ、次の順番で削除候補data portionをバッファしているとする:
さて、GCキューへの追加により
DEL(42)
までがエンキューされたとする:エンキューの過程で永続化が確定している削除レコード4つに遭遇したことになる。このうち3つのレコードは初期状態から存在していたものであるから、実際には4-3=1個のレコードがlump indexからdata portionを除外した本質的な削除操作ということになる。従って、バッファの先頭に存在する
0x1234
が安全に解放できるということになる。更に処理が進み
の状態では5-3=2個のレコードが永続化された本質的な削除操作であることが分かり、新たに
<0x4310, 0x4311, 0x4312, ..., 0x431a>
の10個が一挙に安全に解放可能になることを意味する。最後に
となり、6-3=3個のレコードが永続化された本質的な削除操作であることが分かり、
0x1235
が新たに安全に解放可能になることを意味する。このPRで発生する問題
既存のテストコードの修正について
storage::mod::tests::full
に対する修正の理由https://github.com/frugalos/cannyls/pull/31/files#diff-a2828e4f71806f3e4623d048057803d4R672
storage::mode::tests::confirm_that_the_problem_of_pr23_is_resolved
に対する修正の理由https://github.com/frugalos/cannyls/pull/31/files#diff-a2828e4f71806f3e4623d048057803d4R1005
議論しておくべき点
実際の解放処理をどこで行うか
現在は
run_side_job_once
が呼ばれる度に、その段階で安全に解放可能なデータポーションを全てアロケータに通知して解放するようにしている。https://github.com/frugalos/cannyls/pull/31/files#diff-a2828e4f71806f3e4623d048057803d4R316
リソース使用量に関する議論
GCが行われるまで
DelayedReleaseInfo
にDataPortionU64
を格納し続けることになるため、大量のメモリが必要になる。これは以下の簡単な計算で確認できる:cannyls/src/storage/journal/region.rs
Lines 172 to 176 in 8759940
DataPortionU64
が格納されることになる。例えば PUTの直後にDELETE を繰り返す場合は、40GBのジャーナル空間にPUTとDELETEをそれぞれ3.5億(1件30バイトで計算)エントリ追加できる。8バイト*3.5億=2.8GB
の空間が必要になる。以上の計算はデバイス層を無視してストレージ層を直接使った場合の話で、デバイス層では一定時間コマンドが発行されずにアイドリングが続いた時(デフォルトでは100ms)にGCが呼び出される ため、一般的な使用状況では緩和されることになる。
ただし、100ms以内に常にデータが飛んでくる場合には、結局の所ストレージ層単体の計算となり、大量のメモリが必要になることに変わりはない。
なお一回のGCではデフォルトで4096エンキューされる、すなわち最大で4096エントリ解放可能になるだけであり、3.5億エントリある状況では焼け石に水である。
対策
DelayedReleaseInfo
にサイズ上限を設けたほうが良いだろう。このサイズ上限にリーチした場合にはGCを行わなくてはならないとする。
ただし、GCを行ったとしても解放できるとは限らないことから、あまり抜本的な対策にはなっていない。
パフォーマンス測定
実行時間に関する測定
アロケータの挙動が変わってしまうため、フェアな測定が難しい。
メモリ使用量について
valgrind --tool=massif
を用いて測定できる。