[dbt] データモデルの作成前や作成後にクエリを実行できるHooksを使ってみた

カーテンフックってかけるの結構めんどいですよね
2021.06.04

大阪オフィスの玉井です。

dbtで色々なデータモデルを作成する時、「このクエリを発行する直前に設定変更のクエリを実行したい」とか「このクエリが終わった直後に特定のクエリを流すようにしたい」みたいなこと、よくあると思います。

今回はそういう時に役立つ「Hooks」という機能を紹介します。

Hooksとは

記事の題名の通りなのですが、もう少しカタい言い方をすると「dbt modelの作成とはちょっと異なるタイミングでSQLを実行できる機能」という感じでしょうか。

今回、メインで紹介するのは下記の2種類です。

  • pre-hook
    • modelの作成直前に任意のクエリを実行できる
    • seedやsnapshotでも使用可能
  • post-hook
    • modelの作成直後に任意のクエリを実行できる
    • seedやsnapshotでも使用可能

ユースケースについては、以降の「やってみた」のところを読んでもらうと、しっくりくるかと思います。

やってみた

環境

  • dbt Cloud
  • Snowflake

まずは普通にdbt modelを作成する

下記のmodelを用意しました(sourceとして、既にTPCHのサンプルデータを定義済)。このクエリ自体はここに載っているサンプルクエリそのままです。

{{ config(
      materialized = 'view'
) }}

SELECT
     l_returnflag
    ,l_linestatus
    ,SUM(l_quantity) AS sum_qty
    ,SUM(l_extendedprice) AS sum_base_price
    ,SUM(l_extendedprice * (1 - l_discount)) AS sum_disc_price
    ,SUM(l_extendedprice * (1 - l_discount) * (1 + l_tax)) AS sum_charge
    ,AVG(l_quantity) AS avg_qty
    ,AVG(l_extendedprice) AS avg_price
    ,AVG(l_discount) AS avg_disc
    ,COUNT(*) AS count_order
FROM
     {{ source('tpch_sf1', 'lineitem') }}
WHERE
    l_shipdate <= dateadd(day, - 90, to_date('1998-12-01'))
GROUP BY
     l_returnflag
    ,l_linestatus
ORDER BY
     l_returnflag
    ,l_linestatus

これといった問題はないので、普通に実行できるし、普通にこのビューが作成されます。

ただし、今後のこのビューの使い方によっては、このままだとちょっと不便です。わざわざdbtを使って整えたデータですから、当然、他のメンバーや他ツール等で、このデータを参照することになると思われます。しかし、今回使っているSnowflakeは、作成されたオブジェクトの所有者は、作成したロールがもつことになります。

このままだと、このデータにアクセスできるのは、所有者だけですので(権限の強いシステムロールは例外)、他のユーザー等にこのデータを使ってもらうためには、別途権限の設定を行う必要があります

dbtで作ったデータモデルに対して、Snowflake側でいちいち権限の設定を行うのは面倒ですよね。そういう時にHooksを使うと、楽ができます。

post-hookで、作成したデータモデルに権限を付与する

上記のデータモデルのconfig部分に、post-hookを加えます。

{{ config(
      materialized = 'view',
      post_hook=[
            "grant select on {{ this }} to role dbt_role"
      ]
) }}

...

post-hookは、このデータモデルが作成された直後に、追加で実行するSQLを書くことができます。今回は、dbt_roleというロールに対して、SELECT権限を付与するクエリを設定しました。これにより、このデータモデルが実行される度に、自動で権限設定のクエリが発行されるため、dbt側だけで、Snowflakeの権限設定まで行うことができるようになりました。

このデータモデルを実行してみます。しっかり、GRANT文も発行されていますね。

作成されたデータモデル(ビュー)の権限を確認したところ、しっかりSELECT権限がdbt_roleに付与されています。

pre-hookで、データモデルの作成前に、使用する仮想ウェアハウスを変更する

Snowflakeの場合、色々な仮想ウェアハウスを使い分けることで、柔軟なワークロードが実現できます。dbtを使っていても、「このデータモデルの時は、こっちの仮想ウェアハウスで実行したいな」という時があります。そういう時はpre-hookを使いましょう。

上記のデータモデルのconfig部分に、pre-hookを加えます。

{{ config(
      materialized = 'view',
      pre_hook=[
            "use warehouse x_small_wh"
      ],
      post_hook=[
            "grant select on {{ this }} to role dbt_role"
      ]
) }}

...

pre-hookは、post-hookの逆になります。つまり、データモデルの作成前に実行するSQLを書くことができます。今回は、USE WAREHOUSE文で、使用する仮想ウェアハウスを、別のものに変えます。

このデータモデルを実行してみます。しっかりUSE文が発行されています。

Snowflake側のクエリ履歴を確認したところ、ちゃんとこのデータモデルのCREATE文は、指定した別の仮想ウェアハウスが使われていました。

別の記述方法

今回はデータモデル自体にHooksを記述しましたが、dbt_project.ymlに別途記載することもできます。

models:
  project_name:
      tpc-h:
          +post-hook:
            - "grant select on {{ this }} to role dbt_role"

ちなみに、データモデル側のHooksとdbt_project.yml側のHooksは共存するので、ご利用の際は注意してください。

別の種類のHooks

今回紹介したのは2種類ですが、実はあともう2種類のHooksがあります。

  • on-run-start
    • dbt run等のコマンドの前に実行
  • on-run-end
    • dbt run等のコマンドの後に実行

model単位ではなく、コマンド単位なので「一連のmodel作成全ての前と後」で実行したいクエリは、こちらで指定するとよさそうです。

おわりに

dbtは色々なDWHに対応していますが、この機能はすごくSnowflakeに向いていると思いました。今回の例以外でも、作成したデータモデルをデータシェアリングする時なんかも、post-hooksなんかで共有→権限付与、とスムーズに設定を行えそうです。

ちなみに、公式ドキュメントを読む限り、RedshiftだったらVACUUM処理なんかに使えるとのことでした。