本文由 Google Gemini 生成,仅供参考。
Table of Contents
Open Table of Contents
背景与痛点分析
当前项目的数据 ETL 流程高度依赖于在 DolphinScheduler 上调度的一系列独立 SQL 脚本,主要痛点包括:
- 逻辑重复与不一致:相同的业务逻辑在多个脚本中被反复复制粘贴,导致维护成本高,且存在逻辑不一致的风险。
- 隐晦的依赖关系:表间依赖完全由 DolphinScheduler 的 DAG 图“人肉”维护,追溯困难且容易出错。
- 数据一致性风险:由多个 INSERT 语句构成的任务,不具备原子性,若中间步骤失败,目标表将处于数据被部分加载的“脏”状态。
- 性能瓶颈担忧:对于数据量大的 ETL,将多个数据源的处理逻辑合并到一个查询中,存在内存溢出或超时的巨大风险。
- 调度与回溯的复杂性:当前依赖于向脚本传递
${...}
变量来执行特定品牌或时间范围的数据回溯,方式缺乏规范,易出错。 - 缺乏数据质量保障:没有系统性的数据测试机制,数据问题只能在下游应用或报表中被动发现,响应滞后。
DBT 解决方案
核心原则:从“过程式”到“声明式”的思维转变
放弃:“先 TRUNCATE,再 INSERT A,再 INSERT B……”的过程式脚本思维。
拥抱:“我声明 final_table 的最终内容是 A、B、C 的并集”的声明式模型思维。
关键问题与 DBT 解决方案
-
如何应对大数据量下 UNION ALL 的性能风险?
- 在 dbt/models/ 下创建 intermediate/ 目录。
- 将原脚本中每一个独立的、复杂的 INSERT 源,都改造成一个独立的中间模型。
- 最终的核心模型只负责对这些已经处理好的、干净的中间模型进行 UNION ALL。
-
如何保证数据一致性与任务执行顺序?
- 执行顺序:当在模型 A 中写下
{{ ref('B') }}
时,就声明了 A 依赖于 B。 - 数据一致性:
- 原子性:dbt 对每个模型的构建都是事务性的。
- 幂等性:dbt 模型是幂等的,失败后可简单重跑。
- 执行顺序:当在模型 A 中写下
-
如何管理调度参数与数据回溯?
- 在 dbt_project.yml 中为变量定义清晰的默认值。
- 在 SQL 模型中通过
{{ var('hour', 3) }}
的方式引用。 - 调度时通过命令行传入 JSON 来覆盖默认值实现精准回溯。
-
如何复用逻辑并保障质量?
- 将 CASE WHEN 等通用转换逻辑封装成 macros(宏)。
- 在任意模型中通过
{{ your_macro_name(...) }}
进行调用。 - 在 .yml 文件中为模型的关键字段添加测试(如 unique,not_null,accepted_values)。
DBT 分层建模规范
核心理念:物理目录严格映射逻辑分层
我们采用“层级优先”的组织哲学,项目的物理目录结构必须清晰、直观地反映数据仓库的逻辑分层。开发者仅通过浏览目录,就应能理解项目的宏观架构。这种“约定优于配置”的原则是保障项目长期可维护性的基石。
一个理想的目录结构示例如下:
models/
├── staging/ # 暂存层:源数据 1:1 镜像,只做最基础清洗
│ └── stg_{source}__{table}.sql
├── base/ # 基础共享层:可被多层复用的通用逻辑
│ └── base_{domain}__{concept}.sql
├── dwd/ # 明细数据层:业务过程的明细事实
│ ├── intermediate/ # DWD 层的私有实现细节
│ │ └── int_dwd_{concept}.sql
│ └── dwd_{business_process}.sql
├── dws/ # 公共汇总层:跨业务过程的公共汇总指标
│ └── dws_{domain}__{granularity}.sql
├── dim/ # 维度层:高质量、一致的维度视角
│ └── dim_{dimension}.sql
└── marts/ # 数据集市层(ADS):面向最终应用
├── intermediate/ # Marts 层的私有实现细节
│ └── int_marts_{concept}.sql
└── {sub_domain}/ # 按业务域划分子目录,如 marketing/
└── ads_{report_name}.sql
公共分层
models/ 下的顶级目录定义了数据仓库的“公共 API”,它们是稳定、可信的,下游可以依赖。
-
models/staging/ (暂存层)
定位:ODS 源数据的“备菜区”,是源数据一个干净、可靠、1:1 的镜像。
核心职责:对源表进行最基础的清洗和标准化(字段重命名、类型转换、基础清洗)。
开发规则:一个 Staging 模型严格对应一个源表。禁止进行跨表 JOIN 和复杂业务逻辑。
-
models/base/ (基础共享层)
定位:跨层共享的“通用零件库”。
核心职责:存放那些需要被多个、不同正式分层(如被 DWD 和 DWS 同时)共同消费的高度通用、基础的模型。
开发规则:准入门槛高,只有真正被跨层复用的逻辑才能放入。
-
models/dwd/ (明细数据层)
定位:业务过程的“明细事实记录表”。
核心职责:以业务过程(如下单、支付)中的一个行为主体为中心,关联相关维度,形成大宽表。保留最细粒度的数据。
开发规则:主要进行跨表 JOIN 操作,一般不进行聚合(GROUP BY)。
-
models/dws/ (公共汇总层)
定位:面向分析主题的“公共汇总数据”。
核心职责:基于 DWD 进行轻度聚合,形成跨业务过程的、可复用的汇总指标(如用户的每日活跃汇总)。
开发规则:主要进行 GROUP BY 聚合操作。
-
models/dim/ (维度层)
定位:提供分析上下文的“维度视角”。
核心职责:构建高质量、一致的维度表(如用户、商品、日期)。
开发规则:通常从 Staging 或 DWD 层获取数据,进行清洗、去重、标准化。
-
models/marts/ (数据集市层)
定位:面向最终用户的“精品展厅”,即 ADS(应用数据服务层)。
核心职责:从 DWS、DWD、DIM 层获取数据,构建面向特定业务应用(报表、分析、算法)的最终模型。
开发规则:逻辑应尽可能简单,主要是字段选择和轻度关联。所有复杂计算都应已在 DWS 或 DWD 层完成。
-
intermediate/ (私有中间实现)
定位:任何公共分层内部的“私有草稿区”,用于封装复杂逻辑。
核心职责:将一个复杂的模型(如一个 DWD 宽表)的构建过程,拆解为多个更小、更易于管理的步骤。
开发规则:
- 必须作为子目录存在,如 dwd/intermediate/。严禁创建顶级的 intermediate 目录。
- 其中的模型应使用 int_ 前缀,并优先物化为 ephemeral。
- 严禁被其父目录之外的任何模型 ref()。例如,DWS 层模型不应 ref() dwd/intermediate/ 里的任何模型。
-
seeds/ (种子文件)
定位:管理不常变化的、小规模的静态数据(如:日期维度表、平台信息映射表)。
使用方式:通过
dbt seed
命令加载,在模型中通过{{ ref('seed_file_name') }}
引用。 -
tests/ (自定义数据测试)
定位:实现复杂的、超越标准测试的业务规则校验。
放置内容:任何返回 0 行即为通过的 SELECT 查询(如:校验 DWS 层总金额是否与 DWD 层明细总和相等)。
目录组织:必须镜像 models/ 的顶级目录结构。对 models/dwd/dwd_trade_order.sql 的测试,应放在 tests/dwd/ 目录下。
-
macros/ (宏)
定位:封装可被项目全局复用的 SQL 代码片段,遵循 DRY 原则(如:map_order_status)。
核心建模原则与规范
数据流向:单向、逐层、不跨级
正确流向: source -> staging -> intermediate -> marts。
绝对禁止:
- 向下游调用:如 DWD 模型 ref 一个 DWS 模型,会导致循环依赖。
- 向上游跳层:如 Marts 模型直接 ref 一个 source,绕过了 Staging 层的清洗和标准化。
- 平级跨领域调用:如一个财务 Mart 模型 ref 一个市场 Mart 模型,应将共享逻辑下沉到 Intermediate层。
类型转换的唯一位置:Staging 层
原则:所有对源数据的字段类型转换(CAST)必须且只能在 Staging 模型中进行。 目的:
- 建立契约:Staging 层向所有下游模型承诺其产出字段的类型是稳定、可靠的。
- 尽早失败:如果源数据类型有误,在第一站就立刻报错,便于追溯。
- 下游简洁:Intermediate 和 Marts 层无需再关心类型问题,可以直接进行计算。
- 例外:对于由多个字段计算而来的复合字段,其最终的 CAST 应与其计算逻辑一起,封装在 Intermediate 层。
命名规范
- Staging:
stg_{源系统}__{表名}
(用双下划线区分源和表) - Intermediate:
int_{服务目标/领域}__{业务逻辑}
- Marts:
dwd_{事实表名}
,dws_{主题名}
,ads_{报表名}
- Tests:
assert_{模型名}__{测试描述}
数据质量与测试
Schema Tests
用于对单个字段进行通用的、标准化的断言,直接在模型的 schema.yml 文件中定义:
- unique:断言字段值在表中是唯一的。
- not_null:断言字段值不能为空。
- accepted_values:断言字段值必须是预定义值列表中的一个。
- relationships:断言字段值必须在另一个表的指定字段中存在。
Data Tests
用于执行任何复杂的、自定义的、甚至跨模型的业务逻辑校验:
- 校验数据的时效性(如“数据是否延迟?”)。
- 校验跨层级数据的一致性(如“DWS 的订单数是否与 DWD 匹配?”)。
- 校验复杂的业务规则(如“VIP 客户的客单价是否低于某个阈值?”)。
测试执行
dbt test
:运行所有测试。dbt build
:按顺序运行模型、测试、快照和种子文件(推荐用于生产环境)。
数据仓库运维与治理
文档与元数据管理
- 自动生成文档:通过
dbt docs generate
命令自动生成交互式文档网站。 - 血缘关系可视化:自动生成表级数据血缘关系图,清晰展示模型间的依赖关系。
- 模型和字段描述:在 YAML 文件中为模型和字段添加业务含义描述,提升数据可理解性。
性能优化策略
数仓分层 | 推荐物化方式 | 核心原因与策略 |
---|---|---|
Staging | view | 零存储开销,实时透传源数据,只做最基础的清洗。 |
Intermediate | view 或 ephemeral | 过程数据,无需落盘。如果逻辑复杂,可物化为 table 以切断计算链,方便调试。 |
DWD (增量) | incremental (自定义) | 【核心】 放弃默认 merge。首选分区替换策略,即计算出完整分区数据后通过 REPLACE PARTITION 原子化替换。如果 Doris 表是 UNIQUE KEY 模型,可直接 insert,利用其 UPSERT 特性。 |
DWS/ADS (全量) | table | 面向最终查询,需要高性能。每日全量重刷,或通过自定义的 MERGE 逻辑更新(适用于状态型快照表)。 |
DWS/ADS (增量) | incremental (分区) | 对于每日聚合的指标,直接按天分区 insert 即可,这是最高效、最简单的增量。 |
监控与告警机制
结合 DolphinScheduler 和 DBT 构建完整的监控体系:
- 运行状态监控:通过 DolphinScheduler 监控任务执行状态。
- 数据质量告警:当
dbt test
失败时触发告警。 - 源数据新鲜度监控:配置
dbt source freshness
并加入调度。检查 ODS 源表数据是否在预期内更新,若源头延迟则触发高优告警。 - 性能退化监控:监控模型执行时间,发现性能问题。
- 日志分析:分析 DBT 运行日志,及时发现异常。
版本控制与 CI/CD
- Git 版本控制:所有 DBT 模型、配置和测试都纳入 Git 管理。
- 自动化部署:通过 DolphinScheduler 实现 DBT 模型的自动化调度执行。
- 质量门禁:在 CI/CD 流程中集成
dbt test
,确保数据质量。 - 回滚机制:利用 Git 的历史版本管理实现快速回滚。
DBT 在 Doris + DolphinScheduler 环境下的调度优势
精确的 DDL 控制
- 主要手段:在 SQL 中显式 CAST 确保字段类型精确转换。
- 辅助手段:在 .yml 中定义核心字段的 data_type,作为数据治理和文档化手段。
自动化的数据血缘
- 表级血缘:通过 ref() 和 source() 自动构建数据血缘关系图。
- 字段级血缘:可通过集成 dbt-osmosis 等社区工具或商业数据目录实现。
调度策略
大规模自动化调度(日常例行任务)
- 目标:每日或每小时构建整个数据层。
- 推荐命令:
dbt build --select +marts.dws+
(构建所有 DWS 层及其上游依赖)。
精细化手动操作(开发、调试、数据修复)
- 目标:只运行某个具体的模型,并且只针对某个特定的品牌或日期。
- 核心命令组合:
dbt run \ --models ${MODEL_TO_RUN} \ --vars "${DBT_VARS_JSON}"