📘

DBTの導入と半年間の運用の振り返り(課題と解決策)

2023/09/08に公開2

はじめに

dbt-core を導入して数ヶ月経ち知見も溜まってきました。
一度これまであった課題とその解決策について書き残しておきたいと思います。

記事の想定読者

  • dbt の基礎を理解している

dbtディレクトリ構成(参考までに)

ディレクトリ構成を記載

dbtのベストプラクティスに則りディレクトリの構造を処理毎のレイヤーに分割してます。

  • Staging

    • ソースデータからデータモデルを構築する最小単位を作成する。DRYの原則に則って実装を行う。
      • やること
        • rename
        • 型変換
        • その他、プロジェクト全体で普遍的な変換(ex. yen -> dollar, 日付の形式変換)
        • カテゴリーの変換(caseロジック)
      • やらないこと
        • ソースもとのデータのJOIN(例外)
        • 集計処理
  • Intermediate

    • Staging で作成したモデルを使用してドメイン、ビジネスロジックを当てていく
  • Marts

    • Intermediate のモデルをJOINしエンドユーザーに提供するモデルを作成する
  • Utilities(Optional)

    • データモデルを作成する際に広く必要とされるデータモデルを格納(ex. data spine)

CASE1: Default の Generic Test だけだと物足りない

  • ✅ dbt-utils の generic_test を使用する。

https://github.com/dbt-labs/dbt-utils#generic-tests

  • ✅ dbt-expectations を使用する。

https://github.com/calogica/dbt-expectations

CASE2: docs の description で冗長性を排除したい

  • ✅ 共通カラムの description を作成し各モデルから参照
common_descption.md
{% docs model_a_description %}
This is model_a description
{% enddocs %}

{% docs model_b_description %}
This is model_b description
{% enddocs %}

{% docs type %}
| value | description |
| ---- | ---- |
| A | A type |
| B | B type |
{% enddocs %}
schema.yml
version: 2

models:
  - name: model_a
    description: '{{ doc("model_a_description") }}'
    columns:
      - name: type
        # 共通のdescriptionを参照する。
        description: '{{ doc("type") }}'

  - name: model_b
    description: '{{ doc("model_b_description") }}'
    columns:
      - name: type
        # 共通のdescriptionを参照する。
        description: '{{ doc("type") }}'

CASE3: test coverage を向上させたい

  • ✅ dbt_meta_testing
    • model に特定の generic test / description の記載を強要できます。

https://hub.getdbt.com/tnightengale/dbt_meta_testing/latest/

  • ✅ dbt-coverage
    • test/docs の coverage を計測することが可能です。
    • CICD で実施し特定の閾値を coverage が下回ったらエラーが生じるように設定します。

https://github.com/slidoapp/dbt-coverage

CASE4: model 単位での unit test を行いたい

  • ✅ dbt_unit_testing
    • 入力の model/source を mock 化することで想定通りの挙動になるかをチェックします。

https://github.com/EqualExperts/dbt-unit-testing

CASE5: 複雑な caseロジック / 正規表現ロジックの unittest を行いたい

  • ✅ ロジックを macro 化して、 unittest を行います。
sample_macro.sql
{% macro sample_macro(column_a, column_b) %}
CASE
  WHEN column_b THEN 'ten'
  WHEN column_a = 1 THEN 'one'
  WHEN column_a = 2 THEN 'two'
  WHEN column_a = 3 THEN 'three'
END
{% macro endmacro %}
tests/test_sample_macro.sql
WITH
  test_data AS (
    SELECT
      1 AS column_a
      , false AS column_b
      , 'one' AS expected_value
    UNION ALL
    SELECT
      2 AS column_a
      , false AS column_b
      , 'two' AS expected_value
    UNION ALL
    SELECT
      3 AS column_a
      , false AS column_b
      , 'three' AS expected_value
    UNION ALL
    SELECT
      1 AS column_a
      , true AS column_b
      , 'ten' AS expected_value
  )
  ,test_result AS (
    SELECT
      {{ sample_macro('column_a','column_b') }} as actual_value
      , expected_value
    FROM
      test_data
  )
  SELECT * FROM test_result
  WHERE
    expcted_value != actual_value

CASE6: formatter を導入したい

  • ✅ dbt Cloud でも導入されている sqlfluff を使います。

https://sqlfluff.com/

CASE7: incremental model の修正時のみ full refresh させたい

  • ✅ fileの差分を検知してCICD上で full refresh を行います。

https://zenn.dev/nyanchu/articles/52f10d8af22273

CASE8(dbt*snowflake): コストを可視化したい

  • ✅ まずは dbt-snowflake-monitoring を導入するか検討します。

