写点什么

dbt-tidb 1.2.0 尝鲜

  • 2022 年 9 月 02 日
    北京
  • 本文字数:5469 字

    阅读完需:约 18 分钟

作者: shiyuhang0 原文来源:https://tidb.net/blog/1f56ab48

本文假设你对 dbt 有一定了解。如果是第一次接触 dbt,建议先阅读 官方文档当 TiDB 遇见 dbt

本文中的示例基于官方维护的 jaffle_shop 项目。关于此项目的细节介绍,可以参考当 TiDB 遇见 dbtgithub project page,本文不再赘述


TiDB Cloud 官方在 5 月份开始正式面向全球用户提供全托管的 DBaaS (Database-as-a-Service)服务,支持用户在全托管的数据库上运行关键业务交易和实时分析任务。


同时 TiDB Cloud 还提供了免费试用的 dev tier,可以方便开发者试用、调试。搭配 dev tier,dbt-tidb 易用性大大提高。


恰逢 dbt-tidb v1.2.0 发布,除了支持 dbt-core v1.2.0 之外,它还带来了一些好用的新特性。借此机会,本文将体验 dev tier 并测试 dbt-tidb v1.2.0 的新特性。


对于开发者们,本文还介绍了如何升级 dbt-tidb,可供参考。

Setup

  1. 安装 dbt-tidb v1.2.0

  2. TiDB Cloud 上创建免费的 dev tier,如遇问题可以参考 官方文档

  3. 注册并登录账号,页面会跳转到 TiDB Cloud 控制台。

  4. 点击 Create Cluster,跳转到创建页面,创建参数一般默认即可。

  5. 点击右下角 Create,跳转到 Security Settings,配置 Root Password 与 IP Access List。(点选 Allow Access from Anywhere 可以允许任意 IP 地址的访问)

  6. 点击右下角 Apply,页面跳转回 TiDB Cloud 控制台,等待集群初始化完成。

  7. 初始化完毕后点击 Connect 按钮,即可查看相应 host 与 user 了。可以直接复制 MySQL 连接串以测试集群连通性。



  1. 下载 jaffle_shop 项目

  2. ~/.dbt路径下配置 profiles.yml 中的连接信息

  3. 修改 jaffle_shop 中的 dbt_project.yml,只需修改 profile 为 profiles.yml 中定义的工程名

  4. 在 jaffle_shop 目录下执行 dbt debug 即可验证配置是否正确

Feature

Connection Retry

在 dbt 中,运行 / 构建 / 测试可能会有数百个独立的数据库连接。由于网络等原因导致的单个超时有可能使整个项目运行失败。


因此 dbt-tidb 新增了重试功能来解决暂时性的连接超时问题。


Connection Retry 举例🌰


  1. 在 profile.yml 添加重试次数的配置,同时使用无效用户模拟连接失败的场景

  2. 执行 dbt debug,终端的确显示了相应错误。但想知道是否进行了重试,需要查看 debug 日志

  3. 去 logs 目录下查看 dbt.log,可以发现重试了 3 次,每次间隔 1 秒。最后抛出错误

Grant

在 ELT 之后,我们往往需要对数据进行权限控制。基于此,dbt 从 1.2.0 开始支持 Grant 对 dbt 生成的数据集进行访问控制。相应的 dbt-tidb 也支持了授权机制,能够对 dbt 产生的视图与表进行授权管理。


Gant 目前支持 model, seed 和 snapshots。如果你在 dbt_project.yml 下配置,那么项目内所有资源(model/seed/snapshots 都是资源)都会生效。当然,你也可以像其他配置项一样针对特定资源配置相应的 SQL 或 YAML,它会覆盖 dbt_project.yml 中的配置。


有一点需要注意的是 Grant 不支持创建用户,我们需要在 TiDB 中先创建好所需用户。


