kevyの上にアプリケーションを設計する
kevyはサービングエンジンです。ふだんなら業務モデルをリレーショナルデータベースに載せ、その手前にキャッシュを置く——そういうアプリケーションのための、一次データストアです。このページはその地図にあたります。エンジンが何を提供し、どんな法がそれを境界づけ、どの能力がどこに住んでいるのかを示します。
RDSからの移行を考えているなら、まずこのページを読み、そのうえでクックブックのレシピを1つずつ辿ってください。
7つのプレーン
| プレーン | 何を運ぶか | 場所 |
|---|---|---|
| P0 — 操作 | あらゆる面のあらゆるop。サーバー(RESP)、組み込み(プロセス内)、Lua、アトミックブロック、パイプライン。OP_TABLEは1つで、パリティはCIが強制する。 | docs/verb-reference.md |
| P1 — アトミック性と耐久性 | シングルシャードのアトミックブロック、決定論的順序の全シャードブロック、appendfsync × アトミックコミットのマトリクス、ブロック単位のfsyncバリア。 | docs/persistence.md |
| P2 — インデックス | 宣言型のセカンダリインデックス、4種類(range、unique、text(CJK bigram + BM25)、ann(HNSW))。構成上の派生物(書き込みフックで維持され、ドリフトはゼロ)、ワンホップのhydration(FIELDS)、バックフィルによる再構築。 | docs/indexes.md、docs/text-search.md、docs/vector-search.md |
| P3 — ビューと代数 | インデックス上の名前付き合成(virtual / materialized top-K)、Redisセマンティクスをそのまま持つzset/set代数。 | docs/views.md |
| P4 — フロー | (generation, offset)カーソルを持つCDCフィード(組み込みのoutbox)、ブロッキングpop、ハッシュフィールドTTL、スナップショット読み出しビュー、組み込みの読み取り専用RESPリスナー。 | docs/cdc.md、docs/embedded-listener.md |
| P5 — 証拠 | 宣言した行はすべて計測され、突き合わされる。クラッシュ整合性のカオスゲート、30Mキーの混在スタックsoak。 | bench/VALIDATION-LEDGER.md |
| P6 — 可用性 | ackされたオフセットの真実とハートビートを持つレプリケーション、計画的なハンドオーバー(FAILOVER)とクラッシュフェイルオーバー(クォーラム選挙、選挙のみが書き込み権限を与える、フォークの破棄)、そしてオプトインの整合性ラダー(WAIT、read-your-writesトークン(REPL.TOKEN/REPL.WAIT)、有界ステイルネス(-STALE)、クォーラムリースによる書き込みフェンス)。13個の実行可能なclamp(availgate)がCIで走る。 | docs/availability.md、docs/replication.md |
3つの法
kevyが「出来の悪いRDS」へ滑り落ちるのを防いできた——そしてこれからも防ぐ——憲法です。
法1 — Redisの契約は不可侵である。 kevyが実装するRedisのopは、すべてRedisとまったく同じように振る舞います。新ジャンルの能力が既存verbのセマンティクスを変えることはなく、内部キー空間がSCAN/KEYS/DBSIZEに漏れることもなく、それらは自分専用のコマンド名前空間(IDX.*、VIEW.*、FEED.*、PREFIX.*)に住みます。
法2 — スーパーセットはエンジンのジャンルに属するときだけ。 ある能力が採用されるのは、それがエンジン自身の持てるライフサイクル——宣言する → 維持する → 検証する → 再構築する——を備えた派生データであるときだけです。インデックス、ビュー、フィード、ダイジェストはすべてこのテストを通ります。アプリのロジックを蓄えることは通りません。
法3 — RDSの事象の地平線。 クエリ言語なし、プランナなし、JOINなし、サーバーサイドのバリデーションなし、トリガーなし。エンジンが「宣言されたアクセスパスを*実行する*」のをやめて「問いへの答え方を*決める*」ようになった瞬間、それはRDSです——それも、出来の悪いRDSです。kevyは恒久的にこの地平線の手前にとどまります。
検討したうえで断ったもの
以下はどれも、必要そのものは本物です。ただし機能の形が間違っている——そして、それぞれに手当ての答えがあります。
| こう求めたくなるかもしれない | 代わりに使うもの |
|---|---|
| SQL / クエリDSL | 明示的なインデックスAPI + クックブック |
| クエリプランナ / 自動インデックス選択 | IDX.EXPLAIN(診断専用) |
| JOIN | ワンホップのhydration(FIELDS、VIA)+ アプリ側での組み立て |
| 外部キー / カスケード | アトミックブロック、プレフィックス一括操作、CDCコンシューマ |
| CHECK制約 | アトミックブロック内での読み出し(アプリが判定し、エンジンがアトミックにコミットする) |
| スキーマ / 型付きカラム | バイト列はあなたのもの。型はインデックス作成時に宣言する |
| JSONパスクエリ | ハッシュフィールドこそがカラムモデルである |
| トリガー / 書き込みパスのUDF | CDCコンシューマ——コミット後、疎結合、リプレイ可能 |
| GROUP BY / 集約パイプライン | 自分のモデルの中で、書き込み時に集約を維持する |
| タイムトラベル | スナップショット + CDCの保持期間(recovery-point契約) |
| トランザクショナルoutbox | 不要——フィードがoutboxそのものである |
インデックスなしのWHERE | 意図的に存在しない。アクセスパスをモデル化するか、そのクエリを出さないか |
サービング憲章(恒久的にゲートされるもの)
数値はラチェットです。床は上がることしかありません。定常ライン(計測値はbench/VALIDATION-LEDGER.md)は次のとおりです。
- Redisパリティのスループット。12アングルのperfgate、床 = ベースライン×0.92。
- hydrated行リストのページ p99 < 1ms、ビューのページ < 1ms、index+viewフック経由の書き込みファンアウト p99 < 200µs——フルスタックを載せた1台のサーバー上で。
- IDX.QUERY p99 < 2ms @ 1M行、MATCH p95 < 20ms @ 1M文書、KNN p95 < 30ms かつ recall@10 ≥ 0.90 @ 1M×128d。
- クラッシュの正直さ。書き込み途中でkill -9 → リプレイ → 派生状態は、まっさらから再構築したものと一致する。復旧点 = スナップショット +
(gen, offset)。 - 空のカタログはコストゼロ。どのサブシステムもゼロ税のラインをゲートしている。
どこから始めるか
- プレフィックスとアクセスパスをモデル化する(クックブック §1-2)。
- バルクロードし、そのあとインデックスを宣言する(移行ガイド)。
- ホットなリスト向けにビューを合成する(ビュー)。
- イベントコンシューマをフィードに配線する(CDC)。
- ダイジェストで検証し、ゲートを見張る。
- 1ノードでは足りなくなったら、レプリカを足し、整合性ラダーのどの段に乗るかを選ぶ(可用性)。