DeepSeek总结的DuckLake 入门
来源:https://motherduck.com/
第 2 章 DuckLake 入门
如第 1 章所述,启动并运行 DuckLake 既快速又简单。然而,除了初始设置之外,还有几个架构决策需要考虑:使用哪个后端数据库来托管目录,数据将存储在哪里,以及哪个计算引擎将运行您的工作负载。主要优势在于,这些决策不必在第一天就最终确定。您可以从一个使用 DuckDB 管理目录和本地存储的本地 DuckLake 实例开始,然后随着时间的推移逐步演进,例如将目录迁移到 PostgreSQL,并将数据存储在 S3 等云对象存储中。
本章将探讨主要的 DuckLake 部署模式,并概述每种模式的权衡。它还将提供实际示例,演示如何随着需求的变化来实现这些配置。此外,我们将介绍 DuckLake 的云和 SaaS 选项,以及如何加载数据和执行基本的 CRUD 操作。到本章结束时,您将能够轻松地设置本地 DuckLake 环境和基于云的部署。
对于本书后续的所有示例,以及那些在我们 GitHub 仓库中跟随操作的示例,在 DuckDB 中执行 SQL 脚本就像下面这个命令一样简单:
duckdb-c".read the_file.sql"对于涉及更复杂逻辑(如循环或特殊环境变量)的脚本,我们将使用 Python 3.11 和 UV 作为默认执行模式。对于这些脚本,您可以像下面这样简单地运行它们:
uv run the_script.py在选择部署方法之前,了解可用 DuckLake 目录选项之间的权衡是很有帮助的。表 2-1 总结了最常见的设置,并强调了每种选项在何时可能适用。
表 2-1. 部署选项比较
| 目录类型 | 优点 | 缺点 | 附加说明 |
|---|---|---|---|
| 用户托管 DuckDB | • 极其容易设置 • 无需外部数据库 • 非常适合实验 | • 并发能力有限 • 不理想的多用户环境 | 非常适合开始使用 DuckLake、进行本地开发和小型单用户工作负载 |
| 用户托管 Postgres | • 支持多个并发用户 • 来自云提供商的成熟可靠性和高可用选项 | • 需要管理数据库服务 • 额外的运维开销 | 对于团队需要共享 DuckLake 目录的生产部署的常见选择 |
| MotherDuck | • 最小的运维开销 • 按需无服务器计算 • 高性能缓存 • 可以自带 S3 存储桶进行存储 | • 商业服务 • 对底层基础设施的控制较少 | 适合偏好托管平台的易用性和性能的团队 |
本地 DuckLake 部署
让我们继续为本教程安装 DuckDB CLI 并启动它。根据您的工作站类型,以下是一些不错的安装选项:
•Mac: 使用 Homebrew -brew install duckdb
•Linux: 使用 curl -curl https://install.duckdb.org | sh
•Windows: 从 DuckDB 网站下载或使用 Winget 包管理器
安装好 DuckDB 后,启动 CLI 并安装 DuckLake:
duckdb install ducklake;对于那些更喜欢 DuckDB 图形界面的人来说,您只需在 duckdb 命令中添加--ui标志,它就会弹出一个 Web 用户界面供您编写查询。以下是启动 DuckDB GUI 的示例:
duckdb--ui现在我们已运行 DuckDB 并安装了 DuckLake 扩展,我们可以按如下方式创建一个简单的本地 DuckLake:
ATTACHORREPLACE'ducklake:local_inst.ducklake'asmy_ducklake(DATA_PATH concat(getenv('HOME'),'/dw1'));让我们分解一下ducklake:local_inst.ducklake as my_ducklake的实际含义:
•ducklake:告诉 DuckDB 您想要创建一个 DuckLake 目录
•local_inst.ducklake告诉 DuckDB DuckLake 目录数据库的名称。您可以将其命名为其他名称,例如my_local_instance.db;为了清楚说明它作为数据库的类型,我们认为ducklake后缀在这里最有意义。
•my_ducklake实际上是您附加到 DuckDB 会话时 DuckLake 目录的别名;当您想要在 DuckLake 目录中创建或更新对象(如表)时,它很有帮助。
•DATA_PATH告诉 DuckLake 目录所有表的数据将存储在哪里。
这里您会注意到,当我们指定数据路径时,我们使用了 DuckDB CLI 函数getenv,该函数可以获取环境变量,例如用户的 home 路径。
您会注意到,当您运行上述命令时,您会在启动 DuckDB CLI 的文件夹中看到local_inst.ducklake文件。这就是您的 DuckLake 目录。
从这里开始,我们可以执行一些简单的操作,例如使用以下命令创建一个表:
CREATETABLEmy_ducklake.ordersASSELECT1asorder_id,DATE'2024-01-01'ASorder_date,100.0ASamountUNIONALLSELECT2,DATE'2024-03-04',150;现在我们已经设置了一个本地 DuckLake 目录,让我们介绍一些更实用的操作,例如导入数据和执行基本的创建、读取、更新、删除(CRUD)操作。
导入数据
将数据导入 DuckLake 目录非常简单;鉴于 DuckLake 是 DuckDB 的扩展,您可以访问所有 DuckDB 函数,例如读取 CSV 和 Parquet 文件。在下一个示例中,我们将使用以下 Python 脚本构建一个简单的 CSV 文件:
withopen("customer_orders.csv","w")asf:f.write("""order_id,order_date,customer_id 1001,2026-03-01,C001 1002,2026-03-02,C002 1003,2026-03-03,C003 """)或者,如果您希望完全使用 DuckDB CLI,我们可以按如下方式构建相同的 CSV 文件:
COPY(FROM(VALUES(1001,'2026-03-01','C001'),(1002,'2026-03-02','C002'),(1003,'2026-03-03','C003'))AScustomer_orders(order_id,order_date,customer_id))TO'customer_orders.csv';现在我们有了文件,可以使用以下命令将 CSV 导入到我们的本地 DuckLake 目录:
CREATEORREPLACETABLEmy_ducklake.cust_ordersASSELECT*FROM'customer_orders.csv';-- 查看数据SELECT*FROMmy_ducklake.cust_orders;如果您不熟悉所有 DuckDB 的数据导入函数,我们鼓励您查阅此处的文档。DuckDB 支持读取 CSV、Parquet、JSON 以及许多其他数据源和文件格式。导入命令提供了广泛灵活性,即使是一些更生僻的文件格式也能轻松摄取。
CRUD 操作
DuckLake 支持所有 DuckDB CRUD 操作:
• CTAS
• INSERT
• UPDATE
• DELETE
• MERGE
让我们来看每种语句类型的示例。
CTAS 语句
CTAS 代表 “CREATE TABLE AS SELECT …”。该语句将表的创建和数据的插入合并到单个操作中。CTAS 不是发出一个 CREATE TABLE 语句后跟一个 INSERT INTO 语句,而是允许在一个简洁的命令中执行这两个步骤,这简化了许多数据工程工作流。
下面是一个 CTAS 语句的示例。它创建了一个名为cust_orders的表,如果表已存在则执行替换:
CREATEORREPLACETABLEmy_ducklake.cust_ordersASSELECT1001ASorder_id,DATE'2024-01-01'ASorder_date,'C001'AScustomer_idUNIONALLSELECT1002,DATE'2024-01-02','C002';注意使用CREATE OR REPLACE TABLE AS。这通过自动替换表的任何现有版本,进一步扩展了 CTAS 模式。在实践中,这意味着您可以通过一个语句从查询中重建表,无需先显式删除先前的表。
INSERT 语句
下面是一个 INSERT INTO 语句的示例,它将一条新记录追加到cust_orders表:
INSERTINTOmy_ducklake.cust_ordersVALUES(1004,'2024-01-04','C004');UPDATE 语句
以下示例对cust_orders表中的一条现有记录执行了 UPDATE 操作:
UPDATEmy_ducklake.cust_ordersSETorder_date='2024-01-05'WHEREorder_id=1004;DELETE 语句
您也可以在 DuckLake 中执行 DELETE 语句。下面的示例从cust_orders表中删除了一条记录:
DELETEFROMmy_ducklake.cust_ordersWHEREorder_id=1003;MERGE 语句
为了演示 MERGE 语句,我们将首先创建表的一个修改版本,称为tmp_orders,其中order_date向前移动了一天。这将模拟我们将用于 MERGE 到目标表的暂存数据:
CREATEORREPLACETABLEmy_ducklake.tmp_ordersASSELECTorder_id,date_add(order_date,INTERVAL'1 day')ASorder_date,customer_idFROMmy_ducklake.cust_orders;创建好这个暂存表后,执行 MERGE 就很简单了:
MERGEINTOmy_ducklake.cust_ordersAStargetUSINGmy_ducklake.tmp_ordersASsourceONtarget.order_id=source.order_idWHENMATCHEDTHENUPDATEWHENNOTMATCHEDTHENINSERTBYNAME;关于 DuckDB 对 MERGE 的实现,有几点值得注意。该语句包含了几项便利性——我们之前称之为语法糖。
例如,在WHEN MATCHED子句中,您可以显式列出要更新的列。但是,如果您省略它们,DuckDB 将自动更新源表和目标表之间名称匹配的列。
类似地,在WHEN NOT MATCHED子句中,INSERT BY NAME关键字告诉 DuckDB 通过对齐两个表中相同名称的列来插入值。如果需要,您仍然可以显式列出要插入的列,但BY NAME提供了一个更简单且通常更具可读性的选项。
这些便利性使得常见的 MERGE 操作变得简洁,同时在需要更显式行为时仍然允许完全控制。
本地 Postgres 目录
现在我们已经介绍了在 DuckDB 数据库中创建本地 DuckLake 目录,让我们继续使用本地 PostgreSQL 实例来执行此操作。您可以从官方网站下载 Postgres。如果您使用的是 Mac,我们建议您使用 brew services 按如下方式安装 Postgres:
brewinstallpostgresql安装完成后,我们将在其默认的 postgres 数据库下启动 Postgres,并按如下方式创建/建一个名为ducklake_v1的新数据库。您会注意到我们使用 brew 来启动 Postgres 实例。
brew services start postgresql@16 psql-dpostgres-c"DROP DATABASE IF EXISTS ducklake_v1;"psql-dpostgres-c"CREATE DATABASE ducklake_v1;"请注意,在上述语句中,我们删除并重新创建数据库(如果不存在),以保持本教程的可重复性。对于生产工作负载,您必须非常小心,不要随意运行 drop database 命令。
此时,我们已经启动并运行了一个本地 Postgres 实例,其中包含我们全新的ducklake_v1数据库,并且我们准备使用以下命令将实际的 DuckLake 目录附加到 Postgres,并使用本地数据存储:
ATTACH'ducklake:postgres:dbname=ducklake_v1 host=localhost'ASwh(DATA_PATH concat(getenv('HOME'),'/dwpg1'));从这里开始,您现在可以执行上一节中介绍的所有表 CRUD 操作。
云对象存储的数据路径
到目前为止,示例中使用的都是本地存储来存放 DuckLake 数据文件。然而,DuckLake 同样可以轻松地将其数据存储在云对象存储(如 Amazon S3)中,只需在附加目录时在DATA_PATH参数中指定所需位置即可。
例如:
CREATEORREPLACESECRET aws_creds(TYPES3,provider credential_chain);ATTACHORREPLACE'ducklake:postgres:dbname=ducklake_v1 host=localhost'ASwh(DATA_PATH concat('s3://',getenv('AWS_BUCKET'),'/ducklake_pg'),OVERRIDE_DATA_PATHTrue);在此示例中,DATA_PATH参数指示 DuckLake 将表数据存储在 S3 存储桶中,而不是本地文件系统。
重要提示:数据路径覆盖
当首次附加 DuckLake 目录并指定了DATA_PATH时,该位置成为与该目录关联的默认数据路径。后续的ATTACH操作将继续使用此默认位置,除非您明确提供不同的路径。
OVERRIDE_DATA_PATH参数允许您为当前连接临时覆盖此默认路径。但重要的是要理解,此覆盖是会话范围的,并不会永久更改目录已配置的数据路径。
此外,覆盖数据路径不会迁移任何已写入原始位置的数据。表将继续引用在先前路径下创建的数据文件。因此,我们强烈建议不要为单个表混合使用多个数据路径。这会创建一个难以管理和排查的复杂场景。
如果您需要将现有的 DuckLake 部署迁移到新的存储位置,则必须执行显式迁移。迁移策略将在后面的章节中讨论。
MotherDuck 托管的 DuckLake
另一种部署选项是使用 MotherDuck 的托管 DuckLake 服务。MotherDuck 是一个无服务器云数据仓库,由 DuckDB 计算引擎提供支持。
由于 MotherDuck 是无服务器的,因此没有需要您管理的基础设施。这意味着您可以连接到一个计算实例,其 RAM 范围从几 GB 到超过 1TB,并且该实例将在短短 200 毫秒内创建完成。每个连接都可以获得自己独立的 compute Duckling,因此,就像 DuckLake 使计算民主化一样,不存在可能过载的中心化计算集群。
您也不必放弃本地开发的体验。当您通过 DuckDB 客户端连接到 MotherDuck 时,您可以使用该本地计算环境通过 Dual Execution 运行查询。或者,您可以完全使用 DuckDB 进行本地开发,然后在生产环境中重新运行最终确定的查询,而无需更改代码——SQL 保持不变。
连接到 MotherDuck
有几种方法可以连接到 MotherDuck。最简单的方法是访问 https://app.motherduck.com 并登录 Web UI。它看起来就像 DuckDB UI。如果您以前没有使用过 MotherDuck,您将被自动重定向到入门流程。您可以开始免费试用或使用免费套餐。完成后,您将直接登录到您的帐户,并准备好开始查询。
另一种选择是使用 DuckDB 客户端,并指向特殊的数据库名称md:。这适用于任何客户端,但例如,要使用 CLI 内置的本地 DuckDB UI 进行连接,请运行:
duckdb md:-ui就是这样。系统会提示您登录或注册,然后就可以开始了。
要通过编程方式连接到 MotherDuck 而无需基于浏览器的身份验证流程,请使用访问令牌。通过 UI 登录 MotherDuck 后,单击左上角的“设置”按钮,然后在左侧窗格中选择“访问令牌”。或者,您可以在基于浏览器的 UI 版本中直接访问 http://app.motherduck.com/settings/tokens。
获得该令牌后,将其设置为环境变量motherduck_token。CLI 将自动检测该令牌的存在,对于其他库,可以在连接字符串中传递它。例如,要使用 Python 连接,请使用 DuckDB 库但编辑连接字符串:
# /// script# dependencies = ["duckdb",]# ///importduckdbfromosimportgetenv token=getenv('motherduck_token')con=duckdb.connect(f'md:?motherduck_token={token}')连接到 MotherDuck Postgres 端点
连接到 MotherDuck 的另一个选项是使用 Postgres 端点。MotherDuck 可以支持 Postgres 有线协议,因此任何 Postgres 驱动程序都可以像与 Postgres 通信一样与 MotherDuck 通信。在任何您使用 Postgres 的现有工作流中,您都可以使用 MotherDuck DuckLake。
使用以下命令通过psql连接,将<region>替换为您的 MotherDuck AWS 区域(例如us-east-1):
PGPASSWORD=$MOTHERDUCK_TOKENpsql\-hpg.<region>-aws.motherduck.com\-p5432\-Upostgres\"dbname=sample_data sslmode=verify-full sslrootcert=system"当使用另一个 Postgres 驱动程序时,可以使用 libpq URI 作为连接字符串。此示例使用 Python 的 psycopg Postgres 驱动程序进行连接。
# /// script# dependencies = ["psycopg",]# ///importosimportpsycopg token=os.environ["motherduck_token"]region="us-east-1-aws.motherduck.com"conn_str=f"postgresql://postgres:{token}@pg.{region}:5432/md:?sslmode=verify-full&sslrootcert=system"withpsycopg.connect(conn_str)asconn:withconn.cursor()ascur:cur.execute(""" SELECT title, score FROM sample_data.hn.hacker_news WHERE type = 'story' ORDER BY score DESC LIMIT 5 """)print(cur.fetchall())在 MotherDuck 上的 DuckLake
MotherDuck 提供了几种部署 DuckLake 的选项。操作最简单的是完全托管的 DuckLake。MotherDuck 将提供目录、对象存储和计算资源。完全托管的 DuckLake 具有一些独特的好处,例如自动文件维护和用于提高读取性能的额外缓存。
创建一个完全托管的 DuckLake 就像连接到 MotherDuck 并运行这段 SQL 片段一样简单:
CREATEDATABASEmy_ducklake(TYPEDUCKLAKE);请注意,MotherDuck 上的完全托管 DuckLake 使用的是CREATE DATABASE语法,而不是ATTACH。
如果您更愿意将数据存储在自己的账户中,可以创建一个自带存储桶的 DuckLake。只需提供对象存储桶的凭据,然后在创建 DuckLake 时指定该存储桶的名称即可。
CREATEORREPLACESECRET my_secretINMOTHERDUCK(types3,region getenv('AWS_REGION'),key_id getenv('AWS_ACCESS_KEY_ID'),secret getenv('AWS_SECRET_ACCESS_KEY'),scope getenv('AWS_BUCKET'));CREATEDATABASEmy_ducklake(TYPEDUCKLAKE,DATA_PATH's3://bucket-name/');第三个选项是创建一个自带计算的 DuckLake,其中 MotherDuck 将管理目录,而您提供存储桶和计算环境。要使用此选项,请存储对象存储凭据,在 MotherDuck 上创建 DuckLake,然后将 MotherDuck 元数据目录附加到您想要运行计算的位置。
首先,在直接连接到 MotherDuck 时运行以下查询。
CREATEORREPLACESECRET my_secretINMOTHERDUCK(types3,region getenv('AWS_REGION'),key_id getenv('AWS_ACCESS_KEY_ID'),secret getenv('AWS_SECRET_ACCESS_KEY'),scope getenv('AWS_BUCKET'));CREATEDATABASEmy_ducklake(TYPEDUCKLAKE,DATA_PATH's3://bucket-name/');然后,在您想要运行计算的位置,运行以下命令:
ATTACH'ducklake:md:__ducklake_metadata_<database_name>'AS<database_alias>;当您自带计算时,再次使用了ATTACH语法。
总之,在 MotherDuck 上操作 DuckLake 的选项有:
| 托管选项 | 目录 | 存储 | 计算 | 使用场景 |
|---|---|---|---|---|
| 完全托管 DuckLake | MotherDuck | MotherDuck | MotherDuck | 您想要一个简单的无服务器选项 |
| 自带存储桶 DuckLake | MotherDuck | 自托管 | MotherDuck | 您希望拥有自己的数据 |
| 自带计算 DuckLake | MotherDuck | 自托管 | 自托管 | 您想要使用多个计算引擎 |
