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