DuckDB + dbt Core 本地分析工程环境搭建指南:data-engineering-zoomcamp 之 DuckDB 本地开发全流程
【免费下载链接】data-engineering-zoomcampData Engineering Zoomcamp is a free 9-week course on building production-ready data pipelines. Join the course here 👇🏼项目地址: https://gitcode.com/GitHub_Trending/da/data-engineering-zoomcamp
本指南以>pip install dbt-duckdb
这一条命令会同时安装两个关键组件:
dbt-core:dbt 的核心框架,负责解析项目、编译 SQL、执行模型、运行测试与生成文档;dbt-duckdb:DuckDB 的 dbt 适配器(adapter),它把 dbt 的标准化命令翻译成 DuckDB 可执行的 SQL,并管理连接与事务。
dbt 采用“核心 + 适配器”的插件化架构,dbt-duckdb只是众多适配器之一;这也解释了为什么同一套 dbt 项目可以无缝切换到 BigQuery(dbt-bigquery)等适配器。
Step 3:配置 dbt Profile(~/.dbt/profiles.yml)
dbt 通过profile告诉 dbt 如何连接数据库。由于本仓库已经包含 dbt 项目(taxi_rides_ny/),你不需要运行dbt init,只需在用户主目录下创建或更新~/.dbt/profiles.yml,写入以下完整配置:
taxi_rides_ny: target: dev outputs: # DuckDB Development profile dev: type: duckdb path: taxi_rides_ny.duckdb schema: dev threads: 1 extensions: - parquet settings: memory_limit: '2GB' preserve_insertion_order: false # DuckDB Production profile prod: type: duckdb path: taxi_rides_ny.duckdb schema: prod threads: 1 extensions: - parquet settings: memory_limit: '2GB' preserve_insertion_order: false # Troubleshooting: # - If you have less than 4GB RAM, try setting memory_limit to '1GB' # - If you have 16GB+ RAM, you can increase to '4GB' for faster builds # - Expected build time: 5-10 minutes on most systems各配置项含义如下:
| 配置项 | 取值 | 说明 |
|---|---|---|
taxi_rides_ny(顶层键) | — | profile 名称,必须与项目 dbt_project.yml 中的profile: 'taxi_rides_ny'完全一致,否则 dbt 无法找到连接 |
target | dev | 默认启用的目标环境,通过dbt run --target prod可切换 |
type | duckdb | 数据库类型,对应dbt-duckdb适配器 |
path | taxi_rides_ny.duckdb | DuckDB 数据库文件路径;相对路径以运行 dbt 命令的目录(即taxi_rides_ny/)为基准 |
schema | dev/prod | 模型落库的 schema 名。注意仓库 sources.yml 中,DuckDB 环境下原始数据源指向prodschema,因此 Step 4 的导入脚本会预先创建prodschema |
threads | 1 | 并发线程数,本地环境建议保持 1,避免内存压力 |
extensions | - parquet | 需要自动加载的 DuckDB 扩展。Parquet 是本地导入数据的存储格式,必须加载 |
settings.memory_limit | '2GB' | DuckDB 内存上限。少于 4GB 内存建议改为'1GB';16GB+ 内存可提升到'4GB'以加速构建 |
settings.preserve_insertion_order | false | 关闭插入顺序保留,让 DuckDB 的查询优化器有更大自由度,换取更快的聚合/连接性能 |
[!TIP] 预期的模型构建耗时为 5~10 分钟(取决于机器配置)。如果构建缓慢或内存不足,优先调整
memory_limit,而不是盲目增加threads。
Step 4:下载并导入出租车数据到 DuckDB
配置好 profile 之后,需要把原始数据灌入 DuckDB。首先进入 dbt 项目目录(所有 dbt 与导入操作都以它为工作目录):
cd taxi_rides_ny然后运行下面的 Python 脚本(保存为.py文件后执行,或逐段在 Jupyter / REPL 中运行):
import duckdb import requests from pathlib import Path BASE_URL = "https://github.com/DataTalksClub/nyc-tlc-data/releases/download" def download_and_convert_files(taxi_type): data_dir = Path("data") / taxi_type data_dir.mkdir(exist_ok=True, parents=True) for year in [2019, 2020]: for month in range(1, 13): parquet_filename = f"{taxi_type}_tripdata_{year}-{month:02d}.parquet" parquet_filepath = data_dir / parquet_filename if parquet_filepath.exists(): print(f"Skipping {parquet_filename} (already exists)") continue # Download CSV.gz file csv_gz_filename = f"{taxi_type}_tripdata_{year}-{month:02d}.csv.gz" csv_gz_filepath = data_dir / csv_gz_filename response = requests.get(f"{BASE_URL}/{taxi_type}/{csv_gz_filename}", stream=True) response.raise_for_status() with open(csv_gz_filepath, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"Converting {csv_gz_filename} to Parquet...") con = duckdb.connect() con.execute(f""" COPY (SELECT * FROM read_csv_auto('{csv_gz_filepath}')) TO '{parquet_filepath}' (FORMAT PARQUET) """) con.close() # Remove the CSV.gz file to save space csv_gz_filepath.unlink() print(f"Completed {parquet_filename}") def update_gitignore(): gitignore_path = Path(".gitignore") # Read existing content or start with empty string content = gitignore_path.read_text() if gitignore_path.exists() else "" # Add data/ if not already present if 'data/' not in content: with open(gitignore_path, 'a') as f: f.write('\n# Data directory\ndata/\n' if content else '# Data directory\ndata/\n') if __name__ == "__main__": # Update .gitignore to exclude data directory update_gitignore() for taxi_type in ["yellow", "green"]: download_and_convert_files(taxi_type) con = duckdb.connect("taxi_rides_ny.duckdb") con.execute("CREATE SCHEMA IF NOT EXISTS prod") for taxi_type in ["yellow", "green"]: con.execute(f""" CREATE OR REPLACE TABLE prod.{taxi_type}_tripdata AS SELECT * FROM read_parquet('data/{taxi_type}/*.parquet', union_by_name=true) """) con.close()脚本的执行逻辑分三段,理解它有助于你后续调试:
- 断点续传下载 + 格式转换:对
yellow和green两类出租车、2019 与 2020 两个年份、每年 12 个月共 48 个文件,先判断本地 Parquet 是否已存在(存在则跳过),否则从BASE_URL下载.csv.gz压缩包,用read_csv_auto自动推断 schema,再通过 DuckDB 的COPY ... TO ... (FORMAT PARQUET)转换为列式 Parquet 并删除原压缩包。下载耗时取决于网络,通常需要几分钟。 - 保护仓库整洁:自动在
.gitignore中追加data/,避免几十 GB 的原始数据被误提交进版本库。 - 建库灌数:在
taxi_rides_ny.duckdb中创建prodschema(这正是 sources.yml 中 DuckDB 分支指向的prodschema),并用read_parquet(..., union_by_name=true)按列名自动对齐合并每个类别的全部月份,落成prod.yellow_tripdata与prod.green_tripdata两张原始表。
落库后的这两张表,就是 dbt 项目中source('raw', 'yellow_tripdata')/source('raw', 'green_tripdata')所引用的原始数据源(见 sources.yml),后续所有 staging → intermediate → marts 模型都从它们开始加工。
Step 5:验证 dbt 与 DuckDB 的连接
数据就绪后,在taxi_rides_ny/目录内运行:
dbt debugdbt debug会做三件事:校验~/.dbt/profiles.yml语法、确认 profile 与项目中的profile: taxi_rides_ny匹配、实际连接 DuckDB 并测试读取权限。看到类似All checks passed!的输出即代表连接成功,本地环境已经可以跑dbt run/dbt test/dbt docs generate了。
[!NOTE] 这一步通过之后,本地 dbt 环境即完全就绪。模型运行、测试与文档构建的详细操作,在课程视频中会有系统讲解。
Step 6:为 VS Code 安装 dbt Power User 扩展
如果你使用 Visual Studio Code,官方文档建议安装社区维护的dbt Power User(by AltimateAI)扩展来提升开发体验。
它提供的核心能力:
- dbt 模型的 SQL 语法高亮与格式化;
- 列级(column-level)血缘关系内联可视化;
- 对 dbt 模型、source、macro 的自动补全;
- 交互式文档预览;
- 直接在编辑器内编译并执行单个模型。
为什么不使用 dbt 官方扩展:dbt Labs 推出的官方 VS Code 扩展基于新的 dbt Fusion 引擎,要求使用 dbt Fusion,不支持 dbt Core。而本教程使用的是dbt Core + DuckDB的本地组合,因此需要社区维护的 dbt Power User(by AltimateAI)——它完全兼容 dbt Core(不仅限于 dbt Cloud)、支持包括 DuckDB 在内的所有 dbt 适配器、开源且持续维护。
安装步骤:
- 打开 VS Code;
- 进入扩展面板(
Ctrl+Shift+X/Cmd+Shift+X); - 搜索 "dbt Power User";
- 安装dbt Power User by AltimateAI(注意不要装成 dbt Labs 的版本)。
仓库配套:taxi_rides_ny项目结构与本地运行前提
为了让本地环境真正跑起来,理解仓库自带的 dbt 项目结构至关重要。该项目的完整代码位于 04-analytics-engineering/taxi_rides_ny/,关键文件与本地环境的对应关系如下:
项目配置:dbt_project.yml
name: 'taxi_rides_ny'、version: '1.0.0':项目标识;require-dbt-version: [">=1.7.0", "<3.0.0"]:锁定 dbt Core 版本区间,保证可复现性,本地安装的 dbt 需落在此范围内;profile: 'taxi_rides_ny':与 Step 3 中profiles.yml的顶层键一一对应;- 路径配置:
model-paths: ["models"]、seed-paths: ["seeds"]、macro-paths: ["macros"]等,dbt 按这些约定自动发现文件; vars:定义dev_start_date: '2019-01-01'、dev_end_date: '2019-02-01',用于开发环境的确定性采样窗口;- 模型物化策略:
staging默认为view,intermediate与marts默认为table——这意味着本地跑dbt run时,staging 层不会额外占用磁盘,而中间层与结果层会真实落表。
依赖包:packages.yml
项目依赖dbt-labs/dbt_utils(通用工具宏)与dbt-labs/codegen(代码生成)。首次运行前需执行dbt deps拉取依赖,产物会写入dbt_packages/目录(已被clean-targets声明,可用dbt clean清理)。
分层模型与本地数据流
从源码可以清晰看到 dbt 的标准三层加工链路,这也是本地运行时数据的实际流转路径:
- staging 层(models/staging/):
stg_yellow_tripdata.sql/stg_green_tripdata.sql从 source 读取原始表,统一列名(如tpep_pickup_datetime→pickup_datetime)、做显式cast类型转换,并过滤掉vendorid is null的脏数据;同时 stg_yellow_tripdata.sql 中带有{% if target.name == 'dev' %}的确定性采样逻辑——在dev目标下只保留 2019-01-01 至 2019-02-01 一个月的样本,让本地开发秒级完成,prod目标则处理全量数据; - intermediate 层(models/intermediate/):
int_trips_unioned.sql通过union all合并黄绿两类数据,用'Green'/'Yellow'标记service_type,并补齐两表 schema 差异(如黄车没有ehail_fee则补0,黄车固定trip_type = 1);int_trips.sql进一步关联支付方式等维度; - marts 层(models/marts/):
fct_trips.sql采用materialized='incremental'+unique_key='trip_id'+merge策略构建星型模型的事实表,用left join dim_zones补上上下车区域名,并通过pickup_datetime > (select max(pickup_datetime) from {{ this }})实现增量更新;dim_vendors.sql/dim_zones.sql为维度表,reporting/ 下还有月度区域营收报表。
宏与种子数据
- macros/ 中的
get_trip_duration_minutes.sql封装了跨数据库的dbt.datediff(在 DuckDB、BigQuery 等适配器上行为一致);get_vendor_data.sql用 Jinja 字典在编译期生成 CASE 语句映射 vendor 名称;safe_cast.sql则根据target.type在 BigQuery 的safe_cast与通用cast之间自动切换——这些宏正是本地与云端"一套代码"的关键机制; - seeds/ 提供
taxi_zone_lookup.csv(区域维度)与payment_type_lookup.csv(支付方式字典),可通过dbt seed载入,其约束(如payment_type的unique、not_null测试)定义在 seeds_properties.yml 中。
本地环境验证清单与常见问题
配置完成后,可按下表逐项自检:
| 检查项 | 命令/操作 | 预期结果 |
|---|---|---|
| dbt 是否安装 | dbt --version | 显示 dbt-core 与 dbt-duckdb 版本,且 core 版本满足>=1.7.0, <3.0.0 |
| profile 是否匹配 | dbt debug | All checks passed!,连接 DuckDB 成功 |
| 依赖是否就绪 | dbt deps(在taxi_rides_ny/内) | 成功拉取dbt_utils与codegen |
| 原始数据是否存在 | 打开taxi_rides_ny.duckdb查看 | prod.yellow_tripdata、prod.green_tripdata有数据 |
| 模型能否运行 | dbt run --target dev | staging/intermediate/marts 依次构建成功,dev 目标仅处理 2019-01 采样数据 |
常见问题处理:
memory_limit过高导致构建卡死或 OOM:按 Step 3 的注释,内存不足 4GB 时改为'1GB';- 找不到 profile:确认
profiles.yml位于用户主目录下的~/.dbt/,且顶层键与dbt_project.yml的profile字段一致; dbt run报找不到表:确认 Step 4 脚本已成功创建prodschema 与两张原始表(sources.yml 中 DuckDB 分支的 schema 就是prod);- build 时间远超预期:检查
threads是否为 1、preserve_insertion_order是否为false,并确认是否误在prod目标下跑了全量数据(全量构建 5~10 分钟属正常范围)。
总结
至此,你已经完成了 contenteditable="false">【免费下载链接】data-engineering-zoomcampData Engineering Zoomcamp is a free 9-week course on building production-ready data pipelines. Join the course here 👇🏼项目地址: https://gitcode.com/GitHub_Trending/da/data-engineering-zoomcamp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考