Featured image of post 为什么我从 PostgreSQL 转向 DuckDB

为什么我从 PostgreSQL 转向 DuckDB

用了五年PostgreSQL之后,我开始在分析场景用DuckDB。不是替代,是互补

五年前,我接手一个数据分析项目。当时的选择很自然:PostgreSQL。

理由很充分:稳定、功能全、社区大、文档好。用了五年,没出过大问题。查询性能也够用,加几个索引,大部分查询都能在1秒内返回。

直到去年,数据量从100万行涨到1亿行。

PostgreSQL开始吃力了。一个复杂的聚合查询,以前1秒,现在要30秒。加了索引也没用,因为查询逻辑太复杂,索引帮不上忙。

我开始找替代方案。然后遇到了DuckDB。

PostgreSQL的问题

先说清楚,PostgreSQL是好数据库。真的。

它的强项是OLTP(在线事务处理):高并发、短事务、数据一致性。电商订单、用户系统、支付流水,这些场景PostgreSQL是王者。

但数据分析是OLAP(在线分析处理):低并发、长查询、大量扫描。这两个场景的需求完全不一样。

OLTP需要快速响应单个请求,所以用行式存储,一行数据连续存放。OLAP需要快速扫描大量数据,所以用列式存储,一列数据连续存放。

PostgreSQL是行式存储。当你做SELECT AVG(salary) FROM users WHERE dept = 'eng'时,它要把整行数据都读进来,即使你只需要salary和dept两列。

数据量小的时候,这个开销可以忽略。数据量大了,就是瓶颈。

DuckDB的优势

DuckDB是列式存储,专为OLAP设计。

同样的查询,DuckDB只读salary和dept两列,其他列完全不碰。性能差距是数量级的。

我做了个测试:1亿行数据,计算每个部门的平均工资。

PostgreSQL(加了索引):28秒 DuckDB:1.2秒

23倍的性能差距。

而且DuckDB是进程内数据库,不需要单独的服务。直接嵌入Python,像pandas一样用。没有网络开销,没有连接池,没有服务管理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import duckdb
import pandas as pd

# 直接查询pandas DataFrame
df = pd.read_csv('data.csv')
result = duckdb.execute("""
    SELECT dept, AVG(salary) 
    FROM df 
    GROUP BY dept
""").df()

这种体验,PostgreSQL给不了。

不是替代,是互补

我不是说DuckDB要替代PostgreSQL。

生产系统的订单数据,我还是用PostgreSQL。需要事务、需要并发、需要外键约束,这些DuckDB都不擅长。

我的用法是:PostgreSQL存生产数据,DuckDB做数据分析。

每天凌晨,把PostgreSQL的数据导出成Parquet文件,导入DuckDB。白天的分析查询都在DuckDB上跑,不影响生产系统。

这种架构有个好处:分析查询再重,也不会拖慢线上服务。以前在PostgreSQL上跑复杂查询,经常被投诉"系统卡了"。现在分析查询在DuckDB上跑,PostgreSQL专心处理事务,两边互不干扰。

迁移成本

迁移成本比我想象的低。

DuckDB支持标准SQL,大部分PostgreSQL的查询可以直接跑。只有少数函数不兼容,比如PostgreSQL的generate_series,DuckDB用generate_subscripts。

数据迁移更简单。DuckDB可以直接读PostgreSQL的导出文件,甚至可以直接查询PostgreSQL(通过postgres扩展)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# DuckDB直接查询PostgreSQL
duckdb.execute("""
    INSTALL postgres;
    LOAD postgres;
    
    SELECT * FROM postgres_scan(
        'dbname=mydb user=postgres',
        'public',
        'users'
    )
""")

不过我不建议直接查生产库,还是导出成Parquet比较稳。

踩过的坑

用了半年DuckDB,踩了几个坑。

坑1:并发写入

DuckDB不支持高并发写入。它是单_writer模型,同一时间只能有一个写入操作。如果你的场景需要多个进程同时写入,DuckDB不适合。

我的解决方案:写入还是用PostgreSQL,DuckDB只读。

坑2:内存管理

DuckDB默认用系统内存的70%。如果数据量太大,会OOM。可以设置memory_limit参数,但超过内存的数据得用磁盘溢出,性能会下降。

我现在的做法:分析之前先估算数据量,超过内存的就分块处理。

坑3:持久化

DuckDB可以持久化到文件,但不如PostgreSQL成熟。没有WAL、没有流复制、没有时间点恢复。

所以我不把DuckDB当主数据库用。它只是个分析引擎,数据丢了可以重新从PostgreSQL导入。

适用场景

用了半年,我总结了DuckDB的适用场景。

适合DuckDB:

  • 数据分析、报表生成
  • 数据探索、快速原型
  • 数据管道中的转换步骤
  • 单机分析,不需要分布式

适合PostgreSQL:

  • 生产系统的事务处理
  • 高并发读写
  • 需要强一致性
  • 需要复杂的权限管理

两者结合:

  • PostgreSQL存生产数据
  • DuckDB做数据分析
  • 定期同步,互不干扰

性能对比

做个全面的性能对比。测试环境:1亿行数据,4核CPU,16GB内存。

查询1:简单聚合

1
SELECT dept, AVG(salary) FROM users GROUP BY dept
  • PostgreSQL: 28秒
  • DuckDB: 1.2秒

查询2:多表JOIN

1
2
3
4
SELECT u.name, COUNT(o.id) 
FROM users u 
JOIN orders o ON u.id = o.user_id 
GROUP BY u.name
  • PostgreSQL: 45秒
  • DuckDB: 3.8秒

查询3:窗口函数

1
2
3
SELECT name, salary,
    RANK() OVER (PARTITION BY dept ORDER BY salary DESC)
FROM users
  • PostgreSQL: 32秒
  • DuckDB: 2.1秒

查询4:时间序列聚合

1
2
3
4
SELECT DATE(created_at), COUNT(*)
FROM orders
GROUP BY DATE(created_at)
ORDER BY DATE(created_at)
  • PostgreSQL: 38秒
  • DuckDB: 1.8秒

所有查询,DuckDB都快10-20倍。

给PostgreSQL用户的建议

如果你也在用PostgreSQL做分析,几点建议:

别急着迁移。 先搞清楚你的瓶颈是什么。如果是查询慢,试试DuckDB。如果是并发高,PostgreSQL是对的。

混合使用。 不用二选一。PostgreSQL做事务,DuckDB做分析,各司其职。

先小规模测试。 别一上来就迁移整个系统。先拿一个分析场景测试,看看效果。

注意内存。 DuckDB吃内存,大数据集要提前规划。

学习成本很低。 DuckDB的SQL和PostgreSQL几乎一样,学习成本可以忽略。

最后

从PostgreSQL到DuckDB,不是技术升级,是场景适配。

PostgreSQL是OLTP的王者,DuckDB是OLAP的新秀。两者不是竞争关系,是互补关系。

用了半年,我的感受是:分析查询终于不用等了。以前跑个复杂查询要去喝杯咖啡,现在几秒钟出结果。这种体验,一旦习惯了,就回不去了。

如果你也在用PostgreSQL做分析,试试DuckDB。不是替代,是互补。

Built with Hugo
Theme Stack designed by Jimmy