AI 时代的数据工程实战:dbt + Airflow + DuckDB,3 步搭建现代数据栈

阿里云上云采购季 2核2G云服务器99元/年 续费同价

数据工程师的日子越来越不好做了——业务要实时报表、AI 团队要训练数据集、老板要 BI 看板。传统 ETL 跑不动了?这篇文章带你用 dbt + Airflow + DuckDB 搭一套轻量级但够用的现代数据栈,Python 全家桶,代码可运行。


一、传统数据工程 vs 现代数据栈

先说清楚我们为什么需要这套组合:

维度 传统 ETL(Kettle/SSIS) 现代数据栈
开发方式 拖拽界面 代码(SQL/Python)
版本控制 ❌ 几乎不可能 ✅ Git 原生支持
测试 手动跑一遍看结果 ✅ 自动化单元测试
文档 写 Word/Confluence ✅ 自动生成 Data Catalog
部署 人工操作 ✅ CI/CD 流水线
成本 商业授权贵 开源免费为主
学习曲线 低,但要靠”经验” 中等,但有标准范式

核心变化:从”ETL”到”ELT”——先加载原始数据,再在数据仓库里做转换。这样能充分利用数据库/数仓的计算能力,而且转换逻辑全部代码化。


二、架构总览

┌─────────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│  数据源      │ →  │  Airflow │ →  │  DuckDB  │ →  │   dbt    │
│ (API/文件)  │    │ (调度)    │    │ (存储)    │    │ (转换)    │
└─────────────┘    └──────────┘    └──────────┘    └──────────┘
                                                             ↓
                                                      ┌──────────┐
                                                      │ BI/AI 输出│
                                                      │ (报表/特征)│
                                                      └──────────┘
  • Airflow:任务调度引擎,编排数据采集和模型训练流程
  • DuckDB:嵌入式 OLAP 数据库,单文件部署,查询速度飞快
  • dbt(Data Build Tool):声明式 SQL 转换框架,自动管理依赖和文档

三、Step 1 — DuckDB 快速上手

DuckDB 是近年最火的开源分析型数据库之一。它不需要安装服务端,直接嵌入 Python 进程:

# duckdb_setup.py
import duckdb
import pandas as pd

# 创建/连接数据库(就是一个文件)
conn = duckdb.connect("data_warehouse.db")

# 建表并导入示例电商订单数据
conn.execute("""
    CREATE TABLE IF NOT EXISTS raw_orders (
        order_id VARCHAR PRIMARY KEY,
        customer_id VARCHAR,
        product_name VARCHAR,
        amount DECIMAL(10,2),
        category VARCHAR,
        order_date DATE,
        status VARCHAR
    )
""")

# 用 Python 插入一批测试数据
sample_orders = [
    ("ORD001", "C001", "腾讯云服务器年付", 1200.00, "云服务", "2026-05-01", "completed"),
    ("ORD002", "C002", "阿里云OSS存储包", 299.00, "存储", "2026-05-02", "completed"),
    ("ORD003", "C001", "大模型API调用费", 580.50, "AI服务", "2026-05-03", "processing"),
    ("ORD004", "C003", "华为云CDN加速", 800.00, "网络", "2026-05-04", "completed"),
    ("ORD005", "C002", "域名续费一年", 68.00, "基础服务", "2026-05-05", "cancelled"),
    ("ORD006", "C004", "SSL证书采购", 199.00, "安全", "2026-05-06", "completed"),
    ("ORD007", "C001", "对象存储流量包", 150.00, "存储", "2026-05-07", "completed"),
    ("ORD008", "C003", "企业邮箱套餐", 3600.00, "办公", "2026-05-08", "completed"),
    ("ORD009", "C005", "负载均衡实例", 450.00, "网络", "2026-05-09", "processing"),
    ("ORD010", "C004", "数据库备份服务", 200.00, "数据库", "2026-05-10", "completed"),
]

conn.executemany(
    "INSERT INTO raw_orders VALUES (?, ?, ?, ?, ?, ?, ?)",
    sample_orders
)

print(f"已插入 {len(sample_orders)} 条订单数据")

# DuckDB 的杀手锏——超快聚合查询
result = conn.sql("""
    SELECT 
        category AS 产品分类,
        COUNT(*) AS 订单数,
        ROUND(SUM(amount), 2) AS 总金额,
        ROUND(AVG(amount), 2) AS 平均客单价
    FROM raw_orders
    WHERE status = 'completed'
    GROUP BY category
    ORDER BY 总金额 DESC
""").df()

print("\n=== 销售额按分类统计 ===")
print(result)

