alitrack

DuckDB 数据库整个搬进 S3,单文件时代结束了

DuckDB 的数据库文件终于可以不落盘了——一个新社区扩展把整个可写库连同 WAL 一起塞进对象存储,多个进程还能同时只读挂载。

事情的起点是 9 月 30 日的一个 PR。开发者 dentiny 向 duckdb/community-extensions 提交了新扩展 duckdb_object_storage,当天合并。二进制这几天才陆陆续续上到各个版本的 CDN,我在 10 月 8 日凌晨用 v1.5.6 实测装上了它。

● ● ●

它做了什么

一句话:给 DuckDB 加了一个 SlateDB 虚拟文件系统。你用 duckdb_objfs:// 这个 URI 挂载数据库,库文件、写前日志(WAL)、checkpoint 恢复文件,全部住在对象存储里。

INSTALL duckdb_object_storage FROM community;
LOAD duckdb_object_storage;

ATTACH 'duckdb_objfs://analytics.db' AS db;
CREATE TABLE db.items AS SELECT range AS i, random() AS v FROM range(10000);
CHECKPOINT db;

三个后端可选:local(默认,本地目录)、memory(内存,测试用)、s3。S3 凭证走 DuckDB 自己的 Secret Manager,MinIO 这类兼容服务也支持。路径语义上 duckdb_objfs://analytics.db 是个逻辑名,物理布局由 SlateDB 接管,duckdb_objfs_root 决定它落在本地哪个目录或 S3 桶的哪个前缀下。

duckdb_object_storage 架构:DuckDB 经 SlateDB VFS 挂到三种存储后端

duckdb_object_storage 架构:DuckDB 经 SlateDB VFS 挂到三种存储后端

● ● ●

我实测的部分

本机 v1.5.6 命令行(Linux amd64),local 后端,全流程跑通:

  • 建表 10000 行,CHECKPOINT,DETACH;
  • 重新 ATTACH,count / count DISTINCT / min / max 全部读回,数值对得上;
  • 底层落盘是一套 SlateDB LSM 结构:compacted/ 下一个 778 KB 的 SST 文件,旁边是 manifest/、wal/、compactions/ 目录;
  • 内省函数 duckdb_objfs_io_stats() 显示写入约 1 MB(12 次写请求,平均延迟 1.05 ms),读回只用了 44 次 read、146 字节——大部分数据在重挂前已经在缓存里了。

多进程读也验了:三个 duckdb 进程同时以 READ_ONLY 挂载同一个库,各自 count 全部成功。这符合扩展文档的定位——任意数量的只读进程 + 单个写者。

需要说明这是 local 后端的数据。S3 后端我没有真实凭证去测,那部分的行为以扩展文档为准。

● ● ●

77.5% 这个数字是怎么来的

扩展 README 里有个缓存命中率的示例输出:写入 1200 万行、重开数据库读回,数据缓存命中率 77.572%,元数据缓存 100%。

这是维护者自己跑出来的示例口径,不是基准测试结果,README 原文也标注了"确切数字随运行变化"。我能确认的是这套统计函数真实可用:duckdb_objfs_cache_stats() 返回三层缓存(memory_data / memory_metadata / persistent)的命中、未命中、命中率、逐出字节,duckdb_objfs_io_stats() 返回五类 I/O 操作的请求数和延迟分布,数据来自 SlateDB 的 Foyer 缓存和 OpenDAL 的统计。

● ● ●

该知道的几个边界

Windows 装不了。 扩展的 description.yml 明确把 windows_amd64、windows_arm64 全系列排除在构建平台外,Linux 和 macOS 用户不受影响。

单写者靠兜底,不靠锁。 扩展没有实现文件锁,第二个进程以读写模式挂同一个库不会立刻报错,而是靠 SlateDB 的 manifest fencing 兜底:第二个写者接管,第一个写者在下次 flush 时开始报 I/O 错误。数据不会坏,但失败是延迟的。文档原话把并发写者定义为"配置错误"而不是受支持的模式。

只读有可见性延迟。 只读挂载共享一个缓存的 SlateDB reader,大约每 10 秒轮询一次写者的 manifest,别的进程刚提交的数据可能要等这个间隔才能看到。

所有配置只在首次访问时读一次。 后端、根路径、缓存大小这些 duckdb_objfs_* 设置,在第一次 duckdb_objfs:// 访问时定型,之后改了会直接报错而不是静默忽略。

● ● ●

为什么值得留意

DuckDB 官方的远程方案是 DuckLake:元数据放 SQL 数据库、数据放 Parquet 文件,天然适合 lakehouse 场景。duckdb_object_storage 提供的是另一个方向——你要的是一个完整可写的 DuckDB 库,只是存储层在远端。WAL 也在对象存储里,挂载即用,对临时分析环境、无状态容器这类场景更顺手。

当然,扩展版本号还是 0.1.0,维护者仓库 2 个 star,处于很早期的阶段。单写者的限制也决定了它替代不了真正的多写数据库。但"整个 DuckDB 库住进 S3"这条路走通了,本身就是个值得记一笔的信号。

回头再看开头那个场景:容器明天销毁重建,你的分析库还在 S3 里等着。数据不丢的代价,是接受 10 秒的可见性延迟和一个孤独的写者。对很多临时分析任务,这笔账是划算的。

你平时把 DuckDB 当本地工具用,还是已经在往远端存储挪了?用 S3 后端的踩过什么坑,评论区聊聊。

● ● ●

参考来源

  1. 01
    社区扩展合并 PR:https://github.com/duckdb/community-extensions/pull/2794(2026-09-30 合并,维护者 dentiny)
  2. 02
    扩展源码仓库:https://github.com/dentiny/duckdb-object-storage(MIT 协议)
  3. 03
    community-extensions 仓库扩展定义:https://github.com/duckdb/community-extensions/tree/main/extensions/duckdb_object_storage
  4. 04
    扩展二进制 CDN:https://community-extensions.duckdb.org/v1.5.6/linux_amd64/duckdb_object_storage.duckdb_extension.gz
  5. 05
    本机实测:v1.5.6 CLI (linux_amd64),2026-10-08,local 后端建表/CHECKPOINT/重挂/多进程只读