kevy
kevy 6.3.0 · 移行

なぜ移るのか、
そして何を差し出すのか

話は 2 つあります。Redis から来る場合、プロトコルは同じなので、問うべきは「何の挙動が違うのか」です。リレーショナルデータベースから来る場合、同じものは何ひとつないので、問うべきは「ワークロードのどの部分を移すべきか」です。答えはその一部であり、それがどこなのかを、これから示します。

Redis から移る

クライアントは変わりません。kevy は RESP2 と RESP3 を話し、206 個のコマンドに応答します。既存のライブラリの接続先を変えるだけで、コードもそのまま、redis-cli もそのままです。新しく覚える SDK もプロトコルもありません。

だから本当の問題は、何が得られるのかだけです。得られるものは 4 つあります。そのどれにも価値を感じないなら、Redis に留まってください。あれは見事なソフトウェアであり、乗り換えのための乗り換えは 1 週間の浪費です。

具体的に、何が得られるのか

  1. Redis が動けない場所で動く

    バイナリに組み込み、ブラウザのタブに配り、アロケータのない Cortex-M で起動できます。いまはそのひとつひとつに、独自の API を持つ専用のストレージ層が要ります。ここでは同じエンジン、同じコマンドです。クライアント側に 2 つ目のキャッシュを書いた経験があるなら、それが検討する理由です。

  2. 検索サービスも置き換えられる

    セカンダリインデックス、マテリアライズドビュー、ベクトル KNN、BM25 全文検索がエンジンに入っています。モジュールでもサイドカーでもなく、元データからずれていく 2 つ目のコピーでもありません。Redis と検索クラスタを併用しているチームは、多くの場合ひとつにまとめられます。

    # look up by a field, not just by the key
    IDX.CREATE idx:city ON PREFIX user: FIELD city TYPE str KIND range
    IDX.QUERY  idx:city EQ osaka
    
    # vectors, in the same engine, over the same keys
    IDX.CREATE idx:sem ON PREFIX doc: FIELD vec TYPE vector KIND ann DIM 768 DISTANCE cosine
    IDX.QUERY  idx:sem KNN "<vector>" LIMIT 10
  3. すでに使っている操作が、そのまま速くなる

    同じマシンで Redis 8.10.1 に対して、GET は 1.33 倍、SET は 2.66 倍、INCR は 2.05 倍です。ただし、当てにする前に表の全体を読んでください——LPUSH と ZADD は 10% と 15% しか上回っておらず、リストやソート済みセットがホットパスなら、これは移る理由になりません。

  4. データセットが RAM に収まる必要が、もうない

    ストアに RAM 予算を与えると、最もコールドな値はディスク上の使い捨て value log へ退避し、アクセスされたときに戻ります——コールドキーの上でもすべてのコマンドは変わらず、追記専用ログの永続化コントラクトも無変更です。RAM が保持できるキーの数を、ディスクがデータの量を決めます。大きな値やロングテールのために「Redis と別のディスクストア」を並べる構成を、これがひとつにします。正直な限界も述べます。既定ではオフで、v1 で退避するのは文字列とハッシュ(リスト、セット、ストリームはホットのまま)、そして 64 バイト未満の値は決して退避されません——stub が値と同じ大きさになるからです。

    # kevy.toml
    [tiering]
    budget = "70%"      # or "4gb", or "auto"

Redis を離れることで失うもの

クラスタはありません。レプリカはコピーであって、シャードではありません。AUTH も TLS もありません。そしていくつかのコマンドは挙動が違います——知っておくべき筆頭は、シャードをまたぐ RENAME が原子的ではないこと(複数キーの書き込みは shard 単位でのみ原子的)です。どれもバグではなく、すべてコマンドごとに文書化してあります。移ると決めたあとではなく、決める前に読んでください——各コマンドの本当のコストと、本当の差異

移行の手順——Redis から

# 1. dump what you want to move. it is a RESP file — readable,
#    diffable, and it streams rather than loading into memory.
kevy-cli export -p 6379 --prefix user: dump.resp
-> exported 41023 keys -> dump.resp

# 2. load it. --strict stops on the first error rather than
#    limping onward with a half-migrated keyspace.
kevy-cli import -p 6380 --strict dump.resp
-> imported 82046 ok, 0 errors, offset 4108331

# 3. prove they agree, rather than hoping.
kevy-cli digest -p 6379 user:
kevy-cli digest -p 6380 user:
-> 41023 keys 3bca92aa52269300     # the same hash, or you did not migrate

# an interrupted import resumes where it stopped:
kevy-cli import -p 6380 --resume dump.resp

