代码库方面的最佳实践

本文档介绍了有关 Dataform 代码库的以下信息:

Dataform 中的代码库最佳实践概览

本部分简要介绍了在 Dataform 中管理代码库大小、代码库结构和代码生命周期的最佳实践。

代码库大小的最佳实践

代码库大小会影响 Dataform 中开发的多个方面,例如:

  • 协作
  • 代码库可读性
  • 开发流程
  • 工作流编译
  • 执行工作流

Dataform 会强制执行 API 配额和编译资源限制。代码库过大可能会导致代码库超出这些配额和限制。这可能会导致工作流编译和执行失败。

为降低该风险,我们建议您拆分大型代码库。拆分大型代码库时,您需要将一个大型工作流拆分为多个较小的工作流,这些工作流位于不同的代码库中,并通过跨代码库依赖项进行连接。

通过这种方法,您可以遵守 Dataform 配额和限制,实现精细的流程和权限,并提高代码库的可读性和协作性。不过,管理拆分后的代码库可能比管理单个代码库更具挑战性。

如需详细了解 Dataform 中代码库大小的影响,请参阅代码库大小概览。 如需详细了解拆分代码库的最佳实践,请参阅拆分代码库。

代码库结构的最佳实践

我们建议您在 definitions 目录中构建文件,以反映工作流的各个阶段。请注意,您可以采用最符合您需求的自定义结构。

以下推荐的 definitions 子目录结构反映了大多数工作流的关键阶段:

  • sources,用于存储数据源声明。
  • intermediate,用于存储数据转换逻辑。
  • output,用于存储输出表的定义。
  • extras(可选),用于存储其他文件。

Dataform 中所有文件的名称都必须符合 BigQuery 表命名准则。我们建议 Dataform 代码库中 definitions 目录中的文件名称反映子目录结构。

如需详细了解在代码库中构建和命名文件的最佳实践,请参阅在代码库中构建代码。

代码生命周期最佳实践

Dataform 中的默认代码生命周期包含以下阶段:

  • 在 Dataform 工作区中开发工作流代码。

    您可以使用 Dataform 核心或专门使用 JavaScript 进行开发。

  • 使用工作流设置文件中的设置将代码编译为编译结果。

    您可以使用发布配置和工作区编译替换来配置自定义编译结果。

    借助版本配置,您可以配置整个代码库的自定义编译结果。您稍后可以在工作流配置中安排其执行。

    借助工作区编译替换,您可以为代码库中的所有工作区配置编译替换,从而为每个工作区创建自定义编译结果。

  • 在 BigQuery 中执行编译结果。

    您可以使用工作流配置安排执行或代码库编译结果。

如需在 Dataform 中管理代码生命周期,您可以创建执行环境,例如开发环境、预演环境和生产环境。

如需详细了解 Dataform 中的代码生命周期,请参阅 Dataform 中的代码生命周期简介。

您可以选择将执行环境保留在单个代码库或多个代码库中。

单个代码库中的执行环境

您可以使用工作区编译替换和版本配置在单个 Dataform 代码库中创建隔离的执行环境,例如开发、预演和生产环境。

您可以通过以下方式创建隔离执行环境:

  • 按架构拆分开发表和生产表。
  • 按架构和 Google Cloud 项目拆分开发表和生产表。
  • 按 Google Cloud 项目拆分开发、预演和生产表。

然后,您可以使用工作流配置在预演和生产环境中安排执行。 我们建议在开发环境中手动触发执行。

如需详细了解在 Dataform 中管理工作流生命周期的最佳实践,请参阅工作流生命周期的最佳实践。

多个代码库中的代码生命周期

如需针对代码生命周期的每个阶段量身定制 Identity and Access Management 权限,您可以创建代码库的多个副本,并将其存储在不同的 Google Cloud 项目中。

每个 Google Cloud 项目都充当一个执行环境,对应于代码生命周期的一个阶段,例如开发和生产。

在这种方法中,我们建议在所有项目中保持代码库的代码库相同。如需在代码库的每个副本中自定义编译和执行,请使用工作区编译替换、发布配置和工作流配置。

代码库大小概览

本部分可帮助您了解代码库大小对工作流开发和 Dataform 编译资源用量的影响,以及如何估算代码库的编译资源用量。

Dataform 中的代码库大小简介

