記事を書いた背景
- この記事はアイスタイル Advent Calendar 2022 11日目の記事です
- dbtでモデルを定義すると、基本的には接続情報:profiles.ymlにて指定したデフォルトのスキーマ(データセット)にモデルが出力されます。接続情報にて指定出来るスキーマはターゲット毎に一つのみです
- しかし、BigQueryの運用を行う場合、含まれているデータ内容などに応じて、複数のデータセットを使って権限コントロールが必要になることがあります
- dbtではカスタムスキーマという仕組みがあり、既に存在する解説サイトではモデルの配置されるフォルダ毎にカスタムスキーマ名を変える解説はよくされています
- 実際に運用しようとすると、ターゲットとフォルダの組み合わせでカスタムスキーマ名を決めたい場合など、フォルダ単位以外に細かい制御をしたいニーズがありますが、公式Tutorial以外にニーズに合った記事が少ないのも事実です
- この記事では、dbtでカスタムスキーマの使い方について、少し踏み込んだ解説を行います
想定読者
- dbtとは何かを理解している人
- データウェアハウスの構築・運用を担当している人
- データウェアハウス製品はBigQueryを使用している人
この記事で説明しないこと
- この記事では、dbtとは何かについては説明しません
- この記事では、dbtに関する環境構築方法については説明しません
カスタムスキーマについて
カスタムスキーマとは
- デフォルトでは、すべてのdbtモデルはターゲットで指定されたスキーマに対してビルドされます
- 多くのモデルがあるdbtプロジェクトでは、ターゲットで指定されたスキーマ以外のスキーマに対してモデルをビルドできて便利かもしれません。例えば、論理的にモデルをグループ化するような場合が考えられます
- マーケティング、人事、営業などビジネス領域ごとにグルーピングしたい場合
- 加工途中の中間モデルをステージング用のスキーマに隠蔽し、データ利用ユーザに公開するモデルを分析用スキーマに配置する場合
- このように、ターゲットで指定されたスキーマ以外のことを「カスタムスキーマ」と言います
カスタムスキーマを定義する際の視点
- カスタムスキーマを使って、どのようなスキーマ(データセット)でテーブルを管理するかを決める際の視点としては以下のものがあります
- 生成されるスキーマの命名規約
- 設定されたカスタムスキーマを元に生成するスキーマはデフォルトではカスタムスキーマをターゲットスキーマに連結して生成します
- ターゲットスキーマ名を命名規約に含まない場合、複数のユーザが同時に開発した場合に同一のスキーマにアクセスされてしまいますし、本番環境と開発環境で同一名称のスキーマが出来てしまいます
- dbtデフォルトの命名規約に従うことが出来るかを決める必要があります
- カスタムスキーマを何単位に設定するか
- カスタムスキーマはフォルダ単位やモデル単位で設定することが出来ます
- 中間モデルに関してスキーマを分ける場合などは、こちらに該当します
- 少し実装は複雑ですが、ドメイン+レイヤ単位にスキーマを変えることも可能です
- 生成されるスキーマの命名規約
- それでは、具体的にどのようにカスタムスキーマを設定するのかを次節から見ていきましょう
カスタムスキーマの設定パターン
-
前述の視点を組み合わせると、カスタムスキーマを設定するパターンとして以下のものがありえることが分かります
1. モデル単位にカスタムスキーマを設定し、dbtの命名ルールに従ってスキーマ名を決定する
2. フォルダ単位(レイヤ単位)にカスタムスキーマを設定し、dbtの命名ルールに従ってスキーマ名を決定する
3. 独自の命名ルールに従ってスキーマ名を決定する -
それではカスタムスキーマを用いて、どのようにスキーマ名を決定しているのかを見ていきましょう
-
開発環境にjaffle_shopプロジェクト環境が構築されているものとします
-
profiles.yml
で設定されているtargetはdev/stg/prdの3環境とし、ターゲットスキーマ(ターゲット毎のスキーマ)は{target}_ds
とします
1. モデル単位にカスタムスキーマを設定し、dbtの命名ルールに従ってスキーマ名を決定する
概略
モデルファイルの中でカスタムスキーマを設定し、ターゲットスキーマと組み合わせてスキーマ名を決定します
-
/jaffle_shop/models/staging/stg_customers.sql
を開きます - モデルの先頭に
{{ config(schema='staging') }}
を追加します -
dbt seed
でテストデータを挿入した後、dbt run(target=dev)
を実行します - データセット
dev_ds_staging
にstg_customers
が作成されていることを確認します
2. フォルダ単位(レイヤ単位)にカスタムスキーマを設定し、dbtの命名ルールに従ってスキーマ名を決定する
概略
dbt_project.ymlの中でレイヤ毎にカスタムスキーマを設定し、ターゲットスキーマと組み合わせてスキーマ名を決定します
-
jaffle_shop/dbt_project.yml
を開きます -
modelsセクションを以下の内容に変更します
models: dbt_projects: materialized: table staging: materialized: view +schema: staging
-
dbt seed
でテストデータを挿入した後、dbt run(target=dev)
を実行します -
データセット
dev_ds_staging
が作成され、staging
のモデルが出力されていることを確認します
3. 独自の命名ルールに従ってスキーマ名を決定する
-
この節では、dbtのカスタムスキーマを元にスキーマ名を生成する方法について見ていきます
-
dbtではgenerate_schema_nameというマクロを使って、モデルを構築するスキーマの名前を決定しています。現在のロジックを表現しているマクロのコードは以下の通りです
{% 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 %}
-
このマクロを上書きするために、macroフォルダに独自の命名ルールに従ったスキーマ名を生成するロジックを実装した
generate_schema_name
を定義することで、独自の命名ルールに従ってスキーマ名を生成することが出来ます(generate_schema_name
を上書きするためのファイルはget_custom_schema.sql
という名称にすることが多いようです) -
独自の命名ルールに従ってスキーマ名を決定する方法は、以下の4パターンの方式があります
- 3-1.フォルダ(レイヤ)毎に指定したカスタムスキーマ名をスキーマ名にする
- 3-2.フォルダ(レイヤ)+ターゲット毎に指定したカスタムスキーマ名をスキーマ名にする
- 3-3.フォルダ(レイヤ)毎に指定したカスタムスキーマ名とターゲット名の組み合わせでスキーマ名を生成する
- 3-4. 設定ファイル内で、レイヤ+ターゲット毎にカスタムスキーマを指定し、特定モデルだけは別のスキーマ名を生成する
-
具体的にそれぞれ見ていきましょう
3-1.フォルダ(レイヤ)毎に指定したカスタムスキーマ名をスキーマ名にする
概略
カスタムスキーマ名をフォルダ(レイヤ)単位に指定し、スキーマ名を生成するロジックの中でカスタムスキーマをそのままスキーマ名とします
- 3-1. では、前述した
get_custom_schema.sql
を用いて、スキーマ名を独自の命名ルールで設定します。get_custom_schema.sql
では、カスタムスキーマ名をそのまま返すようにします - カスタムスキーマ名は
dbt_project.yml
でフォルダ(レイヤ)毎に設定していますが、モデル毎に設定しても挙動としては変わりません - それでは、実際に設定してみましょう
-
a.
dbt_project.yml
のmodelsセクションを以下のように書き換えますmodels: dbt_projects: materialized: table staging: materialized: view +schema: staging
-
b.
get_custom_schema.sql
をmacroフォルダ配下に作成し、以下の内容を貼り付けます- カスタムスキーマが定義されている場合はカスタムスキーマをそのまま使用し、カスタムスキーマが未定義の場合はデフォルトスキーマを使用する、という実装をしています
{% macro generate_schema_name(custom_schema_name, node) -%} {%- set default_schema = target.schema -%} {%- if (target.name == 'prd' or target.name == 'dev') and custom_schema_name is not none -%} {{ custom_schema_name | trim }} {%- else -%} {{ default_schema }} {%- endif -%} {%- endmacro %}
-
c. モデルを実行します
-
dbt seed
でテストデータを挿入した後、dbt run(target=dev)
を実行します - データセット
staging
が作成され、staging
フォルダ(レイヤ)のモデルが出力されていることを確認します
-
-
3-2.フォルダ(レイヤ)+ターゲット毎に指定したカスタムスキーマ名をスキーマ名にする
概略
設定時にレイヤ+ターゲット毎に別々のカスタムスキーマを設定し、スキーマ名にそのままカスタムスキーマ名を指定します
- 3-1. との違いは、設定ファイルでカスタムスキーマを設定する際、フォルダ(レイヤ)だけではなく、ターゲット毎に別々のカスタムスキーマを設定します。これまで、
dbt_project.yml
にてフォルダ(レイヤ)毎にカスタムスキーマを設定してきましたが、ターゲット毎に分岐させます -
get_custom_schema.sql
の設定は3-1. 同様、カスタムスキーマをそのまま返します - それでは、実際に設定してみましょう
- a.
dbt_project.yml
のmodelセクションを以下のように書き換えますmodels: dbt_projects: materialized: table staging: materialized: view +schema: | {%- if target.name == "prd" -%} staging_prd {%- elif target.name == "dev" -%} staging_dev {%- else -%} invalid_database {%- endif -%}
- b. macroフォルダ配下に
get_custom_schema.sql
を作成し、以下の内容を貼り付けます{% macro generate_schema_name(custom_schema_name, node) -%} {%- set default_schema = target.schema -%} {%- if (target.name == 'prd' or target.name == 'dev') and custom_schema_name is not none -%} {{ custom_schema_name | trim }} {%- else -%} {{ default_schema }} {%- endif -%} {%- endmacro %}
- c. モデルを実行します
-
dbt seed
でテストデータを挿入した後、dbt run(target=dev)
を実行します - データセット
staging_dev
が作成され、stagingフォルダ(レイヤ)のモデルが出力されていることを確認します
-
- a.
3-3.フォルダ(レイヤ)毎に指定したカスタムスキーマ名とターゲット名の組み合わせでスキーマ名を生成する
概略
設定時にレイヤ毎に別々のカスタムスキーマを設定し、スキーマ名の生成の際にターゲット名と組み合わせて独自の命名規約でスキーマ名を生成します
-
設定ファイルではフォルダ(レイヤ)毎にカスタムスキーマを指定し、スキーマ名を生成するマクロの中でターゲット名を動的に取得し、カスタムスキーマ名と結合してスキーマ名にしています
-
設定ファイルではフォルダ(レイヤ)毎にカスタムスキーマを指定するのは3-1. 同様です
-
それでは、実際に設定してみましょう
- a. dbt_project.ymlのmodelセクションを以下のように書き換えます
models: dbt_projects: materialized: table staging: materialized: view +schema: staging
- b. macroフォルダ配下にget_custom_schema.sqlを作成し、以下の内容を貼り付けます
{% macro generate_schema_name(custom_schema_name, node) -%} {%- set default_schema = target.schema -%} {%- if (target.name == 'prd' or target.name == 'dev') and custom_schema_name is not none -%} {%- set fqn = node['fqn'] -%} {%- set schema = '_'.join(fqn[1:-1]) -%} {{ schema }}_{{ target.name }} {%- else -%} {{ default_schema }} {%- endif -%} {%- endmacro %}
- c. モデルを実行します
-
dbt seed
でテストデータを挿入した後、dbt run(target=dev)
を実行します - データセット
staging_dev
が作成され、stagingフォルダ(レイヤ)のモデルが出力されていることを確認します
-
- a. dbt_project.ymlのmodelセクションを以下のように書き換えます
-
カスタムスキーマ名はマクロの引数に独立して存在するため、この引数をそのまま使用したのが3-1. ですが、ここではnode引数を使用して様々なスキーマ名を生成することを例示したかったので、あえてnodeからfqnを使用してカスタムスキーマ名を取得しています
-
{{ schema }}_{{ target.name }}
を
{{custom_schema_name }}_{{ target.name }}
と書き換えても、同じ結果を得ることは出来ます -
node引数から取得できるのは以下の内容です
パラメータ 概要 alias エイリアス databas デフォルトデータベース名 fqn Fully Qualified Name name モデル名 original_file_path dbtプロジェクトディレクトリ内でのパス package_name dbtプロジェクト名 path モデルディレクトリ内のモデルファイルのパス raw_sql SQLファイルの定義内容 root_path dbtプロジェクトディレクトリまでのパス schema デフォルトスキーマ unique_id プロジェクトで一意なモデルID。model.(dbtプロジェクト名).(モデル名)
3-4. 設定ファイル内で、レイヤ+ターゲット毎にカスタムスキーマを指定し、特定モデルだけは別のスキーマ名を生成する
概略
設定時にレイヤ+ターゲット毎に別々のカスタムスキーマを設定し、スキーマ名の生成の際に基本的にはそのままカスタムスキーマをスキーマとして出力し、特定のルールに該当する場合モデルだけルールに従ったスキーマを出力します
-
設定時にレイヤ+ターゲット毎にカスタムスキーマを設定するのは3-2と同様です。
-
get_custom_schema.sql
は特定のルールに該当するかを判断し、ルールに該当しない場合はカスタムスキーマ名をスキーマ名として出力します。今回のサンプルにおける特定のルールはモデル名=stg_customers
かどうかを判定し、特定ルールの場合は、カスタムスキーマ名_customer
を出力することとします -
今回のサンプルはモデル名で分岐をしていますが、モデル名に業務ドメインを含んでいるのであればモデル名から業務ドメイン名を取得し、レイヤ+ターゲット+業務ドメイン毎に異なるスキーマ(データセット)に出力することも可能です
-
それでは、実際に設定してみましょう
-
a. dbt_project.ymlのmodelセクションを以下のように書き換えます
models: dbt_projects: materialized: table staging: materialized: view +schema: | {%- if target.name == "prd" -%} staging_prd {%- elif target.name == "dev" -%} staging_dev {%- else -%} invalid_database {%- endif -%}
-
b. macroフォルダ配下に
get_custom_schema.sql
を作成し、以下の内容を貼り付けます{% macro generate_schema_name(custom_schema_name, node) -%} {%- set default_schema = target.schema -%} {%- if (target.name == 'prd' or target.name == 'dev') and custom_schema_name is not none -%} {%- set name = node['name'] -%} {%- if (name == 'stg_customers') -%} {%- set schema = 'customer' -%} {{ custom_schema_name | trim }}_{{ schema }} {%- else -%} {{ custom_schema_name | trim }} {%- endif -%} {%- else -%} {{ default_schema }} {%- endif -%} {%- endmacro %}
-
c. モデルを実行します
-
dbt seed
でテストデータを挿入した後、dbt run(target=dev)
を実行します - データセット
staging_dev
が作成され、stg_customers以外のstagingフォルダ(レイヤ)のモデルが出力されていることを確認します - データセット
staging_dev_customer
が作成され、stg_customers
が出力されることを確認します
-
-
最後に
- ある程度の規模の基盤であれば、ターゲット+ドメイン+レイヤ毎にアクセス権限管理が必要になることもあると思います。そのような場合に、当記事は参考になるかもしれません
- ただし、当記事の方式はかなり複雑になるので、データセットはターゲット毎に一つにして、テーブルごとにアクセス権限を設定しても良いかもしれません