深入解锁 dbt——生产环境中的 dbt

44 阅读36分钟

正如你在本书中已经学到的,dbt 是一个非常出色的工具,可以用来构建、维护和扩展你的 data transformation processes。我们已经覆盖了 dbt 的许多方面,包括使用 seed files、构建 incremental models,以及使用 Jinja 和 macros 来填补 SQL 重复性过高时的空白。到目前为止,我们只在 development environment 中运行过这些 transformations;但一旦你准备让 data consumers 使用你构建的数据模型,就需要将 dbt 部署到一个稳定的 production environment 中。

不言而喻,对于任何 software product 来说,development environment 都不应该负责处理 production workloads;dbt 生成的 data products 也不例外。Production dbt runs 不应该存在 single point of failure,而让 data consumers 使用由 development environment 中运行的 jobs 生成的数据,就违反了这一原则。本章中,我们将讨论如何设计 deployment strategy,以确保经过 transformation 和 validation 的数据能够被各类 stakeholders 使用,例如 analysts、data scientists,以及任何需要访问这些数据的人。

在 production 中运行 dbt 涉及多个方面,包括:

  • 理解 environments 是什么,以及它们在 dbt 的 open source 版本和 hosted 版本,也就是 dbt Cloud,中有什么区别。
  • 了解使用 dbt Cloud 与 dbt Core 进行 production deployment 时的 trade-offs。这个主题会在本章中反复出现。
  • 调度 dbt production jobs。
  • 实施 CI/CD pipelines,以自动化 production deployment process。

在最后一章中,我们会覆盖这些主题。到本章结束时,你应该会掌握将 dbt 部署到 production environment 所需的基础知识,并对此感到比较 comfortable。

Understanding Environments

在深入讨论 environments 在 dbt context 中如何工作之前,尤其是在 dbt Cloud 和 dbt Core 之间 environment behavior 与 configuration 的差异之前,我们先简要说明 environments 是什么。Environment 指的是 dbt 被执行、部署或测试的某种特定 configuration 或 setup。一个 environment 表示一个独立 context,它复制了开发、测试和运行 dbt 所需的条件。Environments 通常被创建出来,用于支持 software development lifecycle 的不同阶段,包括 development、testing、staging 和 production。每个 environment 都服务于特定目的,并且可能具有独特的 characteristics、configurations 和 constraints。

Git Workflows and dbt Environments

作为 best practice,你拥有的 dbt environments 数量,应该与你的 git workflow strategy 直接相关。我们看到 teams 使用两种 git workflow strategies 非常成功:Gitflow 和 Feature Branch Workflows。行业中很多人对哪种 git workflow 最好有非常强烈的观点,但这里我们会避免表达主观偏好,而是聚焦于如何实施这两种 git workflow strategies。我们先从 feature branching 开始。

Feature branching 使用一个 shared branch,通常命名为 main,developers 会直接从它创建自己的 feature branches。Developers 直接在 feature branches 中工作。当他们完成工作后,会将自己的 branch merge 到 main branch。对于与 dbt 搭配使用的 teams 来说,这通常是最简单的 git workflow,因为它容易理解,也容易维护。图 10-1 展示了一个使用 feature branching 的 team 的 git tree 示例。

image.png

图 10-1:feature branching 示例

使用 feature branching strategy 时,你只需要维护两个 environments:一个 production deployment environment 和一个 development environment。Production environment 会对应 main branch。当 production dbt jobs 运行时,它们应该使用来自 main branch 的 code,因为它代表最新且 up-to-date 的 code。其次,你需要维护一个 development environment。Development environment 是 developers 可以处理新 features 的地方,不必担心打断 production system。通常,development environment 会指向一个与 production environment 不同的 target database,后面我们会进一步讨论这一点。图 10-2 在前一张图的基础上扩展,展示哪些 branches 属于哪个 environment。

image.png

图 10-2:feature branching 下的 dbt environments 示例

如前所述,这种 branching strategy 是你可以实施的最简单策略之一,对于许多 dbt projects 来说,它可以成为成功的 workflow。这种策略最适合以下场景:

  • 你希望 changes 一旦完成就部署到 production,而不是等到 release cycle 结束。
  • 你需要能够快速 iterate,而不必管理额外的 git branches。
  • 你有兴趣设置 fully automated deployments。
  • 你的 changes 不需要经过严格的 user testing。

我们要覆盖的第二种 branching strategy 是 Gitflow。在 dbt context 中,这种 branching strategy 会是一种 multi-deployment environment strategy。Gitflow 的基础包含两个 core branches:developmaindevelop branch 作为 ongoing development work 的 integration branch。它包含最新的 development code,并作为创建 feature branches 的 base。当 feature branches 中的工作完成后,code 会 merge 到 develop branch。

Note

develop branch 可以命名为 QA、UAT 或其他任何名称,但 develop 是最常见的。

main branch 包含 stable 和 production-ready code,并包含 dbt code 的 latest release。当 develop branch 稳定且 release 已经 scheduled 时,这个 branch 中的 code 会 merge 到 main,从而发生新的 production deployment。Gitflow 可以变得比这更复杂,但对 dbt project 来说,hotfix 和 release branches 带来的额外复杂性通常并不需要。图 10-3 直观展示了这种 Gitflow 变体如何工作。

image.png

图 10-3:Gitflow branching 示例

使用 Gitflow 时,你需要设置三个 dbt environments。与前面讨论的 Feature Branch workflow 类似,main branch 对应 production dbt environment,而 feature branches 对应 development environment。不过,两种策略之间的区别在于多了第三个 environment,它对应 develop branch。这个 environment 通常会命名为 staging、QA 或 UAT。图 10-4 在 Gitflow branching 图的基础上扩展,展示这些 branches 分别在哪些 environments 中运行。

image.png

图 10-4:Gitflow branching 下的 dbt environments 示例

