実運用環境でcustom_schemaを使いたい場合のgenerate_schema_nameマクロを考えてみた

2023.11.09

さがらです。

実運用環境でcustom_schemaを使いたい場合のgenerate_schema_nameマクロを考えてみたので、本記事でまとめてみます。

前置き:generate_schema_nameマクロの書き換えの必要性と難しさ

まず前置きとして、generate_schema_nameマクロの書き換えの必要性と難しさについて述べます。

例として、出力先のスキーマをdbt projectのフォルダごとに分けることなどを考え、custom_schemaを設定したとします。

このとき、デフォルトのgenerate_schema_nameマクロではcustom_schemaを設定すると{{ default_schema }}_{{ custom_schema_name | trim }}の形で出力されてしまい、default_schema(profileなどで設定したスキーマ名)が作られるスキーマ名の先頭についてしまいます。

より具体的には、デフォルトのgenerate_schema_nameマクロは下記のように定義されています。(公式Docより引用)

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

    {%- set default_schema = target.schema -%}
    {%- if custom_schema_name is none -%}

        {{ default_schema }}

    {%- else -%}

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

    {%- endif -%}

{%- endmacro %}

「じゃあ{{ default_schema }}_{{ custom_schema_name | trim }}{{ custom_schema_name | trim }}に書き換えればdefault_schemaが先頭につかなくなるじゃん」と思うかもしれませんが、単純にこうしてしまうと、開発環境でも本番環境でもcustom_schemaを設定している場合に同じスキーマに出力されてしまいカオスになってしまいます。せっかくdbtを導入して開発環境と本番環境をきれいに分離できるのに、これでは意味がないですよね。

そのため、出力先のスキーマを開発環境と本番環境で分けるには、targetを用いて開発環境と本番環境を考慮しつつ、generate_schema_nameマクロを書き換えていく必要があります。

書き換えの前提要件

上述の通りgenerate_schema_nameマクロをどう書き換えていくかをちゃんと考える必要があるのですが、「DWH上でどのようにデータベースとスキーマを管理するか」によって書き換え方が大きく変わってきます。

