はじめに
こんにちは。23新卒としてデータ基盤部に配属され、アナリティクスエンジニアをしている佐藤( @xavifrog ) です。 データ基盤部ではチームトポロジーの概念で事業領域ごとのグループ分けがされており、私が所属するグループでは主にバックオフィス、スポーツドメインのデータ基盤開発・運用を行っています。
本ブログでは2023年上期に行われた横浜 DeNA ベイスターズ(以下 YDB ) のビジネスデータ基盤リプレイスプロジェクトについて取り上げます。本プロジェクトではデータ変換に GCP のフルマネージドサービスである Dataform を採用し、実際に約4ヶ月間運用してきました。その中で行った工夫や今後の問題点、開発・運用期間に感じた Dataform の利点や課題についてお話ししていきます。
技術選定・アーキテクチャ設計
今回リプレイスしたのは BAYSTARS ID や ベイチケ といった YDB が提供するプロダクトのデータを分析する基盤です。旧データ基盤は事業部の方々が必要に迫られて作成されていたもので、view の乱立や変更履歴の追跡ができていないなど、データガバナンス面で問題があり、その改善のためにデータ基盤部主体でのリプレイスプロジェクトが始まりました。開発はデータ基盤部が行いますが、運用は YDB 側の別組織が主となって行うため、運用工数の少ない設計をする必要があります。
全体的なアーキテクチャは Scaled Architecture1 に則り、データを取り込んで提供する Data Provider と、データ利活用のために加工、分析を行う Data Consumer に分けられています。Data Consumer では更にデータクレンジングを行う staging layer とビジネスロジックを取り扱う mart layer に分離され、そこで用いられるデータ変換ツールには Dataform が採用されました。 DeNA のデータ基盤におけるデータ変換ツールは一般に dbt core が用いられており、Dataform の採用は初めての事例となります。採用理由は以下の通りです。
- YDB 側の別組織が主体となって運用を行うため、運用コストを抑える必要がある。その点で GCP のマネージドサービスであり、インフラの管理を必要としない Dataform が優位。
- 同様にインフラ管理の必要がない dbt cloud と異なり、GCP の中で完結すること。
Dataform で利活用しやすい状態に変換されたデータは BI ツールである Looker によってダッシュボードの形で利用者に提供されます。ダッシュボードの開発は利用者からの要望に基づいてアナリティクスエンジニアが一気通貫に対応したり、Looker のモデリング言語である LookML の追加開発が必要なければ利用者自身がダッシュボードの編集を行うなど、様々な方法で行われます。
開発自体は先輩エンジニアをリーダーとし、新卒のデータエンジニア、アナリティクスエンジニア(私)が1名ずつの計3名が中心となって行われました。DeNA TechCon 2024にて、前述の先輩エンジニアの登壇がございます。ぜひご覧ください。
なお、DeNA TechCon 2024 はオンライン配信とオフライン会場での視聴が可能です。 DeNA TechCon 2024 のオフライン会場は DeNA 社員からの招待制となっています。 オフライン参加にご興味ある方は是非 DeNA 社員にお声がけください。
Data Consumer 開発
このセクションでは Dataform を用いた Data Consumer 開発で取り組んだことを紹介します。
開発環境
Dataform の開発は GCP 上に構築された GUI(development workspace) を用いる方法と、オープンソースとして公開されている Dataform CLI を用いてローカルで開発する方法があります。本プロジェクトでは当初 GUI を用いた開発を行っていましたが、クエリのコンパイル完了までのリードタイムといった開発体験面やエンジニアしか Dataform の開発に関わらない体制だったことから CLI に移行しました。
なお、コード品質の観点から、Dataform CLI に内蔵されている formatter(dataform format) を pre-commit で設定し、フォーマットを行っています。
repos:
- repo: local
hooks:
- id: dataform-format
name: dataform format
entry: dataform format .
language: system
pass_filenames: false
staging layer
staging layer ではデータ品質の向上を目的に、生データのクレンジングを行います。具体的には以下の3つを行います。
- 命名や型の統一
- SCD(Slowly Changing Dimension) Type 2 の導入
- Assertion によるデータ品質の担保
命名や型の統一
本データ基盤は複数のデータソースがあり、同様の概念であるものの命名が異なったり、同じ名前であっても型が異なるといった場合があります。これらは後工程の障害となるため、staging layer で差分を吸収します。
具体的には下記のルールに基づいて統一します。
- 命名規則は snake_case に統一
- id, code, version などは基本的に STRING
- 数量が意味を持つものは INTEGER
- 金額系は FLOAT
- 時刻関係は UTC か JST を書く
- date で終わるものは DATE
- createdAt, updatedAt などは TIMESTAMP
- time で終わるものは TIME
- createdTime なのに TIMESTAMP となっているものを createdAt にする等は適宜判断
- 0, 1で表される flag は BOOLEAN
# 例示のための mock コード
config {
type: "table",
tags: ["mock"],
description: "This table is staging layer for mock.mock",
}
SELECT DISTINCT
CAST(MockId AS STRING) AS mock_id,
CAST(MockPrice AS FLOAT64) AS mock_price,
MockCreated AS mock_created_at,
FROM
${ref("mock", "mock")}
SCD(Slowly Changing Dimension) Type 2 の導入
一般的なWebサービスにおいて、データの変更があった場合に該当のレコードを上書きする(SCD Type 1) 設計がされることがありますが、この状態では変更履歴が残らず、特定の日時の状態に遡っての分析を行うことができません。しかし、データ分析起因でプロダクトの実装を変更するのは難しいため、staging layer で毎回全データの洗い替えを行うのではなく、新規や変更があったレコードを既存テーブルに追加する incremental table にすることで SCD Type 2 を導入します。
# 一般公開データセットである google_trends.top_terms での例
## 後述する問題によりCLIでないと動作しません
config {
type: "incremental",
tags: ["google_trends"],
bigquery: {
partitionBy: "refresh_date",
requirePartitionFilter: true,
},
description: "This table is staging layer for google_trends.top_terms",
}
SELECT DISTINCT
rank AS rank,
refresh_date AS refresh_date,
dma_name AS dma_name,
CAST(dma_id AS STRING) AS dma_id,
term AS term,
week AS week,
score AS score,
FROM
${ref("google_trends", "top_terms")}
WHERE
refresh_date > latest_partition_field
pre_operations {
DECLARE latest_partition_field DEFAULT (
${
when(incremental(),
`SELECT MAX(refresh_date) FROM ${self()} WHERE DATE(refresh_date) >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH)`,
`SELECT DATE("2000-01-01")`)
}
)
}
Assertion によるデータ品質の担保
データ品質の観点で Assertion を導入しています。Dataform は config で uniqueKey や nonNull といった 組み込みの Assertion を付与することができますが、2024年1月現在では組み込みの Assertion にパーティションフィルタを指定できないため、パーティションフィルタの指定が必須になっている table ではエラーが発生します。そのため、開発ルールとしてパーティションフィルタを必須としていない view や table では 組み込みの Assertion を設定し、パーティションフィルタを必須としている incremental table では type: Assertion のファイルを別途作成する ことで対応しています。作成している Assertion の種別は以下の2つです。
指定されたカラムで一意になるか
データ分析観点では該当テーブルにおいてどのカラムの組み合わせがユニークかをあらかじめ明示するのは有用です。staging layer ではほぼ全てのテーブルについて unique key に対する Assertion を付与しています。
# 一般公開データセットである google_trends.top_terms での例
config {
type: "assertion",
tags: ["google_trends"],
description: "This assertion is unique assertion for google_trends.top_terms",
}
SELECT
*
FROM
(
SELECT
rank,
refresh_date,
dma_id,
week,
COUNT(1) AS index_row_count
FROM
${ref("google_trends", "top_terms")}
WHERE
refresh_date > "2000-01-01"
GROUP BY
rank,
refresh_date,
dma_id,
week
) AS data
WHERE
index_row_count > 1
SCD Type による差分が想定されたものか
前節で記載の通り、staging layer では SCD Type 2 を導入しています。その場合、元データが SCD Type 1 のテーブルについては staging layer の方が元データよりも件数が多くなるという事象が発生します。こちらは想定される動作ですが、実際に正しい動作をした結果差分が生じているのか、何らかの異常動作によって差分が生じているのかがクエリを叩かないと判断できません。都度クエリを作成するのは手間であるので、あらかじめ Assertion という形で用意しています。
# 一般公開データセットである google_trends.top_terms での例
config {
type: "assertion",
tags: ["google_trends"],
description: "This assertion is equal assertion for google_trends.top_terms",
}
SELECT
*
FROM
(
SELECT
rank AS rank,
refresh_date AS refresh_date,
dma_name AS dma_name,
CAST(dma_id AS STRING) AS dma_id,
term AS term,
week AS week,
score AS score,
FROM
${ref("google_trends", "top_terms")}
WHERE
refresh_date > "2000-01-01"
EXCEPT DISTINCT
SELECT
rank,
refresh_date,
dma_name,
dma_id,
term,
week,
score
FROM
${ref("google_trends__top_terms")}
WHERE
refresh_date > "2000-01-01"
)
UNION ALL
SELECT
*
FROM
(
SELECT
rank,
refresh_date,
dma_name,
dma_id,
term,
week,
score
FROM
${ref("google_trends__top_terms")}
WHERE
refresh_date > "2000-01-01"
EXCEPT DISTINCT
SELECT
rank AS rank,
refresh_date AS refresh_date,
dma_name AS dma_name,
CAST(dma_id AS STRING) AS dma_id,
term AS term,
week AS week,
score AS score,
FROM
${ref("google_trends", "top_terms")}
WHERE
refresh_date > "2000-01-01"
)
上記らはいずれも重要な作業であるものの、一定のルールに基づいた単純作業であり、エンジニアとしては開発体験が良くありません。そこで、BigQuery の schema を参照し、正規表現等を用いたルールを元にクエリの半自動生成を行う自作ライブラリを作成することで開発の効率化と標準化を行いました。こちらは他のプロジェクトでも使えるように汎用化を行い、社内ライブラリとして公開されています。
mart layer
mart layer では KPI など、ダッシュボードで用いられる形にデータを加工します。本プロジェクトにおいては、view 等で作成されている既存のビジネスロジックを staging layer を参照するように書き換え、継ぎ足しによって負債となっていた部分をデータモデリングによって改善しました。なお、staging layer は元データと一対一対応であり、変更される機会は少ないのに対し、mart layer は変更頻度が高いことからレポジトリ自体が分離され、環境も mart layer のみ dev 環境が用意されています。
リプレイスによる改善点と課題
改善点
データガバナンス向上
Dataform の導入により、各所に散らばっていた view が Dataform の生成するデータセットに集まり、なおかつ git ベースの変更履歴追跡が可能になるなど、データガバナンスや活用のしやすさといった面で改善しました。
データ品質・拡張性向上
staging layer における命名・型の統一、assertion の追加により、手間になっていた型等の変換処理が不要になるといった利便性改善やデータ品質の担保を実現しました。また、mart layer におけるデータモデリングによって負債化していた view を整え、存在が検知されていなかった集計ミスの修正や拡張性向上を行うことができました。
課題
ダッシュボード提供までの仕組み化
開発期間中は Dataform, LookML, Looker ダッシュボードとアナリティクスエンジニアが一気通貫に対応し、短期間でダッシュボードの提供を行うことができていました。しかし、引き継ぎ後は一人が3つの工程全てを担当することが難しいために作業を複数人で分割しており、それによって各工程間の接続が上手くいかずに手戻りが発生してしまっています。要因としては以下の通りです。
-
LookML, Looker ダッシュボード上で記述できる複雑な集計ロジックも利用しているが、Dataform, LookML, Looker のどこでロジックを持つのかの明文化ができておらず、開発者の判断になってしまっている。
-
Looker ダッシュボード編集者が LookML の動作確認をするには LookML の本番デプロイが前提になっている。これはコストにより Looker の dev 環境を用意していないが、ダッシュボード編集者は Looker の Developer 権限を持っていないことで本番ブランチしか利用できないため。
今後は暗黙知の明文化や CI など DevOps 的仕組みの導入、レビューによるクエリ品質の担保など、より良い運用体制構築を目指していきます。
開発・運用してみて感じた Dataform の利点・課題
利点
GUI による習得のしやすさ
Dataform はシンプルであり、GUI も BigQuery ライクであること、プレビュー機能が利用できることから、アナリティクスエンジニアリング初学者や、アナリスト等の BigQuery を利用している非データエンジニアの方でも扱いやすいサービスになっていると感じます。
開発・運用コスト
dbt core では CloudRun 等のコンピューティングリソースを構築する必要がありますが、Dataform は GCP マネージドなサーバーレスサービスであるため、環境構築や運用の手間が限りなく低く、クエリの作成などの本質的な作業に集中することができます。また、問題が発生した際に確認するべき範囲が Dataform の中に閉じているため、新米アナリティクスエンジニアであっても問題の特定や修正が容易です。
課題
dbt と比較するとエコシステムが充実していない
dbt power user のような便利なツールが充実している dbt と比較すると、Dataformはエコシステムの面で物足りなさを感じてしまいます。また、dbt は Jinja を用いたマクロを作成できますが、Dataform で同じことをしようとすると JavaScript を使用する必要があり、Python がメイン言語になりがちなデータ基盤部では属人化の懸念が出てきます。ただ、1エンジニアとしては SCD package など、うまく使えばより面白い使い方ができるのではと興味が出ている面はあります。
サービス自体が発展途上
GCP マネージドの Dataform は2023年6月に GA となりましたが、未だに発展途上感は否めません。例えば partition filter が必須となっているテーブルに対する config 経由の Assertion が機能しなかった り、 GCP上のGUIで pre_operations 句が動作しなかった り、サイレントリリースによって一部のパイプラインが失敗した(定常的に発生しているわけではなく、一度のみ)等の問題は発生しています。
ただ、GUI におけるフォーマットボタンの追加やタブ形式 UI への変更、OSS のバージョンアップ追従によるパフォーマンス向上といった改善の取り組みも頻繁に行われており、Dataform の今後に期待しています。
おわりに
本記事では YDB のビジネスデータ基盤のリプレイスプロジェクトについて取り上げました。新基盤ではデータ変換部分に Dataform を採用し、運用コストを抑えつつ、データガバナンスを向上させることができました。ただ、運用面では課題も生まれており、今後それらに対応しつつ、YDB の2024年シーズンを支援していきます。
データ基盤部では別部署でも Dataform の採用事例が生まれ、Dataform の勉強会が発足しました。つい先日も Dataform API の情報を教えていただき、それを元に内蔵ワークフローの実行通知を slack に流す仕組みを構築しました。こちらについても別の機会でお話しできればと思います。 今後も部署間での情報共有を進めつつ、より良い開発・運用体制を追い求めていくことで、dbt と並ぶ選択肢として Dataform と向き合ってまいります。
-
Piethein Strengholt 著、村上 列 訳 「大規模データ管理 ―エンタープライズアーキテクチャのベストプラクティス」(O’Reilly Japan, 2022) より ↩︎
最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。