Gitflow branching strategy 显然比基础 feature branching strategy 更复杂,因为你需要维护一个额外 environment。下面是一些你可能会选择 Gitflow 而不是 feature branching 的原因:

  • 你希望控制 production release cycle,而不是 automated releases。
  • 你希望在发布 changes 到 production 之前完成 quality assurance 和 user acceptance testing。
  • 你需要彻底记录 releases。这在 healthcare 和 finance 等 highly regulated industries 中很常见。

Environment Management with dbt Cloud

现在你已经理解了使用 dbt 开发时 environments 是什么,以及 environments 应如何与 git workflow strategy 对应,接下来讨论如何在 dbt Cloud 中创建和管理 environments。首先,dbt Cloud 只包含两类 environments 的概念:development environments 和 deployment environments。在 dbt Cloud 中,你只能拥有一个 development environment。这个 environment 通常会指向一个被配置为 production clone 的 target database。Developers 可以在这里针对自己的 code changes 运行 dbt jobs,而不会干扰 production jobs。dbt Cloud 中任何其他类型的 environment 都是 deployment environment。这类 environment 可以是 production、QA、UAT 或 CI(Continuous Integration)environment。它与 development environment 的区别在于,organizations 可以拥有 unlimited number of deployment environments。

幸运的是,我们已经在第 2 章中展示过如何配置 development environment。如果你一直跟着本书中的示例操作,那么你一直工作的就是这个 environment。设置 deployment environment 和设置 development environment 一样简单。事实上,由于 dbt Cloud 只允许你拥有一个 development environment,所以你随后创建的任何 environments 默认都会是 deployment environments。我们主要希望你注意的是,当你为这个 environment 配置 target database 时,不应该使用与你 development environment 相同的 database。通过为每个 environment 使用独立 database,你可以确保遵循可靠的 code promotion process,并确保尚未 production ready 的 code 不会在 production database 上运行。Production database 的常见 naming conventions 包括:

  • Prod
  • Production
  • Analytics(这是最 user-friendly 的)

除了创建 deployment environment,你还会从 dbt Cloud UI 管理 environment variables。如果你回忆第 6 章,我们讨论过如何使用 env_var Jinja function,也展示过如何设置 environment variables。请记住,在 dbt Cloud 中创建的每个 environment variable 都可以有一个 project default 作为 fallback,用于当你没有为某个 specific environment 配置该 variable 时使用。

Environment Management with dbt Core

如果你是 dbt Core user,那么 environment management 的责任与 dbt Cloud users 不同。dbt Cloud users 可以直接通过 web UI 管理所有 environment configuration 和 environment variables,而 dbt Core users 需要用非常不同的方式处理。先看使用 dbt Core 时如何配置不同 environments,然后在本节末尾讨论 environment variables。

如果你回忆第 2 章,我们提到当你运行 dbt init command 时,terminal 中会出现一系列 questions,使 dbt 能够为你生成 profiles.yml file。这个 file 是存储 connection profiles 的地方。Connection profile 是一组 configurations,用来告诉 dbt 如何连接到 target database。当你运行 dbt 时,它会在 dbt_project.yml 中搜索 profile name 的 value,然后用这个 value 在 profiles.yml file 中查找正确的 connection profile。我们之前提过,但这里重申一下:当你运行 dbt 时,它会在 terminal 的当前 directory 中搜索这个 file;如果找不到,就会转而在 ~/.dbt/ directory 中查找。

Note

当你运行 dbt init 时,profiles.yml 会被创建并存储在 ~/.dbt/profiles.yml 中。

一个 connection profile 由一个或多个 targets 组成。Targets 通常用于根据你运行 dbt 的 environment,提供不同 connection configurations。一个好记的方式是:connection profile 对应一个 project,而 target 对应一个 environment。图 10-5 展示了一个示例:我们在 my_first_dbt_project profile 中配置了两个 targets:devprod

image.png

图 10-5:包含两个 targets 的 profiles.yml 示例

还要注意第 2 行,我们指示 dbt 使用 dev target。因此默认情况下,当我们运行任何 dbt commands 时,它们会使用 developer credentials 执行,并且 models、seeds 等会 materialize 到我们的 dev database,也就是 dbt_learning database 中。这对 development 很有用;而在 production 中,我们当然希望 dbt 针对 prod target 执行 jobs。这可以通过在任何 dbt command 中将 desired target name 传给 target command-line flag 来实现。例如,如果你想作为 production job 的一部分构建整个 dbt project,可以这样组织 command:

dbt build --target prod

正如第 6 章所讨论的,你可以对 profiles.yml 做一些修改,移除 hardcoded secrets,并用 env_var Jinja function 替换它们。我们建议在部署 dbt 时始终这样做,因为你不希望 secrets 被 check into source control。不过,你也可以在 dbt project 的其他任何地方使用 env_var Jinja function。但使用 dbt Core 时,如何让这些 variables 可供 env_var function 使用呢?这是一个有点复杂的问题,因为它最终取决于你会把 dbt 部署在哪里。让 environment variables 可用的最简单方式,是使用 .env file。这是一个常见 pattern,但不是唯一可用方式。如果你会部署在 AWS infrastructure 中,也可以使用 AWS Secrets Manager 等 secret manager。无论如何,你都需要思考如何在 production environment 中让 environment variables 可用。

Production Jobs

一旦设置好 production environment,你就可以开始将 jobs 部署到 production。当我们说 dbt job 时,指的是在 production environment 中运行一个或多个 dbt commands。通常,jobs 会由 scheduler 触发,例如 cron;也可以由 upstream processes 的 events 触发,例如 data ingestion pipeline 完成;还可以由 orchestration platform 触发。Job 的触发方式有很多种,并且会根据你的 dbt deployment 方式而变化。本节中,我们会覆盖在 dbt Cloud 和 dbt Core 中如何创建 jobs 以及触发 jobs。

dbt Cloud

