DuckLake v1.0:轻量级湖仓一体方案

DuckLake v1.0 全面介绍:一种基于 DuckDB 构建的轻量级湖仓一体存储格式。深入解析 COPY TO 语法、多客户端支持、格式对比矩阵,以及实战变现建议。

引言

过去十年,数据仓库和数据湖的界限逐渐模糊,“湖仓一体”(Lakehouse)的概念应运而生。Databricks 的 Delta Lake、Apache Iceberg、Apache Hudi 这三巨头主导了湖仓格式的演进,但它们都有一个共同的问题:太重了

要让这三个格式跑起来,你需要 Spark、Hive Metastore、HDFS 或对象存储、以及一套编目服务。对于一个中小型团队来说,这不仅是学习成本的陡增,更是运维噩梦。

DuckDB v1.5 带来的 DuckLake 格式,正是为了解决这个问题。

DuckLake 不是要取代 Parquet 或 Delta Lake,而是提供一种适合嵌入式场景的轻量湖仓格式——不需要 Spark、不需要 Metastore,只需要 DuckDB 就能完成从写入、查询到管理的全部流程。

什么是 DuckLake?

DuckLake 是 DuckDB 原生支持的一种结构化湖仓存储格式。它本质上是一组带元数据文件的 Parquet 文件集合,通过事务日志(Transaction Log)来追踪每次写入操作,从而提供 ACID 事务、时间旅行查询、和增量读取能力。

核心特点

  • 零外部依赖:不需要 Spark、Hive、HDFS、或任何编目服务
  • ACID 事务:支持并发写入与隔离(基于文件级别的乐观锁)
  • Schema 演化:支持添加/删除列、修改类型
  • 时间旅行:查询任意历史版本
  • 增量查询:只读取新写入的分片数据
  • 兼容开放格式:底层数据存为 Parquet,任何 Parquet 读取器都能读取

DuckLake 的命名也非常直白:Duck + Lake。DuckDB 是"野鸭",Lake 是"湖"——把湖装进 Duck 里,这本身就是对"轻量湖仓"这一理念的最佳诠释。

安装与配置

DuckLake 作为 DuckDB v1.5 的内置功能,无需额外安装扩展:

-- 检查 DuckDB 版本(需要 v1.5+)
SELECT version();

-- 确认 DuckLake 支持
SELECT * FROM duckdb_extensions() WHERE extension_name = 'ducklake';

对于 Python 用户,同样简单:

import duckdb

con = duckdb.connect()
# DuckLake 直接可用,无需额外 pip 包

COPY TO 语法详解

DuckLake 的核心写入接口是 COPY TO 语句。v1.5 对 COPY TO 进行了大幅扩展,支持直接以 DuckLake 格式写入数据:

基本语法

-- 将查询结果以 DuckLake 格式写入
COPY (SELECT * FROM orders)
TO 'data/orders.ducklake'
(FORMAT DUCKLAKE, APPEND FALSE);

-- 追加写入(创建新版本)
COPY (SELECT * FROM new_orders)
TO 'data/orders.ducklake'
(FORMAT DUCKLAKE, APPEND TRUE);

关键参数

参数类型默认值说明
FORMAT枚举必须设为 DUCKLAKE
APPEND布尔FALSETRUE 追加新数据;FALSE 覆盖整个 Lake
COMPRESSION枚举ZSTDParquet 压缩方式:ZSTD/SNAPPY/LZ4/UNCOMPRESSED
ROW_GROUP_SIZE整数122880每个 Row Group 的行数
OVERWRITE_SCHEMA布尔FALSE允许在追加时改变 schema
PARTITION_BY列名列表按指定列分区存储

高级用法

-- 分区写入 + ZSTD 压缩
COPY (SELECT * FROM events WHERE year = 2026)
TO 'data/events.ducklake'
(FORMAT DUCKLAKE, PARTITION_BY (region, dt), COMPRESSION 'ZSTD');

-- 覆盖 schema 的追加写入
COPY (SELECT id, name, email, signup_date FROM users_v2)
TO 'data/users.ducklake'
(FORMAT DUCKLAKE, APPEND TRUE, OVERWRITE_SCHEMA TRUE);

读取 DuckLake

-- 基本读取(最新版本)
SELECT * FROM 'data/orders.ducklake';

