湖仓系统——第三阶段:数据写入管道

💡 原文英文,约1600词,阅读约需6分钟。
📝

内容提要

湖仓系统第三阶段写入管道中,INSERT 先将数据写入 Parquet 文件并收集列统计,再原子注册到元数据目录。核心原则是先写文件后更新元数据,确保崩溃时仅产生孤儿文件,避免元数据指向缺失文件。事务提交时创建新快照,记录写入文件、列统计与审计日志。

🔎

延伸解读

写入顺序的安全设计

文章强调先写Parquet文件再更新元数据,且元数据更新是原子的。这种顺序确保崩溃时只可能产生孤儿文件(文件存在但无元数据),而不会出现元数据指向缺失文件的情况。孤儿文件仅浪费存储,不会导致查询错误;反之则可能引发扫描失败。这一设计权衡了存储效率与系统正确性,是湖仓系统写入管道的关键安全原则。

事务提交的原子性

提交时,系统在单个Postgres事务中插入新快照、注册数据文件、写入列统计和审计日志,最后调用tx.Commit()使所有变更原子可见。这保证了写入操作的ACID特性,避免部分更新导致元数据不一致。同时,事务基于最新快照读取,提供一致性基础,确保并发写入时的隔离性。

文件路径与ID管理

数据文件按{schema}/{table}/data/ducklake-{uuid}.parquet组织,删除文件存放在deletes子目录。表创建时,从快照的NextCatalogID计数器分配唯一ID,每个列也获得独立field ID,确保全局唯一且永不重用。这种结构便于管理,并为后续阶段(如删除、模式演化)预留扩展空间。

❓

Q&A

湖仓系统第三阶段的写入管道中,INSERT 操作是如何保证数据一致性的?

INSERT 先将数据写入 Parquet 文件并收集列统计,然后原子性地将文件及其统计信息注册到元数据目录。核心原则是先写文件后更新元数据,且元数据更新是原子的,确保崩溃时只产生孤儿文件,避免元数据指向缺失文件。

在写入过程中发生崩溃,系统会处于什么状态?

崩溃发生在文件写入期间:磁盘上有部分文件,无元数据注册,系统状态不变,可清理孤儿文件。崩溃发生在元数据插入但未提交前:磁盘上有完整文件,Postgres 自动回滚,系统状态不变,可清理孤儿文件。崩溃发生在提交后:文件在磁盘且元数据已提交,插入成功。

为什么说孤儿文件是安全的?

孤儿文件(文件存在但无元数据)只会浪费存储空间,永远不会导致查询结果错误。相反,如果元数据指向缺失的文件,则会导致扫描失败。因此先写文件后更新元数据的设计是安全的。

INSERT 操作的具体步骤有哪些?

1. 开始事务并读取最新快照;2. 解析表定义;3. 生成文件路径;4. 写入 Parquet 文件并收集列统计;5. 在元数据中注册数据文件和列统计;6. 创建快照并提交,使所有元数据可见。

事务提交时具体做了哪些操作?

提交时:插入新快照行并递增 ID;为每个待处理数据文件插入记录,关联到新快照;插入每个文件的列统计;插入待处理的删除文件;在审计跟踪中记录变更;最后调用 tx.Commit() 使所有更改原子可见。

数据文件在存储中是如何组织的?

数据文件按目录层次组织,镜像目录结构:{data_path}/{schema_name}/{table_name}/data/ 下存放数据文件(如 ducklake-{uuid}.parquet),deletes/ 下存放删除文件。

创建表时如何分配 ID?

CreateTable 从事务的快照计数器中分配 ID:表本身消耗一个 ID,每个列也消耗一个 ID,确保字段 ID 全局唯一且永不重用。

🏷️

标签

➡️

继续阅读