dbt Cloud 提供多种方式,让你创建、监控和触发 jobs。本节中,我们会聚焦如何通过 dbt Cloud web application 创建 jobs。要开始,可以在 jobs page 中选择 “create job” button,直接在 web application 中创建 job。在这个页面中,你会看到几个选项,包括为 jobs 命名、选择它运行的 environment、定义将运行的 commands,以及选择触发 job 的方式。

在为 job 定义 environment configuration 时,你可以选择 job 应该在哪个 environment 中运行,选择该 job 应运行的 dbt version,或者直接继承 environment 的 version,还可以设置 target name。回忆第 6 章,我们讨论过 target variable,以及它如何让你访问 environment 的 configurations。这在根据运行的 environment 改变 dbt jobs behavior 时很有用。

随后,你可以配置 execution settings。这些 settings 允许你做一些事情,例如定义 timeout,使 job 运行过久时被 kill;但最重要的是,这里列出 job 将要执行的 dbt commands。图 10-6 展示了一个示例,其中我们列出四个 commands 作为该 job 的一部分运行:dbt seeddbt snapshotdbt rundbt test。实际上,这可以简化为一个 command,也就是 dbt build,但我们想演示你可以在 job 中运行任意数量的 dbt commands。

image.png

图 10-6:配置带有多个 commands 的 dbt Cloud job 示例

创建 dbt Cloud job 的最后也是 arguably 最重要的部分,是为 new job 定义 trigger。目前,dbt Cloud 为 jobs 提供三类 triggers:

  • Scheduled
  • Continuous Integration
  • API

本节剩余部分中,我们会聚焦 schedule-triggered 和 API-triggered jobs;Continuous Integration jobs 会在本章后面讨论 CI/CD 时再回过头来讲。先从简单的开始:jobs 可以通过 dbt Cloud 中的 schedule 以两种方式触发。第一种方式是使用它们的 simple scheduling tool,选择 jobs 应该在哪些 days 运行,以及以什么 interval 运行。例如,你可以选择 job 应该在 Monday-Friday 每小时运行一次。不过,如果这还不足以满足你的 scheduling needs,也可以提供一个 cron schedule,dbt Cloud 会使用它来触发你的 job。如果你是 cron scheduling 新手,或者需要复习,图 10-7 提供了 cron syntax。

image.png

图 10-7:Cron syntax

触发 dbt Cloud jobs 的第二种也是最灵活的方式,是使用 API。借助 dbt Cloud API,你可以做这些事情:

  • Create、update 或 delete a job
  • Get the run status of a job
  • Trigger a job
  • Administrative tasks,例如 list users、update licenses、list projects 等

通过使用这个 API,你可以 programmatically 触发 dbt Cloud jobs。常见方式是在 data pipeline 的 upstream tasks 完成后,使用 orchestration tool,例如 Airflow,调用该 API 中的 endpoints。例如,假设你有一个 Airflow DAG,它有三个步骤:

  1. 从 CRM ingest data
  2. 从 social media ad platforms ingest data
  3. 在 task one 和 task two 完成后触发 dbt

在这个 pipeline 中,task three 会向 dbt Cloud 发起 API call,触发你定义的 job。事实上,使用 Airflow 触发 dbt Cloud jobs 非常简单,因为已经有 Airflow Operators 抽象掉了触发 dbt jobs 和轮询 status 的复杂性。但 Airflow 并不是唯一可以与 dbt Cloud 无缝集成的 orchestration tool。你也可以非常轻松地集成 Azure Data Factory、Prefect 和 Dagster,把 dbt Cloud jobs 作为包含 dbt 之外 tasks 的 data pipeline 中的一个 task 来触发。

dbt Core

当谈到在 production 中运行 dbt Core jobs 时,相比 dbt Cloud,你拥有更大灵活性,但代价是复杂度更高。在 production 中运行 dbt Core 更灵活,因为你几乎可以在任何能够部署 software 的地方运行它,也可以用任何你能想象的方式触发 jobs。然而,complexity 的 trade-off 在于,你不仅需要负责运行 jobs,还必须关心用于运行 jobs 的 infrastructure。使用 dbt Cloud 时,infrastructure concern 已经被抽象掉了,而这也正是付费 SaaS solution 应该做的事情。

一开始,设置 infrastructure 来运行 dbt Core 可能听起来令人畏惧,但实际上可以非常简单。对许多 teams 来说,保持简单是一个很好的起点。除非你的 team 有 unlimited resources,否则我们建议从一个简单的 dbt Core deployment strategy 开始,然后随着对 additional flexibility 的需求出现,再逐步增加复杂度。表 10-1 提供了一些 dbt Core 常见 deployment patterns,以及相对于其他策略而言它们对应的 flexibility 和 complexity。

表 10-1:dbt Core deployment complexity vs. flexibility

StrategyComplexityFlexibility
GitHub ActionsLowLow
Virtual MachineMediumLow
ContainerizedMediumHigh
Orchestration ToolHighHigh

这些策略会逐步变得更复杂,但确实存在一些时候,采用更复杂的 deployment strategy 是合理的。如果我们从零开始构建 data team 并实施 dbt Core,我们会保持简单,从使用 GitHub Actions 运行 production jobs 开始。这个策略部署起来极其容易,因为你不必担心管理任何 infrastructure。GitHub Actions 会在一个 virtual machine 中运行,当 workflows 被触发时,GitHub 会替你启动该 virtual machine。此外,GitHub Actions 可以使用 cron schedule 触发。如果你对此感兴趣,我们已经在本书 Git repo 中包含了一个 example workflow。

如果 GitHub Actions 对你不可用,那么也可以通过直接在 virtual machine 中安装 dbt,并在该机器上创建 cron schedule 来运行 dbt。不过,我们不推荐这个策略,因为你必须管理 virtual machine。此外,dbt 本身并不 resource intensive,因为它只是 compile 你的 SQL 和 Python code,然后把它发送到 data platform。因此,从结果来看,用 virtual machines 在 production 中运行 dbt 通常有些 overkill。由于 virtual machine 提供的 flexibility 较低,我们建议你沿着 complexity ladder 往上走,使用 containerized deployment 和 / 或 orchestration platform。

