找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4351

积分

0

好友

563

主题
发表于 2 小时前 | 查看: 4| 回复: 0

你写了一个笔记应用,用户在一篇 20KB 的文档上改了 1000 次,你想保留每一次修改,于是每保存一次就往库里插一行完整副本。等用户回头翻历史,数据库里已经躺着 20MB 几乎一模一样的文本。

这是版本历史存储的老难题。最容易写的方案就是每一版全文直接落库,代价你心里有数:绝大多数字节都是重复的。

Simon Willison 在 2026 年 8 月遛狗时想了个偷懒的办法。

一个偷懒到有点蠢的想法

遛狗路上他琢磨:与其每行存一版,不如把所有历史版本拼成一个 JSON 字符串数组,然后对整个数组做一次 zstd 压缩。版本之间那么多重复文字,压缩器总能压掉吧?

他没自己写。他打开 ChatGPT 的语音模式,把想法口述了一遍,再让模型用 Python 把原型做出来。模型跑了 38 分钟,交出了能跑的代码和一套基准测试。

结果超出预期。同一篇 20KB 文档、1000 次修订,原始快照 20.4MB,压成单个 zstd JSON 数组后只有 80.3KB。

你不需要自己写 diff

这个方案最妙的地方在于:你完全没有实现版本差分。

传统做法要设计 delta 格式,记录第 5 版相对第 4 版改了哪几个字符,读取时再一层层 patch 回去。Simon 的方案里,每一版全文都老老实实留在数组里,去重这件事全交给 zstd。压缩器发现相邻字符串有大量公共子串,自然会把它们合并掉。

等于用现成的压缩算法,免费替你实现了 diff/patch。代码简单到只有两列:history_blob 存压缩后的字符串数组,history_timestamps 存一组整数时间戳(不压缩,因为时间戳本身没什么可压的)。

两种写法

原型给了两个实现。

WholeBlobHistoryStore 最直白。 一张 documents 表,current_text 放当前原文,history_blob 放压缩历史。每次 replace,先 BEGIN IMMEDIATE 抢到写锁,把旧当前文本追加进历史数组,重新压缩整个 BLOB,再写新当前文本。当前版本不重复进历史,内容没变的保存直接跳过,事务保证不丢修订。

ChunkedHistoryStore 多一张 history_chunks 表。 思路一样,但历史被切成一块一块。每块攒够(比如 128 个版本,或解压后 JSON 到 2MB)就封口成不可变块,新版本写进下一块。旧块从此不再动。

代价藏在二次方里

直白版有个刺眼的问题。它每次编辑都要把越来越大的数组解压、追加、重新压缩。累计工作量接近二次方。

数据不会骗人。1000 次编辑,直白版总共花了 26.8 秒,最后一次编辑的延迟高达 49.9 毫秒。文件确实只有 80KB,但把它造出来的过程,是在反复重写一个不断膨胀的历史。

分块版把这条曲线拉平了。分块 128 同样存下 1000 版,总耗时 1.53 秒,晚期延迟 2.2 毫秒,存储只从 80.3KB 涨到 109.9KB。这一个架构改动,几乎吃掉了全部存储优势,同时换回了线性写入。

完整对照

策略 历史存储 1000次总耗时 晚期编辑延迟
逐行快照 20.4 MB 70 ms 0.061 ms
逐行 + Zstd 7.5 MB 218 ms 0.241 ms
单个压缩 JSON BLOB 80.3 KB 26.8 s 49.9 ms
分块 64 154.9 KB 0.93 s 0.96 ms
分块 128 109.9 KB 1.53 s 2.20 ms

时间戳本身再占 10KB 出头。

它没在骗你

有人会怀疑:是不是因为英文文本好压,结论才成立?Simon 专门测了高熵随机内容。

每次只替换 20 个随机字节,1000 版历史压到 45.8KB。替换 10%,涨到 1.6MB。各版本完全不相关,直接 16MB,几乎没跨版本收益。

这说明方案是诚实的。省多少,取决于每版到底引入了多少新信息。连续版本越像,越省;毫无关联,正确退化成不省。

你能拿去用在哪

这个用通用压缩器当 delta 引擎的思路,不只对文本有效。配置项、JSON 文档、小体积结构化数据,只要版本间有大量重复,都能这么玩。如果你经常跟 SQLite 打交道,或者需要为应用内置版本管理能力,不妨在云栈社区数据库/中间件/技术栈板块看看更多实操讨论。

真正的取舍很清楚:逐行快照读快写快但占空间;单个压缩 BLOB 占空间极小,但写入是二次方;分块在两者之间取了平衡点。选哪个,看你读得多还是写得多。

zstd 在长历史下又小又快,代价是多一个依赖。只要零依赖,退回 zlib 同样成立,收益还在。

一句话

把一个朴素到有点蠢的想法做出来,认真测一遍,结果证明它在存储上极其有效,代价是写入膨胀,而分块用一个很小的改动同时保住了两边。

这大概就是 Simon 一直坚持的那套:先原型,再测,最后让数据说话。

参考来源:Simon Willison 博客《Research: SQLite compressed text-history prototypes》及其开源原型仓库。




上一篇:Docker Compose 多网络隔离配置实战:Web与DB独立组网
下一篇:10万Star的DeepSeek Harness有TUI了:dsh-TUI终端体验
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-8-19 23:47 , Processed in 1.152230 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表