[ver1.5新機能]dbtでモデル自体をバージョン管理し、後続のモデルへの影響を確認してから最新のモデルをリリースできる「Versions」を試してみた

2023.05.29

さがらです。

先日、dbt-coreのver1.5がリリースされました。

dbt-core ver1.5の新機能として、「Versions」がリリースされました。この機能を試してみたので、本記事で試した内容をまとめてみます。

Versionsとは

一言でいうと、モデル自体をバージョン管理し、後続のモデルへの影響を確認してから最新のモデルをリリースできる機能です。

dbtはSELECT文の記述でモデルと言う形で簡単にデータ変換処理を定義できますが、急にあるモデルにおいてデータ型が変更になったりカラムを削除したり、ということを行うと後続のモデルに影響を及ぼしてしまいエラーを出すようになってしまう可能性もあります。

そんなときに役立つのが今回追加された「Versions」です!これを使うことで、dbtのモデルについてバージョン管理を行いモデルのプレリリース期間を設けた上で最新バージョンに移行するという流れをスムーズにできるため、モデルでデータ型変更やカラムの削除などがあっても、エラーを起こすことなく最新バージョンに移行することができます。

先日私が試したContractsと併せて使うと、より正確にモデルのデータ型を担保しつつ、後続のモデルに影響がないことを確認してから最新のモデルをリリース出来るので、おすすめです!

検証概要

検証環境

  • DWH:Snowflake
  • dbt Cloud
    • dbt-Core:ver1.5 ※Environmentより設定

検証内容

中間モデルのintermediate_customers.sqlで、カラム名がわかりづらいと不評だったfirst_order列をfirst_order_dateに変更することを考えたとします。このとき、急に後続のモデルやに影響がでないようにVersionを使ってみます。

Versions適用前の各ファイル

Versionsの設定をを適用させる前の各ファイルは下記のように定義されているとします。

  • intermediate_customers.sql
select
    customer_id,
    first_name,
    last_name,
    first_order,
    number_of_orders
from
    {{ ref("int_customer_order_history_joined") }}
  • intermediate_customers.yml
models:
  - name: intermediate_customers
    config:
      contract:
        enforced: true

    columns:
      - name: customer_id
        data_type: varchar
        constraints:
          - type: not_null
          - type: primary_key
        tests:
          - unique
          - not_null

      - name: first_name
        data_type: varchar

      - name: last_name
        data_type: varchar

      - name: first_order
        data_type: date

      - name: number_of_orders
        data_type: number
  • mart_customers.sql ※intermediate_customers.sqlを参照する後続のモデル
select
    *
from
    {{ ref('intermediate_customers') }}
  • データリネージ

カラム名を変更したモデルをversion:2としてプレリリースする

まずintermediate_customers.sqlで、first_order列をfirst_order_date列に名称変更することを行います。ただ、後続のモデルに影響がでないようにVersionsの設定を適用し、プレリリースします。

まず、intermediate_customers_v2.sqlという新しいモデルを下記のように定義します。_v2という接尾辞は後でVersionsを設定する際に使うバージョン値となります。

select
    customer_id,
    first_name,
    last_name,
    first_order as first_order_date,
    number_of_orders
from
    {{ ref("int_customer_order_history_joined") }}

続いて、intermediate_customers.ymlを以下のように変更します。主な変更点は下記のとおりです。

  • latest_version: 1として、intermediate_customersの現行バージョンは1であることを宣言する
  • versions:のところで、各バージョンの差分のみを記述する
    • v: 1は、既存のyamlのカラム定義をそのまま引用するためinclude: allだけ定義する。alias: intermediate_customersとすることで、生成されるオブジェクトの名称の末尾に_v1がつくことを回避できるため、これはお好みで
    • v: 2は、include: allexclude: [first_order]としてfirst_order以外のすべての定義はそのまま使うように宣言し、first_order_dateを別途定義することで、version:2での差分を表すようにする