最后两种在 production 中运行 dbt Core jobs 的策略,containerized deployment 和 orchestration platforms,实际上并不是 mutually exclusive。如果你熟悉在 containers 中运行 software,就会知道需要某种东西来 orchestrate 这些 containers,例如 Kubernetes 或 AWS Elastic Container Service(ECS)。除此之外,你也可以包含一个 data orchestration platform 来触发 dbt Core 的 containerized deployment。如果你想了解更多 containerized deployments,一个很好的资源是 Shimon Ifrah 的书 Deploy Containers on AWS(2019)。

一个示例是:将 dbt Core 放在 Docker container 中,把这个 container 包含在 AWS ECS task 中,然后让 Airflow 触发 ECS task。我们通常只会在两种情况下推荐将 dbt Core 部署在 container 中,并 / 或由 data orchestration platform 触发:一是你有 external factors 想用来触发 dbt;二是你希望在 dbt runs 完成后触发 downstream tasks。例如,在 dbt run 完成后,你需要 data orchestration platform 触发 Business Intelligence tool 中的数据刷新。如果你有这种 use case,或类似 use case,那么这种 deployment strategy 的额外复杂度可能是合理的。

可以想象,dbt deployment 很容易迅速变得过度复杂。因此,我们鼓励你在设计策略时认真思考 trade-offs。设计 deployment strategy 时需要牢记的事项包括:

  • Complexity vs. flexibility。
  • 记住 dbt 并不 resource intensive,因此不需要在 512GB Bare Metal machine 上运行它。大多数情况下,一个 micro EC2 instance 已经绰绰有余。
  • dbt 是否应该由 external upstream tasks 触发?
  • dbt 是否应该触发 external downstream tasks?
  • 你的 team 是否有 bandwidth 支持复杂 deployment?
  • 更复杂的 deployment strategy 是否为 business 增加额外价值?

CI/CD

dbt 的核心哲学之一,是把 software engineering best practices 带入 data transformations 的世界。贯穿本书,我们讨论过这些 best practices 中的一些,包括 collaborative development、testing、version control 和 documentation。为了扩展这个 idea,本节会聚焦 CI/CD,它可以说是 software development 中最重要的 best practices 之一。CI/CD,也就是 Continuous Integration / Continuous Deployment,是自动化 testing 和 software deployment 的过程,同样的思想也可以扩展到 dbt projects。CI/CD 的目标是确保对 codebase 的 changes 能够自动且可靠地集成到 production,使 teams 能够更快交付新的 code releases。

CI/CD 被拆成两个本质不同的 components。第一个是 Continuous Integration,它的目标是在 developers 将 changes merge 到 stable production codebase 之前,捕获 bugs、merge conflicts 或其他 integration issues。通常通过针对 changed code 运行 builds 和 tests 来实现。每个 CI pipeline 可以在不同时间触发,但最常见的方式之一,是当 developer 打开 pull request(PR)时触发 pipeline。在 dbt projects 开发语境中,CI pipeline 通常会在 PR 打开时 invoke dbt,来 build 和 test 对 models、seeds 和 snapshots 的 changes。本节后面会讨论为 dbt 设计 CI pipelines 的策略。

CI/CD 的第二部分是 Continuous Deployment,其中 code changes 的 release 以及这些 changes 的 deployment 被自动化。Continuous Deployment 通常发生在 Continuous Integration pipelines 通过之后,并且 developer 已经将 changes merge 到 production codebase 之后。CD 支持 frequent releases,因为 releases 已经被自动化,因此当 CD 被实施后,就不再需要等待 formal releases。为了成功实施 Continuous Deployment,teams 通常依赖多种 tools,例如 Git 这样的 version control systems、build tools(例如 Jenkins 和 CircleCI)、Terraform 这样的 infrastructure-as-code,以及 deployment automation scripts。拥有 robust testing strategies 很重要,包括 data quality tests 和 unit tests,用来确保 deployed code changes 的可靠性。

总体而言,CI/CD 可以理解为一个将 code changes 从 development environments 自动推进到 production environments 的过程,包括构建和测试 changes、自动化 releases 和 deployments,以及监控 deployments 来确保其稳定。对于 dbt codebases,我们发现实施 CI/CD 是一种值得遵循的 desirable practice。本节剩余部分中,我们会聚焦于在 dbt Cloud 和 dbt Core 中构建 CI/CD pipelines 的不同 components。

Setting Up an Environment for Continuous Integration

关于运行 CI pipelines 和 environments,有两种观点。第一种是直接使用 production environment;第二种是设置 dedicated CI environment。虽然你可以使用 production environment 运行 CI pipelines,而且很多人也这么做,但我们建议你创建 dedicated environment 来运行 dbt CI pipelines。在我们看来,这是最理想的 setup,因为它能最好地分离 production workflows 和 Continuous Integration workflows 的 concerns。

在 dbt Cloud 中设置 Continuous Integration pipeline 非常简单,因为你只需要创建一个 additional deployment environment。过去,我们会将 CI environments 配置为使用一组不同于 production environment 的 credentials。这被证明更适合 monitoring,因为我们可以轻松追踪到底是哪个 user 对 database 执行了 queries。

对于 dbt Core 来说,如果要设置 additional environment 用于 CI,则需要修改 profiles.yml file,并向 project profile 添加一个 additional target。图 10-8 展示了如何配置这个 target 的示例。根据你希望 CI pipelines 如何运行,可能还需要对 CI environment 做一些 additional changes。我们稍后会讨论你可能想做哪些改动。

image.png

图 10-8:profiles.yml file 中 CI target 示例

Running a CI Job Using State Comparison

