kevy4.0

迁移——playbook 与工具链

分两半。playbook:如何把一个在线 RDS 负载分阶段搬上 kevy,每个阶段有自己的验证钳位与回滚点。工具链kevy-cli):把数据搬进、搬出、在 kevy 服务器之间搬运——并证明搬运是忠实的——的那组命令。建模问题(「我的 JOIN 变成什么?」)在 rds-workloads.md;可跑的模式在 cookbook

分阶段 playbook

七个阶段。承重性质是:直到写切换之前的每个阶段,RDS 始终是事实源,回滚就是「把应用指回去」——永远不是「反向迁移数据」。不要跳阶段;每个阶段的验证钳位,正是让下一个阶段变得乏味的东西。

阶段 0——盘点

列出负载实际在跑的每张表和每种查询形状(事实源是你 ORM 的查询日志或 pg_stat_statements,不是 schema)。逐条对照 rds-workloads 矩阵分类:大多数行能机械映射;标出撞上拒绝构造的那些(热路径上的 N 路 join、HAVING、深 OFFSET 分页、临时报表 SQL)。

阶段 1——schema 映射

把盘点结果变成一份声明脚本:前缀、字段布局、类型,然后是每一行 IDX.CREATE / VIEW.CREATE。现在就应用矩阵里的类型规则——DECIMAL 转最小货币单位整数、DATETIME 转 epoch i64、NULL 转字段缺席——因为这些是写格式决定,回填之后再改代价高昂。

阶段 2——变更捕获(双写窗口开启)

在回填快照之前就开始捕获 RDS 写入,让缝隙里什么都掉不下去。两条路:

无论哪条路,kevy 写入必须是幂等重建形状HSET 整行、集合类 DEL + 重加),这样与回填重叠也无害。

阶段 3——回填

在已知捕获位置对 RDS 做快照,导出为 RESP 命令文件,批量载入:

# 从你的 RDS 导出生成重建帧(每行一条 HSET):
#   HSET user:42 name ada email ada@example.com …
kevy-cli import -p 6004 --strict rows.resp        # ≥200k cmd/s

先导入,后声明索引(延迟索引规则):回填以批量速度从既有行构建每个索引——比给每条导入行付一次写钩子便宜几个数量级。然后跑阶段 1 的声明脚本,等 IDX.LIST 报告 state=ready(细节见载入带索引的 keyspace)。

阶段 4——读切换(按端点金丝雀)

逐端点翻转读流量,不搞 big-bang。为每个端点挑它需要的一致性阶梯级别(availability.md):浏览/列表类端点走第 0 级(异步副本读);「我刚写的,得读回来」的流程用 REPL.TOKEN/REPL.WAIT(第 1 级)或直接读主节点。单节点 kevy 没有阶梯可想——每次读都是主节点。

阶段 5——写切换 + 回滚窗口

把写入方翻到 kevy,并在同一次部署里启动反向镜像:一个 CDC 消费者把 kevy 的 feed 以 SQL 重放回 RDS(FEED.READUPDATE/INSERT,cookbook recipe 13)。此时 RDS 成了落后 kevy 几秒的温备;你的回滚方案是「把应用指回去」,不是「反向迁移」。

翻转之前,把序列 key 播种到 RDS 高水位之上(对新 key INCRBY seq:order <rds_max_id>)——经典的 auto-increment 冲突是阶段 5 最锋利的一条边。

阶段 6——退役

信心固化之后(数周干净的 diff、一次演练过的故障切换),停掉反向镜像,给 RDS 做最后一次归档 dump,把运维故事完全搬到 kevy 上:快照 + AOF(+ feed 游标恢复点)照 persistence.md,副本/故障切换姿态照 availability.md

常见坑


工具链(kevy-cli

kevy-cli export  -p 6379 --prefix user: dump.resp
kevy-cli import  -p 6004 --strict dump.resp        # ≥200k cmd/s
kevy-cli import  -p 6004 --resume dump.resp        # 中断之后续传
kevy-cli digest  -p 6004 user:
kevy-cli diff    hostA:6379 hostB:6004 user: order:
kevy-cli copy-prefix   -p 6004 --rate 5000 user: staging:user:
kevy-cli delete-prefix -p 6004 --rate 5000 --dry-run tmp:
kevy-cli inspect -p 6004 user:

线格式

export 写出一条纯 RESP 命令流的重建帧——DEL + SET/HSET/RPUSH/SADD/ZADD,TTL 用绝对 PEXPIREAT。这让文件与 redis-cli --pipe 双向兼容:kevy 的导出能喂 Redis,任何 RESP 命令文件也能喂 kevy-cli import——包括你自己从 RDS dump 生成的那份(playbook 的阶段 3)。

每个 key 打头的 DEL 让重放从零重建——对每种类型都真正幂等(否则 RPUSH 这类追加 verb 会在重复导入时把列表内容翻倍)。

一致性与可续传性

验证

PREFIX.DIGEST <prefix>(服务器 + embedded 的 prefix_digest)返回 [count, hex64]——对规范化行字节的顺序不敏感校验和(hash 字段与 set 成员排序,zset 按 score 位再按成员,list 按顺序——list 的顺序就是身份)。它对 shard 数与插入顺序不敏感,所以能跨拓扑比较。kevy-cli diff A:port B:port prefix… 在任何不一致时以非零退出。

TTL 不参与摘要(它们会衰减);值参与。

批量操作

copy-prefix 通过读取 + 重建帧把每行搬到新前缀下(服务器有意不提供 COPY verb;TTL 以绝对期限携带)。delete-prefix 走 SCAN + UNLINK。两者都接受 --rate N(令牌桶,从第一个操作起严格限速),删除还支持 --dry-run

载入带索引的 keyspace

等待索引就绪

IDX.CREATE 立即返回,在后台回填;完成之前查询回答 -INDEXBUILDING。标准等法是轮询 IDX.LIST——它的 state 列从 building 翻到 ready

until kevy-cli -p 6004 IDX.LIST | grep -A1 my_index | grep -q ready; do sleep 1; done

文本索引的回填速度随文档大小变化:小行约 7s/百万,而多 KB 的正文大约 85s/百万(实测:20 万封邮件大小的正文 17s)。

一条命令验证整场迁移

kevy-cli diff 一次调用即可跨两台在线服务器比较任意多个前缀——优先用它,而不是逐前缀的摘要对:

kevy-cli diff 127.0.0.1:6004 127.0.0.1:6005 msg: mbox: usr: tag: session:

大导出

dump 是未压缩的 RESP 文本(快、可 grep)。10GB+ 的 keyspace 用 gzip 管道——格式是流友好的:

kevy-cli export -p 6004 /dev/stdout | gzip > dump.kevy.gz
gunzip -c dump.kevy.gz | kevy-cli import -p 6005 --strict /dev/stdin

--resume 需要真实文件来放 .progress 边车——要可续传就先解压到磁盘。)

索引在批量载入之后创建:索引引擎的回填以批量速度从既有数据构建(实测约 7s/百万行),胜过付一百万次逐写钩子维护。这是操作顺序,不是开关:

kevy-cli import -p 6004 dump.resp
kevy-cli -p 6004 IDX.CREATE users ON PREFIX user: FIELD age TYPE i64 KIND range

Gate:bench/onrampgate.sh(百万行往返、≥200k cmd/s 导入、kill -9 续传收敛、±20% 限速精度)。