今回、私は以下の要件のもとgenerate_schema_nameマクロを書き換えてみました。

  • 実行環境
    • dbt:dbt Cloud Enterpriseエディション
    • DWH:Snowflake
  • targetについて
    • dev:開発環境、dbt CloudでのIDEで実行する時
    • prod:本番環境、dbt CloudのProduction Environmentでジョブを実行する時
  • データベース構成
    • RAWデータベース:生データを管理する
    • STAGINGデータベース:dbtのプラクティスに沿った、カラム名の変更や型変更など最低限の加工を行ったデータを管理する
    • DWHデータベース:One Big TableやDimensional Modelingなどのモデリングを行ったデータを管理する
    • MARTデータベース:各用途ごとにまとめられたデータマートを管理する
  • 各データベースのスキーマ構成
    • 本番環境のスキーマ:データの分類・用途・部署・プロジェクトなどの粒度でスキーマを作る
    • 開発環境のスキーマ:各開発者ごとにdbt_<名前>のスキーマを作る ※デフォルト設定に準ずる
    • CI環境のスキーマ:dbt CloudのCIジョブの実行に合わせて自動でスキーマが生成・削除される(参考ブログ
  • seedの扱いについて
    • seedについても、開発環境と本番環境で出力先のスキーマを分けることを考える。この理由は、seedもスキーマを分けないと、間違った内容のデータが本番環境に反映されてしまうリスクがあるため。
    • 本番環境のスキーマ:指定したcustom_schemaの名称でスキーマを作る
    • 開発環境のスキーマ:指定したcustom_schemaに加えて各開発者ごとにdbt_<名前>のprefixをつけ、スキーマを作る

私が考えたgenerate_schema_nameマクロ

ということで前置きが長くなりましたが、私が考えたgenerate_schema_nameマクロを書き換えたコードがこちらになります。(書き換え方法ですが、./macros/フォルダ内にget_custom_schema.sqlを作成し以下のコードを貼り付ければOKです。)

ポイントとしては、主に以下3つの観点で条件分岐を行っております。

  • targetがprodかどうか
  • node.resource_typeseedかどうか
  • custom_schema_nameは定義されているかどうか
{% macro generate_schema_name(custom_schema_name, node) %}

    {% set default_schema = target.schema %}
    
    {# targetが「prod」かつ対象のオブジェクトが「seed」かつcustom_schemaの定義ありの場合、seed向けの「custom_schema」に #}
    {% if target.name == 'prod' and node.resource_type == 'seed' and custom_schema_name is not none %}
        {{ custom_schema_name | trim }}

    {# targetが「prod」でないかつ対象のオブジェクトが「seed」かつcustom_schemaの定義ありの場合、「default_schema」を先頭に付けたseed向けの「custom_schema」に #}
    {% elif target.name != 'prod' and node.resource_type == 'seed' and custom_schema_name is not none %}
        {{ default_schema }}_{{ custom_schema_name | trim }}

    {# targetが「prod」かつcustom_schemaの定義ありの場合、「custom_schema」に #}
    {% elif target.name == 'prod' and custom_schema_name is not none %}
        {{ custom_schema_name | trim }}

    {# custom_schemaの定義なしの場合、「default_schema」に #}
    {% elif custom_schema_name is none %}
        {{ default_schema }}

    {# 上述の条件に合致しない(targetが「prod」でないがcustom_schemaの定義あり、など)の場合、「default_schema」に #}
    {% else %}
        {{ default_schema }}
    {% endif %}

{% endmacro %}

実行してみるとどうなるか

では、実際に上述のgenerate_schema_nameマクロでどのように動くのかを見てみたいと思います。

各種ファイル・targetの設定

  • dbt_project.yml ※今回の検証に関連する箇所のみ記載
seeds:
  dbt_4layer_datastack_test:
    +database: sagara_4layer_test_raw
    +schema: seed_data

models:
  dbt_4layer_datastack_test:
    +materialized: view # 下記以外のフォルダでModelを定義してしまった場合に、テーブルを作らないように
    staging:
      +materialized: view
      +database: sagara_4layer_test_staging
      jaffle_shop:
        +schema: jaffle_shop
    dwh:
      +materialized: table
      +database: sagara_4layer_test_dwh
      finance:
        +schema: finance
    mart:
      +materialized: table
      +database: sagara_4layer_test_mart
      finance:
        +schema: finance
      intermediate: 
        +materialized: ehemeral
  • 開発環境
    • targetdev
    • schemadbt_ssagara ※default_schemaとなる

  • 本番環境
    • targetprodを設定
    • schemaunavailable_schema ※default_schemaとなる。ただ、今回のgenerate_schema_nameマクロでは本番環境は基本的にcustom_schemaに格納されるため、使用不可であることがわかるスキーマ名を指定。

※dbt Cloudにおけるtargetの設定については、下記ブログをご覧ください。

開発環境(target=dev)のとき

dbt CloudのIDE上で、dbt seeddbt build --select +suppliesというコマンドを続けて実行したあとのSnowflakeのデータベースとスキーマを確認してみます。

dbt seedによって、生データを管理するSAGARA_4LAYER_TEST_RAWデータベースに、custom_schemaで指定したスキーマ名に開発者専用のスキーマ名をprefixとしてつけたDBT_SSAGARA_SEED_DATAスキーマが作られています。

dbt build --select +suppliesによって、最低限の加工を行うSAGARA_4LAYER_TEST_STAGINGデータベースに開発者専用のDBT_SSAGARAスキーマが、データマートを管理するSAGARA_4LAYER_TEST_MARTデータベースに 開発者専用のDBT_SSAGARAスキーマが作られています。

本番環境(target=prod)のとき

dbt CloudでProductionと設定したEnvironmentのジョブで、dbt seeddbt build --select +suppliesというコマンドを続けて実行したあとのSnowflakeのデータベースとスキーマを確認してみます。(上述の開発環境におけるスキーマは削除してから実行しています。)

dbt seedによって、生データを管理するSAGARA_4LAYER_TEST_RAWデータベースに、custom_schemaで指定した本番環境に該当するSEED_DATAスキーマが作られています。

dbt build --select +suppliesによって、最低限の加工を行うSAGARA_4LAYER_TEST_STAGINGデータベースにcustom_schemaで指定した本番用のJAFFLE_SHOPスキーマが、データマートを管理するSAGARA_4LAYER_TEST_MARTデータベースに custom_schemaで指定した本番用のFINANCEスキーマが作られています。

参考記事

generate_schema_nameマクロの書き換えについては、下記の記事が色々なパターンで書かれており非常に参考になります。(私も今回の検証に当たり何度も読ませて頂きました。)

ぜひこちらも併せてご覧ください。