想象一下,你刚刚完成一个 feature,并希望将 feature branch merge 到 main branch。你的 team 已经设置了一个 CI pipeline,每当创建新的 pull request 时,就 invoke 一次 full dbt build。可以想象,随着 codebase 增长,这个 CI pipeline 的 runtime 会线性增长。这会降低 developer efficiency,因为每次创建 pull request 时,你都必须坐着等待整个 dbt project 完整 build 并 pass。幸运的是,dbt 提供了一个名为 state 的 node selector,用来提升这个 feedback loop 的速度,并更好符合 CI/CD practices。

state method 是一种 node selector,你可以用它修改 dbt command,例如 dbt builddbt rundbt test。这个 method 允许你只 build 自己做出的 changes,而不是 build 整个 project。这个 node selector 的工作方式是,将你当前 changes 的 manifest.json 与 project 之前某个版本的 manifest.json 进行比较。在 production CI pipeline 的 context 中,你会与当前 stable production deployment 的 manifest 进行比较。本节剩余部分中,我们会看如何使用 state comparison 构建 CI pipelines,让它们在针对 main branch 打开 pull request 时触发。不过,dbt Labs 有一篇很值得阅读的文章: discourse.getdbt.com/t/how-we-sp…

和大多数与 production deployments 相关的事情一样,使用 dbt Cloud 和 dbt Core 构建 CI pipelines 的方式有很大差异。我们先看如何在 dbt Cloud 中构建这类 workflow,然后再构建一个类似的 dbt Core workflow。通过这样做,我们可以理解使用 SaaS offering 和自行构建 dbt CI pipelines 之间的 differences 和 trade-offs。

Note

state method 只是众多 node selectors 之一。如果你对运行更复杂、更具体的 dbt jobs 感兴趣,建议查看第 4 章 “Node Selection” section。

dbt Cloud

dbt Cloud 有一个内置 feature,名为 Slim CI,它简化了运行和测试 dbt transformations 的 CI pipeline 实现过程。Slim CI jobs 被配置为:当 pull request 针对 default branch,例如 main,被打开时触发;将你的 changes 与某个 deployment job 进行比较;并使用我们刚才讨论的 state node selector 运行。通过在 dbt Cloud 中设置 Slim CI job,你的 CI pipeline 只会运行 code changes 及其 downstream dependencies。

要使用 dbt Cloud 设置 CI job,需要做几件事:

  1. 可选:为 CI workflows 创建一个新的 environment。
  2. 确保 dbt Cloud 已经连接到你的 Git platform,例如 GitHub、Azure DevOps 等。
  3. 创建一个新的 dbt Cloud job。

我们已经讨论过如何创建 isolated CI environment,而且这一步本来也是 optional 的,所以直接跳到确保 Git platform 已与 dbt 集成。此外,在设置 dbt Cloud account 时,你应该已经连接到了 Git provider。如果你已经这么做,并且使用 GitHub,可以检查 repository settings 中的 GitHub Apps section,应该会看到类似图 10-9 的 dbt Cloud app。其他 Git platforms 也应有类似设置,让你验证 dbt Cloud 已集成。如果你没有看到任何显示 dbt Cloud 已连接的信息,应回到第 2 章,确认你已经正确连接 dbt Cloud 到 Git platform。

image.png

图 10-9:确认 dbt Cloud 已连接到 GitHub

现在你已经确认 dbt Cloud 与 Git platform 集成,就应该继续设置一个 new job。设置 dbt Cloud 中的 CI jobs 时,有三件特别需要做的事情。第一,是选择你希望 defer to 的 job,用于 state comparison。在 dbt Cloud UI 中,会有一个名为 “Defer to a previous run state?” 的 drop-down menu,允许你从 account 中选择另一个 job。对于 CI 来说,你应该选择目标 environment 的 job,也就是你正在将 changes merge 到的 environment 的 job。因此,如果你是从 feature branch merge 到 main,就应该 defer to production job。当这个 CI job 被触发时,dbt Cloud 会 fetch 你选择 defer to 的 job 的 manifest,从而知道本次 run 应该 build 哪些 models、seeds 等。

选择好用于 state comparison 的 job 后,需要定义作为 CI job 一部分运行的 dbt command。为了简单起见,我们使用 dbt build command,但你可能已经猜到,需要稍微修改这个 command,使它只 build changes。为此,可以将 command 修改为:

dbt build --select state:modified+

这个 command 以典型的 dbt build 开始,但随后使用 node selection 进行修改,其中我们使用了 state node selector。我们指示 dbt 只 build modified nodes 以及它们的 downstream dependencies,加号表示 downstream dependencies。可以想象,build downstream dependencies 很有用,因为这样可以让你确信自己的 changes 没有意外破坏其他东西。

最后一步是设置这个 job 的 trigger。如果你回忆本章前面,我们提到 dbt Cloud jobs 可以使用三种 triggers,而我们尚未讨论的是 Continuous Integration trigger。要使用这个 trigger,需要选择名为 “Run on Pull Request” 的 box。顾名思义,这个 job 会在 pull request 被打开时触发。在幕后,dbt Cloud 会监听来自 Git platform 的 webhooks,这些 webhooks 提供与 pull requests 被打开、更新、关闭等相关的 events。每当 dbt Cloud 接收到其中一个 webhook,就会触发你的 CI job。如果你使用 GitHub,你会在 pull request 中看到来自 dbt Cloud GitHub app 的 status check,显示 CI job 的状态,如图 10-10 所示。

image.png

图 10-10:GitHub PR 上的 dbt Cloud CI job status 示例

完成这些步骤后,你就成功设置了第一个 dbt Cloud CI job。使用 Slim CI 是提升 CI jobs runtime、并在你确信 changes 稳定之前阻止它们进入 production 的好方法。此外,dbt Cloud Slim CI process 最近的一些 updates 通过 autocanceling 已经过时的 jobs 进一步优化了流程。这可以帮助节省 cloud data warehouse bill,因为 dbt 不会使用不必要的 resources。

