Data Engineering Zoomcamp 本地环境搭建实战:用 DuckDB + dbt Core 构建 Analytics Engineering 开发环境
【免费下载链接】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
本指南以 Data Engineering Zoomcamp 2027 届 Module 4(Analytics Engineering)的本地环境配置文档为核心,讲解如何在个人电脑上以DuckDB 作为本地分析数据库、dbt Core 作为转换框架,完整跑通一个真实 dbt 项目(taxi_rides_ny)的从零安装、数据装载、连接验证到编辑器提效的整条链路。读完本文,你将掌握~/.dbt/profiles.yml的完整配置语义、2019–2020 纽约出租车数据的本地装载方案、dbt debug的验证方法,以及 VS Code 下 dbt 开发扩展的正确选型,并能在小内存机器上通过内存调优与增量构建稳定运行整个项目。
背景:为什么选择 DuckDB + dbt Core 作为本地方案
Module 4 的完整 dbt 项目位于 cohorts/2027/04-analytics-engineering/taxi_rides_ny,其中包含 staging → intermediate → marts 三层模型(stg_yellow_tripdata、stg_green_tripdata、int_trips_unioned、int_trips、fct_trips以及若干维度表)。要本地运行这套模型,需要一个无需云账号、可在内存中高速执行 SQL 的数据库——DuckDB 正合适:它是进程内(in-process)嵌入式分析数据库,不需要单独的服务进程,也天然适配 dbt 这种"模型即 SQL"的开发方式。
本仓库为 Module 4 提供了两条配置路径:
- 本地路径(本文主题):DuckDB + dbt Core,参见 setup/local_setup.md;
- 云端路径:BigQuery + dbt Cloud,参见 setup/cloud_setup.md。
本地路径适合零成本、离线练习与快速迭代;若机器内存不足(4 GB 以下),官方更推荐改走云端路径或 GitHub Codespaces(详见下文"内存与性能调优"一节)。
整个配置流程共 6 步:安装 DuckDB → 安装 dbt → 配置 dbt Profile → 下载并装载数据 → 验证连接 → 安装 VS Code 扩展。所有 dbt 命令都必须在taxi_rides_ny/目录内执行,因为 dbt_project.yml 中通过profile: 'taxi_rides_ny'指明了项目对应的 dbt Profile 名称。
第 1 步:安装 DuckDB
DuckDB 是本地分析负载下的理想选择:它运行在你的进程内,无需网络、无需账号,天然适配教学与实验场景。安装方式有两种:
- 安装 CLI:按操作系统从 DuckDB 官方安装说明下载对应版本的可执行文件;
- 安装 Python 客户端:
pip install duckdb。
官方文档的偏好是安装 CLI,但两种方式皆可。需要注意:本教程后续的数据装载脚本会以 Python 方式import duckdb调用其 API,而 dbt 侧则是通过dbt-duckdb适配器驱动 DuckDB,因此两种方式在链路中都会被用到(至少 Python 包必须安装)。
第 2 步:安装 dbt 与 DuckDB 适配器
一条命令安装 dbt 全家桶:
pip install dbt-duckdb该命令会同时安装两个组件:
- dbt-core:dbt 核心框架,负责解析项目、编译 SQL、管理模型依赖与执行计划;
- dbt-duckdb:dbt 的 DuckDB 适配器,让 dbt 能够连接并操作 DuckDB 数据库。
提示:如果你打算日后切换云数仓,dbt 的适配器机制允许你通过安装不同适配器(如
dbt-bigquery)无缝切换目标数据库,而模型 SQL 大多无需改动——这正是 taxi_rides_ny 项目中sources.yml用 Jinja 条件分支同时支持两种后端的原因(见下文第 3 步的源码印证)。
第 3 步:配置 dbt Profile(连接 DuckDB)
3.1 不需要dbt init
本仓库已内置完整的 dbt 项目taxi_rides_ny/,因此不要运行dbt init新建项目,只需为已存在的项目配置连接。dbt 通过~/.dbt/profiles.yml找到数据库连接信息,Profile 名称必须与项目中的profile: 'taxi_rides_ny'一致。
3.2 完整 Profile 配置
创建或更新~/.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 systems3.3 每个参数的语义与调优建议
| 参数 | 含义 | 说明 |
|---|---|---|
target | 默认激活的输出目标 | 设为dev,日常开发默认走 dev;构建生产数据时用--target prod显式切换 |
type | 适配器类型 | duckdb对应dbt-duckdb适配器 |
path | DuckDB 数据库文件路径 | 相对路径基于执行 dbt 命令的目录(即taxi_rides_ny/),会在该目录生成taxi_rides_ny.duckdb文件 |
schema | 模型落库的 Schema | dev 与 prod 使用不同 schema(dev/prod),实现开发与生产数据隔离 |
threads | 并行构建模型的线程数 | 设为 1 可显著降低并发构建带来的内存峰值压力,小内存机器的推荐值 |
extensions | 需要自动加载的 DuckDB 扩展 | 本项目的 Parquet 数据装载依赖parquet扩展,必须保留 |
memory_limit | DuckDB 内存上限 | 默认 DuckDB 会用掉约 80% 系统内存,容易触发 OOM;建议设为总内存的约 50%(如 8 GB 机器设'4GB'),详见内存调优文档 |
preserve_insertion_order | 是否保持插入顺序 | 设为false可让 DuckDB 减少排序开销、节省内存,换取构建速度 |
参数取值建议(来自duckdb_troubleshooting.md):内存低于 4 GB 时把
memory_limit降到'1GB';16 GB 以上可上调至'4GB'加速构建。多数机器上整个项目首次构建约需 5–10 分钟。
3.4 源码印证:Profile 名称如何与项目绑定
打开项目的 dbt_project.yml 可以看到:
name: 'taxi_rides_ny' version: '1.0.0' require-dbt-version: [">=1.7.0", "<3.0.0"] profile: 'taxi_rides_ny' model-paths: ["models"] test-paths: ["tests"] seed-paths: ["seeds"] macro-paths: ["macros"] snapshot-paths: ["snapshots"]几点关键信息:
profile: 'taxi_rides_ny'与~/.dbt/profiles.yml中的顶层键一一对应,这是 dbt 解析连接的入口;require-dbt-version: [">=1.7.0", "<3.0.0"]限定了 dbt 版本区间,保证可复现性,请确保你的dbt-core在此范围内;- 各
*-paths定义了 dbt 在不同目录查找模型、测试、种子、宏与快照,配置必须与仓库目录结构一致。
另外,sources.yml通过 Jinja 条件分支同时兼容 DuckDB 与 BigQuery 两种后端,佐证了本配置跨数仓的可移植设计(models/staging/sources.yml):
sources: - name: raw database: | {%- if target.type == 'bigquery' -%} {{ env_var('GCP_PROJECT_ID', 'please-add-your-gcp-project-id-here') }} {%- else -%} taxi_rides_ny {%- endif -%} schema: | {%- if target.type == 'bigquery' -%} nytaxi {%- else -%} prod {%- endif -%}即:DuckDB 路径下,原始数据源指向taxi_rides_ny数据库的prodschema;BigQuery 路径下则指向GCP_PROJECT_ID项目的nytaxi数据集。
第 4 步:下载并装载 NYC 出租车数据
4.1 装载脚本全貌
Profile 配置完成后,需要把 2019–2020 年黄色(yellow)与绿色(green)出租车数据装载进 DuckDB。官方提供的脚本如下(保存为load_data.py或在taxi_rides_ny/目录下直接执行):
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()4.2 脚本做了什么
- 流式下载:从 DataTalksClub 的 NYC TLC 数据仓库(
BASE_URL)按{类型}_tripdata_{年}-{月}.csv.gz命名规则,下载 2019–2020 共 24 个月 × 2 种车型的压缩 CSV;stream=True+ 8 KB 分块写入避免一次性载入内存; - 断点续传:若同名 Parquet 已存在则跳过(
Skipping ...),支持中断后重跑; - 格式转换:用 DuckDB 的
COPY ... TO ... (FORMAT PARQUET)把 CSV.gz 转成列式 Parquet,转换后立即删除 CSV.gz 释放磁盘; - 装载入库:打开
taxi_rides_ny.duckdb,创建prodschema,并用read_parquet('data/{taxi_type}/*.parquet', union_by_name=true)将每个目录下所有 Parquet 文件合并装载为prod.yellow_tripdata与prod.green_tripdata两张表——union_by_name=true保证跨文件字段按名称对齐合并; - 维护 .gitignore:自动把
data/目录写入.gitignore,避免数 GB 的原始数据被误提交进 Git。
注意:数据装载的目标 schema 是
prod,而~/.dbt/profiles.yml中 dev 与 prod 两个 target 的 schema 分别为dev/prod。也就是说,原始数据常驻prodschema,dev 目标下的模型则在其devschema 中构建——这与 sources.yml 中 DuckDB 分支schema: prod的指向完全吻合。因此,用 dev 目标跑模型时,dbt 同样能读到prodschema 中的原始表。
4.3 数据规模与下载耗时
该脚本下载的 yellow/green 出租车数据横跨 24 个月,包含数千万行记录(DuckDB 需在本地内存中完成装载与后续建模),下载耗时取决于网络状况,通常需要数分钟。如果你的网络较慢或磁盘紧张,可以先只跑部分月份验证链路,再全量下载。
第 5 步:验证 dbt 与 DuckDB 的连接
在taxi_rides_ny/目录下执行:
dbt debugdbt debug会依次校验:
- dbt 版本与
require-dbt-version约束是否满足; - 能否定位到
~/.dbt/profiles.yml中的taxi_rides_nyProfile; - 能否用
devtarget 建立到 DuckDB 的实际连接(若连接成功会输出 "Connection test: OK" 之类的成功信息)。
这是配置正确性的第一道关卡:任何 Profile 路径、schema 名称或扩展配置错误都会在此暴露。连接通过后,即可按后续课程视频运行dbt build、dbt test与dbt docs generate等命令。
第 6 步:为 VS Code 安装 dbt Power User 扩展(可选但推荐)
6.1 为什么不用官方 dbt 扩展
dbt Labs 官方发布的dbt Extension(基于新的 dbt Fusion 引擎)要求 dbt Fusion,不支持 dbt Core。而本项目使用的是 dbt Core + DuckDB 的本地开发方式,因此需要选择社区维护的dbt Power User by AltimateAI扩展,理由如下:
- 与 dbt Core 完全兼容(而非仅支持 dbt Cloud);
- 支持所有 dbt 适配器,包括 DuckDB;
- 由社区积极维护且开源;
- 为本地开发提供丰富的功能集。
6.2 dbt Power User 的核心能力
- dbt 模型的 SQL 语法高亮与格式化;
- 列级血缘(lineage)可视化;
- 对 dbt 模型、source、宏的自动补全;
- 交互式文档预览;
- 直接在编辑器内完成模型编译与执行。
6.3 安装步骤
- 打开 VS Code;
- 进入扩展面板(快捷键
Ctrl+Shift+X/Cmd+Shift+X); - 搜索 "dbt Power User";
- 安装dbt Power User by AltimateAI(注意别装成 dbt Labs 的版本)。
至此,你的本地 dbt 环境已完全就绪。后续的模型运行、测试与文档构建会在课程视频中逐步讲解。
进阶:用仓库真实模型跑通 dbt 生命周期
连接验证通过后,建议按 dbt 项目分层顺序逐步构建,既能验证链路,又能观察每层的物化策略(这些策略定义在 dbt_project.yml 的models:段:staging 物化为 view、intermediate 与 marts 物化为 table):
dbt build --select stg_yellow_tripdata dbt build --select stg_green_tripdata dbt build --select int_trips_unioned dbt build --select int_trips dbt build --select fct_trips构建过程中可以对照以下源码理解各层职责:
- staging 层:
stg_yellow_tripdata.sql统一列名(vendorid→vendor_id、tpep_pickup_datetime→pickup_datetime)、做类型转换并过滤vendorid is null的脏数据;同时通过{% if target.name == 'dev' %}只保留 2019-01 一个月的数据,实现开发环境的快速采样(stg_yellow_tripdata.sql)。这与 dbt_project.yml 中vars.dev_start_date/dev_end_date的采样意图一致; - intermediate 层:
int_trips_unioned.sql用UNION ALL合并绿黄两种车型,并通过service_type字段区分来源、补齐 yellow 车型缺失的trip_type/ehail_fee(int_trips_unioned.sql);int_trips.sql用dbt_utils.generate_surrogate_key生成trip_id、join 支付类型维度表,并用QUALIFY row_number() ... = 1按vendor_id, pickup_datetime, pickup_location_id, service_type去重(int_trips.sql); - marts 层:
fct_trips.sql以materialized='incremental'+unique_key='trip_id'+incremental_strategy='merge'配置增量物化,首次全量构建后仅按pickup_datetime增量处理新记录,并 LEFT JOIN 区域维度表补充上下车 borough/zone 信息(fct_trips.sql)。
依赖的 dbt 包(dbt_utils、codegen)声明在 packages.yml,首次构建前需运行dbt deps拉取。种子数据(如taxi_zone_lookup、payment_type_lookup)的定义与测试见 seeds/seeds_properties.yml。
进阶:内存不足(OOM)时的调优方案
DuckDB 是进程内数据库,数千万行数据的建模完全依赖本地 RAM。官方在 duckdb_troubleshooting.md 中给出了按内存规模的判断基准:
- 4 GB RAM:很可能 OOM,建议改用 GitHub Codespaces(免费档含 4 核/8 GB 与 8 核/16 GB 机型)或云端路径;
- 8 GB RAM:部分模型可能 OOM,需要调低内存设置;
- 16+ GB RAM:默认设置即可顺畅运行。
若坚持本地运行,按优先级采取以下手段:
- 收紧
profiles.yml内存参数:显式设置memory_limit(约总内存 50%)、threads: 1、preserve_insertion_order: false; - 失败后增量续跑:
dbt build中途失败无需全量重来,修正问题后执行dbt retry,只重跑失败/跳过的模型; - 逐个模型构建:用
dbt build --select <模型名> --target prod一次只构建一个模型,降低峰值内存(见上文"用仓库真实模型跑通 dbt 生命周期"的命令序列); - 利用增量模型:
fct_trips已配置为增量物化,首次全量构建成功后,后续运行只处理新数据,内存压力大幅下降; - 环境侧优化:构建前关闭浏览器/IDE 等占用内存的应用;使用 SSD 提升 DuckDB 内存溢出落盘速度;尽量避免在 Docker 容器内运行(容器内存上限可能低于系统总内存)。
结语与后续学习路径
至此,你已经完成了 Data Engineering Zoomcamp Module 4 的本地环境搭建:安装 DuckDB 与 dbt、配置~/.dbt/profiles.yml双目标 Profile、下载并装载 2019–2020 出租车数据、通过dbt debug验证连接,并选装了适合 dbt Core 的 VS Code 扩展。接下来就可以进入课程核心内容:在 taxi_rides_ny 项目中运行模型、编写测试与生成文档。
如果想了解云端替代方案(BigQuery + dbt Cloud),参见云端配置指南;若在构建中遇到内存问题,完整的排查清单见DuckDB 内存故障排查;模块各章讲义(dbt 基础、项目结构、sources、models、seeds/macros、测试、文档、命令等)可从 04-analytics-engineering/README.md 进入。
提示:官方在本地配置文档中留了一个进阶挑战——尝试用 Docker Compose 或 Python 虚拟环境(如
uv)复现整套安装流程。这不仅能加深对依赖与连接机制的理解,也是学习环境隔离的绝佳练习。
【免费下载链接】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),仅供参考