-- 时间旅行:读取指定版本
SELECT * FROM 'data/orders.ducklake' (VERSION 3);

-- 时间旅行:读取指定时间戳
SELECT * FROM 'data/orders.ducklake' (TIMESTAMP '2026-05-09 12:00:00');

-- 查看版本历史
SELECT * FROM ducklake_versions('data/orders.ducklake');

管理操作

-- 压缩(合并小文件)
CALL ducklake_compact('data/orders.ducklake');

-- 清理过期版本
CALL ducklake_vacuum('data/orders.ducklake', KEEP_VERSIONS 10);

-- 获取统计信息
SELECT * FROM ducklake_stats('data/orders.ducklake');

多客户端支持

DuckLake 的优秀之处在于,它不仅被 DuckDB 自身支持,还能被多种生态工具读取。以下是主要客户端的支持情况:

Python (DuckDB + PyArrow)

import duckdb
import pandas as pd

con = duckdb.connect()

# 写入 DuckLake
con.execute("""
    COPY (SELECT * FROM range(1000000) t(id))
    TO 'test.ducklake' (FORMAT DUCKLAKE)
""")

# 读取为 Pandas DataFrame
df = con.execute(
    "SELECT * FROM 'test.ducklake'"
).df()

# 读取为 PyArrow Table
import pyarrow as pa
table = con.execute(
    "SELECT * FROM 'test.ducklake'"
).arrow()

R 语言

library(duckdb)
library(dplyr)

con <- dbConnect(duckdb())

# 读取 DuckLake
df <- tbl(con, "test.ducklake") %>%
  filter(id > 500000) %>%
  collect()

print(df)

Java / JDBC

// pom.xml: 添加 duckdb-jdbc 依赖
Connection conn = DriverManager.getConnection("jdbc:duckdb:");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(
  "SELECT count(*) FROM 'data/orders.ducklake'"
);
while (rs.next()) {
    System.out.println(rs.getLong(1));
}

Node.js

const duckdb = require('duckdb');
const db = new duckdb.Database(':memory:');
db.all("SELECT * FROM 'data/orders.ducklake' LIMIT 10",
  (err, rows) => {
    if (err) throw err;
    console.log(rows);
  }
);

命令行 CLI

# 通过 DuckDB CLI 直接查询
duckdb -c "SELECT region, count(*) FROM 'data/sales.ducklake' GROUP BY region"

# 导出为 CSV
duckdb -c "COPY (SELECT * FROM 'data/sales.ducklake') TO 'export.csv' (HEADER TRUE)"

Parquet / Delta Lake / Iceberg / DuckLake 对比

这是一个详细的维度对比表,帮助你在做技术选型时做出明智决策:

维度ParquetDelta LakeApache IcebergDuckLake v1.0
类型列式文件格式湖仓表格式湖仓表格式轻量湖仓格式
ACID 事务❌ 不支持✅ 乐观并发控制✅ 乐观并发控制✅ 文件级乐观锁
Schema 演化❌ 不支持✅ 支持✅ 支持✅ 支持
时间旅行❌ 不支持✅ 默认 30 天✅ 按快照✅ 按版本/时间戳
增量查询❌ 全部扫描✅ 按版本✅ 按快照✅ 按版本
分区裁剪✅ 利用统计信息✅ 分区修剪✅ 分区隐藏✅ 分区裁剪
文件压缩❌ 需外部工具✅ OPTIMIZE 命令✅ rewrite 操作ducklake_compact
元数据管理❌ 无Hive Metastore / AWS GlueHive / REST / Nessie无需 Metastore
运行引擎任意引擎Spark / Flink / Trino / DuckDBSpark / Flink / Trino / DuckDBDuckDB 原生
CPU 架构x86 / ARM / RISC-Vx86 / ARMx86 / ARMx86 / ARM / RISC-V
嵌入式场景⚠️ 可用但无事务❌ 太重❌ 太重天生支持
外部依赖Spark + Hive + HDFSSpark + Hive + HDFS零依赖
查询性能⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
写入性能⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
生态成熟度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐ (快速演进)
开源协议Apache 2.0Apache 2.0Apache 2.0MIT

