当前位置: 首页 > news >正文

DeepSeek总结的从 DuckDB 迁移到 chDB基准测试

来源: https://github.com/chdb-io/cookbook/tree/main/migration-from-duckdbBENCHMARK.md

迁移基准测试 —— 深度探讨

本文是从 DuckDB 迁移到 chDB指南的配套文档。指南的第 5 节将环境/场景/结果/摘要内联呈现;本文件则包含不适合指南风格流程的部分:摄取路径方法、并排 SQL 的案例研究、DataFrame 往返操作矩阵以及存储引擎权衡细节。

有关可运行代码和复现步骤,请参阅 README.md。规范结果位于benchmark/results_aligned.json


为什么这里有 18 个查询,而指南只突出了 16 个

我们运行了18 个查询,以在 DuckDB 用户可能使用的每个维度上公平评估两个引擎:类型化 JSON (Q1–Q3)、pandas 兼容 API (Q4)、AI 代理检索 (Q5)、漏斗/序列聚合 (Q6–Q7)、多百分位数 (Q8)、Parquet 上的基线分析 SQL (Q9–Q13)、参考查询 (Q14–Q15)、Parquet → DataFrame 导出 (Q16),以及两个存储引擎探测(Q17 持久存储工作流,Q18 主键范围扫描)。

Q17 和 Q18 在 chDB 不利的方向上产生了很大的标题差距(例如,DuckDB 在 Q17 上快约 14 倍)。经检查,这些差距反映了chDBMergeTree存储引擎的设计选择——MergeTree在写入时构建排序索引并维护主键记账,这些成本在多个后续查询中摊销,但在 5 个查询的工作流或仅触及几行的查询上会表现为原始开销。它们不是查询内核的性能差距,并且这种架构选择(一次写入排序,多次廉价扫描)对于 chDB 典型的遥测/分析工作负载是正确的,尽管它输掉了这些特定的微基准测试。

如果指南的第 5 节将 Q17/Q18 与内核查询并列,那么标题数字会误导读者为典型的代理或笔记本工作负载做出引擎选择决策。因此,指南突出了 16 个内核查询 (Q1–Q16),我们在本文件的末尾完整记录了 Q17/Q18(存储引擎权衡)——既是为了透明,也是为了让任何工作负载确实是“一次性 ETL + 少量后续查询”的人能够做出明智的选择。


数据加载方式(摄取路径方法)

JSON 工作负载 (Q1–Q3) 被平等地加载,但设计上存储方式不同,因为存储策略正是指南第 2.1 节比较的内容:

  • chDB:CREATE TABLE events (data JSON) ENGINE = MemoryINSERT INTO events SELECT … FROM file('events.jsonl', 'JSONEachRow')——JSON类型在加载时提取类型化的子列,因此data.user.tier.:String稍后读取的是一个压缩列。
  • DuckDB:CREATE TABLE events AS SELECT … FROM read_json('events.jsonl'),列类型化为JSON(DuckDB v1.2+ 类型化JSON)。路径访问在查询时针对编码值进行解析。

两个引擎都摄取相同的 JSONL 文件,并使用引擎提供的类型化JSON类型。Q1–Q3 查询数字仅为查询时间(摄取时间不计入任何引擎的计时器)。chDB 在摄取期间摊销了每个路径的提取成本,而 DuckDB 没有,但 DuckDB 随后在查询时为每行支付该成本。对于一次摄取多次查询的工作负载(代理-事件模式),这是我们想要衡量的架构权衡。对于一次摄取一次查询的工作负载,这会使几百毫秒的差异偏向 DuckDB——如果这对您很重要,请在您自己的数据上量化这一点。

分析 SQL 工作负载 (Q9–Q15) 在两个引擎上读取相同的六个 Parquet 文件。


案例研究——为什么 chDB 赢得每个工作负载

案例 A — 类型化 JSON 子列:路径访问编译为列读取(Q1,快 8.4 倍)—— §2.1

两个引擎都将列存储为JSON类型(DuckDB v1.2+ 也提供类型化JSON类型,而非旧的“存储为文本”表示),并接受相同的路径访问语法。架构差异在于子列提取发生的时间——chDB 在加载时,DuckDB 在查询时——成本立即显现。

chDB— 带有类型化子列后缀的路径表示法:

