上周三下午四点,老板在群里甩了一句「看下这半年复购率是不是掉了」,我打开运营给的 CSV,217万行,双击用 Excel 打开,进度条走到 73% 的时候,风扇起飞,然后它白屏了。不是卡,是直接白屏。我等了六分钟,强退。
这事不新鲜,但我那天较劲了。同一份文件,我把手边五个工具都跑了一遍。
先交代环境,不然数字没意义:MacBook Pro M1 Pro,16G 统一内存,macOS 14.5。文件是 217 万行 × 14 列的订单明细,CSV 裸文件 486MB。每个工具都只做同一件事——按 user_id 分组,算出每个用户的订单数和最近一次下单时间,也就是最普通的 group by,没有 join,没有窗口函数,我不想让测试变成炫技。
结果都是跑三遍取中位数,第一次算预热:
- Excel 365:没跑完。Excel 单个工作表上限 1,048,576 行,217 万行装不下,我硬塞只进了前一半,剩下的得拆表。Power Query 倒是能读取全量,但刷新花了 11 分钟,中途我手贱点了下别的 sheet,直接卡住,最后从任务管理器里杀掉的。
- Pandas 2.2.2:read_csv 花了 17.8 秒,groupby + agg 3.4 秒,内存峰值 3.9G。能用,但要等。而且得小心 dtype 推断,user_id 那列被读成 object 的时候,内存直接翻到 7G 多,我第一次跑就是这么翻车的。
- Polars 1.4:scan_csv 惰性加载再 collect,整个流程 1.9 秒,内存峰值 1.2G。第一次跑完我以为自己按错了,还重跑了一遍。
- DuckDB 1.1.3:1.4 秒。SQL 写的,
SELECT user_id, count(*), max(created_at) FROM read_csv_auto('orders.csv') GROUP BY 1。跟 Polars 差不多,但它赢在我不用切脑子,写 SQL 不用查 API。 - ClickHouse 24.6:导入 MergeTree 花了 6.2 秒(本地单节点),查完 0.08 秒。但装它、建表、想清楚 order by 字段,前后花了我 40 分钟。
单看这些数字,结论挺爽的:DuckDB 和 Polars 完胜,Pandas 该退休了,Excel 赶紧删。这类文章网上一抓一大把,我不缺这一篇。我想说点别的。
那天我算出复购率是 3 秒的事,真正花时间的是后面那两小时——我去找运营确认「复购」到底怎么定义。是按同一 user_id 30 天内下两次单,还是不同订单号但同一天不算?退货的订单要不要剔?公司内购账号要不要排除(数据里混着 2000 多个 @company.com 的账号)?这些问题,DuckDB 一秒都帮不了我,Excel 也不会。
我后来有个挺糙的比喻:大家比的是查询延迟(query latency),但真正决定你加不加班的是决策延迟(decision latency)——从老板开口,到你给出一个他敢拿去做决策的答案,中间的总时长。这个总时长里,工具跑得快慢占的比例,可能连 10% 都不到。
那天我记了下时间,总共 2 小时 43 分钟:等 Excel 卡死 6 分钟;装 ClickHouse 加建表 22 分钟(后来压根没用上);跟运营来回确认口径 55 分钟;写 SQL 和调 3 分钟;做图表 20 分钟;剩下 57 分钟是等一句「这数据准吗」的回复。
所以我现在的选型标准变了,不再是「谁快」,而是三个更烦人的问题:
- 它能不能让非技术同事自己跑一遍?DuckDB 再快,运营同事也装不明白。Metabase 直接连 DuckDB 文件这种组合,反而比一个本地跑得飞起的脚本值钱。
- 它的结果能不能钉住口径?ClickHouse 里写死的 view,比微信群里的截图靠谱一百倍。数据这东西,能追溯比能秒出重要。
- 从原始表到你能问出问题,中间要经过几次人工搬运?每多一次「导出 CSV 发微信」,就多一次口径走样,这跟工具快慢完全无关。
顺手给几个具体建议,按数据量分:
- 小于 50 万行,别折腾,Excel + Power Query 就够,别听人劝你上 Spark,你那是拿高射炮打蚊子。
- 50 万到 3000 万行,DuckDB 或者 Polars,单机跑。DuckDB 更推荐给会 SQL 的人,Polars 推荐给写 Python 的。这俩我都在生产里用,DuckDB 唯一让我烦的是多进程同时写同一个 .duckdb 文件会报锁,得排队,批量任务要自己串起来。
- 3000 万行以上再考虑 ClickHouse、Doris 这类。但先问自己一句:真的需要秒级响应吗?我见过太多公司上了 ClickHouse,日活用户 40 个,集群闲置率 90%。
最后说个反常识的。我们组今年把一部分原本跑在 Spark 上的任务挪回了单机 DuckDB。不是 Spark 不好,是我们的数据规模根本喂不饱它——每天增量 200 万行左右,Spark 起 executor 那点时间,DuckDB 早跑完了。省下来的钱也不多,一个月不到 2000 块,但省下来的是排障时间,这个比钱贵。
当然我这个结论很挑场景,你要是每天增量 5 亿行,当我没说。