kevy
kevy 6.3.0 · 迁移

你为什么要搬,
以及搬过来的代价

这是两场完全不同的对话。从 Redis 过来,协议是一样的,要问的是哪些行为不一样。从关系型数据库过来,没有一处是一样的,要问的是这份负载里到底哪一部分该搬——答案是只搬一部分,而且我们会说清楚是哪一部分。

从 Redis 过来

你的客户端不用改。kevy 说 RESP2 和 RESP3,实现了 206 条命令。把你现有的库指过来就行,代码不动,redis-cli 不换。没有新的 SDK 要接,也没有新协议要学。

所以真正要问的只有一句:你能换到什么。只有四样。如果这四样对你都没有价值,那就留在 Redis——它是一件极好的软件,为换而换,只是白白搭进去一个星期。

具体能换到什么

  1. 它能跑在 Redis 跑不到的地方

    嵌进二进制,发到浏览器标签页,在一颗没有分配器的 Cortex-M 上启动。今天这些场合各要一层自己的存储、一套自己的 API;在这里,它们是同一个引擎、同一批命令。如果你曾经为客户端那边另写过一个缓存,这就是值得看一眼的理由。

  2. 它顺带把搜索服务也替掉

    二级索引、物化视图、向量 KNN、BM25 全文检索都在引擎里——不是模块,不是 sidecar,也不是一份会慢慢和原数据对不上的副本。原本要跑 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%,如果 list 或者 sorted set 是你的热路径,那这就不是你该搬的理由。

  4. 数据集不必再装进 RAM

    给 store 一个 RAM 预算,最冷的值就下沉到磁盘上一份可丢弃的 value log,访问时换回——冷键上每条命令不变,append-only 日志的持久化契约不动。RAM 决定你能放多少个键,磁盘决定你能放多少数据。这替掉的是「Redis 加一个单独的磁盘存储」的拆分——大 value、长尾负载不用再分家。诚实的边界:它默认关闭,v1 只下沉字符串和 hash(list、set、stream 留在热层),64 字节以下的值从不下沉——stub 会和值一样大。

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

离开 Redis 要放弃什么

没有集群。副本是拷贝,不是分片。没有 AUTH,没有 TLS。还有几条命令的行为不一样——最要紧的一条:多键写只在单个 shard 内原子(一次调用就把全部返回,游标为 0),跨 shard 的 RENAME 不是原子的。这些都不是 bug,而且每一条都在命令文档里写着。请在决定之前把这份清单读完,不要等决定之后:每条命令真实的代价和真实的偏差

怎么从 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 导出,导入 kevy,再校验两边一致。下面每一条命令都真的跑过。

从关系型数据库过来

不要搬你的数据库。要搬的是它身上那一部分从来就不是数据库问题的东西。

会话。限流。功能开关。任务队列。还有那一行每个请求都要读、却从来没人拿去 join 的热点行。在大多数应用里,这些都住在 Postgres 里,而它们正是被敲得最狠的那些行——不是因为关系型数据库做不好,而是因为它们从来就不是提问。它们是查表。key 你早就知道了。

Postgres 最擅长的那些事,留给 Postgres——join、临时查询、分析,以及跨无关行的真隔离事务。kevy 接走对外服务的那条路径,让数据库喘口气。

而且单表的服务型读也能搬。TABLE.DECLARE 把类型化列、二级索引和复合 ORDER BY 路径声明一次——或者用 kevy-sql 直接编译你手上现成的 PG/MySQL schema 文件——单张表的读路径(索引化的 WHERE、余下的过滤、ORDER BY、翻页、COUNT)就编译到 kevy 索引上,查询期没有任何计划器。kevy-sql 是构建期的编译器,不是 SQL 引擎:join 和临时 SQL 被按名拒绝,它们留在 Postgres。这正是大多数应用真正在用 ORM 的那一部分。

哪些部分该搬

按负载逐条来。红色的那三行,是最多人搞错的地方。

负载搬吗为什么
会话、令牌*搬按 key 查表,带一个 TTL。数据库一直是在帮你的忙,那本来就不是它的活。
限流、计数器*搬带过期的 INCR 是原子的,而且 O(1)。在 SQL 里,这是压在你最热那一行上的行锁。
任务队列*搬list 和 stream,带消费组和逐条确认。所谓队列表,不过是一套加了额外步骤的加锁约定。
功能开关、配置*搬一直在读,很少写,从来不 join。
单表读(过滤、排序、翻页)*搬把表的访问路径声明一次——或者用 kevy-sql 编译你的 schema 文件——索引化的 WHERE + ORDER BY + LIMIT 这类读始终是一次查表。见用索引扛住读
聚合(计数、合计)*多数该搬物化视图在写入路径上就把它更新好,不必每次读都重算一遍。
跨多张表的 join!不要搬kevy 没有 join,以后也不会长出 join。这正是 Postgres 存在的意义。
分析、临时查询!不要搬这里没有查询计划器,也没有优化器。不要尝试。
跨无关行的事务!不要搬MULTI 是按 shard 生效的,不是全局的。如果你需要跨整个 keyspace 的可串行化隔离,你需要的是数据库。

红色那三行不是待办清单,是拒绝——kevy 不会长出 join,也不会长出优化器,因为把这两样做砸了,比不做还糟。每一种关系型负载,以及它在这里真实的代价,其中也包括那些诚实答案就是留在 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.

不要一次性切换。一次只搬一种负载,让数据库继续做事实来源,然后测。

如果你以后又想走

同样这三条命令,反过来跑一遍就行。kevy-cli export 写出的是一个普通的 RESP 文件,任何 Redis 兼容的服务端都能导入,而 digest 能证明这份拷贝没有走样。迁移指南里,搬出去这件事写得和搬进来一样细——我们宁可你走得干净,也不希望你是因为被卡住才留下。