SELECTdata.response.status.:StringASstatus,count(*)ASnFROMeventsGROUPBYstatusORDERBYnDESC

.:String后缀指向 chDB 在加载时提取的类型化子列。读取是 O(1) 进入一个具有其自身最小/最大值和压缩的压缩列——无需每行 JSON 解析。

DuckDB— 针对JSON类型列的相同路径语法:

SELECTdata.response.statusASstatus,count(*)ASnFROMeventsGROUPBYstatusORDERBYnDESC

DuckDB 的类型化JSON在查询时针对编码值解析路径(而不是读取预先提取的类型化子列)。结果是正确的;每行成本更高。

在 100 万条合成代理-事件记录上:DuckDB 35 毫秒 → chDB 4 毫秒(8.4 倍)。双路径和过滤加分组变体(Q2,Q3)即使增加了算术运算,也显示出 3.0–3.8 倍的胜率。对于累积 JSON 工具输出上下文并重复切片它的代理管道来说,这是整个基准测试中最大的独立胜利。

案例 B — DataStore:一个 DuckDB 没有等价物的即插即用 pandas API—— §2.2

DataStore 是使 chDB 成为已经用 pandas 编写的代理代码库的可行替代品的原因。相同的源代码,只需更改一行导入语句:

importdatastoreaspd# 唯一更改的一行df=pd.read_parquet("yellow_tripdata_2024-01.parquet",columns=["passenger_count","fare_amount","tip_amount","trip_distance"])df.groupby("passenger_count").agg(avg_fare=("fare_amount","mean"),sum_tip=("tip_amount","sum"),avg_dist=("trip_distance","mean"),n=("fare_amount","count"),).to_pandas()# 物化惰性计划

DataStore 通过 chDB 引擎将表达式编译为 ClickHouse SQL;惰性的.to_pandas()调用物化结果。过滤器、分组、连接、时间分桶、窗口函数、字符串和日期时间访问器——每个常见的 pandas 习惯用法都保持相同的形状。

覆盖率:约 300 个 pandas 风格的方法(209 个DataFrame+ 56 个.str+ 42+ 个.dt访问器),外加 334 个 ClickHouse SQL 函数作为 DataStore 方法公开,用于 pandas 没有本地名称的事物。对于已经使用 pandas 的代理和笔记本代码,迁移只需更改一行import,代码库的其余部分保持不变。DuckDB 没有等效的 pandas 方法表面——DuckDB 的 Python API 公开了 SQL 表面(通过替换扫描,您可以直接针对 DataFrame 执行duckdb.sql("SELECT … FROM pdf"),无需register()),但是以df.filter(...).groupby(...).agg(...)链式 pandas 调用编写的代码库必须重写为 SQL。使用 chDB,链式语法保持不变。

案例 C — 向量搜索:集成优于原始扫描速度(Q5)—— §2.3

生产代理的检索路径很少只是余弦相似度——它看起来像:

JSON 类型化元数据过滤器 → 向量 top-K 余弦 → SQL 连接会话历史 → 返回 DataFrame

chDB 将整个管道作为一个 SQL 语句在一个进程内引擎上运行:向量在Array(Float32)中,会话内存位于chdb.session.Session('path'),JSON 元数据作为类型化子列(案例 A),以及顶部的分析 SQL。DuckDB 在内核中拥有原始距离函数(array_cosine_distance),因此线性扫描检索不需要扩展,但周围的组件并未共置——没有原生的会话抽象(外部 SQLite / Postgres / Redis),并且 HNSW 索引需要INSTALL/LOAD vss。在 DuckDB 上,检索管道仍然需要支付 3-8 倍的 JSON 元数据过滤差距(案例 A)。

对于孤立的内核——SELECT id, cosineDistance(emb, [...]) ORDER BY d LIMIT 10,在 10 万 × 384 维随机单位向量上,无索引——DuckDB 35 毫秒 vs chDB 64 毫秒。这是一个真实但操作上很小的 30 毫秒差距;两个引擎都远低于 100 毫秒的交互阈值。使用近似最近邻索引(chDB 向量跳过索引,DuckDBvss),线性扫描的差异无关紧要。对于代理来说,有趣的比较是工作流,而不是内核。

