40 KiB
source_url, ingested, sha256, discovered_from
| source_url | ingested | sha256 | discovered_from | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| https://techblog.goinc.jp/entry/2022/06/14/090000 | 2026-07-02 | c0be9eddbfa6f4a21a7a1354fe2d8fb67a8eba9e876e302c9b001c219bdcc726 |
|
タクシーアプリ「GO」、法人向けサービス「GO BUSINESS」、タクシーデリバリーアプリ「GO Dine」の分析基盤を開発運用している 伊田 です。今回、dbt と Dataform を比較して Dataform を利用することにしましたので、導入経緯および Dataform の初期構築を紹介します。
※ 本記事の対象読者は ELT ツールを利用している方を対象にしています
これは MoT Engineer Challenge Week 2022 Spring の記事です。
はじめに
本記事では、まず、dbt および Dataform というツールについて簡単に説明させて頂き、次に現在データ分析チームが抱えている課題について取り上げます。その後、2つのツールについて検証した内容を紹介し、その結果、Dataform の導入に至った経緯を説明します。また、最後に Dataform の初期構築で工夫した点についても紹介させて頂きます。
ツール導入に至るまでに様々な記事を参考にさせて頂きました。最初に謝辞を述べさせて頂きますとともに、参考にしたサイトは本記事の最後に一覧として記載させて頂いています。
※ 検証および初期構築は千田と 伊田 で実施しました
※ 検証は Engineer Challenge Week を利用して実施しました
dbt / Dataform とは
dbt, Dataform という2つの製品は、 ELT のうち、Transform をするためのツールです。つまり、分析基盤にデータが格納された後に、 SQL を発行してデータの加工処理をするためのツールで、加えて null チェックや unique チェックなどのテスト、 ドキュメンテーション 、データリネージ、データパイプラインの実行・スケジューリング等の管理もすることができます。
dbt
- 公式サイト
- SaaS 版と CLI 版(OSS)があります
- SaaS 版は3つのプランがあります
- Free: 個人の検証目的の場合は無料で使えます
- Team: チームで開発する場合は1人あたり $50 / Month 掛かります
- Enterprise: SSO や Custom SLAs など、より高度な機能が提供されます
- Free: 個人の検証目的の場合は無料で使えます
Dataform
前提
- 弊社の分析基盤は GCP BigQuery です。よって、以降の検証は BigQuery に関してのものです
- BIツールは Looker を利用しています
- 以前から Cloud Composer (Airflow) を利用したワークフローが稼働しています
- データエンジニア、データアーキテクトとの人数対比で、データアナリストは約5倍程度在籍しています
課題
現在、分析チームには、データマートのリリース速度や品質に課題があります。
- データマートのリリース速度が遅い
- データエンジニアの人数が少ない 2. エンジニアしかデータマートが作れない(Docker/Airflow の知識が必要)
- 品質が悪い
- テストをする仕組みがない(そこまで手が回っていない)
結果として、下記の事象が発生しています。
- 新規依頼から構築完了までに時間が掛かるので、アナリストが簡単に構築できる BigQuery スケジューリングクエリでデータマートを生成している
- 依存関係が定義できないので、巨大な SQL ができやすい
- Looker にデータマート代わりの ビジネスロジック が入っている
- 上流のデータが変わった時に気づけない(欠損やデータの期待値が違うなど)
- 利用者側からのアラートがあがって初めて気づくこともある
こうした課題への対応として諸々機能がそろっている dbt や Dataform の検討をしました
- データマートのリリース速度の改善
- 今すぐデータエンジニアやデータアーキテクトの人数を増やすことは難しいため、データアナリストでもデータマートが作れる状態にしたい 2. データアナリストが触りやすい GUI ツールを導入することが望ましい
- 品質の改善
- モニタリングをするために、テスト機能が必要になる 2. テストをするために、テストがしやすい形に SQL を分割して書き直す必要がある 3. 分割した結果、中間View/Tableが増えるため、依存関係を考慮したスケジューラーが必要になる
検証
検証内容
- 普及度: 将来性や困った時に解決しやすいか
- 利用コスト: 予算確保および横展開のしやすさ
- 学習コスト: ツール利用の敷居の低さ
- 機能比較: 課題に対して必要な機能がそろっているか
- 運用: 運用のしやすさ
検証結果
普及度
Google 検索による結果が下記です
- dbt: 約 18,700,000 件
- dataform: 約 320,000 件
※ 2022/3/31 確認
利用コスト
- dbt:
- 1人あたり $50 / Month 最大40人まで
- 加えて、参照権限のみのユーザーが50人分付与される
- それ以上は Enterprise に移行する必要があると思われる
- 1人あたり $50 / Month 最大40人まで
- Dataform: 無料
学習コスト
主観的なものとなりますが、基本的には SQL + dbt / Dataform のお作法に則る形であるので、データアナリストが触る部分としては、dbt も Dataform もそこまで学習コストは高くないと感じました。
一部コア部分の作り込みや CLI 版については多少学習コストが必要だと思います。
機能比較
機能比較には、 こちら の チュートリアル を参考に行いました。
※ 主要なものを取り上げており、すべての機能を網羅しているわけではありません
データモデル定義
- dbt: SQL と YAML で構成される。 YAML にテスト、ドキュメントなどを記述する。Jinja やマクロを利用した柔軟な記述ができる。 SQL に config を設定することで、個々の SQL の挙動を制御できる
- Dataform: SQLX として、 SQL 、テスト、ドキュメントを1ファイルに記述する。 JavaScript を利用した柔軟な記述ができる。SQLX に config を設定することで、個々の SQL の挙動を制御できる
前処理、後処理
- dbt: pre-hook, post-hook を利用することで、クエリの前後に処理を挟むことができる
- Dataform: pre_operations, post_operations を利用することで、クエリの前後に処理を挟むことができる
データロード
ソース定義
- dbt:
- dbt の外側で作成されたテーブルについて、source を宣言することで SELECT文の中で参照できるようになる。SELECT文でテーブル名をベタ書きせずに、
{{ source('table_name') }}とするとデータリネージで表示されるようになるdbt source freshnessコマンドでデータの鮮度チェックができる
- dbt の外側で作成されたテーブルについて、source を宣言することで SELECT文の中で参照できるようになる。SELECT文でテーブル名をベタ書きせずに、
- Dataform:
- Dataform の外側で作成されたテーブルについて、declaration を宣言することで SELECT文の中で参照できるようになる。SELECT文でテーブル名をベタ書きせずに、
{{ ref('table_name') }}とするとデータリネージで表示されるようになる
- Dataform の外側で作成されたテーブルについて、declaration を宣言することで SELECT文の中で参照できるようになる。SELECT文でテーブル名をベタ書きせずに、
クエリの部品化
- dbt: ephemeral という機能を利用することで、 SQL を部品化できる。さらに、Jinja や macro を利用して柔軟な書き方ができる
- Dataform: JavaScript を利用して、 SQL を部品化できる
Viewの作成
- dbt: View を作成する。
create or replace viewが実行される - Dataform: View を作成する。
create or replace viewが実行される
Tableの作成
- dbt: Table を作成する。
create or replace tableが実行される - Dataform: Table を作成する。
create or replace tableが実行される
Tableの作成 incremental model
- dbt:
- Merge 文を実行することで増分・差分処理を実現する
- 初回実行時および、
--full-refreshオプションをつけるとcreate or replace tableが実行される
- 初回実行時および、
- Merge 文を実行することで増分・差分処理を実現する
unique_key の指定がない場合は Insert 処理
merge into dest
using (
select
.
.
.
from source
where
created_at > (select max(created_at) from dest)
) as source
on False
when not matched then insert
.
.
.
unique_key の指定がある場合は Upsert 処理
merge into dest
using (
select
.
.
.
from source
where
created_at > (select max(created_at) from dest)
) as source
on dest.id = source.id
when matched then update set
.
.
.
when not matched then insert
.
.
.
incremental_strategy で insert_overwrite を指定した場合は DELETE INSERT による パーティション 置換処理
-- 定義ファイル
-- 当日と前日分を取得する。柔軟にやる場合は macro を使う
{% set partitions_to_replace = [
'date(current_date)',
'date(date_sub(current_date, interval 1 day))'
] %}
{{
config(
materialized='incremental',
incremental_strategy = 'insert_overwrite',
unique_key='order_id',
partition_by={
'field': 'order_date',
'data_type': 'date'
},
partitions = partitions_to_replace
)
}}
select
id as order_id,
user_id as customer_id,
order_date,
status
from research_dbt.raw_orders
{% if is_incremental() %}
where order_date in ({{ partitions_to_replace | join(',') }})
{% endif %}
merge into \`myproject\`.\`research_dbt\`.\`stg_orders\` as DBT_INTERNAL_DEST
using (
select
id as order_id,
user_id as customer_id,
order_date,
status
from research_dbt.raw_orders
where order_date in (date(current_date),date(date_sub(current_date, interval 1 day)))
) as DBT_INTERNAL_SOURCE
on FALSE
when not matched by source
and DBT_INTERNAL_DEST.order_date in (
date(current_date), date(date_sub(current_date, interval 1 day))
)
then delete
when not matched then insert
(\`order_id\`, \`customer_id\`, \`order_date\`, \`status\`)
values
(\`order_id\`, \`customer_id\`, \`order_date\`, \`status\`)
- Dataform:
- Merge 文を実行することで増分・差分処理を実現する
- 初回実行時および、
--full-refreshオプションをつけるとcreate or replace tableが実行される
- 初回実行時および、
- Merge 文を実行することで増分・差分処理を実現する
uniqueKey の指定がない場合は Insert 処理
insert into dest
select ... from source
where created_at > (select max(created_at) from dest)
unique_key の指定がある場合は Upsert 処理
merge dest T
using (
select
.
.
.
from source
where created_at > (select max(created_at) from dest)
) S
on T.id = S.id
when matched then update set
.
.
.
when not matched then
.
.
.
updatePartitionFilter の指定がある場合は パーティション のプルーニングが行われる
-- 定義ファイル
-- 前日分以降を更新対象にする。柔軟にやる場合は pre_operations を使う
config {
type: "incremental",
uniqueKey: ["order_id"],
bigquery: {
partitionBy: "order_date",
updatePartitionFilter: "order_date > DATE(TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY))"
}
}
select
id as order_id,
user_id as customer_id,
order_date,
status
from ${ref("raw_orders")}
where
order_date > DATE(TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 2 DAY))
merge \`myproject.research_dataform.stg_orders\` T
using (
select
id as order_id,
user_id as customer_id,
order_date,
status
from \`myproject.research_dbt.raw_orders\`
where
order_date > DATE(TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 2 DAY))
) S
on T.order_id = S.order_id
and T.order_date > DATE(TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 2 DAY))
when matched then
update set \`order_id\` = S.order_id,\`customer_id\` = S.customer_id,\`order_date\` = S.order_date,\`status\` = S.status
when not matched then
insert (\`order_id\`,\`customer_id\`,\`order_date\`,\`status\`) values (\`order_id\`,\`customer_id\`,\`order_date\`,\`status\`)
テスト
- dbt:
unique:column_nameがユニークな値になっているかnot_null:column_nameがnullを含んでいないかaccepted_values:column_nameが決められた値になっているかrelationships: テーブルのキーがテスト対象のテーブルのキーと結合できるか- 任意のテストを書きたい場合はマクロを書くか、 dbt_utils にテスト用のマクロが用意されているので利用する
- Dataform:
ドキュメント、データリネージ
- dbt: テーブルのドキュメントを作成することができる
- Dataform: テーブルのドキュメントを作成することができる
スナップショット
- dbt:
- 初回は全レコードのスナップショットを作成する
- 2回目以降は、strategy に従って対象レコードのみスナップショットを作成する
- strategy
strategy='timestamp'の場合、unique_key, timestamp 列 を参照して変更があればスナップショットを取得する -strategy='check_cols'の場合、unique_key をもとに、対象となるカラムに変更があればスナップショットを取得する - 画像は id, user_id, order_date, status までが対象テーブルの スキーマ で、以降は dbt が付与した情報
- 初回は全レコードのスナップショットを作成する
- Dataform:
- incremental model としてスナップショットを取得する
- updated_at を参照して、SELECT句に CURRENT_TIMESTAMP() を付与して 差分バックアップ を取っていくイメージ
- incremental model としてスナップショットを取得する
ジョブ実行
- dbt: ref 関数を使用することで依存関係が定義され、ジョブ実行時に依存関係を考慮して順次実行してくれる。指定したタグに紐付いたモデルのみ実行等もできる
- Dataform: ref 関数を使用することで依存関係が定義され、ジョブ実行時に依存関係を考慮して順次実行してくれる。指定したタグに紐付いたモデルのみ実行等もできる
運用
スケジューラー
リカバリ / backfill
Slack通知
導入判断
結論
結論としては、Dataform を選択することにしました。不確定要素が多い中では、Dataform のほうがスモールスタートしやすいと判断しました。
理由
- 課題に対しては dbt / Dataform ともにクリア
- アナリストが自由にデータマートを作るために GUI が必要である
- テスト機能が必要である
- アナリストが自由にデータマートを作るために GUI が必要である
- 導入までのハードルは Dataform が低い
今後の展望として
結果が出て機能が物足りない場合は、dbt への移行も検討したいと思います。基本的な思想は同じなので移行は難しくなく、実績があれば予算も取りやすいと考えています。
今回は Dataform を選択しましたが、dbt と Dataform、この2つは素晴らしい製品だと思います。特に気に入っているのは ref 関数です。この関数があることでデータリネージとして可視化ができ、調査時に依存関係を簡単に把握することができます。また、ジョブ実行時も依存関係を考慮して自動的に順次実行してくれるのが嬉しいと感じています。
初期構築
ここからは Dataform 導入にあたり初期構築をどのようにしたか紹介したいと思います。
※ ここからは チュートリアル 程度の知識がある前提で記述しています
SaaS版とCLI版の併用
下記の理由から SaaS 版と CLI 版を併用することにしました。
- データアナリスト:スケジューリングクエリや Looker に組み込まれているロジックを Dataform 側に寄せる。スケジューラーの機能もあることからデータアナリストは SaaS 版で完結することができる
- データエンジニア、データアーキテクト:元々データ連携処理であったり、データマートの生成を Airflow 上で実行していることから、Dataform の処理を Airflow で設定した日付注入して実行したい。 リカバリ や backfill の時に変数指定ができる CLI 版を使いたい
運用の流れとしては下記を想定しています。
- データアナリストがデータアーキテクトのサポートの元、 SaaS 版でデータマートを作成する
- 単発の場合は SaaS 版で完結し、本格運用に乗る場合はデータエンジニアに運用を引き継いてAirflow から実行できるように整備する
GitHub連携
コードは GitHub と連携しています。
環境
本番環境と開発環境は、 GCP プロジェクトでわけています(デー タセット 配下は同じ構成)。
- 本番: prod-project
- 開発: dev-project
environments.json
デフォルトは開発環境に向くようにして、master にマージされて初めて本番環境に処理が向くようにしています。
{
"environments": [
{
"name": "development",
"configOverride": {},
"gitRef": "develop"
},
{
"name": "production",
"configOverride": {
"defaultDatabase": "prod-project"
},
"gitRef": "master"
}
]
}
ディレクトリ構成
definitions 配下(SQL 置き場)はベストプ ラク ティスに則って ディレクト リを切りました。
- reporting: データマート層
- staging: データウェアハウス層
- sources: データレイク層
- playground: Dataform の機能テスト用
また、 SaaS 版で生成された初期ファイルに加えて、 CLI 版の利用や各種 スクリプト を tools 配下に切っています。
.
├── definitions
│ ├── playground
│ ├── reporting
│ ├── sources
│ └── staging
├── includes
│ └── date_config.js
├── dataform.json
├── dataform_prod.json
├── environments.json
├── package-lock.json
├── package.json
└── tools
├── cli
└── scripts
ファイルの命名
テーブル名.sqlx としています。
例えば、データマートにテーブルを作る場合は下記となります。
- definitions
- reporting
- dataset_id
- table_name.sqlx
- dataset_id
- reporting
スキーマの指定、タグの指定
- Dataform では スキーマ を省略して書くことができますが、BigQueryでは、別デー タセット 同一テーブル名が存在する場合があるので、Dataform が解釈できるように スキーマ を必ず指定します。 スキーマ の指定は config と ref 関数で指定します。
- データパイプラインをスケジューリングして動かすために、一緒に処理が動く単位で同一のタグ付けをします (SaaS 版で動かす場合でも、Airflow で動かす場合でもタグ付けします)。
dataset_id.table_name の場合
config {
type: "incremental",
tags: ["dataform_test_dag_v1"],
schema: "dataset_id",
uniqueKey: ["id"],
bigquery: {
partitionBy: "DATE(ts)",
updatePartitionFilter: "ts >= raw_start_ts"
}
}
SELECT
.
.
.
FROM ${ref("ref_dataset_id", "ref_table_name")}
動的な日付指定
SaaS 版、 CLI 版ともに動的な日付を指定できるような JavaScript を作成しました。
まず、dataform.json に下記の通り変数を定義しています。
- targetStartTs: 対象期間いつから
- targetEndTs: 対象期間いつまで
- shouldOverrideVars: この変数が true のときに、targetStartTs、targetEndTs の変数を使って上書きする
dataform.json
{
"warehouse": "bigquery",
"defaultSchema": "dataform",
"assertionSchema": "dataform_assertions",
"defaultDatabase": "dev-project",
"vars": {
"shouldOverrideVars": "false",
"targetStartTs": "2022-04-01 09:00:00+9",
"targetEndTs": "2022-04-01 10:00:00+9"
}
}
includes/date_config.js
最終的に生成する日付は4つです。
- start_ts: 対象期間いつから
- end_ts: 対象期間いつまで
- raw_start_ts: start_ts からマージンを取ったタイムスタンプ
- raw_end_ts: end_ts からマージンを取ったタイムスタンプ
日付を4つ定義しているのは、処理対象のテーブルにはストリーミングインサートで取り込み時間 パーティション 分割テーブルに挿入されたデータがあり、そのようなテーブルに対しては、_PARTITIONTIME に raw_start_ts と raw_end_ts を使って一時フィルタリングを行い、最終的に created_at のような実際に処理対象としたいタイムスタンプに start_ts と end_ts を使って絞り込むためです。
SaaS 版で実行する時は shouldOverrideVars は必ず false です。
CLI 版で実行するときは、 shouldOverrideVars は true を指定して、 targetStartTs と targetEndTs に任意の期間を指定します。
date_config.js
function getStartTs(unit, start_ago) {
if (\`${dataform.projectConfig.vars.shouldOverrideVars}\` == "true") {
return \`TIMESTAMP('${dataform.projectConfig.vars.targetStartTs}')\`;
} else {
return \`TIMESTAMP_TRUNC(TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL ${start_ago} ${unit}), HOUR)\`;
}
}
function getEndTs(unit, end_ago) {
if (\`${dataform.projectConfig.vars.shouldOverrideVars}\` == "true") {
return \`TIMESTAMP('${dataform.projectConfig.vars.targetEndTs}')\`;
} else {
return \`TIMESTAMP_TRUNC(TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL ${end_ago} ${unit}), HOUR)\`;
}
}
function getRawStartTs(unit, start_ago, start_margin) {
return \`TIMESTAMP_SUB(${getStartTs(unit, start_ago)}, INTERVAL ${start_margin} ${unit})\`
}
function getRawEndTs(unit, end_ago, end_margin) {
return \`TIMESTAMP_ADD(${getEndTs(unit, end_ago)}, INTERVAL ${end_margin} ${unit})\`
}
/*
BigQuery Scripting
*/
function createTemporaryFunctionGetHourUnitTs(start_ago=1, end_ago=0, start_margin=0, end_margin=0) {
return \`"""
create temporary function getHourUnitTs(ts STRING) AS (
CASE ts
WHEN 'raw_start_ts' THEN ${getRawStartTs('HOUR', start_ago, start_margin)}
WHEN 'raw_end_ts' THEN ${getRawEndTs('HOUR', end_ago, end_margin)}
WHEN 'start_ts' THEN ${getStartTs('HOUR', start_ago)}
WHEN 'end_ts' THEN ${getEndTs('HOUR', end_ago)}
END
);
"""\`
}
function createTemporaryFunctionGetDayUnitTs(start_ago=1, end_ago=0, start_margin=0, end_margin=0) {
return \`"""
create temporary function getDayUnitTs(ts STRING) AS (
CASE ts
WHEN 'raw_start_ts' THEN ${getRawStartTs('DAY', start_ago, start_margin)}
WHEN 'raw_end_ts' THEN ${getRawEndTs('DAY', end_ago, end_margin)}
WHEN 'start_ts' THEN ${getStartTs('DAY', start_ago)}
WHEN 'end_ts' THEN ${getEndTs('DAY', end_ago)}
END
);
"""\`
}
module.exports = {
createTemporaryFunctionGetHourUnitTs,
createTemporaryFunctionGetDayUnitTs
};
使い方としては下記です。
createTemporaryFunctionGetHourUnitTs
引数(=デフォルト値)
- start_ago=1
start_tsがスケジュール実行時間の何時間前か
- end_ago=0
end_tsがスケジュール実行時間の何時間前か
- start_margin=0
raw_start_tsがstart_tsの何時間前か
- end_margin=0
raw_end_tsがend_tsの何時間後か
pre_operations 内で、EXECUTE IMMEDIATE FORMAT を実行することで、create temporary function を実行し、日付を取得できるようにしています。
-- createTemporaryFunctionGetHourUnitTs
pre_operations {
EXECUTE IMMEDIATE FORMAT(${date_config.createTemporaryFunctionGetHourUnitTs(
/* start_ago = */ 1,
/* end_ago = */ 0,
/* start_margin = */ 24,
/* end_margin = */ 24)});
}
SELECT
.
.
.
FROM $ref("dataset_id", "table_name")
WHERE
_PARTIONTIME >= getHourUnitTs('raw_start_ts')
AND _PARTIONTIME < getHourUnitTs('raw_end_ts')
AND created_at >= getHourUnitTs('start_ts')
AND created_at < getHourUnitTs('end_ts')
2022/5/2 10:10 (JST) に実行した場合、
- start_ts: 2022-05-02 00:00:00 UTC
- end_ts: 2022-05-02 01:00:00 UTC
- raw_start_ts: 2022-05-01 00:00:00 UTC
- raw_end_ts: 2022-05-03 01:00:00 UTC
となります。
その他ツール類
ここからは Dataform 導入にあたり整備したツール類を紹介します。
Docker関連
tools/ cli 配下は下記のようになっています。
tools/cli
├── Dockerfile
├── README.md
├── compiled
├── compiled_json_analyzer.js
├── deploy.sh
├── df-credentials.json
├── df-credentials_prod.json
├── docker-compose.yaml
├── docker-compose_prod.yaml
└── settings.json
2種類あるファイルは、本番環境と開発環境用で無印が開発環境用です。docker image は本番用と開発用で切り分けています。
Dockerfile
env=” prod” が渡されると本番用です。
FROM node:17-buster-slim
ARG _env=""
# 基本的に依存するものはないのでコンテナ内で使う可能性があるものを追記する
RUN apt-get update \
&& apt-get dist-upgrade -y \
&& apt-get install -y --no-install-recommends \
vim \
jq \
&& apt-get clean \
&& rm -rf \
/var/lib/apt/lists/* \
/tmp/* \
/var/tmp/*
WORKDIR /usr/app/dataform
RUN npm i -g @dataform/[email protected]
# dataform cli 使用時の設定
COPY tools/cli/settings.json /root/.dataform/
# OAuth 認証のため接続先のプロジェクトのみが記載されている
COPY tools/cli/df-credentials${_env}.json /usr/app/dataform/.df-credentials.json
# 資材
COPY definitions /usr/app/dataform/definitions
COPY includes /usr/app/dataform/includes
COPY dataform${_env}.json /usr/app/dataform/dataform.json
COPY package.json /usr/app/dataform/
RUN dataform install .
ENTRYPOINT tail -f /dev/null
setting.json
dataform init で生成されるファイルです。
{
"allowAnonymousAnalytics": true,
"anonymousUserId": "your-anonymous-user-id"
}
df-credentials.json
同じく、dataform init で生成されるファイルです。
{
"projectId": "dev-project",
"location": "US"
}
docker-compose.yaml
version: "3"
services:
dataform:
# image: your-image-path
build:
context: ../../
dockerfile: tools/cli/Dockerfile
args:
_env: ""
container_name: dev
volumes:
- ~/.config/gcloud:/root/.config/gcloud
- ../../definitions:/usr/app/dataform/definitions
- ../../includes:/usr/app/dataform/includes
# - ./compiled:/usr/app/dataform/compiled
# - ./compiled_json_analyzer.js:/usr/app/dataform/compiled_json_analyzer.js
この docker image を Airflow の GKEPodOperator で呼び出して Dataform を実行しています。
実行コマンドは下記です。
- actions を指定すると、対象のテーブルと対象テーブルの assertion が実行されます。
- vars を指定すると、変数を指定できます。この例では、対象期間いつから、いつまでを指定しています。
- Airflow から日付を取得して変数として注入し、かつ上述の date_config.js と組み合わせることで任意の期間のデータを生成することができます。
dataform run \
--actions destination \
--vars=shouldOverrideVars=true,targetStartTs='YYYY-MM-DD hh:mi:ss+9',targetEndTs='YYYY-MM-DD hh:mi:ss+9'
Airflow 用コード変換ツール
dbt の こちら の記事を参考に、Airflow の1タスク = Dataform の1テーブル生成処理としたかったのでツールを作りました。ただし、Airflow 上で DAG の解析に負荷を掛けることをしたくないため、Airflow 上で動的に作るのではなく、タスクの依存関係を考慮した Airflow 用のコードを出力するツールを用意しました。
dbt の manifest.json に相当するデータは下記のコマンドから出力できます。
dataform compile --json > manifest.json
クエリ生成ツール
Dataform で コンパイル されたクエリをファイルとして生成したくて compiled_ json _analyzer.js というツールを用意しました(dbt は コンパイル 時にクエリが出力されます)。
docker コンテナ内で下記のコマンドを打つと、 json ファイルを解析して SQL ファイルに変換してくれます。
dataform compile --json | node compiled_json_analyzer.js
declaration 用コード生成ツール
既存のテーブルを Dataform の declaration として取り込みたいので、BigQuery のデー タセット を指定すると、デー タセット 配下のテーブルを declaration ファイルとして出力するツールを用意しました。
おわりに
本記事では、dbt と Dataform を比較検討し、Dataform の導入に至った背景を説明しました。また、Dataform の初期構築のア イデア も紹介させて頂きました。
今後は Dataform を分析チーム内に浸透させ、当初の課題だったデータアナリストが気軽にデータパイプラインを作れない状況を減らし、野良スケジューリングクエリを Dataform に移行させることや、 ビジネスロジック を Looker に作り込まないように是正をしていきたいと考えています。加えて、分析基盤のデータの品質向上に注力できる状態を作っていきたいと考えています。
この比較記事が皆様のご参考になれば幸いです。
参考
- dbtとDataformを比較し、dbtを使うことにした
- dbt Cloudで始めるデータパイプライン構築のdbt入門
- Airflowの処理の一部をdbtに移行しようとして断念した話
- タイミーのデータ基盤品質。これまでとこれから。(問題3: ETLパイプラインにおける加工処理の負債)
- データエンジニア界隈で話題のdbt(data build tool)のまとめ
- dbtを触ってみた感想
- [dbt] 作成するデータモデルに関するドキュメントを生成する
- Building a Scalable Analytics Architecture With Airflow and dbt
- Dataform を導入してみた話
- Data Engineering Study #13 - ELT・データモデリングツール特集回


