CDC — 変更フィード(FEED.* / changes_since)
kevyは、適用されたすべての書き込みを消費可能な変更ストリームとして公開します。AOFがログに記録し、レプリカが適用するのと同じ効果フレームです。典型的なコンシューマは、キャッシュの無効化、検索インデックスの更新、下流のミラー——RDSスタックで「outboxテーブル」がやっている仕事を、テーブルなしで、というものです。
configで有効にします。
[feed]
enabled = true
# feed_buffer_size = "64mb" # per shard, caps at 1gb組み込みならConfig::default().with_feed(0)です。
カーソル:(generation, offset)
すべてのストリーム位置は(generation, offset)の組です。
offsetは、1つのgenerationの内側で、適用された書き込みごとに1ずつ増えます。generationは、1つの途切れないオフセット履歴を識別します。ある(generation, offset)は、永遠に同じストリームの接頭辞を指します。- クリーンなシャットダウンと再起動は両方を保存します。コンシューマは中断したところから再開できます。
- 連続性が破れるとgenerationが進み、オフセットは0から振り直されます。
FLUSHALL、スナップショットからの復元、そしてクリーンでないシャットダウン(クラッシュ)がそれにあたります。generationが進んだことは、コンシューマにとって「自分のビューが古いかもしれない」という合図です。再構築してから再開してください。
サーバー面
FEED.SHARDS→:N— 独立したストリームの数(シャードごとに1本。キーはシャードに写像されるので、キー単位の順序はストリーム内で保証されます。シャードをまたぐ順序は存在しません)。FEED.TAIL <shard>→*2 [:generation, :next_offset]— 新しいコンシューマが出発点にするカーソル。FEED.READ <shard> <gen> <offset> [COUNT n] [PREFIX p ...]→*3 [:generation, :next_cursor, *frames]、各フレームは*2 [:offset, *argv]。これをポーリングします。フレームリストが空なら追いついたということです。COUNTは1回の呼び出しあたりのフレーム数を制限します(デフォルト256、最大4096)。PREFIX(繰り返し可能、OR)はキーのプレフィックスでフレームを絞り込みます。ただしfail-openです。キーのレイアウトが素直なシングルキーの形をしていないフレーム(マルチキーのDEL、MSET、FLUSHALLなど)は、常に配送されます。フィルタリングがカーソルを変えることはないので、再同期なしにフィルタを変更できます。
エラー
-FEEDRESYNC <gen> <tail>— あなたのカーソルは提供できません(generationが古い、あるいはそのオフセットがバックログから追い出された)。派生状態を再構築し(たとえば自分のプレフィックスをSCANする)、応答に載ってきた(gen, tail)から再開してください。-ERR feed cursor ahead of stream— 未来から来たカーソル。呼び出し側のバグです。
組み込み面
let store = kevy_embedded::Store::open(Config::default().with_feed(0))?;
let (gen, off) = store.changes_tail()?; // start cursor
let batch = store.changes_since(gen, off, 256, &[b"user:"])?;
for change in &batch.changes { /* change.offset, change.argv */ }
let (gen, off) = batch.next; // resume herefeed_shards()は1を返します。組み込みの書き込みパスは全シャードを1本のストリームに直列化するため、同じコンシューマループが両方の面に対してそのまま動きます。FeedError::Resync { generation, tail }が-FEEDRESYNCに対応します。
配送セマンティクス
at-least-once。 再同期による再構築のあと(そしてフェイルオーバーのいくつかの形において)、すでに適用したフレームをもう一度見ることがあります。コンシューマは冪等でなければなりません(キャッシュの無効化は自然に冪等です)。フレームは適用された効果を運ぶため(たとえばZINTERSTOREはDEL + 素のZADDとして現れます)、送り元の状態に関係なく、適用は決定論的です。
保持期間はインメモリのバックログです(シャードごとのfeed_buffer_size)。この窓より遅れると-FEEDRESYNCを受け取ります。オンディスクのアーカイブは存在しません——耐久性のある話は、下のrecovery-point契約を参照してください。
Recovery-point契約(PITR)
フィードを有効にした状態で取ったスナップショットは、そのヘッダにストリームカーソルを記録します。スナップショットのデータと同じ「追記なし窓」の中で凍結された値です。
スナップショットS + Sのカーソル以降のフィードフレーム = 以降の任意のカーソル時点における、正確なその状態。
kevy_persist::read_snapshot_cursor(path)がそれを読み戻します(v2.3より前のスナップショットではNone)。リストアの訓練はこうです。まっさらなデータディレクトリにスナップショットをロードし、記録されたカーソルからFEED.READのフレームをリプレイする——バイト単位で正確な状態が復元され、PREFIX.STATSやキーのダンプで検証できます。訓練用のスクリプトはbench/diskgate.shのrestore行にあります。
プレフィックス単位の統計
PREFIX.STATS <prefix>(サーバー)/ Store::info_prefix(組み込み)は、あるバイトプレフィックスの下にある生きたキー数とTTL付きキー数を報告します。O(キー空間)の走査なので、運用ダッシュボード向けであり、ホットパス向けではありません。
これは何ではないか
シャードをまたぐグローバル順序はありません。サーバーサイドのコンシューマ位置もありません。コンシューマグループもありません。クエリ述語もありません(プレフィックスはバイト列のフィルタです)。それらが必要なら、あなたが説明しているのはメッセージブローカーかRDSです。kevyのフィードは意図的に事象の地平線の手前で止まります(リポジトリのドキュメントにある「3つの法」の設計ノートを参照)。