PostgreSQL码农集散地

DuckDB再次修改游戏规则,DuckLake 支持实时入湖

DuckLake 改变数据入湖游戏规则: 实时入湖

今天看到一篇 DuckDB 的文章, 介绍通过 Data Inlining 的能力实现数据实时入湖.

https://ducklake.select/2026/04/02/data-inlining-in-ducklake/

Data Inlining 本质是:用数据库替代小文件,让 Lakehouse 第一次具备“低延迟写能力”。

玩法:

-- Global: change the default for all tables
SET ducklake_default_data_inlining_row_limit = 50;

-- Per-table: override for a specific table
ALTER TABLE lake.readings SET (data_inlining_row_limit = 100);

-- Disable inlining entirely
SET ducklake_default_data_inlining_row_limit = 0;

ATTACH 'ducklake:sensors.ducklake' AS lake (DATA_PATH 'sensor_data/');

CREATE TABLE lake.readings (
    sensor_id INTEGER,
    temperature DOUBLE,
    ts TIMESTAMP
);

INSERT INTO lake.readings VALUES (1, 21.5, '2025-03-27 10:00:00');
INSERT INTO lake.readings VALUES (2, 22.1, '2025-03-27 10:00:10');
INSERT INTO lake.readings VALUES (1, 21.8, '2025-03-27 10:00:20');

None of these inserts created a Parquet file. 
Instead, all three rows live in the catalog database 
in an inlined data table named ducklake_inlined_data_table-id_schema-version. 
If we peek inside the catalog:

ATTACH 'sensors.ducklake' AS catalog_db;
SELECT * FROM catalog_db.ducklake_inlined_data_1_1;

┌────────┬────────────────┬──────────────┬───────────┬─────────────┬─────────────────────┐
│ row_id │ begin_snapshot │ end_snapshot │ sensor_id │ temperature │         ts          │
│ int64  │     int64      │    int64     │   int32   │   double    │      timestamp      │
├────────┼────────────────┼──────────────┼───────────┼─────────────┼─────────────────────┤
│      0 │              2 │         NULL │         1 │        21.5 │ 2025-03-27 10:00:00 │
│      1 │              3 │         NULL │         2 │        22.1 │ 2025-03-27 10:00:10 │
│      2 │              4 │         NULL │         1 │        21.8 │ 2025-03-27 10:00:20 │
└────────┴────────────────┴──────────────┴───────────┴─────────────┴─────────────────────┘

If a delete targets a row that is still inlined, 
DuckLake handles it in place by setting the 
end_snapshot column on that row. 
No deletion file is created. For example:

DELETE FROM lake.readings WHERE sensor_id = 2;

┌────────┬────────────────┬──────────────┬───────────┬─────────────┬─────────────────────┐
│ row_id │ begin_snapshot │ end_snapshot │ sensor_id │ temperature │         ts          │
│ int64  │     int64      │    int64     │   int32   │   double    │      timestamp      │
├────────┼────────────────┼──────────────┼───────────┼─────────────┼─────────────────────┤
│      0 │              2 │         NULL │         1 │        21.5 │ 2025-03-27 10:00:00 │
│      1 │              3 │            5 │         2 │        22.1 │ 2025-03-27 10:00:10 │
│      2 │              4 │         NULL │         1 │        21.8 │ 2025-03-27 10:00:20 │
└────────┴────────────────┴──────────────┴───────────┴─────────────┴─────────────────────┘

1. 什么是 Data Inlining

核心思想:

不再为“小数据变更”写 Parquet 文件,而是直接写入元数据数据库。

传统流程是:

写数据 → 生成 Parquet 文件 → 在元数据中登记文件  

而 DuckLake 改成:

小数据 → 直接写入 metadata 表(inline)  
大数据 → 写 Parquet  

👉 本质:把“小数据路径”从“文件系统”改为“数据库”

2. 为什么需要这个机制?

传统 Lakehouse(Iceberg / Delta)的问题:

  • 插入1行数据 → 也要写一个文件

  • 导致:

    • 大量小文件(small file problem)
    • 元数据膨胀
    • 频繁 compaction
    • S3 / OSS round-trip 成本高

👉 结论:

小变更 = 极其低效的 IO 模型

3. DuckLake 的解决方案

✅ 小变更:直接 inline 到 metadata

  • insert / delete 少量行 → 写 metadata 表
  • 不生成文件

✅ 大批量:正常写 Parquet

  • 达到阈值后再 flush

👉 等价于:

metadata = write buffer(写缓存)

4. 数据是否可查询?

可以,而且是实时可见

  • inline 数据 + Parquet 数据统一查询
  • 对用户透明

👉 本质:

metadata 表 ≈ 一个“实时增量层”

5. 支持的操作

  • 小规模 INSERT → inline table
  • 小规模 DELETE → inline delete table

6. 触发条件(关键参数)

data_inlining_row_limit = 10(默认)  
  • 小于这个行数 → inline
  • 大于 → 写文件

可以:

  • 全局配置
  • connection 配置
  • table级配置

7. 后续处理(关键机制)

inline 数据不会一直留在 metadata:

👉 会被:

积累 → 合并 → flush 成 Parquet 文件  

设计哲学

1. 本质:把 Lakehouse 的写路径“分层”

传统:

写入 = 文件系统(统一路径)  

DuckLake:

写入 = 双路径  

小数据 → DB(低延迟)  
大数据 → 文件(高吞吐)  

👉 这是典型的:

冷热写路径分离(Hot/Cold write path)

2. 本质:把 metadata 升级成“数据层”

传统认知:

metadata = 辅助信息  

DuckLake:

metadata = 小数据存储层  

👉 这一步非常关键:

数据湖第一次把“数据库”重新拉回核心路径

3. 本质:解决 Lakehouse 三大痛点

痛点1:小文件爆炸

解决:

→ 不写小文件

痛点2:高延迟写入

传统:

写文件 → 更新 metadata → 可见  

DuckLake:

写 DB → 立即可见  

痛点3:高并发写冲突

传统:

  • 文件级锁
  • manifest 冲突

DuckLake:

  • 用数据库事务解决

👉 本质:

用 RDBMS 替代 object storage 的并发控制

4. 本质:Lakehouse 向 OLTP 借脑子

这其实是一个很深的趋势:

能力
传统 Lakehouse
DuckLake
写入
文件
DB + 文件
并发控制
文件协调
事务
小数据
很差
很强
延迟
秒级
毫秒级

👉 结论:

DuckLake = Lakehouse + OLTP 思维融合

5. 一个更高级的理解(你应该关心的)

这不是优化,而是范式变化:

旧范式(Iceberg / Delta)

“一切都是文件”

新范式(DuckLake)

“文件 + 数据库协同”


关键判断

1. 这是 Lakehouse 方向上最重要的创新之一

因为它解决的是:

写路径,而不是读路径

而历史上:

  • Parquet → 优化读
  • 向量化 → 优化读
  • 索引 → 优化读

👉 写路径一直是短板

2. 它的代价也很明确

不是没有 tradeoff:

  • metadata DB 成为瓶颈
  • 内存 / catalog 膨胀
  • flush 策略不可控(目前)
  • 生态还不成熟

3. 它真正适合的场景

非常明确:

✅ 高频小写入(stream / CDC)
✅ 实时分析
✅ feature store
✅ AI 数据流水线

不适合:

❌ 纯离线批处理
❌ 超大规模 append-only(直接写 Parquet 更简单)

一句话总结

Data Inlining 本质是:用数据库替代小文件,让 Lakehouse 第一次具备“低延迟写能力”。