在继续之前,我们想讨论 Slim CI jobs 会在 database 中的哪里 build models。你可能已经在想这个问题,因为我们没有提到要设置额外 database 或 schema 供 dbt build。原因是,dbt Cloud 实际上会作为 Slim CI process 的一部分为你处理这件事。每当 dbt Cloud 中触发一个新的 CI job,就会在 environment 的 target database 中创建一个 new schema。Schema 会遵循命名 convention:dbt_cloud_pr_<job id>_<pr id>。dbt 会尝试在这个 schema 中 build 你的所有 changes;当 job 完成后,dbt 会尝试 drop 这个 schema。

在本文写作时,dbt Cloud 生成的 temporary schema 有一些已知 limitations,包括:

  • job 完成后 schema 没有 drop。
  • 某些 changes 被 build 到 target schema(s) 中,而不是 PR schema 中。

这两个 limitations 通常都与你修改了 generate_schema_namegenerate_database_name macros 有关。为了解决这个问题,可以进一步修改这些 macros,使其考虑 temporary PR schema,并使用类似下面的 logic。具体可能需要根据你的 custom schema logic 调整:

{%- set default_schema = target.schema -%}
{%- if custom_schema_name is none -%}
  {{ default_schema }}
{%- elif target.name == 'ci'-%}
  {{ default_schema }}_{{ custom_schema_name | trim }}
{%- else -%}
  {{ custom_schema_name | trim }}
{%- endif -%}

dbt Core and GitHub Actions

如果你运行的是 dbt Core,那么很遗憾,你无法使用 dbt Cloud app 来运行 CI jobs,因为该 app 能直接与 GitHub 等不同 Git platforms 集成。相反,你需要使用 CI tool 重新创建这个流程。在为 dbt Core 创建 CI job 时,我们会采取很多与 dbt Cloud 相同的方法。本节示例中,我们会创建一个 GitHub Actions workflow file,实现大部分 dbt Cloud Slim CI 提供的相同 functionality。

我们展示的流程与 dbt Cloud 实施 CI jobs 的最大区别在于:我们不会创建 temporary PR schema,而是会 clone 整个 target database,在这个 database 中运行 CI job,然后在 CI job 完成后 drop 这个 database。不幸的是,并不是所有 cloud data platforms 都支持 clone entire databases,但 Snowflake 支持。如果你使用的是不同 data platform,需要进一步思考这个流程,因为可能需要修改其中一部分,使其生成 temporary schemas。不过,如果你使用 Snowflake,我们更偏好这种 approach,因为它让 CI jobs 和 production jobs 之间多了一层隔离,因为它们运行在不同 databases 中。此外,这也是我们 preferred method,因为它比生成 temporary schemas 更容易实现。不是说生成 temporary schemas 很难,而是我们非常重视效率和 time to value。

高层来看,我们需要构建三个 new files,并修改一个 existing file:

  1. 创建一个 macro 来生成 PR database。
  2. 创建一个 macro 来 drop PR database。
  3. 更新 profiles.yml,使其指向 CI database。
  4. 创建一个 GitHub Actions workflow file。

对于前两步,需要创建几个 macros。Listings 10-1 和 10-2 提供了使用 Snowflake cloning feature 生成和 drop PR database 的 macros 代码。注意在这两个 macros 中,我们都引用了 target.database,并将名为 DBT_PR_NUMBER 的 environment variable 中的 value append 到 database name 末尾。当构建 GitHub Actions workflow 时,我们会将这些 macros 指向 production target,并传入一个包含 PR number 的 variable。这实际上会创建一个 production database 的 clone,每个 unique pull request 对应一个 database。

{% macro clone_database_for_ci() %}
  {% set sql='create or replace database ' + target.database + '_' + env_var('DBT_PR_NUMBER') + ' clone ' + target.database %}
  {% do run_query(sql) %}
{% endmacro %}

Listing 10-1:为 CI clone target database 的 macro

{% macro drop_database_for_ci() %}
  {% set sql='drop database ' + target.database + '_' + env_var('DBT_PR_NUMBER') %}
  {% do run_query(sql) %}
{% endmacro %}

Listing 10-2:drop temporary CI database 的 macro

在创建 workflow file 之前,先更新 profiles.yml,使它指向已经 clone 的 database,而不是 production database。为此,我们会更新 database name,将 DBT_PR_NUMBER environment variable append 上去,如图 10-11 所示。这会针对我们正在构建的 CI pipeline 的每次 invocation,动态更新 CI target,使其指向正确 database。

image.png

图 10-11:指向 CI database 的 updated CI target

最后,我们需要创建 workflow file,供 GitHub 运行 CI pipeline。GitHub 会查找名为 .github/workflows 的 directory,并根据其中的 YAML files 生成 workflows。为了让 CI pipeline 工作,我们需要在 workflow file 中包含多个步骤:

  1. Check out main branch。
  2. 在 virtual machine 中设置 Python。
  3. 安装 project dependencies。
  4. 针对 main branch 运行 dbt deps
  5. 针对 main branch 运行 dbt compile
  6. Copy production target folder,用于 state comparison。
  7. Check out PR branch。
  8. 运行 dbt deps
  9. 运行 clone_database_for_ci macro。
  10. 使用 state node selector 运行 dbt build
  11. 在最后运行 drop_database_for_ci macro。

这里步骤很多,但我们主要可以聚焦第 9 到第 11 步。第 9 步和第 11 步分别使用 dbt run-operation command 执行 clone_database_for_cidrop_database_for_ci macros,以便为我们 clone temporary CI database。我们会把第 9 步中的 command 指向 production target,使其在创建 clone 时使用正确的 target.database

Note

如果你查看 workflow file,会看到我们已经将 DBT_PR_NUMBER 设置为可用的 environment variable。dbt 会自动设置这个 environment variable,但你也可以通过 GitHub 传入其他 variables,并将它们存储为 secrets: docs.github.com/en/actions/…

