DuckDB 2.0 为什么更快
文章摘要
DuckDB 2.0 将于今年秋天正式发布,alpha 版已经放出。MotherDuck 的 Mehdi Ouazza 在自己的 M5 笔记本上,以及从家里的网络直连 AWS S3,实测了几项他认为最重要的新特性,视角是「建表、写管道的人」而不是数据库引擎开发者。他的核心观点是:2.0 确实更快,但要拿到提速,你得了解自己数据的形态,有时还得重新建模。他也提醒,所有数字都来自一台机器和家用网络,引用前请自己跑一遍。
一、异步 I/O:查 S3 快了两到三倍。 这是作者最喜欢的特性,因为查询本身一个字都不用改。他用一个 2.2 GB 的 Parquet 文件(Stack Overflow 投票数据,2.28 亿行,2268 个行组)按投票类型计数,只读四列中的一列,约 230 MB。1.5.5 版耗时 18.8 秒,2.0 alpha 只要 7.7 秒。原因在于工作方式的变化:在 1.5.5 里,18 个工作线程各自轮流「下载、等待、解码」,等网络时 CPU 闲着,解码时又没有下载在进行,同时最多只有 18 个下载。2.0 引入了一个专门下载的线程池,提前抓取几十个行组放进缓冲区,工作线程只负责解码,总有现成的行组可处理,网络和 CPU 同时忙起来。控制这一行为的是 read_ahead_depth 参数,默认 -1(根据线程数自动设定),设为 0 就回到旧行为。其他测试:23 个大 Parquet 文件共 13.6 GB,从 11.8 秒降到 3.9 秒;一个 1.7 GB 的 CSV 从 116 秒降到 55 秒;但 30 个约 1 MB 的小文件只从 3.7 秒降到 3.3 秒,因为时间都花在每个文件的往返请求上(先读文件尾,再读数据),预读无能为力。作者顺带强调,把数据湖存成成千上万个 1 MB 的 Parquet 文件本来就是坏习惯,2.0 救不了它,基本功依然重要。
二、递归 CTE:深层父子关系数据的大提升。 DuckDB 团队重写了递归 CTE 引擎,官方称图可达性查询快了 40 倍。作者用一张「经理、员工」两列的组织架构表来解释:递归 CTE 就是在表上循环,从 CEO 开始,每一轮找出上一轮那些人的下属,直到某一轮找不到新人为止,组织架构有几层就要跑几轮。组织架构、文件夹树、物料清单、回复楼层、数据血缘、git 历史,本质上都是「父、子」两列的表,区别只在于深度。1.5 的问题是每一轮都要回头重新扫描整张表,几千层就是几千次全表扫描。2.0 只读一次表,在父列上建一次查找结构,之后每一轮只查刚找到的那几行,开销取决于实际触及的行数,而不是「轮数乘以表大小」。他生成了一个 2 万次提交的 git 仓库,从 HEAD 回溯到根:1.5.5 每次耗时 1.8 到 16 秒不等,2.0 alpha 每次都是 0.10 秒。结论是:如果你要遍历很深的父子链,2.0 能把过去要交给图数据库的活变成一条普通查询;像组织架构这样的浅层结构则看不出太大差别。建议把层级保存为一张用整数 id 的父子表,递归需要携带深度、成本之类的值时使用 USING KEY。
三、VARIANT:比 JSON 更小更快。 VARIANT 现在是 DuckDB 的一等数据类型,关键词是「拆分」(shredding):写入行组时,DuckDB 会检查 JSON 列,把在大多数行中出现、且值类型始终一致的字段抽出来存成真正的列,罕见字段和类型不一致的字段(比如某行是数字、另一行是字符串)则留在一个二进制剩余块里。最理想的场景是结构化日志:level、service、latency_ms、trace_id 每行都有且类型固定。陷阱是,如果 latency_ms 一行是 231、另一行是 “231ms”,它就会掉进剩余块,所以要保持值类型一致。作者用 500 万条事件做了三种存储的对比:JSON 字符串占 224 MB,VARIANT 85 MB,每个字段一列的普通表 45 MB。在涉及被拆分字段的过滤和按国家求和查询上,VARIANT 约 60 毫秒,比解析 JSON 文本(约 370 到 410 毫秒)快 6 倍左右,与类型化的列(约 50 毫秒)相差不到 20%;而 1.5.5 虽然已有 VARIANT 类型但没有拆分,同样的查询要 5 秒左右,2.0 快了 78 倍。例外是列表查询:把 VARIANT 列表转换成 VARCHAR 数组在这个 alpha 版要花近 2 秒,反而比 JSON 路径慢。作者重申建模的金科玉律:「把你知道的建模出来」,每个查询都会碰的字段值得拥有真正的列,长尾留在 VARIANT 里,热查询暂时别做列表转换。
其他亮点。 2.0 加入了触发器,支持「转换表」,可以在更新价格时自动把新旧值写入历史表,不必再靠每个访问该表的工具自己记得写历史;支持嵌套 schema;支持在 CTE 中执行 DML,比如 DELETE ... RETURNING 再插入另一张表,实现原子搬移行;提供 Spark SQL 方言兼容模式,方便迁移管道;远程文件缓存被逐出时可以溢写到临时目录而不必重新下载,在 300 MB 内存限制下,第二次读取一个 854 MB 的 S3 Parquet 文件从 23.9 秒降到 0.35 秒;命令行加入了 SQL 格式化器和可查询的历史记录;read_json 现在能把带时区偏移的 ISO-8601 时间戳识别为 TIMESTAMPTZ,不再悄悄丢掉偏移;密钥可以限定在单个连接内。客户端与服务器协议 Quack 也随这次发布进入 1.0。文末给出了一行命令安装 alpha 版 CLI,并说明 MotherDuck 会在正式发布前后支持 2.0。
HN 评论精华
本帖 220 分、60 多条评论。技术讨论不多,相当一部分篇幅被「这篇文章是不是 AI 写的」占据。
「LLM 味」之争是最大的一串。 scythmic_waves 开头就说虽然喜欢文中的可视化,但行文有浓重的 LLM 气息,举了「One setting drives this」「The cost is now about the rows you actually touch」这类句子,说这种文风让他「脑子打结」。kristianp 挑出「2.0 does not rescue it」,认为没有人类会这么写,还说「I’ll explain what that means on a table you already know」那句里的表他并不认识,递归 CTE 那节也没真正讲清楚优化是怎么做的,并推荐了 DuckDB 官方讲递归 CTE 的文章。themeiguoren 说他用 Pangram 检测,正文被判定 95% 为 AI 生成。jdehesa 和 LoganDark 提出另一种解释:作者应该不是英语母语者,有些说法是从母语直译过来的,LoganDark 认为文中既有明显的非母语痕迹,也夹杂着 LLM 句式,作者可能借助了 LLM 翻译或起草。gbalduzzi 则说他没感到 LLM 味,至少不至于被困扰。gchamonlive 坚持认为这种评论违反了 HN「不要发表浅薄否定」的社区准则,近乎集体歇斯底里,与 johnfn 等人来回争了很多轮;majormajor 回应说,这类文风不仅影响可读性,还往往意味着内容没有经过认真核查,他让 Claude 重写这种文字时,常常得到「我之前说过头了,重新核对后其实……」这样的回答。
这串讨论还延伸到 LLM 为什么会写出这种文风。vlovich123 疑惑:既然 LLM 是用人类文字训练的,为什么会写出不像人写的句子?mediaman 解释说模型还经过 RLHF 和基于可验证奖励的强化学习,而「文笔好」很难写成验证器;simondotau 认为 LLM 把人类表达的多样性压缩成一种「病态的中位数」风格,99% 的时候效果很好,剩下 1% 中位数恰好落在没什么真人会站的位置,人造感就特别明显。atombender 分享了一个技巧:要求模型使用「简化技术英语」(ASD-STE100)写作,可读性会好很多,afiori 也说这么做有效。
DuckDB 本身是不是也「AI 化」了。 Eridrus 说他试用 2.0 alpha 时有一种「氛围编程」的感觉,bambax 担心 DuckDB 若走上这条路就完了。kristianp 指出 DuckDB 官方文章称自 1.5 以来有 1 万次提交,这个数字对人类团队来说高得出奇;wippler 调侃说连 DuckDB 自己的文章也满是 LLM 痕迹。larodi 半开玩笑地说,2.0 之所以快,是因为 Atlas 和 Fable 轮流在源码和运行时里追踪瓶颈;gwerbin 认为这完全是合理的技术,LLM 擅长综合细节、跑临时实验,很适合调试和性能优化;larodi 只是好奇为什么文章没有披露,contrast 回应说这篇文章讲的是 2.0 为什么更快,不是 DuckDB 怎么开发的,就像财务分析师不会声明自己用了 Excel。
技术层面的讨论。 stacktraceyo 补充说,2.0 新的 C++ 扩展 API 也会让扩展的开发和分发更快。jiggawatts 希望更多数据库引擎采用 Umbra、CedarDB 那样基于任务的设计,认为大多数引擎仍是「N 个线程」加交换算子的并行模式,异步 I/O 管理很差,DuckDB 在这方面进步了,但某种意义上是在追赶几十年前的研究成果;他常问开发者一个问题:给你一台 1024 核、带宽匹配但延迟很高的机器,你能让单条查询把它跑满吗?他说几乎所有软件的答案都是「不能」,比如 SQL Server 单条查询最多只用 64 个硬件线程。这又引出一段关于能否并行计算整文件哈希的讨论,jiggawatts 以 Merkle 树作答。OutOfHere 则不客气地指出,他提到的两个数据库都不开源。robertclaus 和 tiagod 对文章的取舍不满:2.0 加入了触发器这么重要的功能,文章却一笔带过,反而花大篇幅讲慢速网络下的 S3 读取和递归 CTE,robertclaus 表示他宁愿自己去读发布说明。OneManHorde 提醒,虽然 2.0 让递归 CTE 快了很多,DuckDB 早就有处理图数据的 DuckPGQ 扩展,可以优雅地处理这类对传统关系数据库颇具挑战的查询。