案例 D —windowFunnel:一行代码进行漏斗分析(Q6,快 2.61 倍)—— §2.8

对于每个上车区,找到在一小时窗口内匹配以下序列的最长前缀:低票价(<$15)高票价(>$50)机场行程

chDB— 一个聚合函数:

SELECTPULocationID,windowFunnel(3600)(tpep_pickup_datetime,fare_amount<15,fare_amount>50,Airport_fee>0)ASfunnel_levelFROMtripsGROUPBYPULocationID

DuckDB— 没有原生的漏斗函数,所以需要一个 CTE 链,在事件流上使用LAG(step,1) / LAG(ts,1) / LAG(step,2) / LAG(ts,2),再加上CASE WHEN step=3 AND prev1=2 AND prev2=1 AND EPOCH(ts-prev2_ts) < 3600 THEN 3 …。完整的 DuckDB 版本在workload_aligned_duckdb.pyQ6 中——大约三十行代码,而 chDB 只有六行。

在 1800 万行上:274 毫秒 → 105 毫秒(2.61 倍)。DuckDB 版本峰值内存约为 2.0 GB RSS(为LAG排序的中间结果);chDB 峰值为 1.4 GB。

案例 E —sequenceCount:单个聚合中的模式匹配(Q7,快 4.54 倍)—— §2.8

计算每个上车区出现序列低票价 → 高票价 → 非常高票价的次数(然后求和)。

chDB

SELECTPULocationID,sequenceCount('(?1)(?2)(?3)')(tpep_pickup_datetime,fare_amount<15,fare_amount>50,fare_amount>70)ASseq_countFROMtripsGROUPBYPULocationID

'(?1)(?2)(?3)'模式是一个类似正则表达式的表达式,作用于事件谓词——chDB 内置了匹配引擎。sequenceMatch(布尔变体)和每窗口变体紧随其后。

DuckDB基本上需要与案例 D 中漏斗情况相同的 LAG-CTE 结构,再加上一个GROUP BY来汇总计数。在 1800 万行上:230 毫秒 → 51 毫秒(4.54 倍),并且 chDB 版本只有几行代码,而 DuckDB 大约有三十行。

案例 F —quantilesTDigest:一个草图,多个百分位数(Q8,快 2.35 倍)—— §2.9

票价金额的 P50、P95 和 P99,忽略零金额行程——两个引擎都支持单草图多百分位数 API。

chDB

SELECTquantilesTDigest(0.5,0.95,0.99)(fare_amount)ASpctsFROMfile('yellow_tripdata_2024-*.parquet','Parquet')WHEREfare_amount>0

DuckDB— 列表形式的approx_quantile(单次扫描,单个 TDigest 草图——与 chDB 公平比较):

SELECTapprox_quantile(fare_amount,[0.5,0.95,0.99])ASpctsFROMread_parquet(...)WHEREfare_amount>0

两个查询都从数据上的单个 TDigest 草图生成一个数组[p50, p95, p99]。实现差异体现在原始吞吐量上:在 1800 万行上,64 毫秒 → 27 毫秒(chDB 快 2.35 倍)。百分位仪表板模式无处不在(SLO 报告,p99 延迟分析),因此即使草图内核上有 2 倍的优势,在集群规模上也会累积。

chDB 系列也更广泛——quantilesExactquantilesGKquantilesBFloat16Weighted——当您需要与 TDigest 不同的精度/内存权衡时,API 形状保持相同。

案例 G — Parquet → DataFrame 导出:零拷贝物化(Q16,冷启动快 1.61 倍 / 热启动快 2.99 倍)

加载一个 Parquet 文件并将完整结果作为 pandas DataFrame 返回——这是输出路径。这是 chDB 在零拷贝博客(2026 年 1 月,ClickBench 命中,100 万行)中发布的“DataFrame 导出比 DuckDB 快 24%”声明背后的操作。在我们 300 万行 × 19 列的 NYC TLC 文件上:冷启动 392 毫秒 → 244 毫秒(快 1.61 倍,减少 38%);热启动 325 毫秒 → 108 毫秒(快 2.99 倍,减少 67%)。两者均达到或超过博客。机制:chDB 的__arrow_c_stream__零拷贝 SIMD 路径直接将列物化到 NumPy 中,无需中间的 Arrow → pandas 复制。