Redis から export し、kevy に import して、両者が一致することを確かめます。以下のコマンドは、すべて実行したものです。

リレーショナルデータベースから移る

データベースを移してはいけません。移すのは、そもそもデータベースの問題ではなかった部分です。

セッション。レート制限。フィーチャーフラグ。ジョブキュー。すべてのリクエストが読み、誰も結合しないホットな行。たいていのアプリケーションでこれらは Postgres の中にあり、叩かれ続けているのもそこです。リレーショナルデータベースがそれらを苦手だからではありません。それらが、そもそも問い合わせではなかったからです。ただの参照です。キーは、もう分かっています。

Postgres には、他に並ぶもののない仕事を残してください——結合、アドホックなクエリ、分析、無関係な行にまたがる本物の分離レベルを伴うトランザクション。kevy は配信の経路を引き受け、データベースに夜を返します。

そして、単一テーブルの配信用の読み取りも移せます。TABLE.DECLARE で型付きカラム、セカンダリインデックス、複合 ORDER BY パスを一度だけ宣言する——あるいは手元の PG/MySQL スキーマファイルを kevy-sql でコンパイルする——それだけで、1 つのテーブルの読み取りパス(インデックスされた WHERE、残りのフィルタ、ORDER BY、ページング、COUNT)が kevy のインデックスにコンパイルされます。クエリ時のプランナはありません。kevy-sql はビルド時のコンパイラであって SQL エンジンではありません。join とアドホック SQL は名前つきで拒否され、Postgres に残ります。ほとんどのアプリケーションが ORM に実際に求めているのは、その部分です。

どの部分を移すべきか

ワークロードごとに。赤い 3 行が、よく間違われるところです。

ワークロード移すべきか理由
セッション、トークン*はいTTL つきの、キーによる参照です。データベースは仕事としてではなく、厚意でやってくれていました。
レート制限、カウンタ*はい期限つきの INCR は原子的で O(1) です。SQL では、最もホットな行に対する行ロックになります。
ジョブキュー*はいリストとストリーム。コンシューマグループと、メッセージごとの確認応答があります。キュー用のテーブルは、手数の増えたロックの慣習にすぎません。
フィーチャーフラグ、設定*はい絶えず読まれ、めったに書かれず、結合されることはありません。
単一テーブルの読み取り(絞り込み、順序、ページング)*はいテーブルのアクセスパスを一度宣言する——あるいはスキーマファイルを kevy-sql でコンパイルする——だけで、インデックスされた WHERE + ORDER BY + LIMIT の読み取りは参照のままです。読みを捌くを参照してください。
集計(件数や合計)*多くの場合マテリアライズドビューが書き込みの側で最新に保つので、読むたびに計算し直す必要がありません。
複数テーブルにまたがる結合!移すなkevy に結合はなく、今後も持ちません。それは Postgres の仕事です。
分析、アドホックなクエリ!移すなクエリプランナもオプティマイザもありません。試さないでください。
無関係な行にまたがるトランザクション!移すなMULTI はシャード単位であって、全体ではありません。キースペース全体で直列化可能な分離が要るなら、必要なのはデータベースです。

赤い 3 行は、やることリストではありません。拒否です——kevy は結合もオプティマイザも持ちません。どちらも中途半端にやるくらいなら、やらないほうがましだからです。リレーショナルなワークロードのすべてと、ここでの実際のコストを書いてあります。正直な答えが「Postgres に置いたままにしてください」になるものも含めて。

移行の手順——データベースから

# 1. pick ONE workload. sessions are the usual first, because
#    nothing joins against them and losing one is survivable.

# 2. write to both for a week. reads still come from Postgres.
#    you are checking that the shapes match, not that it is fast.

# 3. flip reads to kevy. keep the dual write.
redis-cli SET session:$SID "$JSON" EX 3600

# 4. when it has been boring for a fortnight, drop the table.

# then do the next workload. rate limits, then queues, then
# whichever of your read paths a secondary index can answer.

一度に切り替えることはしません。ワークロードをひとつ移し、データベースを正としたまま、測ってください。

また出ていきたくなったら

同じ 3 つのコマンドが、逆向きにも使えます。kevy-cli export はプレーンな RESP ファイルを書き出し、Redis 互換のサーバーなら、どれでもそれを取り込めます。そして digest が、コピーが忠実であることを証明します。移行ガイドは、入ってくる手順と同じ丁寧さで、出ていく手順も扱っています——動けなくなって留まられるより、きれいに出ていってもらうほうが、私たちとしてもずっといいと考えています。