代码库的大小会影响 Dataform 中开发的以下方面:

  • 协作。多个协作者在大型代码库中工作可能会创建过多的拉取请求,从而增加合并冲突的风险。

  • 代码库可读性。如果单个代码库中构成工作流的文件数量较多,则可能很难浏览该代码库。

  • 开发流程。单个代码库中大型工作流的某些区域可能需要自定义权限或流程(例如调度),这些权限或流程与应用于工作流其余部分的权限和流程不同。代码库过大,难以针对工作流的特定部分定制开发流程。

  • 工作流编译。Dataform 会对编译资源实施用量限额。代码库过大可能会导致超出这些限制,从而导致编译失败。

  • 工作流执行。在执行期间,Dataform 会在工作区中运行代码库代码,并将资源部署到 BigQuery。代码库越大,Dataform 运行它所需的时间就越长。

如果代码库的大尺寸对 Dataform 中的开发产生负面影响,您可以将代码库拆分为多个较小的代码库。

关于代码库编译资源限制

在开发期间,Dataform 会编译工作区中的所有代码库代码,以生成代码库中工作流的表示。这称为编译结果。Dataform 会对编译资源强制执行用量限额。

您的代码库可能会因以下原因而超出用量限额:

  • 代码库代码中存在无限循环 bug。
  • 代码库代码中存在内存泄漏 bug。
  • 代码库规模较大,大约有 1, 000 多个工作流操作。

如需详细了解编译资源的用量限额,请参阅 Dataform 编译资源限制。

估算代码库的编译资源用量

您可以估算代码库的以下编译资源的使用情况:

  • CPU 时间用量。
  • 实际用时用量。
  • 代码库中定义的操作的生成图表的最大序列化数据总大小。

如需大致估算代码库编译的当前编译时间用量,您可以在本地 Linux 或 macOS 机器上对 Dataform 工作流的编译进行计时。

  • 如需为工作流的编译计时,请在代码库中运行以下格式的 Dataform CLI 命令 dataform compile:

    time dataform compile
    

    以下代码示例展示了执行 time dataform compile 命令的结果:

    real    0m3.480s
    user    0m1.828s
    sys     0m0.260s
    

您可以将 real 结果视为实际用时的粗略指标,将 user 结果视为代码库编译的 CPU 时间用量的粗略指标。

如需大致估算代码库中生成的操作图的总大小,您可以将图的输出写入 JSON 文件。您可以将未压缩的 JSON 文件的大小视为总图大小的粗略指标。

  • 如需将工作流的已编译图的输出写入 JSON 文件,请在代码库中运行以下 Dataform CLI 命令:

    dataform compile --json > graph.json
    

拆分代码库

本部分讨论了拆分 Dataform 代码库和管理跨代码库依赖项的策略。

代码库是 Dataform 中的核心单元。代码库存储构成工作流的所有 SQLX 和 JavaScript 文件,以及 Dataform 配置文件和软件包。您可以将工作流存储在单个代码库中,也可以将工作流拆分到多个代码库中。

在 Dataform 中拆分代码库具有以下优势:

  • 遵守 Dataform 编译资源限制。将大型工作流拆分为多个较小的代码库可降低超出 Dataform 编译资源限制的风险。
  • 精细化流程。您可以为工作流的每个拆分片段和开发该片段的团队单独设置流程,例如持续集成 (CI) 规则。
  • 精细化权限。您可以为工作流的每个拆分片段以及开发该片段的团队单独设置权限,以提高工作流的整体安全性。
  • 通过最大限度地减少处理工作流的每个拆分片段的协作者数量来改进协作。
  • 提高代码库的可读性。将构成大型工作流的文件拆分到多个代码库中,可以更轻松地单独浏览每个代码库,而不是一次性浏览整个工作流。
  • 与执行整个工作流相比,加快工作流的每个拆分片段的工作流执行速度。

在 Dataform 中拆分代码库有以下缺点:

  • 每个 Dataform 代码库及其对应的 Git 代码库都需要自定义持续集成和持续开发 (CI/CD) 配置。
  • 每个 Dataform 代码库及其对应的 Git 代码库都需要自定义调度配置。
  • 难以管理位于多个代码库中的工作流对象之间的依赖关系。
  • 缺乏对分布在多个代码库中的 SQL 工作流的全面有向无环图 (DAG) 可视化。在每个代码库中,生成的 DAG 仅表示完整工作流的一部分。

拆分代码库的策略

拆分代码库时,您需要将构成父级 SQL 工作流的文件拆分为较小的子级工作流,并将其存储在单独的 Dataform 代码库中。