选型建议

  • 你已经用了 Spark/Flink 生态 → 用 Delta Lake 或 Iceberg
  • 你只需要一个文件格式 → 用 Parquet
  • 你是中小团队,想要湖仓能力但不想要 SparkDuckLake 是最优选
  • 你需要嵌入式或移动端数据方案 → DuckLake (DuckDB 的嵌入式设计天生适配)
  • 你做初创项目,快速验证想法 → DuckLake,零运维成本

完整可执行 SQL 示例

以下是一个端到端的实战示例,模拟电商订单分析场景:

-- ========================================
-- DuckLake 实战:电商订单分析
-- ========================================

-- 1. 准备数据
CREATE OR REPLACE TABLE raw_orders AS
SELECT * FROM (VALUES
  (1001, 'Alice',    '电子', 2999.00, '2026-05-01'::DATE),
  (1002, 'Bob',      '服装',  459.00, '2026-05-01'::DATE),
  (1003, 'Charlie',  '食品',   89.90, '2026-05-02'::DATE),
  (1004, 'Alice',    '图书',   79.00, '2026-05-03'::DATE),
  (1005, 'David',    '电子', 1599.00, '2026-05-03'::DATE),
  (1006, 'Bob',      '食品',  120.50, '2026-05-04'::DATE),
  (1007, 'Eve',      '服装',  899.00, '2026-05-04'::DATE),
  (1008, 'Charlie',  '电子', 4599.00, '2026-05-05'::DATE),
  (1009, 'Alice',    '食品',  210.00, '2026-05-06'::DATE),
  (1010, 'David',    '图书',  150.00, '2026-05-06'::DATE)
) t(order_id, customer, category, amount, order_date);

-- 2. 写入 DuckLake(版本 1)
COPY raw_orders TO 'ecommerce.ducklake' (FORMAT DUCKLAKE);

-- 3. 查看版本历史
SELECT * FROM ducklake_versions('ecommerce.ducklake');

-- 4. 查询:类目销售额汇总
SELECT category,
       count(*) AS order_count,
       round(sum(amount), 2) AS total_sales,
       round(avg(amount), 2) AS avg_amount
FROM 'ecommerce.ducklake'
GROUP BY category
ORDER BY total_sales DESC;

-- 5. 追加新订单(版本 2)
INSERT INTO raw_orders VALUES
  (1011, 'Eve',    '电子', 3200.00, '2026-05-07'),
  (1012, 'Bob',    '图书',   55.00, '2026-05-07');

COPY (SELECT * FROM raw_orders WHERE order_id > 1010)
TO 'ecommerce.ducklake' (FORMAT DUCKLAKE, APPEND TRUE);

-- 6. 时间旅行:查看版本 1 的数据
SELECT sum(amount) AS version_1_total
FROM 'ecommerce.ducklake' (VERSION 1);

-- 7. 时间旅行:查看最新数据
SELECT sum(amount) AS latest_total
FROM 'ecommerce.ducklake' (VERSION 2);

-- 8. Schema 演化:添加新列
ALTER TABLE raw_orders ADD COLUMN shipping_address VARCHAR;
UPDATE raw_orders SET shipping_address = CASE
  WHEN customer = 'Alice'   THEN '北京市海淀区'
  WHEN customer = 'Bob'     THEN '上海市浦东新区'
  WHEN customer = 'Charlie' THEN '广州市天河区'
  WHEN customer = 'David'   THEN '深圳市南山区'
  WHEN customer = 'Eve'     THEN '杭州市西湖区'
END;

-- 9. 覆盖写入(包含新列,版本 3)
COPY raw_orders TO 'ecommerce.ducklake'
(FORMAT DUCKLAKE, OVERWRITE_SCHEMA TRUE);

-- 10. 验证 Schema 演化成功
DESCRIBE SELECT * FROM 'ecommerce.ducklake' (VERSION 3);

-- 11. 高级分析:窗口函数
SELECT customer, category, amount,
       sum(amount) OVER (PARTITION BY customer) AS customer_total,
       rank() OVER (PARTITION BY category ORDER BY amount DESC) AS category_rank
FROM 'ecommerce.ducklake' (VERSION 3)
ORDER BY category, category_rank;

-- 12. 压缩与清理
CALL ducklake_compact('ecommerce.ducklake');
CALL ducklake_vacuum('ecommerce.ducklake', KEEP_VERSIONS 3);

