一个 YAML 搞定 DuckDB 集群——我写了个 Quack 进程管家,170K QPS 零故障
quack-proxy:633 行 Go,管理 N 个 DuckDB+Quack 进程,自动健康检查 + 重启,一把梭生成 HAProxy 配置。
DuckDB 团队 5 月 12 号发布了 Quack 协议,原生解决了多客户端并发写入的问题。这是好事。但要用好 Quack,你还得自己写 shell 脚本、systemd unit、健康检查、负载均衡——每个 DuckDB 文件都得配一套。
没人在乎运维,直到凌晨两点收到告警。
这就是 quack-proxy 要做的事。
为什么需要这个?
假设你有三个 DuckDB 数据库文件,想把它们都暴露成 Quack 端点: |
1# 现状:手动管理 |
三个文件就这么烦了。生产环境 20 个文件呢?
quack-proxy 的做法:
1# quack-proxy.yaml
2shards:
3 - name: orders_2024
4 database: /data/orders_2024.db
5 - name: orders_2025
6 database: /data/orders_2025.db
7 - name: customers
8 database: /data/customers.db
1quack-proxy start # 一条命令,全部搞定
自动分配端口(9491、9492、9493),自动装 Quack 扩展,自动生成 token,自动健康检查,崩了自动重启。
架构
1quack-proxy (Go daemon, ~10MB RSS) |
跟 systemd 的哲学一样:管生管养管埋。每个 DuckDB 子进程的生命周期完全由 quack-proxy 控制——启动、监控、重启、优雅关闭。
压力测试
我在这上面跑了四轮压测。环境:WSL2,8 vCPU,两个 shard。工具是 hey,全打 HTTP 层(Quack RPC 端点,非 SQL 查询)。
|
c=10 是甜点,8 核机器上 83K QPS。c=50 反而掉到 33K——上下文切换开销超过了并发收益。
两个 shard 同时打,各 c=20,持续 30 秒,每个 shard 100 万请求: | ||||||||||||||||
|
两百万请求,3 个超时,错误率 0.00015%。QPS 差异 ~23% 只是因为两个进程抢 8 个核——内核调度不均匀,正常现象。
直接 kill -9 DuckDB 进程:
杀一个 shard:~2 秒恢复 杀两个 shard:~6 秒全部恢复
quack-proxy 的健康检查每 5 秒跑一次,检测到进程没了立刻重启。DuckDB 重新安装 Quack 扩展需要 ~5 秒。
c=10 持续压两个 shard,30 分钟:
|
代码量
一共 633 行 Go,5 个源文件: | ||||||||||||||||||
|
对比一下同类工具:Rust 写的进程管理器通常 2000+ 行起。Go 的 goroutine 天然映射子进程管理——os/exec 开一个 goroutine 就是一个 DuckDB 实例,不需要自己写事件循环。
和 duckdb_fdw 的配合
quack-proxy 是我写 duckdb_fdw v2.0 时顺带做出来的。duckdb_fdw 让 PostgreSQL 能通过 Quack 协议访问远程 DuckDB,但它不负责管 Quack 服务器本身。 |
1-- PG 中配置 duckdb_fdw,指向 quack-proxy 管理的 HAProxy VIP |
quack-proxy 管 DuckDB 进程,HAProxy 做负载均衡,duckdb_fdw 在 PG 里提供 FDW 接口——三层协作,各司其职。
不是什么
说清楚不做什么,比说做什么更重要:
|
获取
1go install github.com/alitrack/quack-proxy/cmd/quack-proxy@latest |
或者直接 clone:
1git clone https://github.com/alitrack/quack-proxy.git
2cd quack-proxy && go build -o quack-proxy ./cmd/...
MIT 协议,随便用。
要求:Go 1.21+,DuckDB ≥ 1.5.2(带 Quack 扩展)。
Quack 协议把 DuckDB 从嵌入式引擎变成了真正的客户端-服务器数据库。但 DuckDB 团队只给了协议,没给运维工具。quack-proxy 补的就是这最后一公里。
周末项目,633 行 Go。写得糙,但能用。