在本章中,我们将探索 dbt 中 sources 和 seeds 的概念。这两项内容大体上彼此独立,但由于它们主题相对较小、却非常重要,所以我们将它们合并到同一章。Sources 指原始数据源,例如来自 databases 的数据,它们会作为 dbt 的输入。Source tables 会通过一个 Jinja function 在 dbt models 中被引用,我们会在本章后面进一步讨论。另一方面,Seeds 指用于填充小型、不变数据初始集合的数据,通常用于 reference data 或 lookup tables。Seeds 仍然会在 models 中通过 Jinja function 引用,但它们使用的 command 与 sources 不同。
本章中,我们将讨论在 dbt 中处理 sources 和 seeds 的 best practices,并提供如何在真实场景中使用它们的示例。我们会介绍它们是什么、使用它们的好处,以及常见的使用和配置方式。对 models 有基本理解会有帮助,models 会在第 4 章介绍;但我们认为第 1 章中已经做了足够介绍,可以让你在这里理解相关内容。不过,我们会在下一章更深入地覆盖 models。
What Are Sources?
在 dbt 中,source 引用的是你所连接 database 中的 databases、schemas 和 tables,dbt 可以从这些位置读取 data。Sources 定义在 YAML files 中,并在 model 创建过程中被引用。你会希望在搭建 dbt project 时把这件事作为标准实践,原因有很多。与其在每个 script 或 model 中都引用 data location,不如使用 sources,在 sources.yml file 中只命名和定义一次。这在你开始处理多个 environments 时尤其重要,例如 development、QA、production,而这些 environments 位于不同 databases 或 schemas 中。我们会在接下来的几个 sections 中深入讨论这一好处以及其他好处。
Note
拥有多个 sources.yml files 非常常见,尤其是在较大的 dbt project 中。你不能重复使用同一个 name,因此需要给它们起一个对 developer 来说可识别的名称。通常,每个 source YAML file 支持 model hierarchy 中的单个 folder 或一组 folders,并且会命名为能够引用其 connection 的名称。
默认情况下,dbt 不会为你创建 source file,因此你需要自己创建。创建它有很多不同策略,但刚开始时,我们建议在 models folder 的 root 中创建 sources.yml files,并将所有内容存放在那里。你可以随意命名 files,但为了简单起见,我们建议先从 sources.yml 开始。随着 sources 增加,你可能会考虑添加更多 source files,这完全没问题。dbt 允许你灵活地按自己喜欢的方式设置。第一次创建 sources.yml file 时,它显然是空的。为了使用它,你需要添加正确的 YAML syntax。YAML syntax 对 spacing 和 tabs 非常敏感,所以刚开始搞错几次也不用担心。你会逐渐掌握。如果使用 dbt Cloud,dbt 在识别错误并告诉你如何修复方面也做得很好。
Reminder
你只能为已经存在于 dbt target data store 中的数据创建 sources。
Sources 在 source YAML files 中定义,并嵌套在 sources: key 下。你可以让 sources.yml file 捕获基本信息,也可以添加其他多种内容来扩展它。为了简单起见,我们先只看基础内容。假设我们有一个名为 raw 的 database,它存在于 dbt 连接到的 data store 中。raw database 有多个 schemas,以及与这些 schemas 关联的 tables。在我们的示例中,有一个名为 product 的 schema,以及两张 tables:items 和 purchases。在基础层面上,图 3-1 展示了我们如何在 YAML file 中组织这些内容。
图 3-1:基础 sources.yml file
name 是 source 的名称,也是你稍后将在 source Jinja function 中使用的第一个 value。命名非常直观,不过我们仍然希望明确说明每个 config 的含义。database 是 database 的名称。schema 是 database 中 schema 的名称。最后,tables 是 tables 的名称。在这个示例中,schema name 是唯一非必需项,但我们认为最佳实践是始终像必需项一样包含它。如果你不使用 schema value,那么 name value,也就是 source name,会被解释为 schema name。
你可能也会疑惑 “version: 2” 是什么意思。在 dbt 早期,YAML files 的样子与现在看到的版本非常不同,因此他们添加了 version,使结构更容易扩展。虽然 version 2 目前是这些 files 唯一被支持的版本,但它仍然作为 required key 被保留下来,以防未来引入新的 file structure。保留这个字段比其他替代方案容易得多。尽管它当前是静态值,但为了让 sources.yml file 正常工作,这个 value 必须始终存在。此外,在你希望 dbt 在 project 中使用的任何 YAML files 中,它也都必须存在。
现在我们已经看过 source YAML file 的一些基础 components,让我们看看还能用它做什么。先从图 3-1 的基础示例开始,添加图 3-2 中展示的一些额外项。
图 3-2:带有 additional values 的基础 sources.yml file
这里可以看到,我们添加了 description、source freshness 和 tests。Descriptions 可以添加到 YAML files 中,用于记录其中的 objects。它们并不只用于 sources.yml,也可以用于第 4 章将介绍的 schema.yml files。它们非常有价值,因为在构建 documentation site 时会被使用。dbt 会读取 YAML files,并为你填充这些信息。我们将本书第 9 章专门用于 documentation,会在那里更深入覆盖这个主题。
我们也可以在这个 file 中添加 tests。正如图 3-2 所示,我们在 items table 的 id column 上使用了两个 dbt built-in tests:not_null 和 unique。这些 tests 会确保 id column 在该 source 被 downstream models 处理之前,没有任何 NULL values,并且 id 只包含 unique values。我们还没有在本书中覆盖 tests,但会在第 8 章更详细讨论。不过,我们想提前指出,可以在 models 和 sources 上使用 tests。
最后一个新选项是 freshness。这是一个用于检查是否存在 stale data 的选项,也就是 data 是否在可配置时间内没有更新。它会触发 warning 或 error。我们会在本章后面的 section 中进一步覆盖它。
一旦你在 sources.yml 中定义了 sources,就可以在 dbt models 和 tests 中通过引用 source name 来使用它们。为此,你会使用名为 source 的 Jinja function,其形式如下:
{{ source('source_name', 'table_name') }}
当运行 model 时,这会让 dbt 从 source_name source 中的 table_name table 读取 data。本章后面会进一步说明。
Complete Source Properties Syntax
我们已经提到了一些 sources.yml file 可用选项,但这里想提供完整 YAML syntax。下面展示了当前支持的所有可以添加到 sources.yml file 中的选项:
version: 2
sources:
- name: <string> # required
description: <markdown_string>
database: <database_name>
schema: <schema_name>
loader: <string>
loaded_at_field: <column_name>
meta: {<dictionary>}
tags: [<string>]
overrides: <string>
freshness:
warn_after:
count: <positive_integer>
period: : minute | hour | day
error_after:
count: <positive_integer>
period: minute | hour | day
filter: <where-condition>
quoting:
database: true | false
schema: true | false
identifier: true | false
tables:
- name: <string> #required
description: <markdown_string>
meta: {<dictionary>}
identifier: <table_name>
loaded_at_field: <column_name>
tests:
- <test>
- ... # declare additional tests
tags: [<string>]
freshness:
warn_after:
count: <positive_integer>
period: : minute | hour | day
error_after:
count: <positive_integer>
period: minute | hour | day
filter: <where-condition>
quoting:
database: true | false
schema: true | false
identifier: true | false
external: {<dictionary>}
columns:
- name: <column_name> # required
description: <markdown_string>
meta: {<dictionary>}
quote: true | false
tests:
- <test>
- ... # declare additional tests
tags: [<string>]
- name: ... # declare properties of additional columns
- name: ... # declare properties of additional source tables
- name: ... # declare properties of additional sources
可以看到,配置 sources.yml file 有相当多选项。本章和本书后续内容中,我们会继续讨论其中多个选项。
Benefits of Sources
我们已经提到过在 dbt project 中使用 sources 的几个好处,现在进一步深入一些细节。
Reusability
通过在 central location,也就是 sources.yml file 中定义 sources,你可以在 dbt project 的多个 models 中复用它们。不需要在每个引用它的 model 中 hardcode source information。
Maintainability
在 sources.yml 中定义 sources,使你能够将 data source locations,也就是 database、schema 和 table names,与实际 dbt code 分离。这让维护 dbt project 更容易,因为你可以在一个地方更新 source location details,而不是在多个 models 和 tests 中分别更新。如果你在多个 environments 之间推送 code,例如 development、QA、production 等,并且 schema objects 的命名不同,也可以在 source files 中引用 environment variables。我们会在本书后面更详细覆盖这一点。
Consistency
使用 sources 后,所有与 connection 相关的 details 都存放在一个位置。如果 database 或 schema name 变化,不需要更新每个引用它的 model,只需要在一个地方更新。如果有多个 developers 在同一 project 中工作,每个人也都知道 connections 在哪里被引用。此外,你也有一个统一位置可以为 source data 添加 descriptions 和 tests。
Lineage
将 sources 与 source() Jinja function 一起使用,会影响 model run order,也就是 dbt DAGs。如果有 tests、source freshness checks 等需要优先执行,这可以确保它们确实先发生。
我们认为,在 dbt project 中创建和管理 sources 是 dbt 的核心能力,也是你绝对应该做的事情。
Referencing Sources in Models
使用 sources 的主要 use case,是在 transformation models 中引用它们。为此,需要使用 source Jinja function。source Jinja function 会替换 SQL statement 中用于 table names 的部分,其语法如下:
{{ source('source name','table name') }}
在这个 syntax 中,你用 source name 替换 source name,用 table name 替换 table name。使用图 3-2 中展示的内容,让我们看 Listing 3-1 中的示例。
select
items.*
from {{ source('Product', 'items') }} as items
inner join {{ source('Product', 'purchases') }} as purchases
on items.items_id = items.id
Listing 3-1:使用 source function 的基础 query
在这个示例中,我们为两张 tables 输入了 source name,即 Product,以及要读取的 table name,即 items 和 purchases。如果查看 dbt 如何 compile 这个 query,它会像 Listing 3-2 中展示的那样。
select
items.*
from Product.public.items as items
inner join Product.public.purchases as purchases
on items.items_id = items.id
Listing 3-2:Listing 3-1 的 compiled query
Pro Tip
在 dbt Cloud 中开发时,你可以输入 __,也就是两个 underscores,来自动填充 source syntax,以及许多其他 Jinja functions 的 syntax。
你只能引用存在于 sources.yml file 中的 sources。如果它们不存在,就会报错,直到你添加它。如果 source 已经存在但仍然报错,请检查大小写是否一致。dbt 的许多部分是 case sensitive 的,而这里就是其中之一。作为本书作者,我们刚开始学习 dbt 时,花了很多时间排查问题,最后发现只是某个地方不该大写却大写了。还值得一提的是,spaces 和 proper quoting 也很重要,并且也可能导致 failures。
Note
你只能引用存在于 sources.yml file 中的 sources。如果不存在,就会报错,直到你添加它。
我们经常看到的其他问题,是 sources.yml file 中写错,或者在 models 中添加 source 时写错。如果 things aren’t working,可以检查以下内容:
- 确保你在
schema.ymlfile 中把 values 放在正确位置。例如,确认没有无意中把 database name 放到 schema name 应该在的位置。 - 检查你引用的 source 是否真的存在于 data store 中。这听起来显而易见,但有时你快速填充 sources 时,可能是在依赖你以为知道的内容,而不是你确认知道的内容。
- 确保用于连接 dbt 到 data store 的 account 有访问 source destination 的权限。
- 确保 source Jinja function 中的 letter casing 与
sources.ymlfile 中一致。如果一个地方首字母大写,另一个地方小写,就可能出现问题。
Source Freshness
正如我们已经讨论过,dbt 只是一个 transformation tool,因此它依赖其他 data pipelines、services 和 / 或 processes 先将 data 落到 data store 中。尽管这部分是单独管理的,但已经 ingest 到 data store 中的数据,是 dbt 正常运行的关键。幸运的是,dbt 提供了一种方式来帮助我们处理这一点,也就是 source freshness。只需增加少量额外 configurations,dbt 就可以监控 source tables 中 data 的 “freshness”。这对确保 upstream data pipelines 处于健康状态、并且仍有 new data 进入非常有用。更具体地说,source freshness 指 source 中 data 的 timeliness。如果 source 拥有 up-to-date data,它就被认为是 “fresh”;而 “not fresh” 的 source 可能包含 outdated data。
dbt 中的 source freshness 并不打算替代 ingestion processes 上的良好 monitoring。你仍然需要 monitoring。不过,它是对 monitoring 的补充性检查。在一些组织中,负责 data ingestion 的人和使用 dbt 的人之间可能存在职责分离。如果负责 ingestion 的人没有建立良好 monitoring,或者没有与使用 dbt 的人沟通,那么拥有 source freshness 这样的选项非常好。我们仍然强烈建议找到办法建立 monitoring,和 / 或弥合沟通 gap。
Note
dbt 中的 source freshness 不能替代对 ingestion processes 的良好 monitoring。
Source freshness 的另一个好处是,如果你将该检查作为 transformation load 的第一步运行,那么可以在继续运行剩余内容之前提前 error out。当你希望避免在 data stale 时运行完整 transformation process,这非常有帮助。即使你已经知道存在 ingestion problem,如果 jobs 仍然按 schedule 启动,那么知道它们会在早期自动 fail 也很有帮助。一旦 ingestion issue 被解决,你的 jobs 就会无需人工干预地恢复正常。我们偏好在 transformation process 中采用 fail early and loudly 的 approach,也就是当事情未按预期工作时,尽早且明确失败。
Configuring Source Freshness
要配置 source freshness,需要向 sources.yml files 中添加一些 additional configuration。图 3-2 中,我们展示过一个包含 source freshness 的示例,并简单提到了这个主题。现在让我们更深入。先从理解 YAML syntax 开始,如图 3-3 所示。
图 3-3:Source freshness syntax
图 3-4 展示了另一个只配置了 source freshness 的 sources.yml file 示例。
图 3-4:配置了 source freshness 的 sources.yml 示例
在图 3-4 中,我们用几种方式添加了 source freshness configuration,以展示可用选项。先看 source freshness 的 syntax,再看这些选项:
freshness
这是用于告诉 dbt 你正在配置 source freshness 的 block。
warn_after
它告诉 dbt:如果 source 在配置时间内没有刷新,则触发 warning message。Warnings 不会使 build 失败,但会触发 message 并写入 logs。要设置时间,需要提供 period,即 minute、hour、day,以及 count。
error_after
它告诉 dbt:如果 source 在配置时间内没有刷新,则触发 error message。Errors 会使 build 失败,直到问题解决。要设置时间,需要提供 period,即 minute、hour、day,以及 count。
loaded_at_field
这是 table 中的 date/time column,dbt 用它来检查 freshness。你可以使用任何 date/time column,但建议使用专门属于 ingestion process 的 metadata column,用于跟踪 data 何时被 loaded。
filter
这里可以添加 WHERE clause 到 freshness check 中,以限制 scanned data 量。当你想提升 performance,而这个 check 耗时太长时,它特别有用。不过请注意,这个 value 只适用于 source freshness query,不适用于 sources.yml file 中的其他内容。
现在你理解了 configuration values,让我们回到图 3-4,看看我们如何设置它。首先,我们为 database,也就是 Product,设置了一个 default configuration,它将应用于我们添加的所有 tables。这个 default configuration 表示使用 DateLoaded field 检查 freshness,如果该 date/time 早于 24 小时则 warn,如果早于 48 小时则 error。我们不必在 database level 添加它,也可以在 table level 添加。
Note
Source freshness 可以在 database level 或 table level 配置。
Database level 的 source freshness 会为其他所有内容创建 default,但这不意味着我们不能 override 它。我们可以 override warning time、error time、check column,甚至告诉它完全不要检查 freshness。要 override,只需要在 YAML file 中更 granular 的 level 指定 value。例如,如果有一张 table 想要更严格或更宽松的设置,可以为这一张 table 指定 values。在 dbt 中,更 granular 的设置始终优先。因此,如果你应用了 default,但想为部分 objects override,是可以做到的。
如何处理这一点有大量自定义空间,不同 teams / organizations 会采用不同 approaches。作为本书作者,我们通常从 table level 配置 source freshness 开始,并将其限制在那些我们知道会产生较高数据变化量的 essential tables 上。多数情况下,load 这些 tables 的 process 也会 load 其他 tables,因此如果存在 ingestion issue,只检查几个地方就能知道。我们也会确保 ingestion pipelines 上已有良好 monitoring,这样能在 dbt 告诉我们之前就知道有问题。
Executing Source Freshness Checks
dbt 不会自动将 source freshness checks 作为 dbt build 或 dbt run 的一部分运行,而是提供了一个单独 command 来处理这件事:
dbt source freshness
如果你想确保 data 是 up to date,需要确保它在运行 transformations 之前执行。你也可以考虑设置一个更频繁运行的独立 process,与 transformation process 分开,这样可以更早捕获问题。
如果使用 dbt Cloud,那么在创建新 job 时,有一个 checkbox 可以勾选,让它自动执行该检查,如图 3-5 所示;或者你也可以在流程中添加一个执行 dbt source freshness 的步骤。
图 3-5:dbt Cloud 中包含 source freshness 的 checkbox
Other Source File Options
我们已经覆盖了创建 sources.yml file 所需的 basics。不过,如果你查看本章前面展示的 syntax,会发现还有一些没有覆盖的选项。其中一些会在本书剩余章节中更详细覆盖。
loader
用于描述将指定 source data 加载到 data store 中的工具的文本。该 field 只用于 documentation purposes。
meta
meta field 是一种向 dbt 中的 object 附加 text metadata 的方式。它可以添加到 sources.yml files 中,并且可以包含 data source owner、source 是否包含 sensitive data,或 source system name 等内容。你可以按自己的需要使用它,它是一种将 metadata 附加到 objects 的方式,扩展了 dbt out-of-the-box coverage 之外的内容。
tags
这些是 optional metadata,可以附加到 models / objects 上,之后可用作 resource selection syntax 的一部分。例如,运行 dbt run 这样的 dbt command 时,可以 filter to running for or against models with tags。
quoting
它指定 dbt 在使用 source data 时,是否应该 quote databases、schemas 和 identifiers。dbt 会根据 data store 自动设置 default values。通常不建议修改它,除非你有特定理由。
Utilizing Sources in Multiple Environments
如果你计划为 warehouse 创建多个 environments,例如 Dev、QA、Prod 等,而且你确实应该这样做,并且需要在 dbt project 中支持这些 environments,那么我们强烈建议考虑在 sources.yml file 中,对任何可能因 environment 而变化的内容使用 environment variables。这意味着,如果你的 database 或 schema names 在 development 和任何 deployment environments 之间不同,就需要考虑这一点;否则,当你 promote code 时,project 会失败。要引用 environment variable,需要在 YAML files 中使用 env_var function。Environment variables 可以用于 profiles.yml、dbt_project.yml 和 sources.yml files。
我们经常看到 Snowflake users 为所有 environments 使用同一个 instance,因为它支持 storage 和 compute 分离。Teams 可以在 environments 之间使用独立 warehouses,也就是 compute buckets,并使用特定 roles 限制访问,而不必承担运行独立 instances 的额外复杂性。不过这样做时,一个 database name 只能使用一次,这就会导致 environments 之间存在 naming variance。这正是 environment variables 发挥作用的地方。通过在 sources.yml file 中添加 variable,dbt 可以根据当前运行的 environment,从 project 中解释这些 values。
我们会在本书第 10 章更详细覆盖这个主题,但这里先提前提到,让你知道这是可行的。
Generating Your Source File
生成 sources.yml 时,有几个选项。你可以手动输入所有内容,也可以 programmatically 创建它。如果有很多 sources 和 objects 需要引用,这可能会花很长时间。我们认为,手动填充所花的时间可能是值得的,因为这是一个添加 descriptions、source freshness、tests 等内容到 sources.yml file 的机会,而这些内容你可能原本会推迟。请记住,这件事只需要填充一次。
如果有很多 sources 和大量 tables,那么生成 sources.yml 可能是一个漫长过程。幸运的是,我们可以利用 dbt package hub(hub.getdbt.com)中的 codegen package,为我们生成大部分 scaffolding。这个 package 中有一个名为 generate_source 的 macro,可以为 source 生成 lightweight YAML,你可以将其 copy and paste 到 sources.yml file 中。由于这是一个 open source package,从现在到你阅读本书时可能已经发生变化,所以我们不会提供具体使用步骤。不过,我们相信这个功能会一直存在,并建议查看 dbt package hub 上的最新版 package instructions。codegen package 是 dbt arsenal 中的常用工具,本书中也会覆盖它,主要是在第 6 章。
What Are Seeds?
Seeds 是 dbt project 中的 comma-separated values(CSV)files,dbt 可以将它们 load 到 data store 中。它们最适合小型、不变的 datasets,并且这些 datasets 可能没有 source system 可供拉取。Seeds 会存储在 dbt project 中、接受 version control,并且可以 code review;而其他 source data 则维护在 dbt project 之外。好的 use cases 包括 mapping tables,例如 country codes 到 country names、美国州缩写到美国州名,或其他某类 static mapping;也可以创建你需要手动维护的列表,用于 include 或 exclude。糟糕的 use cases 包括大型变化 datasets、加载导出为 CSVs 的 raw data,或任何包含 sensitive information 的 data。
在决定是否在 dbt project 中使用 seeds 时,可以考虑以下几点:
Data freshness
Seeds 通常用于向 database 插入一组 data,并不意味着要经常修改或更新。这意味着 seed 中的数据通常不会随时间发生太大变化。如果需要定期更新 data,那么 seeds 可能不是最佳选择,因为这会制造大量额外 manual work。
Data volume
Seeds 旨在用于设置 database 所需的小量初始数据。如果有大量 data 需要定期 load 到 database 中,seeds 可能不是最高效的选择。此时,你可能需要考虑其他选项。作为一般经验规则,我们认为 seeds 通常应少于 5000 行。此外,当 seed files 超过 1MB 时,dbt 无法进行 checksum state comparison。
Data dependencies
Seeds 常用于设置 downstream models 所需的 reference data。如果 models 依赖的数据经常变化或定期更新,那么 seeds 不是最佳选择。
在 dbt 中,seeds 通常存储在 dbt project 的 seeds directory 中,并会在 warehouse 中 materialized as tables。你需要运行一个单独 command 来用 seed data 填充 tables,这个 command 叫:
dbt seed
当你运行 dbt seed 时,dbt 会读取 seed directory 并创建 tables。dbt build 运行时也会自动构建 seeds。
让我们看一个名为 USStates.csv 的 seed file 示例,它将 state abbreviations 映射到 state names。图 3-6 展示了该 file 的部分内容。
图 3-6:USStates.csv seed file 示例
对于 seed file,第一行是 headers,后续每一行包含 data。该 file 的结构与任何 text version 的 CSV file 一样。文件必须格式正确,否则在尝试 build 时可能会出错。当出现问题时,我们最常看到的问题通常发生在 header line,通常与 formatting 或 headers 与 columns 数量不匹配有关。
Seed 生成后,会在 warehouse 中 materialize 为一张 table,table 名称与 file name 相同。如果你想改变这个行为,有 configuration options 可用,但不能像其他 models 那样直接在 file 本身中修改。我们建议直接将 CSV file 命名为你希望 warehouse 中 table 被称呼的名字。值得注意的是,每次调用 dbt seed 或 dbt build 时,dbt 都会重新构建与 seed files 关联的 tables。Seeds 在 project 中的功能非常类似 models,并且使用 ref() Jinja function 引用。我们会在下一章更详细覆盖 models 和 ref() function。
Executing Seeds
要在 data warehouse 中构建 seed data,只需要运行:
dbt seed
该 command 会 load 位于 dbt project 的 seeds-path directory 中的所有 CSV files。类似 dbt run command,你也可以添加 --select flag,将其限制为只构建某个特定 seed file。
使用图 3-6,我们可以执行以下 commands 之一,在 warehouse 中构建美国州列表。第一个选项展示运行基础 command,第二个选项展示只运行特定 seed:
-- Run all seeds including USStates.csv
dbt seed
-- Run just the USStates.csv seed
dbt seed --select USStates.csv
dbt build command 也会自动为你构建 seeds,因此不需要与它一起再运行 dbt seed command。与往常一样,dbt 会为你构建 DAG,并且会在任何 downstream model 能使用 seeds 之前先构建 seeds。
Summary
本章中,我们看到了 sources 在 dbt project 中扮演的重要角色。我们了解到,将 sources 管理在 transformation models 之外有多大优势,以及当 source 发生变化时,更新起来有多容易。Sources 被包含在自己的 YAML file 中,并且高度可配置,有许多额外选项,例如 descriptions、tests 和 source freshness。你的 sources.yml file 必须手动创建并填充;不过,dbt Labs 有一个名为 codegen 的 open source package 可以在这里提供帮助。
我们也看到了 seeds 在 dbt project 中的作用。Seeds 是我们可以创建的 CSV files,非常适合那些可能不属于 source data 的小型、不变数据。Seed data 存储在 dbt project 中,并受到 source control 和 version control。Seeds 的处理方式与 models 非常类似。下一章中,我们将深入 models,而 models 从根本上说是使用 dbt 最重要的方面之一。