FASTERについてのメモ(日本語)
- KVSライブラリ
- 組み込みKVSとしての使用が可能
- プロセス間でのストレージ共有は想定していない?
- メモリ以上のDBサイズを設定可能(メモリ+ファイル読み書き)
- メモリオンリーも可
- メモリオンリーにした場合、ディスク行きになる部分は単純にキーごと捨てられる=古いものから消えていく
- LiteDBと多少領域が被るかも(最終的に目指す所は異なるが)
- ログデータベース(耐障害性)とインメモリデータベース(性能)の良い所どりを目指す
- ユーザー定義のupdate、readコールバックを設定可能
C#版ではRoslyn,C++版ではテンプレートを使って実現?- C++はテンプレート
- 新版(少なくとも2019.4.5.2以降)ではUnsafeコードを使うことにより、Roslyn依存を排除
- C#とC++の二つの実装が存在
- 両者間での性能比較の数値は無し
- 三種類のストレージ出力
- オンメモリ(In-Memory)
- 追記のみログ(Append-Only)
- 複合(HybridLog)
- Upsertに特に注力
- 同時書き込みスレッド数に比例したスループット向上
- オンメモリにある場合、構築済みデータに対する書き込み性能はスレッド数に比例して増加する
- ConcurrentDictionaryに対してかなり有利となる
- オンメモリにある場合、読み込みはConcurrentDictionaryとほぼ変わらない
- https://github.com/Microsoft/FASTER/wiki/Performance-of-FASTER-in-C%23
- Concurrent
- 非同期処理に関してはEAPパターンを使用している
- 内部的にはAPM
- キーの列挙は現在サポートしていない
FasterKV.Log.Scan
は存在するが、Compact
しない限り値更新後の旧レコードまで持ってくるので、最新版のレコードのみ列挙したい場合には難しい(列挙時に旧バージョンのアドレスが取得できるため、一応不可能ではない)- TODOには入っている
- レプリケーション等の機能は無し
- 用途的に必要なさそう
- 更新でも追記するため、定期的な
Compact
実行は必須- 100000回のlong,longな更新で5-6MB程度ログが溜まるため、空間効率は優先度低めっぽい
- long,longなデータに対して100000回程度の更新の後のCompactは、ディスク書き出しがされていてもほとんど数~数十msec
- 100_0000回だと数百msec程度かかったので、割と頻繁にCompactしてもいいかもしれない
- 数秒に一回程度の頻度でも問題ないかもしれない
- その他のパフォーマンスインパクトはどうなるのか?
- ほとんど影響はなかった
- 資料を見る限り、完全に止まるということは無いかもしれない
- スレッドごとに
StartSession
またはContinueSession
し、終了時にStopSession
する必要がある- しないと各種操作時に例外
- await後はスレッドが大体変更されるので、async awaitと混在してはならない
- upsertあるいはread時にmonotonicSerialNumという値を渡す必要があるが、
ContinueSession
時に、最後にRead
等で与えた数値が返される
- KeyとValue両方にBlittableな型を使用するのが一番効率が良い
- Key,Valueどちらも引数無しのコンストラクタを持っていなければならない
- Key,ValueどちらかがBlittableではない場合、GenericAllocatorが使用される
- GenericAllocator使用で、かつKey,Valueにclass使用時はSerializerSettingの追加が必須
- Windowsで動くときは、ストレージ操作で特殊実装しているので、非Windows環境ではまた特性が異なる可能性がある
- オンメモリの場合はさほど変わらないと予想
- エンディアン等は考慮しないでそのまま出力しているようなので、マシン外に持ち出すことは恐らく考えられていない
- ファイルに書き出されたデータ
- 古いデータからディスクに書き出される
- 新しいデータの方が読出しが早くなる率が高い
- ファイルに書き出されたデータを読みだそうとすると、Readで常にPENDINGが返ってくるようになる
- PENDINGが返ってくる場合は、SingleReader,ConcurrentReader,ReadCompletionCallbackを適切に実装する必要がある
- SingleReader,ConcurrentReaderでdstに値を入れ、ReadCompletionCallbackのctxに、outputで渡された値を入れる
- オンメモリに載らないデータを扱う場合、Functionsの実装は必須
- 古いデータからディスクに書き出される
- FasterKV.EntryCountは重い処理とみなした方が良い
- Blittableでなく、かつstructでもないオブジェクトに関しては、IObjectSerializerが使われる
- 実データはObjectDeviceで指定した領域に保存される
- 本体ログの方には開始点のポインタのみが記録される
- IObjectSerializerの意味は以下の通り
- BeginSerialize(Stream stm)
- Serialize開始前に渡されるデータストリーム
- このstmに対してバイナリデータを書き込む
- Serialize(ref T obj)
- バイナリデータをBeginSerializeで渡されたストリームに書き込む
- オフセットは調整済みなので、うかつに動かさないこと
- EndSerialize()
- Serialize後に呼ばれる
- Begin,Serializeで作成したオブジェクト等の後始末を行う
- BeginSerialize(Stream stm)
- パラメーターについて(https://microsoft.github.io/FASTER/tuning/Log.html)
- PageSizeBits
- 1ページ辺りのビット数
- 小さいと空間効率が悪くなり、ディスク行きになるデータが多くなる
- 可能な限り増やすべきだが、そうすると後述のMemoryBitsを増やさなければならなくなる
- 増やしすぎるとリカバリ等で致命的なダメージになりやすいかもしれない
- MemoryBits
- オンメモリで持っておくページの最大サイズ
- FASTERでは最低でも16ページが保持できることを期待しているため、
PageSizeBits + 4 <= MemoryBits
でなければならない - 許す限り大きな値を取っておくのが吉
- SegmentBits
- ディスクに保存するログファイルのサイズ
- メモリに乗り切らないログデータがディスクに書き出される
- ディスクに書き出されるときは常に
2^SegmentBits
バイトになる - MemoryBitsと同じサイズにすれば大体間違いない
- 大きくしすぎると、ログファイル作成時やFlush時に時間がかかり、後述のチェックポイント作成に時間がかかる場合がある
- とはいえ、小さくしすぎると膨大な数のファイルを作成するため、やはりチェックポイント作成に時間がかかる
- PageSizeBits
- データはデフォルトで永続化されない
- 明示的にCheckpointを作成しなければならない
- チェックポイントにはIndexCheckpointとHybridLogCheckpointが存在
- HybridLogCheckpointの方に実データが存在
- IndexCheckpointの方は、ht.datという名前で
IndexSize * 64
Bytesのファイルが作成されるので注意
- ReadCacheSettingを設定していた場合、HybridLogのチェックポイントのみ作成可能
- どのチェックポイントから復元するかは、チェックポイント作成時にGUIDを渡されるのでそこから判断する
- ローリングしたい場合は
CheckpointCompletionCallback
でどうこうする?- でも自身のチェックポイントIDがわからないので問題はある
- チェックポイント作成中は他のスレッドは妨害されないので、性能低下は無いらしいが、実際はどうなのか
- snapshotsを選択した場合、HybridLogCheckPointにはメタ情報+フラッシュされた全データが記録される
- DBフルバックアップと同じ
- 場合によっては数秒以上かかるので、それなりに重い処理
- 毎回データ内容をフルダンプするため、ディスク効率は悪い
- ログをフラッシュし、その後チェックポイントにはメタデータ情報のみを記録
- Snapshotより短時間で完了可能(ログFlushの時間次第)
- ログデータに不整合が発生した場合、チェックポイントが機能しない可能性がある
- Compactによっても機能しなくなる可能性がある
- 整合性よりは、継続性を重視したい(停止→開始前後の処理時間を短くしたい、チェックポイントを頻繁に出力したい)場合に使う
C#版はデフォルト実装はWindows(x86系)のみ対応- dotnet core及びnet46をサポートして、プラットフォームの制約は無くなった
ユーザー定義処理をオーバーヘッド無しで行うため、C#版は初回起動時に対象データ構造に合わせた処理をコード生成している- Unsafeコードで代替
- C++版はクロスプラットフォーム(x86系のみ)対応
- Win+32bit,Win+64bit,Linux+32bit,Linux+64bit
- Mac,xBSDは不明(多分できない)
- プラットフォームごとにビルドが必要
- どのプラットフォームでもcmakeが必要
- Win版は特にVS2017+C++サポートが必要
- Linux版のビルドの場合、依存関係にあるライブラリが若干多い
- vcpkgを使えば楽かもしれない
- Win+32bit,Win+64bit,Linux+32bit,Linux+64bit
FasterKVと同一のオブジェクトストレージを利用した、ログ格納と読出しに特化したデータ構造。 FasterKVに比べて準備は少ないので、始めるには良い。
- LogDeviceにdeleteOnCloseフラグを付けると、若干挙動が怪しくなるので注意→用途的に使う場面は考えられない?
- 扱えるデータ型は
byte[]
のみ - 追加は
Enque()
、検索はScan()
削除はTruncateUntil()
となる- 削除は、指定アドレスまで切り詰め処理を行う
TruncateUntil
のみ
- 削除は、指定アドレスまで切り詰め処理を行う
Enque()
で使えるIReadOnlySpanBatchの用途は不明Scan
で返ってくる要素は直接列挙はできないため、GetAsyncEnumerable()
でIAsyncEnumerable
を取得して処理を行う- 削除で下手な値を指定すると、次の
Scan
時に例外が起きる- 2のべき乗なら大体安心か?
- 検索、削除で下手なアドレスを指定すると、例外が起きたりする
- 検索で終端アドレスに
long.MaxValue
等の大きい値を指定すると、処理が返ってこなくなる- ドキュメントでは特に言及されていないため、バグか使い方を間違えている可能性がある
TailAddress
、CommittedUntilAddress
を終端アドレスとして指定すると良い