Grant 举例🌰


  1. 在 TiDB 中创建用户,注意在 dev tier 中用户名必须带前缀(和 root 用户的前缀保持一致)

  2. 在 jaffle_shop 项目中的 dbt_project.yml 增加 grant 配置

  3. 在 jaffle_shop 项目下执行 dbt seed

  4. 成功后查询 TiDB:


  • 41y7Jq2g5sBr2ia.user1 被赋予了 Select + Insert 权限


  • 41y7Jq2g5sBr2ia.user2 被赋予了 Select 权限


  • 41y7Jq2g5sBr2ia.user3 被赋予了 Insert 权限

Cross-database macros

dbt 的一个强大之处就是它可以复用宏(可以理解为函数),dbt-util 就是官方提供的一个工具仓库,我们可以通过引入 dbt-util 复用其封装好的宏。dbt 1.2.0 将其中的 Cross-database macros 从 util 迁移到了 core,这意味着你无需引入 dbt-util 就可以直接使用它们。


对此,dbt-tidb 也做了相应适配工作。现在,你可以直接在 dbt-tidb 中使用下列函数,使用方式可以参考 dbt-tidb 官网


  • bool_or


  • cast_bool_to_text


  • dateadd


  • datediff


  • date_trunc


  • hash


  • safe_cast


  • split_part


  • last_day


  • cast_bool_to_text


  • concat


  • escape_single_quotes


  • except


  • intersect


  • length


  • position


  • replace


  • right


以 datediff 举例🌰


  1. 执行 dbt seed 生成 raw_orders 表

  2. 在 models 目录下创建 datediff.sql,计算 raw_orders 表中订单时间和 2018-01-01 相差的天数

  3. 执行 dbt run -s datediff 指定运行 datediff,执行成功后查询 TiDB 结果如下

Upgrade dbt-tidb to support new dbt-core

上文介绍了 dbt-tidb v1.2.0 带来的诸多新特性。那么新特性是如何实现的,dbt-tidb 又是如何进行版本升级的呢?下文将会给你带来答案。


关于构建 dbt adapter 的细节可以参考 dbt 官方文档 ,本节则会带来版本升级的相关经验。

版本规则

dbt-tidb 版本与 dbt-core(官方维护的内核)一样遵循 Semantic Versioning


为了避免兼容性问题,dbt-tidb 选择与 dbt-core 保持一致版本,同版本间才能相互兼容工作。即 dbt-tidb 1.2.0 也仅支持 dbt-core 1.2.0。虽然官方升级时会尽量避免兼容性修改,但兼容性修改还是会发生的。如 dbt-core 1.2.0 为了支持 retry connection 特性新增了可覆盖的方法,如果 adapter 实现了该方法,那么也就无法运行在 dbt-core 1.1.0 之上了(除非代码进行版本判断,嵌入两种逻辑)


基于此,在 dbt-core 发布 1.1.0 与 1.2.0 之后,dbt-tidb 也需要分别发布 1.1.0 与 1.2.0 版本。

调研

当我们进行版本升级,第一步就是要调研需要支持哪些特性。


以下几种调研的途径,你可以结合使用多种方式


  1. 查看 dbt-core 的 release note,重点关注针对 adapter 的新特性。最终梳理需要实现的新特性。


  1. 有时候,dbt 官方会在 Github Discussion 中整理 adapter 升级需要支持的特性。这时候,你就可以放心大胆依据它来升级。


  1. 官方的版本升级文档


  1. 参考其他 adapter 的实现,你可以在 Available adapters 找到所有的 adapter


  1. 不推荐的选择:不实现特性,而只修改打包时 dbt-core 的版本。此时无法享受任何版本升级带来的新特性。


dbt-tidb 主要依据第一、二种方式,整理出需要实现的特性如下表:


dbt-tidb 1.1.0


  • 废弃 Python 3.7,支持 Python 3.10


  • 使用新的测试框架进行测试


  • 在 incremental 中支持多 unique key


dbt-tidb 1.2.0


  • 支持 Connection retry 特性


  • 支持 grant 特性,进行权限配置


  • 支持 Cross-database macros (dbt-util 包下的部分 macros 被迁移至 dbt-core)


  • 新增 BaseDocsGenerate 与 BaseValidateConnection 测试

使用测试