接下来,我们会使用 node selection 运行 dbt build,但这个 command 会比在 dbt Cloud 中运行的那个稍微复杂一些。它稍微复杂,是因为我们必须告诉 dbt 用于比较的 state files 存在哪里,同时也需要告诉 dbt 使用哪个 target。Listing 10-3 展示了这个 command 在 workflow file 中的样子。

dbt build \
--select state:modified+ \
--state ./production_target/target \
--profiles-dir . \
--target ci

Listing 10-3:workflow file 中使用 node selection 的 dbt run 示例

如果你有兴趣在自己的 project 中运行这个 workflow,可以在本书 repo 的以下路径找到完整 workflow file:

~/.github/workflows/build_dbt_diffs_ci.yml

可以看到,使用 dbt Core 运行 CI job 比使用 dbt Cloud 更复杂。自行构建 CI pipeline 而不是使用 dbt Cloud,需要更多 initial setup;但通常一旦跨过 setup curve,这些 workflows 往往就能稳定工作。如果你不熟悉构建 GitHub Actions,或者其他 Git platform 的 equivalent,我们建议考虑使用 dbt Cloud。

Linting

很多人在 formatting code、naming conventions 或其他 coding standards 上都有自己的偏好。根据我们的经验,写 SQL 的人对如何 format code 的偏好往往最强烈。示例包括:

  • Trailing vs. leading commas
  • CAPITAL vs. lower keywords
  • CamelCase vs. snake_case

这类清单还可以继续列下去,但在这些 stylistic differences 上浪费宝贵 developer time 没有意义。相反,你的 organization 应该为 codebase 建立 standard style guide,明确所有 commit 到 main branch 的 code 应该如何 style。在 software development 世界中,linting 指的是分析 code 中的 stylistic issues、是否遵守 coding standards,以及 codebase 中的不一致性的过程。Linting tool,也称为 linter,会基于一组 predefined rules 运行,这些 rules 定义 repository 的 coding standards 和 best practices。通过将 linting 纳入 development process,你和 team 可以提升 code quality、强制执行 coding standards、尽早捕获 errors、增强 collaboration,并促进 codebase maintainability。Linters 有助于为 codebase 中的 stylistic decisions 建立 consistency 和 reliability。

我们强烈认为,你应该将 linter 作为 Continuous Integration pipeline 的一部分来实施,因为它有助于维护进入 production codebase 的代码在 style quality 上的高标准。最常用的 SQL linters 之一叫 SQLFluff。SQLFluff 极其 customizable,可以帮助你解决前面提到的所有 stylistic differences,甚至更多。SQLFluff 会检查是否遵守它称为 rules 的规则,下面列出部分规则:

  • Enforcing table aliases
  • Preventing ambiguity,例如要求使用 inner join 而不是 join
  • Enforcing a keyword capitalization standard
  • Requiring coalesce instead of ifnull
  • Preventing the use of subqueries and requiring CTEs instead

这个列表还可以继续。我们鼓励你实施 SQLFluff。我们也喜欢将 SQLFluff triggers 作为 CI pipelines 的一部分,这样在 pull request merge 到 main branch 之前,可以确保遵守 proper linting rules。我们在本书 repository 中包含了一个 GitHub Actions workflow file,可以在 pull request 打开时触发 SQLFluff linting。此外,你也可以将其他 open source tools 纳入 CI workflows,例如 dbt-checkpointdbt-project-evaluator。本书前面已经讨论过这些工具,但我们认为值得考虑将它们加入 automated checks。

Continuous Deployment

对于 Continuous Deployment 和 dbt Cloud 来说,你甚至不需要思考 production-ready code 如何对你创建的 jobs 可用。相反,每次 dbt Cloud 中的 job 开始时,dbt 会简单地 fetch production codebase 的 latest state。这简化了 dbt deployment process,因为在 Continuous Deployment 方面不需要你付出任何 effort。

而运行 dbt Core deployments 时情况完全不同,因为你没有付费给 SaaS company,让它替你处理 deployment。因此,需要由你构建 Continuous Deployment process,使 production code 的 new releases 发生时,这些 changes 能在 dbt jobs 中可用。这个 process 会因你如何部署 dbt Core 而差异很大。例如,如果使用 GitHub Actions 部署 dbt Core,那么这个 process 非常简单,因为每次 GitHub Action 被触发时,你只需要 checkout production codebase。因此,这种 deployment approach 没什么需要考虑的。但如果你有更复杂的 approach,例如 containerized deployment,就需要一个 automated pipeline 来完成以下步骤:

  • 构建 new containers,并 push 到 container repository。
  • 让它们可供 container orchestration tool 使用,例如 K8s、ECS 等。
  • 使用 updated dbt code 重新部署 data orchestration tool,使其能够访问新代码。

由于 dbt Core 有很多不同 deployment options,我们无法覆盖每一种 Continuous Deployment pipeline 的构建步骤。但我们希望你意识到,在考虑 production 中运行 dbt Core 时,这是必须考虑的事情。

Final Deployment Considerations

本章中,我们已经覆盖了很多需要思考的事项,用于决定你希望在 dbt Cloud 中运行 production deployment,还是使用 dbt Core 设计自己的 deployment。我们覆盖了一些关键 components,例如 environment management、triggering jobs 和 CI/CD pipelines。但如果你刚开始管理 production deployment of dbt,还有一些 additional considerations 很容易被忽略。本节最后,我们会覆盖 dbt documentation deployment、accessing logs,以及 alerting / monitoring。

Documentation Deployment

第 9 章中,我们覆盖了 generated documentation 在 dbt 中如何工作,并主要聚焦如何轻松将 documentation 部署到 dbt Cloud。在 dbt Cloud 中,这很简单:在某个 production job 中打开一个按钮,指示 dbt Cloud 为你 generate 和 publish new documentation。如果你使用 dbt Core,这个 process 会变得更复杂。我们列过一些使用 dbt Core 时部署 documentation 的方式,但没有展开太多细节。部署 dbt documentation 的两种简单 approaches 包括:在 AWS S3 上发布为 static site,或者发布到 Netlify。

