cannyls
cannyls copied to clipboard
異常終了時の再起動後に意図しないデータを読み込んでしまう
問題
CannyLS v0.9.3以前では、プロセスの異常終了後に再起動すると本来読み込めるべきでないデータが読み込めてしまう。
状況の詳述
以下のような計算の流れを考える: (完全なコードについてはこのissueのためのリポジトリをご覧ください)
- 2つの異なるlumpid,
lump_id1とlump_id2を準備する。 - まず
lump_id1にデータ"hoge"をputする。- ここでジャーナル領域のディスクへの同期が行われる。
- 次に
lump_id1を削除する。 - 次に
lump_id2にデータ"foo"をputする。- この時、既にメモリアロケータは"hoge"のあった領域を再利用可能として認識してしまっているため、ここに"foo"を書き込んでしまう。
- プロセスがcrashして再起動する。
-
lump_id1からデータが読み込める。これだけであればロールバック的な挙動として容認できるが、読み出したデータが"foo"になってしまっている。
let lump_id1 = LumpId::new(1_111);
let lump_data1 = storage.allocate_lump_data_with_bytes(b"hoge").unwrap();
storage.put(&lump_id1, &lump_data1); // lump_id1に"hoge"を書き込む。
storage.journal_sync(); // ジャーナルのディスクへの同期を行う。
storage.delete(&lump_id1); // lump_id1を削除する。
let lump_id2 = LumpId::new(22_222_222);
let lump_data2 = storage.allocate_lump_data_with_bytes(b"foo").unwrap();
storage.put(&lump_id2, &lump_data2); // lump_id2に"foo"を書き込む。
// ここでプロセスがcrashする。
let nvm = track!(FileNvm::open(path))?;
let mut storage = track!(Storage::open(nvm))?;
println!("{:?}", storage.get(&lump_i1).unwrap()); // ===> "foo"
回避策
deleteを行う際には毎回syncを行う。
より良い対応に向けて
上の回避策では大幅にパフォーマンスが低下するため、以下の何れかの条件のもとでより良いパフォーマンスを満たす対応策を打ち出したい:
- ロールバック的な挙動(すなわち、削除した後で読み込めても、確実に書き込んだ値が返ってくる)は許す
- アロケータにメモリ位置を解放した通知を行うタイミングを遅らせる(遅らせた分だけ空き容量が今より常に減ったように見えるかもしれない)
これは問題ですね...。 対応としては「アロケータにメモリ位置を解放した通知を行うタイミングを遅らせる」が良さそうに感じました。 (解放依頼はすぐにアロケータに発行せずにジャーナル領域のキューに積んでおいて、unreleased_headの更新タイミングで、永続化されたことが分かっている分に関しては、キューから出して、アロケータに実際の解放依頼を投げる)
quickな返信ありがとうございます。 タイミングを遅らせる方針で手元でコード変更しているので、近い内にPRに出来るかと思います。 (ロールバック的な挙動を許す方は書きはしたものの、実装案は特にないです……)
👍