您可以选择通过以下任一方式拆分代码库:

  • 每个开发团队一个代码库。
  • 每个网域一个代码库,例如销售、营销或物流。
  • 一个中央存储库,以及每个网域一个存储库,后者使用中央存储库的内容作为数据源。

如需将父级工作流托管在第三方 Git 托管平台中,您需要将包含子工作流的每个单独代码库分别连接到专用的第三方 Git 代码库。

管理跨代码库依赖项

拆分代码库最高效的方法是将父级 SQL 工作流划分为自包含的子级工作流,从而创建独立的代码库。独立代码库不使用其他代码库的内容作为数据源。此方法不需要管理跨代码库依赖项。

如果无法避免跨代码库依赖项,您可以通过将一个代码库拆分为一系列代码库来管理这些依赖项,其中一个代码库依赖于其前一个代码库,并且是其后一个代码库的数据源。代码库及其依赖项的顺序必须最能反映父级工作流的结构。

您可以使用 Dataform 数据源声明在代码库之间创建依赖项。您可以将其他 Dataform 代码库中的 BigQuery 表类型声明为正在修改的代码库中的数据源。声明数据源后,您可以像引用任何其他 Dataform 工作流操作一样引用它,并使用它来开发工作流。

如果您安排在具有跨代码库依赖项的代码库之间拆分的工作流的执行,则必须按照跨代码库依赖项的顺序逐个运行代码库。

我们建议避免将一个代码库拆分为一组具有双向依赖项的代码库。当一个代码库是另一个代码库的数据源,同时又将该代码库用作数据源时,就会发生代码库之间的双向依赖关系。代码库之间的双向依赖关系会使父工作流的调度和执行以及开发流程变得复杂。

在代码库中构建代码

本部分介绍了在 Dataform 代码库的根 definitions 目录中构建工作流文件并为其命名的最佳实践。definitions 目录的推荐结构反映了工作流的各个阶段。您可以采用任何符合您业务需求的结构。

您可能出于以下原因需要在 definitions 目录中构建工作流代码:

  • 通过将团队指定到工作流的选定部分,改进代码库协作。
  • 在组织发生变化时提高工作流的可维护性。
  • 改进代码库中的导航。
  • 提高代码库的可伸缩性。
  • 最大限度地减少团队的管理开销。

Dataform 代码库中的根 definitions 目录包含用于创建工作流元素的代码。您可以将 definitions 目录中的文件整理成反映工作流结构的目录结构。

开发工作流时,您需要声明源表并对其进行转换,以创建可用于业务或分析目的的输出表。

您可以区分工作流的三个关键阶段:

  1. 数据源声明。
  2. 源数据转换。
  3. 转换后的源数据中的输出表的定义。

definitions 目录中的以下子目录结构反映了工作流的关键阶段:

sources
数据源声明和源数据的基本转换(例如过滤)。
intermediate
从 sources 读取数据并转换数据的表和操作,然后您可以使用转换后的数据来定义 outputs 表。在 Dataform 将这些表运行到 BigQuery 后,通常不会向其他流程或工具(例如商业智能 [BI] 工具)公开这些表。
outputs
Dataform 在 BigQuery 中运行表后,由流程或工具(例如 BI)使用的表的定义。
extra
工作流主流水线之外的文件,例如包含为其他用途(如机器学习)准备的工作流数据的文件。可选的自定义子目录。

有关“sources”目标的最佳做法

sources 子目录包含工作流的第一阶段:源数据的声明和基本转换。

在 sources 子目录中,存储数据源声明和用于过滤、分类、转换或重命名列的表。

避免存储合并了多个来源的数据的表。

转换存储在 intermediate 子目录中的表内的 sources 数据。

如果您声明来自多个池(例如 Google Ads 或 Google Analytics)的数据源,请为每个池指定一个子目录。

以下示例展示了包含两个来源池的 sources 子目录结构:

definitions/
    sources/
        google_ads/
            google_ads_filtered.sqlx
            google_ads_criteria_metrics.sqlx
            google_ads_criteria_metrics_filtered.sqlx
            google_ads_labels.sqlx
            google_ads_labels_filtered.sqlx
        google_analytics/
            google_analytics_users.sqlx
            google_analytics_users_filtered.sqlx
            google_analytics_sessions.sqlx

如果您在同一架构中声明多个数据源表,则可以将其声明合并到单个 JavaScript 文件中。

如需详细了解如何使用 JavaScript 创建数据源声明,请参阅专门使用 JavaScript 创建工作流。

以下代码示例展示了在一个 JavaScript 文件中声明的一个架构中的多个数据源:

[
  "source_table_1",
  "source_table_2",
  "source_table_3"
].forEach((name) =>
  declare({
    database: "gcp_project",
    schema: "source_dataset",
    name,
  })
);

为了保护工作流免受数据源更改的影响,您可以为每个数据源声明创建一个视图,例如 analytics_users_filtered.sqlx。视图可以包含源数据的基本过滤和格式设置。 将视图存储在 sources 子目录中。

然后,在创建 intermediate 或 outputs 表时,引用视图而不是原始源表。通过此方法,您可以测试源表。 如果源表发生更改,您可以修改其视图,例如添加过滤条件或重新转换数据。

有关“intermediate”目标的最佳做法

intermediate 子目录包含工作流的第二阶段:转换和汇总来自一个或多个来源的源数据。

在 intermediate 子目录中,存储对 sources 子目录中的一个或多个来源的源数据进行重大转换的文件,例如联接数据的表。intermediate 子目录中的表通常会查询源表或其他 intermediate 表中的数据。

使用 intermediate 表创建 outputs 表。通常,在 Dataform 将 intermediate 表运行到 BigQuery 后,这些表不会用于其他用途(例如数据分析)。您可以将 intermediate 表视为支持创建输出表的数据转换逻辑。

我们建议您记录和测试所有 intermediate 表。

有关“outputs”目标的最佳做法

outputs 子目录包含工作流的最后阶段:根据转换后的数据创建用于业务目的的输出表。

在 outputs 目录中,存储您计划在 Dataform 将其运行到 BigQuery 后在其他流程或工具中使用的表,例如报告或信息中心。outputs 目录中的表通常会查询 intermediate 表中的数据。

按 outputs 表相关的商业实体(例如营销、订单或分析)对表进行分组。为每个商业实体指定一个子目录。

如需在 BigQuery 中单独存储输出表,您可以为输出表配置专用架构。如需了解如何配置表架构,请参阅替换表设置。

以下示例展示了包含 sales 和 marketing 商业实体的 outputs 子目录结构:

definitions/
    outputs/
        orders/
            orders.sqlx
            returns.sqlx
        sales/
            sales.sqlx
            revenue.sqlx
        marketing/
            campaigns.sqlx

我们建议您记录和测试所有 outputs 表。

命名策略

Dataform 中所有文件的名称都必须符合 BigQuery 表命名准则。

我们建议 Dataform 代码库的 definitions 目录中的文件名称反映子目录结构。

在 sources 子目录中,文件名应指向文件相关的来源。将来源名称作为前缀添加到文件名中,例如 analytics_filtered.sqlx

在 intermediate 子目录中,文件名应标识子目录,以便协作者可以清楚地区分 intermediate 文件。选择一个唯一前缀,并仅将其应用于 intermediate 目录中的文件,例如 stg_ads_concept.sqlx。

在 outputs 子目录中,文件名应简明扼要,例如 orders.sqlx。如果您在不同的实体子目录中有同名的 outputs 表,请添加一个标识实体的 前缀,例如 sales_revenue.sqlx 或 ads_revenue.sqlx。

以下示例展示了 definitions 目录中的子目录结构,其中文件名符合推荐的命名策略:

definitions/
    sources/
        google_analytics.sqlx
        google_analytics_filtered.sqlx
    intermediate/
        stg_analytics_concept.sqlx
    outputs/
        customers.sqlx
        sales/
            sales.sqlx
            sales_revenue.sqlx
        ads/
            campaigns.sqlx
            ads_revenue.sqlx

编译和调度的最佳实践

为代码库配置计划执行和编排工作流时,请遵循以下最佳实践,以确保可靠编译:

  • 使用发布配置进行预定的生产执行:发布配置会按照可预测的时间表编译代码库代码,并存储生成的编译结果。当您的生产执行流水线运行时,它们会立即针对此保存的结果执行,而不会向远程 Git 代码库发起网络请求。
  • 将 commitish 编译限制为开发和低频任务:直接在 Git commitish(例如分支、标记或提交 SHA)上调用 CreateCompilationResult 需要 Dataform 在每次调用时通过网络克隆代码库。避免将生产 Cron 作业或编排 DAG 配置为以高频率从 Git commitish 重复编译。
  • 配置自动重试和错峰时间表:如果您的架构需要在外部调度器中从 Git commitish 进行编译,请配置采用指数退避算法的自动重试,并错开并发 cron 计时器。如需查看详细的重试代码示例和问题排查步骤,请参阅无法访问远程仓库。

后续步骤