https://hub.getdbt.com/get-select/dbt_snowflake_monitoring/latest/

  • ✅ dbt-snowflake-monitoring が要件に合わない場合。
    dbt-snowflake-monitoring の github コードを参考に自分でコストモデルを作成します。
    以下のブログでで、コスト計算ロジックが解説されています。

https://select.dev/posts/cost-per-query

CASE9: dbt Labs のベストプラクティスに則ってリファクタしたい

  • ✅ dbt_project_evaluator を使用する。

https://github.com/dbt-labs/dbt-project-evaluator

参考記事

CASE10: 環境別で DB の SCHEMA 名を変更したい。

  • ✅ generate_schema_name macro を作って対応する。

https://docs.getdbt.com/docs/build/custom-schemas

generate_schema_name.sql
{% macro generate_schema_name(custom_schema_name, node) -%}

    {%- set default_schema = target.schema -%}

    # target.nameによってスキーマのロジックを変更する。
    {%- if target.name not in ['production'] and custom_schema_name is not none -%}

         {{ default_schema }}_{{ custom_schema_name | trim }}

    {%- else -%}

        {{ default_schema }}

    {%- endif -%}

{%- endmacro %}

CASE11: documents の overview を上書きしたい

  • ✅ docs block を作成して上書きする。
models/overview.md
{% docs __overview__ %}
# This is a sample project
{% enddocs %}

https://docs.getdbt.com/docs/collaborate/documentation#setting-a-custom-overview

CASE12: documents に表示するモデルを制御したい

  • ✅ model にdocs fieldを追加する
models/schema.yml
version: 2

models:
  - name: model_name
    docs:
      show: true | false

https://docs.getdbt.com/reference/resource-configs/docs

CASE13: model yml の作成を高速化したい

  • ✅ ChatGPT request prompt を作成して使用する
step1. dbt-style YAMLをdbtに学習させる(Jaffle Shopのymlを拝借)
ChatGPT 学習用サンプルテキスト
Memorize this as an example of dbt-style YAML file;

version: 2

models:
  - name: customers
    description: This table has basic information about a customer, as well as some derived facts based on a customer's orders

    columns:
      - name: customer_id
        description: This is a unique identifier for a customer
        tests:
          - unique
          - not_null

      - name: first_name
        description: Customer's first name. PII.

      - name: last_name
        description: Customer's last name. PII.

      - name: first_order
        description: Date (UTC) of a customer's first order

      - name: most_recent_order
        description: Date (UTC) of a customer's most recent order

      - name: number_of_orders
        description: Count of the number of orders a customer has placed

      - name: total_order_amount
        description: Total value (AUD) of a customer's orders

  - name: orders
    description: This table has basic information about orders, as well as some derived facts based on payments

    columns:
      - name: order_id
        tests:
          - unique
          - not_null
        description: This is a unique identifier for an order

      - name: customer_id
        description: Foreign key to the customers table
        tests:
          - not_null
          - relationships:
              to: ref('customers')
              field: customer_id

      - name: order_date
        description: Date (UTC) that the order was placed

      - name: status
        description: '{{ doc("orders_status") }}'
        tests:
          - accepted_values:
              values: ['placed', 'shipped', 'completed', 'return_pending', 'returned']

      - name: amount
        description: Total amount (AUD) of the order
        tests:
          - not_null

      - name: credit_card_amount
        description: Amount of the order (AUD) paid for by credit card
        tests:
          - not_null

      - name: coupon_amount
        description: Amount of the order (AUD) paid for by coupon
        tests:
          - not_null

      - name: bank_transfer_amount
        description: Amount of the order (AUD) paid for by bank transfer
        tests:
          - not_null

      - name: gift_card_amount
        description: Amount of the order (AUD) paid for by gift card
        tests:
          - not_null
step2. prompt を使用して dbt-style YAML を生成させる。
Write me a dbt style YAML file to describe the following table:

create or replace TRANSIENT TABLE STAGING.BASE.TEST_TABLE (
  ID NUMBER(38,0),
  CREATED AT TIMESTAMP_NTZ(9),
  UPDATED AT TIMESTAMP_NTZ(9),
  VERSION NUMBER(38,0),
  NAME VARCHAR(16777216)
)

https://docs.getdbt.com/blog/create-dbt-documentation-10x-faster-with-chatgpt

CASE14: dbdocs でドキュメントを管理したい

  • ✅ dbterd を使用する

https://dbterd.datnguyen.de/1.2/index.html

dbdocsの参考記事

Discussion

yingqiyingqi

素晴らしいですね、私たちは基本的に同じことをしています。ドキュメントの自動生成にはdbt-osmosisを使用しました。

nyanchunyanchu

dbt-osmosis良いですね!!カラムの自動追加も良いですが、何よりも上流モデルからカラムのドキュメントを受け渡せるのが素晴らしい!!