-- 13. 最终验证
PRINT 'DuckLake 实战验证完成!';
SELECT category,
       sum(amount) AS total,
       count(*) AS orders
FROM 'ecommerce.ducklake'
GROUP BY category;

执行结果参考

┌──────────┬──────────────┬──────────┐
│ category │ order_count  │ total    │
│ varchar  │    int64     │ decimal  │
├──────────┼──────────────┼──────────┤
│ 电子     │           3  │ 9798.00  │
│ 服装     │           2  │ 1358.00  │
│ 食品     │           3  │  420.40  │
│ 图书     │           3  │  284.00  │
└──────────┴──────────────┴──────────┘

变现建议

DuckLake 作为一款新兴的轻量湖仓格式,在多个方向上具备商业化变现潜力:

1. DuckLake 数据管道服务

面向对象:中小企业和独立开发者

  • 构建基于 DuckLake 的数据管道编排服务(类似轻量版 Airbyte)
  • 提供 SaaS 平台:用户配置数据源,自动写入 DuckLake 格式
  • 收费模式:按存储量 + API 调用次数
  • 预估月费:$29–$199/月,取决于数据量

2. DuckLake 数据可视化工具

面向对象:业务分析师、非技术用户

  • 构建 DuckLake 原生可视化 BI 工具(类似轻量版 Metabase)
  • 利用 DuckDB 的嵌入式特性,浏览器端 + DuckDB WASM 直接读取 DuckLake
  • 核心卖点:不需要后端服务,直接文件拖拽即可分析
  • 变现模式:开源社区版 + 企业版(权限管理、团队协作)

3. 专用 DuckLake 转换服务

面向对象:有存量数据的企业

  • 提供 JSON / CSV / 数据库 → DuckLake 格式的一键转换服务
  • 企业版支持增量同步和 CDC(Change Data Capture)
  • TAM(可寻址市场):所有使用 CSV 和 JSON 做数据分析的中小企业

4. DuckLake 数据市场

面向对象:数据提供方和消费者

  • 建立一个基于 DuckLake 格式的数据交易市场
  • 数据提供方上传 DuckLake 格式的数据集
  • 消费者按量或按订阅付费下载
  • 核心优势:DuckLake 格式本身支持时间旅行,可以提供历史版本回溯

5. 嵌入式 / IoT 方案

面向对象:边缘计算设备、IoT 网关

  • 在树莓派 / Jetson Nano 等设备上运行 DuckDB + DuckLake
  • 用于数据采集、本地聚合、增量上传
  • 对比传统方案:不需要部署 SQLite 再转 Parquet 的两步走流程
  • 可定价:按部署节点数收费($5/节点/月)

6. 培训与咨询

面向对象:DuckDB 和 Lakehouse 新手

  • 制作《DuckLake 从入门到精通》付费课程(Udemy / 独立平台)
  • 提供企业内训服务(DuckDB + DuckLake 最佳实践)
  • 技术咨询:传统数仓迁移到 DuckLake 方案
  • 定价参考:入门课程 $49.9,企业内训 $2000–$5000/天

总结

DuckLake v1.0 是 DuckDB 生态中一个极具战略意义的新成员。它打破了"湖仓一体 = 重型基础设施"的固有认知,证明了在单个嵌入式 OLAP 引擎上也能实现完整的湖仓能力。

它的核心价值定位非常清晰:

  1. 零依赖部署 —— 不需要 Spark、Hive Metastore、HDFS
  2. 开箱即用的 ACID —— 每个 DuckDB 实例都是一个完备的湖仓引擎
  3. 极低的 TCO —— 从硬件、运维到人力成本都大幅降低
  4. 无缝兼容 —— 底层 Parquet 确保数据不被锁定

对于数据从业者来说,DuckLake 最令人兴奋的一点是:它把湖仓能力从数据中心带到了笔记本、树莓派、甚至浏览器。当你能在笔记本电脑上运行一个完整的 ACID 湖仓时,数据工程的可能性边界在向外拓展。

DuckLake 不是来取代 Delta Lake 或 Iceberg 的——在大规模数据中心场景下,成熟的三巨头生态仍有不可替代的优势。但对于中小团队、初创公司、个人开发者,以及边缘计算场景而言,DuckLake 可能是迄今为止最优雅的选择。

欢迎在评论区分享你对 DuckLake 的看法和使用经验!