# 导出给 AI 训练用
result.to_csv("features/sales_by_category.csv", index=False)
print("\n特征文件已导出至 features/sales_by_category.csv")

为什么选 DuckDB 而不是 PostgreSQL?

场景 DuckDB PostgreSQL
单机数据分析(<100GB) ⭐⭐⭐ 极快 ⚠️ 还行
多用户并发写入 ⚠️ 不擅长 ⭐⭐⭐ 强项
嵌入式/无服务端部署 ✅ 一个 .db 文件 ❌ 需要进程
Python/Pandas 集成 ⭐⭐⭐ 原生 ⚠️ 需 psycopg2
列式存储查询性能 ⭐⭐⭐ 比内存还快 ⚠️ 行存为主

对于中小企业的数据仓库场景(日增 < 100GB),DuckDB 完全够用,而且零运维成本。


四、Step 2 — dbt 数据转换

4.1 项目初始化

pip install dbt-core dbt-duckdb
mkdir my_data_project && cd my_data_project
dbt init my_data_project --adapter duckdb
cd my_data_project

4.2 配置 profiles.yml

# ~/.dbt/profiles.yml
my_data_project:
  outputs:
    dev:
      type: duckdb
      path: ../data_warehouse.db
      threads: 4
  target: dev

4.3 编写 dbt Model(核心!)

-- models/stg_orders.sql
-- 清洗层(Staging):对原始数据做标准化处理

SELECT
    order_id,
    customer_id,
    TRIM(product_name) AS product_name,
    amount,
    CASE
        WHEN category IN ('云服务', '网络', '数据库') THEN '基础设施'
        WHEN category = 'AI服务' THEN 'AI与智能'
        WHEN category = '存储' THEN '存储与备份'
        ELSE '其他'
    END AS category_group,
    order_date,
    status
FROM {{ source('raw', 'raw_orders') }}
WHERE status != 'cancelled'
-- models/fct_daily_revenue.sql
-- 聚合层(Fact):按天统计营收

WITH stg_orders AS (
    SELECT * FROM {{ ref('stg_orders') }}
)

SELECT
    order_date AS date_key,
    COUNT(DISTINCT order_id) AS total_orders,
    COUNT(DISTINCT customer_id) AS active_customers,
    ROUND(SUM(amount), 2) AS daily_gmv,
    ROUND(AVG(amount), 2) AS avg_order_value
FROM stg_orders
GROUP BY order_date
ORDER BY date_key
-- models/fct_customer_rfm.sql
-- RFM 分析:客户价值分层

WITH customer_metrics AS (
    SELECT
        customer_id,
        MIN(order_date) AS first_order_date,
        MAX(order_date) AS last_order_date,
        COUNT(*) AS frequency,
        SUM(amount) AS monetary
    FROM {{ ref('stg_orders') }}
    GROUP BY customer_id
)

SELECT
    *,
    CASE
        WHEN monetary >= 3000 AND frequency >= 2 THEN 'VIP客户'
        WHEN monetary >= 500 THEN '高价值客户'
        WHEN frequency >= 1 THEN '普通客户'
        ELSE '新客户/流失风险'
    END AS customer_segment
FROM customer_metrics
ORDER BY monetary DESC

4.4 定义 Source 和 Tests

# models/schema.yml

version: 2

sources:
  - name: raw
    tables:
      - name: raw_orders
        description: 原始订单表
        columns:
          - name: order_id
            description: 订单ID
            tests:
              - unique
              - not_null

models:
  - name: stg_orders
    description: 订单清洗层
    columns:
      - name: order_id
        tests:
          - unique
          - not_null
          - relationships:
              to: ref('stg_orders')
              field: order_id

  - name: fct_daily_revenue
    description: 日营收事实表
    tests:
      - dbt_utils.expression_is_true:
          expression: "daily_gmv >= 0"

  - name: fct_customer_rfm
    description: 客户RFM价值分层表

4.5 运行 dbt

# 运行所有 model 并生成文档
dbt run
dbt test       # 执行自动化测试
dbt docs generate && dbt docs serve  # 启动本地文档站

五、Step 3 — Airflow 任务调度

5.1 安装与配置

pip install apache-airflow
export AIRFLOW_HOME=~/airflow_home
airflow db init  # 初始化元数据库

5.2 DAG 定义

# dags/data_pipeline_dag.py
from datetime import datetime, timedelta
from airflow import DAG
from airflow.operators.python import PythonOperator
from airflow.providers.common.sql.operators.sql import SqlOperator
import duckdb
import subprocess
import logging

default_args = {
    'owner': 'data_team',
    'depends_on_past': False,
    'retries': 1,
    'retry_delay': timedelta(minutes=5),
}