在开发前,我想先介绍如何进行测试。因为我建议使用 Test Driven Development(TTD) 的方式进行开发 dbt adapter。即:先编写测试,然后进行对应功能实现,通过测试即认为支持该功能。


自 dbt-core 1.1.0 开始,dbt 就为 adapter 开发者提供了全新的一套测试框架。DBT 正在大力推广新测试框架,相比于旧的测试框架,该新框架的一个好处就是它随着 dbt-core 一起发版。这样就能及时对相应特性或 BUG 修复进行测试。


得益于该测试框架,adapter 基本无需自己编写测试就可以对相应功能进行测试。关于测试框架如何使用,可以参考 Testing a new adapter


dbt-tidb 1.1.0 开始使用新的测试框架,引入 basic 包,以测试基础的 dbt 功能,另外 incremental 多 unique key 的支持暂时也放在了 basic 包下


dbt-tidb 1.2.0 又根据新增特性补充了以下测试


  • Basic 包:新增 BaseValidateConnection 与 BaseDocsGenerate ,分别用于测试连接与文档生成相关功能


  • Grant:新增 grant 包,用于测试 grant 特性


  • Util:新增 util 包,用于测试从 dbt-util 迁移来的 Cross-database macros

如何开发

我们以 grant 特性为例介绍如何进行新特性支持。


添加测试


在上一步中我们已经介绍过如何测试。对于 grant,我们需要增加如下测试:


class TestModelGrantsTiDB(BaseModelGrants):    pass

class TestIncrementalGrantsTiDB(BaseIncrementalGrants): pass

class TestSeedGrantsTiDB(BaseSeedGrants): pass

class TestSnapshotGrantsTiDB(BaseSnapshotGrants): pass

class TestInvalidGrantsTiDB(BaseInvalidGrants): pass
复制代码


其中我们直接使用 pass 不进行任何实现修改,只继承测试框架的默认实现。


实现特性


接下来就是实现特性。一般可以通过覆盖默认宏或是覆盖默认方法来进行拓展,具体应该覆盖哪些,可以参考如下:


  • dbt 官方人员可能会在 Github discussions 中介绍如何实现


  • 参考 dbt-core 该特性相应 pr


  • 参考其他 adapter


通过官方仓库 discussion 中整理的 1.2.0 升级汇总。我们发现 grant 主要通过覆盖 dbt-core 的宏实现,主要需要实现如下宏:


  • get_show_grant_sql:返回授权信息(通过查看相关代码,可以发现返回格式需为 grantee (用户名) + privilege_type(权限类型))


  • get_grant_sql:进行授权


  • get_revoke_sql:收回授权


以下是相关实现:


get_show_grant_sql


我们首先查询 TiDB 的 mysql.tables_priv 表获取权限信息。然后筛选出对应的库表,接着轮询 Select、Insert、Update、Delete 四种权限,最后按用户 + 权限的格式输出。对应 SQL 如下


{% macro tidb__get_show_grant_sql(relation) %}
select case(Table_priv) when null then null else 'select' end as privilege_type, `User` as grantee from mysql.tables_priv where `DB` = '{{relation.schema}}' and `Table_name` = '{{relation.identifier}}' and Table_priv like '%Select%' union ALL select case(Table_priv) when null then null else 'insert' end as privilege_type, `User` as grantee from mysql.tables_priv where `DB` = '{{relation.schema}}' and `Table_name` = '{{relation.identifier}}' and Table_priv like '%Insert%' union ALL select case(Table_priv) when null then null else 'update' end as privilege_type, `User` as grantee from mysql.tables_priv where `DB` = '{{relation.schema}}' and `Table_name` = '{{relation.identifier}}' and Table_priv like '%Update%' union ALL select case(Table_priv) when null then null else 'delete' end as privilege_type, `User` as grantee from mysql.tables_priv where `DB` = '{{relation.schema}}' and `Table_name` = '{{relation.identifier}}' and Table_priv like '%Delete%'
{% endmacro %}
复制代码


get_grant_sql