models:
  - name: intermediate_customers
    latest_version: 1
    config:
      contract:
        enforced: true

    columns:
      - name: customer_id
        data_type: varchar
        constraints:
          - type: not_null
          - type: primary_key
        tests:
          - unique
          - not_null

      - name: first_name
        data_type: varchar

      - name: last_name
        data_type: varchar

      - name: first_order
        data_type: date

      - name: number_of_orders
        data_type: number

    versions:
      - v: 1
        columns:
          - include: all
        config:
          alias: intermediate_customers

      - v: 2
        columns:
          - include: all
            exclude: [first_order]
          - name: first_order_date
            data_type: date

この上で、新しいversion:2を参照するには、{{ ref('intermediate_customers', version=2) }}のようにref関数を記述する必要があります。実際に、プレリリースのversion:2のモデルを参照するmart_customers_test.sqlを下記のように定義してみました。

参照先としても、_v2で作られたオブジェクトを参照するようになっています。

select
    *
from
    {{ ref('intermediate_customers', version=2) }}

また既存のversion:1のモデルを参照する際には、{{ ref('intermediate_customers') }}と記述するだけでlatest_version:に定義されているversionを参照する用になっています。そのため、latest_version:1の場合にはversion:1のモデルを参照するようになっています。

実際、intermediate_customers.sqlを参照する後続のモデルmart_customers.sqlでは、下記のように特に定義を変えていないですが、version:1のaliasであるintermediate_customersを参照するようになっています。

ちなみにこの2つのバージョンを持つ状態で、dbt build --select intermediate_customers+を実行すると、下図のようにversion:1、version:2どちらも実行されます。

このときのデータリネージは下図のように表されます。個人的には、リネージ上でもversionsがわかると嬉しいですね!

カラム名を変更したversion:2のモデルを本番リリースする

プレリリースしたカラム名を変更したversion:2のモデルの検証が上手くいったと仮定し、実際に本番リリースしてみます!

まず、version:2をリリースしたいintermediate_customers.ymlについて、以下のように変更します。変更点のポイントは下記です。

  • latest_version: 1から、latest_version: 2に変更する
  • v: 1でversionの接尾辞を付けないようにaliasを設定していた場合は、v: 2の方にalias: intermediate_customersと言った形でaliasを転記する
models:
  - name: intermediate_customers
    latest_version: 2
    config:
      contract:
        enforced: true

    columns:
      - name: customer_id
        data_type: varchar
        constraints:
          - type: not_null
          - type: primary_key
        tests:
          - unique
          - not_null

      - name: first_name
        data_type: varchar

      - name: last_name
        data_type: varchar

      - name: first_order
        data_type: date

      - name: number_of_orders
        data_type: number

    versions:
      - v: 1
        columns:
          - include: all

      - v: 2
        config:
          alias: intermediate_customers
        columns:
          - include: all
            exclude: [first_order]
          - name: first_order_date
            data_type: date

また、元々version管理をしていなかった場合は元々のversion:1のモデルを定義するファイル名がintermediate_customers.sqlとなっていると思いますので、これをintermediate_customers_v1.sqlと名称変更することで、過去のバージョンであることを明示的にするようにします。※これを行わないと、上のyamlを変更したタイミングで「同じモデルが2つあるよ」というコンパイルエラーが出てきます…

  • version:1として運用していたファイル名がintermediate_customers.sqlの場合 ※エラーが発生する

  • version:1として運用していたファイル名をintermediate_customers_v1.sqlに変更した場合

この状態で、dbt build --select intermediate_customers+を実行してみると、version:2として定義したカラム名を変更後のモデルが実行され、元々運用していたmart_customersもversion:2のカラムfirst_order_dateを参照して構築されたことがわかります。これで、version:2の本番リリースは完了です!

最後に

dbtでモデル自体をバージョン管理し、後続のモデルへの影響を確認してから最新のモデルをリリースできる「Versions」を試してみました。

正直、dbt1.5の新機能の中で一番複雑な機能で、運用もちゃんと考えないと逆に混乱を招いてしまう可能性もある機能だと感じました。ただ有効に使えれば、データ型の変更やカラムの削除・名称変更時など後続のモデルに影響を及ぼす変更を行うときに、トラブルを起こさずに移行することが出来ます!