重要——与Python(df)不同。Q16 是输出路径。Q13/Q15 的Python(df)表函数是输入路径(现有的 pandas DataFrame → SQL → DataFrame),经过不同的机制。那里的性能取决于操作——请参阅下面的“DataFrame 往返——输入取决于操作”。


说明——§2.5 的连接优势不是基准测试行

16 个内核查询衡量固定输入形状上的内核性能。它们不衡量 §2.5——约 80 种格式、12 种以上连接器、三个流式引擎的核心内建表面——这体现在部署形态上,而不是毫秒级别:没有INSTALL/LOAD链,没有需要 pip 安装到代理运行时的 MongoDB / Redis 客户端,没有单独的 Kafka / RabbitMQ / NATS 消费者进程,没有在 SQL 之前对 Protobuf / Avro / MsgPack 输入进行 Python 端解码。对于数据已经是干净 Parquet 文件的代理来说,这无关紧要;对于数据是周围系统发出的数据洪流的代理来说,这是最大的单一操作差异,并且在任何单引擎查询计时中都是不可见的。


DataFrame 往返——输入取决于操作

Q16(输出路径)和 Q13/Q15(输入路径)经过不同的机制;输入路径的结果取决于操作,而不是任一引擎的全面胜利:

路径操作结果
输出 — Parquet → DataFrame (Q16)完整文件导出chDB 冷启动快 1.61 倍,热启动快 2.99 倍
输入,进程中热启动,1000 万行COUNT(*)chDB 快 1.4 倍
输入,进程中热启动,1000 万行过滤 +COUNTchDB 快 1.1 倍
输入,进程中热启动,1000 万行宽 (60 列) DF 上的COUNT(*)chDB 快 2.1 倍
输入,进程中热启动,1000 万行GROUP BYDuckDB 快 1.0–1.3 倍
输入,子进程冷启动 (Q13 / Q15)GROUP BYDuckDB 快 1.6–2 倍(主要是 chDB 引擎启动成本)

操作类型比引擎选择更重要——chDB 在轻量级聚合上明显胜出;DuckDB 在GROUP BY上有微弱的优势。对于短暂的 Lambda 风格调用,子进程冷启动的惩罚是真实存在的,但不是稳定的Python(df)register()的差异。

运行benchmark/bench_input_path_scale.pybenchmark/bench_input_path_variants.py可在您自己的硬件上复现这些数字。


存储引擎权衡(Q17 / Q18)

这是指南未在其主要结果中包含的两个查询,原因已预先说明:它们衡量的是 chDBMergeTree存储引擎的设计选择,而不是查询内核性能。以下是完整的数字和架构原理,以便任何工作负载处于此角落的人都能做出明智的决定。

Q17 — 持久存储工作流(CREATE TABLE … AS SELECT+ 5 个后续查询)

DuckDB129 毫秒vs chDB1854 毫秒—— DuckDB 在此特定形态上快约 14 倍。

发生了什么:chDB 的MergeTree写入时构建排序索引——整个行范围在主键上排序,部分在后台合并,存储布局支付前期成本,以便后续查询可以使用稀疏主键索引、区域地图修剪和跳过索引来廉价读取。DuckDB 的持久存储是单个文件,没有单独的排序步骤,没有每列索引——写入更快,读取使用 Parquet 风格的区域地图。

这种权衡是真实的:如果您的工作负载是一次性 ETL + 5 个后续查询,您将承担前期排序成本而无法摊销它,并且 DuckDB 的单文件写入以 14 倍的因子获胜,因为绝对时间由CREATE TABLE步骤主导。如果您的工作负载是持久化一次 + 针对同一表运行数百个查询(chDB 为之设计的典型可观测性/多租户分析形态),则前期成本会被摊销,MergeTree的索引结构会领先。

对于一次性 ETL 然后查询的工作流,DuckDB 的单文件写入是正确的选择。对于具有许多后续读取的长期持久表,chDB 的MergeTree是正确的选择。Q17 的标题数字反映了第一种形态。

Q18 — 在排序的时间戳列上进行主键范围扫描

DuckDB0.4 毫秒vs chDB2.9 毫秒—— 绝对差距2.5 毫秒