dag = DAG(
    dag_id='daily_data_pipeline',
    default_args=default_args,
    start_date=datetime(2026, 5, 1),
    schedule_interval='0 8 * * *',     # 每天 8 点执行
    catchup=False,
    tags=['data-engineering', 'production'],
)


def extract_task(**context):
    """步骤1:数据抽取——模拟从业务系统拉取数据"""
    logging.info("📥 开始数据抽取...")

    # 这里可以换成真实的 API 调用或数据库同步
    # 示例:从 CSV/API 加载数据到 DuckDB
    conn = duckdb.connect("data_warehouse.db")

    # 模拟增量抽取:只取昨天的数据
    yesterday = context['execution_date'].strftime('%Y-%m-%d')
    logging.info(f"抽取日期范围: {yesterday}")

    # 实际项目中这里对接你的业务数据库/API
    # conn.execute("INSERT INTO raw_orders SELECT * FROM staging WHERE order_date = ?", [yesterday])

    conn.close()
    return f"✅ 抽取完成,目标日期: {yesterday}"


def transform_task():
    """步骤2:执行 dbt 转换"""
    logging.info("🔄 开始 dbt 转换...")
    result = subprocess.run(
        ["dbt", "run", "--project-dir", "/path/to/my_data_project"],
        capture_output=True,
        text=True,
        cwd="/path/to/my_data_project"
    )
    if result.returncode != 0:
        raise Exception(f"dbt run 失败: {result.stderr}")
    logging.info("✅ dbt 转换完成")


def load_to_ai_features():
    """步骤3:导出 AI 特征数据"""
    logging.info("🤖 开始生成 AI 特征数据...")

    conn = duckdb.connect("data_warehouse.db")

    # 导出 RFM 分层结果供推荐模型使用
    rfm_data = conn.sql("""
        SELECT * FROM fct_customer_rfm
        ORDER BY monetary DESC
    """).df()

    rfm_data.to_csv("/mnt/ml_features/customer_rfm.csv", index=False)
    logging.info(f"✅ 已导出 {len(rfm_data)} 条客户特征")

    # 导出每日趋势供预测模型使用
    trend_data = conn.sql("""
        SELECT * FROM fct_daily_revenue
    """).df()

    trend_data.to_csv("/mnt/ml_features/daily_trend.csv", index=False)
    logging.info(f"✅ 已导出 {len(trend_data)} 条趋势特征")

    conn.close()


def notify_success(context):
    """通知任务成功"""
    logging.info("🎉 今日数据流水线执行完毕!")


# 定义 Task 及依赖关系
with dag:
    t1 = PythonOperator(
        task_id="extract_data",
        python_callable=extract_task,
    )
    t2 = PythonOperator(
        task_id="run_dbt_transform",
        python_callable=transform_task,
    )
    t3 = PythonOperator(
        task_id="export_ai_features",
        python_callable=load_to_ai_features,
    )
    t4 = PythonOperator(
        task_id="notify",
        python_callable=notify_success,
    )

    # 依赖链:抽取 → 转换 → 特征导出 → 通知
    t1 >> t2 >> t3 >> t4

5.3 启动 Airflow

# 启动 Web UI(默认 http://localhost:8080)
 airflow webserver --port 8080 &
 airflow scheduler

六、完整对比:这套方案 vs 传统方案

维度 Kettle + MySQL dbt + Airflow + DuckDB Spark + Hive
搭建时间 半天 1 小时 3 天+
运维成本 低 极低 高(集群维护)
数据量上限 百万行 亿行(单机) PB 级
团队协作 ❌ ✅ Git + PR Review ✅
文档自动生成 ❌ ✅ dbt docs ⚠️ 部分
适合团队规模 1~3 人 3~20 人 20+ 人

结论:如果你的团队在 20 人以内、日数据量在亿级行以下,dbt + Airflow + DuckDB 这套方案是性价比最高的选择。不需要 Hadoop,不需要 Spark 集群,一台云服务器就能跑起来。


总结

现代数据工程的精髓不在于工具多牛,而在于可复现、可测试、可追溯。

  • DuckDB 替代你那台跑得半死不活的 MySQL 分析库
  • dbt 把散落各处的 SQL 变成有版本管理的代码资产
  • Airflow 让每天凌晨 3 点手动跑脚本的日子成为历史

这三样东西加在一起,就是中小团队最实用的数据基建方案。别再犹豫了,今天就开始搭吧!


👤 作者简介

一枚在大中原腹地(河南)卖公有云的从业者,主营腾讯云/阿里云/华为云,曾踩坑无数,现专注AI大模型应用落地。关注公众号「AI热点日报」,围观AI前沿动态~

博客:y1cloud.cn


发表评论