这两个选项都是常见 approaches,复杂度略有不同。首先,部署到 Netlify 是最简单的方式,因为你可以通过它们的 command-line tool 将 documentation push 到那里;如果你付费使用 Netlify,也可以设置 authentication。这可以在没有太多 administration overhead 的情况下,把 documentation 放在安全墙之后。第二个选项,部署到 AWS S3,也很简单,因为你可以使用 AWS CLI 部署;但 trade-off 是当你想把 documentation site 放到某种 authentication 后面时,复杂度会更高。不过,如果你选择将 documentation site 托管在 AWS S3 上,需要决定保护它的最佳方式。

自行部署 documentation,而不是让 dbt Cloud 处理的最后一个 consideration,是可以 customize documentation。如果你自己部署 documentation,几乎可以 override 任何想要的东西。根据我们的经验,我们很喜欢这样做,因为可以更新 CSS,把 company logo 放到 documentation 顶部,而不是 dbt logo;也可以 override documentation colors,使其更符合 company brand。当然,这会增加 complexity,但如果你对此感兴趣,确实可以做到。GitLab 已经 open sourced 了它们的 dbt project,我们建议查看其 repository 中这个 directory,看看它们如何 customize dbt documentation: gitlab.com/gitlab-data…

Monitoring and Alerting

想象一下,你有一个 production dbt job,应该每小时在第 15 分钟运行一次,用于更新 business-critical dashboard。但现在是下午 1:30,一个 stakeholder 给你发消息,告诉你 dashboard 这个小时还没有更新。让 stakeholder 主动联系你,告诉你 data 已经 stale 或 data 不准确,并不是 monitoring data transformation pipelines 的理想方式。在将 business-critical data products 部署到 production 之前,最好提前思考这些事情。Alerting 和 monitoring 是 production deployment of dbt 中最重要的 considerations 之一。

Alerting 和 monitoring 有很多类别,但我们聚焦三类:

  • Data freshness
  • Data quality
  • Job results

你应该实施的第一项 monitoring 是 data freshness checks,可以配置 dbt 针对 sources 进行验证。这很有用,因为它可以帮助你识别前面场景中的 root,也就是 stakeholder 提到 dashboard 数据过期。在第 3 章中,我们讨论过 source data freshness checks;借助它,当 source data out of date 时,dbt 可以抛出 error 或 warning。没有这些 checks,dbt runs 中可能会出现 silent failures,因为 transformation jobs 会照常运行,但它们不会处理任何 data。

接下来,你应该实施 data quality checks。第 8 章中我们详细讨论过 data quality checks,但这里想再次强调:在进入 production 之前实施这些检查非常重要。Data quality tests 也有助于防止 silent failures,因为它们可以确保你关于 data expected output 的 assumptions 是有效的。这些 tests 可以从 not null tests、uniqueness tests,到更复杂的 tests,例如监控 data 中 unexpected trends。

最后,你应该 monitoring job results,因为它们是你捕获 loud failures 的地方。在 dbt Cloud 中,你可以配置 email 和 Slack notifications,使其根据 jobs 的某些 criteria 触发。假设你希望每次 production job failed 时 alert 一个 Slack channel。这是有用信息,但如果能获得关于 failures 的 additional context 会更好。这正是 monitoring tools 发挥作用的地方。例如,你可能希望配置 dbt artifacts 发送到 Datadog、Metaplane 或类似工具,从而在 failures 发生时发送带 context 的 alerts。这类 tools 也可以确保当 jobs 延迟运行、根本没有运行,或者运行时间比正常情况更长时,你也会收到 alerts。此外,你可能会考虑设置 PagerDuty 等工具,使 on-call team members 在 jobs 持续失败时收到 alert。不过,如果选择向 PagerDuty 发送 alerts,我们鼓励你非常谨慎地选择哪些事件会触发 on-call incident。否则,alerts 很容易变得 noisy,并最终被整个 team 忽略。

Summary

贯穿本书,我们一路从在 development environment 中设置 dbt 并学习 dbt 的主要 components,走到本章讨论将 dbt 部署到 production 时需要考虑的事项。我们重点比较了 dbt Cloud 与自行设计 dbt Core deployment strategy,因为二者之间有非常明显的 trade-offs。如果用一个词总结差异,那就是:simplicity。如果选择使用 dbt Cloud 部署到 production,那么你把 deployment complexities 的负担交给 dbt,但这当然有成本,因为 dbt Cloud 是付费 SaaS product。

当谈到将 dbt Core 部署到 production 时,你的 deployment options 更广泛也更灵活。但与 dbt Cloud 相比,这带来的 trade-off 是 complexity。不过,如果符合以下情况,我们并不认为你应该回避 dbt Core deployment:

  • 你的 team 较小。
  • 你的 team 中有熟悉 DevOps 的人。
  • 你希望完全控制 deployment。

无论你选择使用 dbt Cloud 部署,还是 DIY,都需要理解如何设置 production-grade jobs。你需要考虑 jobs 需要以什么 frequency 运行,以及它们是 scheduled 还是 event triggered。

在部署到 production 之前,也应该考虑实施 CI/CD practices,这样你就能在 deployment 前对 code quality 有信心。常见 CI/CD pipelines 包括对 pull request 中新增或修改的 dbt code 进行运行和测试,许多 teams 也会实施 linting。你可以让 CI/CD 变得复杂,也可以保持简单,但我们提醒你:只有当复杂度带来更多收益时,才增加复杂度。

最后但绝不最不重要的是,你需要确保 project 设置了一定程度的 monitoring 和 alerting。如果没有,而 jobs 失败了,并且它们一定会失败,你就有破坏 data products 信任的风险。因此,最好永远领先于这些不可避免的 failures,并内置 alerting,使你能在 stakeholders 之前知道 failures。