比率看起来令人担忧(DuckDB 优势约 7 倍),但按绝对值计算,这是在仅触及少量行的查询上的几毫秒。在这个规模上,chDB 方面的主要成本是MergeTree的主键记账(稀疏索引查找,标记范围解析)——在较大行数下不可见的固定开销,但在实际扫描工作低于毫秒时变得可见。DuckDB 在此形态上的查找基本上是仅元数据。

随着范围扩大,相对差距急剧缩小,在匹配行数 > 10 万时,chDB 达到或超过 DuckDB。Q18 的形态(小范围,排序列,适合几个标记)确实是 DuckDB 的领域,但仅限于毫秒级预算,而 chDB 根本就不是您会首先考虑的引擎。

综合解读

标题比率(DuckDB 优势 14 倍和 7 倍)在数学上是正确的。它们对于典型的 chDB 工作负载不是选择决定性的——它们衡量的是存储设计的前期成本,该成本仅在跨许多针对相同数据的查询中才能得到回报,以及为数十亿行表设计的索引结构的常数时间开销。为代理或笔记本工作负载在 chDB 和 DuckDB 之间进行选择的读者应将其视为边界信息(“如果您的工作负载完全像这样,请留在 DuckDB 上”),而不是内核性能的结论。

这也是指南突出 16 个内核查询的原因:将 Q17/Q18 与 Q1–Q16 一起展示会使读者锚定在最大的标题差距上,而这在此处是引擎最不相关的维度。

http://www.jsqmd.com/news/873807/

相关文章:

  • 2026年亲测AI论文网站合集(实测甄选版)
  • 佛山公司法诉讼律师哪位专业 - 资讯纵览
  • 【AI入门知识点】Harness 是什么?为什么 DeepSeek 要组建 Harness 团队?
  • AI项目GPU选型策略:任务匹配、显存计算与TCO优化指南
  • 线路板清洁度检测设备/检测仪/分析系统优质产品 ,西恩士工业 - 工业设备研究社
  • MuMu模拟器12 HTTPS抓包失效原因与系统级证书注入方案
  • 工业AI落地:从数据冷启动到高质数据工程实战
  • 深圳SMP纹发培训机构哪家最有实力 - 资讯纵览
  • GEO 2.0时代:当大模型开始“理解“品牌,优化逻辑彻底变了
  • 企业内如何通过Taotoken实现API访问控制与审计
  • iTunes登录协议逆向解析:设备指纹与动态挑战响应机制
  • 实战指南:使用ZXing.Net解决.NET应用中的条码识别与生成问题
  • 线路板清洁度分析金属、非金属、纤维杂质,西恩士工业 - 工业设备研究社
  • 2026北京一次性餐盒包装盒厂家怎么选?瀚隆包装当之无愧top级 - 企业深度横评dyy6420
  • Unity后台运行实战:iOS音频模式与Android前台服务双平台方案
  • 2026年AI论文写作工具实测排行,哪款真正适合一站式撰稿?
  • FlashAttention的OOM排查:为什么显存够了还是报内存不足?
  • 2025模型压缩范式:硬件感知剪枝与数据流驱动量化
  • 2026年北京餐饮外卖打包盒厂家推荐:瀚隆包装为什么适合单店与连锁餐饮共同选择? - 企业深度横评dyy6420
  • 紧急更新|Midjourney官方刚悄悄调整water rendering pipeline!3小时内必须掌握的4项prompt重写准则
  • Unity 2D农场游戏交互协议设计:从砍树到种田的统一架构
  • Unity WebGL文本输入解决方案:DOM桥接与IME兼容架构
  • 重庆全屋定制工厂哪个更实惠 - 资讯纵览
  • Unity后台运行实战指南:Android前台服务与iOS后台模式配置
  • Unity开发者首选VSCode配置指南:高效替代Visual Studio
  • 北海少儿舞蹈培训机构哪家更受青睐 - 资讯纵览
  • 线路板清洁度萃取+分析全套设备实力厂家推荐,西恩士工业 - 工业设备研究社
  • WzComparerR2完整指南:冒险岛游戏数据提取与可视化分析工具
  • 95%的企业AI项目都死在落地前?揭秘三大进化方向,让AI真正赋能业务!
  • 这次终于选对了!高效论文写作全流程AI论文网站推荐(2026 最新)