Redis 数组类型:漫长开发的简短故事
我在一月初开始为 Redis 开发新的数组数据类型。这个 PR 直到现在才合入仓库,所以这份代码酝酿了四个月。实现阶段我算是兼职在做(说算是兼职,是因为有好几周其实是全职投入的,有时候想让自己离开键盘还挺难的),即便在 LLM 出现之前,这个实现本身大概也就是四个月的工作量。不同的是,在同样的时间跨度里,我能做的事情要多得多。接下来就是这段经历的简短故事。
第一个月里,我只是在写规格文档。阐述新数据类型的设计理由、C 语言结构体、所采用的稀疏表示、以及用于环形缓冲区和 ARINSERT 的数组游标的精确语义。我一开始连续好几天手写这份长长的规格说明,后来起初与 Opus 结对协作,再后来 GPT 5.3 发布后,我就把所有的设计和开发都切换到了 Codex 上。从那以后,系统编程任务我就只用 GPT 5.x 了。得益于 AI,这份规格通过反复的反馈、关于何为最佳设计、何为恰当折衷、何为过度设计的智力交锋,得到了大幅演进。
从第二个月开始,我开始使用自动编程(如果你更喜欢,也可以叫自动编码)来实现,并持续审查所生成的代码。随后我意识到,自己选择的间接层级是错的。我真的很希望人们能够执行 ARSET myarray 293842948324 foo 这样的操作,而无需巨大的内存分配就能一切正常工作。我原本设计的 directory + slices(稀疏与稠密)的两级结构是不够的。正因为有了 AI,我没有做任何妥协,决定更进一步。当满足特定条件时,数据结构会在内部改变形态,变成一个由切片化的稠密 directory 组成的超级 directory,而这些 directory 又指向实际的数组切片(默认每个切片 4096 个元素)。这一设计既保留了我想要的内部“本质上就是数组”的表示方式和所追求的内存特性,又能让 ARSCAN 和 ARPOP 在扫描现有数组时,耗时与已有元素的数量成正比,而非与索引跨度成正比。
接下来,就到了逐行阅读所有代码的时候。一切都能正常运行,而且多亏了 AI,这个类型拥有大量的测试,但表面上能跑通并不意味着就是最优的。我发现了许多我不想要的小的低效之处或设计错误,于是开启了对多个模块进行人工与 AI 辅助重写的过程。完成这一阶段后,在第三个月里,我开始以多种不同方式对实现进行压力测试。我开始确信,它真的已经足够稳固、有用且设计良好。
然后……事情发生了。在为不同用例建模以检验数据结构是否好用时,我开始把 markdown 文件放进 Redis 数组中。因为文件与它非常契合。就在此时,由于我正为其他目标与 agent 一起工作,我意识到我可以拥有自己所需的那个集中存放 skills markdown 文件的知识库,于是出于自身需求,我决定实现 ARGREP。但我还想要正则表达式。该选哪个库呢?
我最终选择了 TRE(感谢 Ville Laurikari(维勒·劳里卡里)!),因为当你在 Redis 中使用 regexp 时,你需要确保不会出现时间或空间上的病态模式。但 TRE 在一个特定且极其有用的场景下效率非常低,那就是匹配 foo|bar|zap。于是在 GPT 的帮助下,我对它进行了优化,修复了几个潜在的安全问题,并扩展了测试。一切就绪。
你知道这一切中最大的体会是什么吗?对于高质量的系统编程任务,你仍然必须全程深度参与,但我探索到了一个若非如此就会跳过的复杂度层级。AI 为两件事提供了安全网:一是某些非常繁重乏味的庞大任务(比如后来添加并测试的 32 位支持),二是确保复杂算法中没有明显缺陷所需的虚拟人力。撰写最初那份庞大的规格说明是后续工作的关键,正如逐行审查 sparsearray.c 和 t_array.c 的每一行代码并在不合适的地方进行修改是关键一样。
关于用例我没有多费笔墨,因为我已尝试在 PR 本身的说明信息中详细记录:
https://github.com/redis/redis/pull/15162
所以在这里重复自己并没有太大意义。只需说,我真的认为 Redis 是时候拥有一个将数值索引作为语义一部分的数据类型了。
我希望 Array PR 能尽快被接受,让我们能从它所开启的新用例中受益。当然,欢迎任何反馈。谢谢。
随机一篇博客