使用标准 grant SQL 对多用户进行授权,注意用户需使用双引号。对应 SQL 如下:


{%- macro tidb__get_grant_sql(relation, privilege, grantees) -%}    grant {{ privilege }} on {{ relation }} to {{ '\"' + grantees|join('\", \"') + '\"' }}{%- endmacro -%}
复制代码


get_revoke_sql


使用标准 revoke SQL 对多用户收回授权,用户同样需使用双引号。对应 SQL 如下:


 {%- macro tidb__get_revoke_sql(relation, privilege, grantees) -%}    revoke {{ privilege }} on {{ relation }} from {{ '\"' + grantees|join('\", \"') + '\"' }}{%- endmacro -%}
复制代码


修复错误


实现完成之后,我们需要运行测试检查是否能够通过。当发现并没有通过时,我们一般有以下方式去修复错误:


  1. 根据错误输出,判断错误原因进行修复,一般的 SQL 格式错误都可以用这种方式发现。


  1. 查看 dbt-core 中该特性对应的 pr。

  2. 查看是否修改了一些已被 adapter 覆盖的宏 / 方法,如果是,那么 adapter 可能也需要相应修改。

  3. 查看是否还有新增的其他可被覆盖的宏 / 方法。


  1. 参考其他 adapter 支持的代码。查看是否有任何遗漏


在支持 grant 的过程中,就基于第二种方法发现 dbt-tidb 之前已经覆盖了 incremental 与 snapshot 宏。而在 grant 特性支持 pr 中,dbt-core 修改了这两个宏的默认实现。dbt-tidb 也需要进行相应修改:


{% materialization incremental, adapter='tidb' %}
-- other code {% set grant_config = config.get('grants') %}
-- other code {% set should_revoke = should_revoke(existing_relation, full_refresh_mode) %} {% do apply_grants(target_relation, grant_config, should_revoke=should_revoke) %} -- other code {%- endmaterialization %}
复制代码


该代码首先获取 grant 配置,然后调用 apply_grants 应用上文实现的 get_grant_sql 方法。


同时,也发现需要覆盖新增的 call_dcl_statements 宏,来将多条 SQL 变为单条 SQL 依次请求。因为 dbt-tidb 暂时还不支持多 SQL 请求,如下:


{% macro tidb__call_dcl_statements(dcl_statement_list) %}    {% for dcl_statement in dcl_statement_list %}        {% call statement('grant_or_revoke') %}            {{ dcl_statement }}        {% endcall %}    {% endfor %}{% endmacro %}
复制代码


修复测试


测试中可能还会发现一些错误,这些错误并不是因为我们没有实现该特性,而是因为一些兼容性问题,测试本身需要一些修改。关于如何修改测试,Testing a new adapter 中也有介绍


dbt-tidb 支持授权时就进行了测试修改。因为在授权失败时,不同的 adapter 可能会抛出不一样的错误,那么自然需要改写授权失败的信息,使其符合 TiDB 的报错:


class TestInvalidGrantsTiDB(BaseInvalidGrants):    def grantee_does_not_exist_error(self):        return "You are not allowed to create a user with GRANT"
def privilege_does_not_exist_error(self): return "Illegal privilege level specified for"
复制代码

Conclusion

本文结合 dev tier 与 dbt-tidb 举例试用了 dbt-tidb v1.2.0 带来的主要特性。


同时以 dbt-tidb 为例,介绍了升级 dbt adapter 的流程与技巧。也欢迎大家对 dbt-tidb 任何形式的贡献。


发布于: 刚刚阅读数: 3
用户头像

TiDB 社区官网:https://tidb.net/ 2021.12.15 加入

TiDB 社区干货传送门是由 TiDB 社区中布道师组委会自发组织的 TiDB 社区优质内容对外宣布的栏目,旨在加深 TiDBer 之间的交流和学习。一起构建有爱、互助、共创共建的 TiDB 社区 https://tidb.net/

评论

发布
暂无评论
dbt-tidb 1.2.0 尝鲜_新版本/特性解读_TiDB 社区干货传送门_InfoQ写作社区