dbtの新しいセマンティックレイヤー「MetricFlow」を触ってみた

# はじめに

dbtのセマンティックレイヤーに今後組み込まれる予定である`MetricFlow`について、実際にインストールしてtutorialを試してみました🦄
dbtでは、これまで`dbt_metrics`というメトリック管理用のパッケージが採用されていましたが、今後それが廃止になり、MetricFlowに移行になります。


# dbtとは

https://zenn.dev/dbt_tokyo/books/537de43829f3a0/viewer/what_dbt



# dbt Semantic Layerとは

https://docs.getdbt.com/docs/use-dbt-semantic-layer/dbt-semantic-layer

簡単に言えば、「収益みたいなビジネス指標を計算するロジックをいろんなツール上でバラバラにやるんじゃなくて切り出してdbtで管理しようぜ」 ってやつです。

# MetricFlowとは

https://transform.co/metricflow/

# dbt_metrics から MetricFlowへ

dbtはセマンティックレイヤーの中で使用していたdbt_metricsというパッケージを廃止してMetricFlowを採用することを発表しました。


dbtの開発元であるdbt Labs社は、2023年2月にMetricFlowの開発会社であるTransform社を買収しています。

https://www.getdbt.com/blog/press-release-dbt-acquisition-transform/


なぜdbt_metricsパッケージを廃止するのかについては以下の記事が参考になります。

https://docs.getdbt.com/blog/deprecating-dbt-metrics

セマンティックレイヤーの開発をする上で、先行しているMetricFlowを取り入れた方が実現が早まるということですね。

dbt_metricsは、7 月下旬の dbt-core v1.6 のリリースに伴いサポートが廃止される予定です。

# MetricFlowで何ができるか？

## 1. JOINに対応
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268801/c7c47abc-1af5-8105-f5e9-b92700f85269.png)
図 www.getdbt.comより引用

メトリクスの計算にJOINが使えるようになります。
これは便利そうですね。

### 2. データ プラットフォームのサポートの拡張

現在dbt セマンティックレイヤーはSnowflakeのみ対応していますが、今後BigQueryなど他のデータプラットフォームにも対応していくとのことです。
（ちなみに、dbt_metrics自体はBigQueryにも対応しています)

### 3. より最適化されたクエリ プランと SQL 生成

内部的に最適化されたSQLを生成してくれるようです。
もともとdbt_metricsではjinjaテンプレートを使って力技でメトリック定義をSQLに変換していましたが限界があったようです。(※1)
実際、dbt_metricsを使って複数のメトリクスを組み合わせたりすると、コンパイルされたSQLが複雑すぎてBigQueryの「too many subqueries or query is too complex.」エラーが出てつらいときがありました。

### 4. より複雑なメトリクス タイプ

dbt_metricsよりも使用できるメトリクスタイプが増えるようです。

### 他にもたくさん

ほかにも追加される or 追加される予定の機能がたくさんありますので、ぜひ元記事をチェックしてみてください。

https://www.getdbt.com/blog/dbt-semantic-layer-whats-next/

# MetricFlow触ってみた

前置きが長くなりましたが、実際にMetricFlowを動かしてみたいと思います。
MetricFlowの公式リポジトリを参考に進めていきます。
セットアップした後、用意されているtutorialを試します。

https://github.com/dbt-labs/metricflow

動作環境
```
Python 3.9.16
MetricFlow 0.140.0
pip 22.0.4
pipenv version 2023.5.19
```

## セットアップ

Pythonやpipのインストールは割愛します。
(Metriflowの対応Pythonバージョンは`<3.10`なので注意)

MetricFlowのインストール
```bash
> pip install metricflow
```

MetricFlowのセットアップ
```bash
> mf setup
```

ようこそ画面がでて、どのDWH使うか聞いてきます。

```
🎉 Welcome to MetricFlow! 🎉

Please enter your data warehouse dialect.
Use 'duckdb' for a standalone demo.

Dialect (bigquery, databricks, duckdb, postgresql, redshift, snowflake):
```

`duckdb`を選べば、面倒なDWHのセットアップや認証を必要とせずローカルでデモを動かせるので今回はこれを選びます。

`~/.metricflow/config.yml`が生成されます。

では、MetricFlowのチュートリアルを動かしてみましょう

# MetricFlowのチュートリアルを試す

以下のコマンドを実行します。
```bash
> mf tutorial
```

するとヘルスチェックが実行されます。
```
❯ mf tutorial
For specifics on the health-checks, please visit https://docs.transform.co/docs/deployment/integrations/dw/
✔ Health checks completed.
• ✅ duckdbsqlclient - SELECT 1: Success!
• ✅ duckdbsqlclient - Create schema 'mf_demo': Success!
• ✅ duckdbsqlclient - Create table 'mf_demo.health_report' with a SELECT: Success!
• ✅ duckdbsqlclient - Drop table 'mf_demo.health_report': Success!
❓ Are the health-checks all passing? Please fix them before continuing [y/N]:
```
全部 ✅なら「y」で進みます。先に進むと、チュートリアル用のテーブルがduckdb内に生成され、MetrifFlowのチュートリアルを実行できるようになります。

### 補足：チュートリアル用のメトリクス定義とテーブル
チュートリアルで使われるメトリクスの定義ファイルは`~/.metricflow/sample_models`に格納されています。

```
❯ tree ~/.metricflow/sample_models
/Users/y-tsuzaki/.metricflow/sample_models
├── mf_demo_countries.yaml
├── mf_demo_customers.yaml
└── mf_demo_transactions.yaml
```
どのようなyamlファイルなのかは以下からご確認ください。 dbt_metricsとは異なる構文になっていることがわかります。

https://github.com/dbt-labs/metricflow/blob/main/metricflow/cli/sample_models/transactions.yaml

チュートリアルでは、`countries`, `customers`, `transactions`の３つのテーブルを使います。

---- 
ここからは、MetricFlowの公式ドキュメントのチュートリアルを参考に進めます。

https://docs.transform.co/docs/metricflow/metricflow-tutorial

:::note warn
`mf tutorial`コマンドで表示されるサンプルコマンドと若干の違いがありますのでご注意ください。
:::

### メトリクスの一覧を見る

`mf list-metrics`コマンドを使います。

```
❯ mf list-metrics
```

メトリクスが一覧で表示されます。デモ用のメトリクスにはキャンセル数やキャンセル率、収益などのメトリクスを確認できます。
```:OUTPUT
✔ 🌱 We've found 13 metrics.
The list below shows metrics in the format of "metric_name: list of available dimensions"
• cancellations: customer__country__region, customer__ds, ds, is_large, quick_buy_transaction
• cancellation_rate: customer__country__region, customer__ds, ds, is_large, quick_buy_transaction
• revenue_usd: customer__country__region, customer__ds, ds, is_large, quick_buy_transaction
• cancellations_mx: customer__country__region, customer__ds, ds, is_large, quick_buy_transaction
• transaction_usd_na: customer__country__region, customer__ds, ds, is_large, quick_buy_transaction
• transaction_usd_l7d_mx: customer__country__region, customer__ds, ds, is_large, quick_buy_transaction
• transaction_usd_mtd: customer__country__region, customer__ds, ds, is_large, quick_buy_transaction
• transaction_usd_na_l7d: customer__country__region, customer__ds, ds, is_large, quick_buy_transaction
• transaction_amount_usd: customer__country__region, customer__ds, ds, is_large, quick_buy_transaction
• transactions: customer__country__region, customer__ds, ds, is_large, quick_buy_transaction
• quick_buy_amount_usd: customer__country__region, customer__ds, ds, is_large, quick_buy_transaction
• quick_buy_transactions: customer__country__region, customer__ds, ds, is_large, quick_buy_transaction
• new_customers: country__region, ds
```

### メトリックに含まれるディメンションを見る

`mf list-dimensions`コマンドを使います。

```
❯ mf list-dimensions --metric-names new_customers
```


```:OUTPUT
✔ 🌱 We've found 2 common dimensions for metrics ['new_customers'].
• country__region
• ds
```

### 複数のメトリクスの中で使用可能な（=共通の）ディメンションを見る

`--metric-names`に複数のメトリクスを指定します。
```
> mf list-dimensions --metric-names new_customers,transactions
```

```:OUTPUT
✔ 🌱 We've found 1 common dimensions for metrics ['new_customers', 'transactions'].
• ds
```

`ds`というディメンションが表示されました。先ほどは`country__region`も表示されましたが、どちらのメトリクスでも共通で使えるのはディメンションは`ds`だけなので、`ds`のみ表示されます。


### メトリクスをクエリする

`mf query`でクエリすることができます
```
> mf query --metrics transactions --dimensions metric_time --order metric_time
```

```:OUTPUT
✔ Success 🦄 - query completed after 0.43 seconds
| metric_time         |   transactions |
|:--------------------|---------------:|
| 2022-03-07 00:00:00 |              2 |
| 2022-03-08 00:00:00 |              2 |
| 2022-03-09 00:00:00 |              1 |
| 2022-03-10 00:00:00 |              2 |
| 2022-03-11 00:00:00 |              1 |
| 2022-03-12 00:00:00 |              1 |
(略）
```

日毎のトランザクション数が表示されました。

### SQLを見る

先ほどのコマンドに`--explain`を追加します

```
> mf query --metrics transactions --dimensions metric_time --order metric_time --explain
```

```:OUTPUT
✔ Success 🦄 - query completed after 0.30 seconds
🔎 SQL (remove --explain to see data or add --show-dataflow-plan to see the generated dataflow plan):
SELECT
  metric_time
  , SUM(transactions) AS transactions
FROM (
  SELECT
    ds AS metric_time
    , 1 AS transactions
  FROM mf_demo.mf_demo_transactions transactions_src_0
) subq_2
GROUP BY
  metric_time
ORDER BY metric_time
```

メトリックがどのようなSQLに変換されるのか確認することができます。

### ディメンションによってメトリックをグループ化する

複数のディメンションを指定することができます。
今回は`quick_buy_transaction`というディメンションを追加しています。

```
❯ mf query --metrics transactions --dimensions metric_time,quick_buy_transaction --order metric_time
```

```:OUTPUT
✔ Success 🦄 - query completed after 0.39 seconds
| metric_time         | quick_buy_transaction   |   transactions |
|:--------------------|:------------------------|---------------:|
| 2022-03-07 00:00:00 | Quick Buy               |              1 |
| 2022-03-07 00:00:00 | Not Quick Buy           |              1 |
| 2022-03-08 00:00:00 | Quick Buy               |              1 |
| 2022-03-08 00:00:00 | Not Quick Buy           |              1 |
| 2022-03-09 00:00:00 | Not Quick Buy           |              1 |
(略）
```

日毎のトランザクション数をさらにquick_buy_transaction列で分割できています。

### JOINが必要なディメンションの追加

多段的（multi-hop)なJOINが必要なディメンションも追加することができます。
以下の例では`customer`テーブルに紐づく`country`テーブルの`region`ディメンションを追加しています。
ディメンションには`customer__country__region`のように`__`で連結した表現を使います。

```
> mf query --metrics transactions --dimensions metric_time,customer__country__region --order metric_time
```

```:OUTPUT
✔ Success 🦄 - query completed after 0.73 seconds
| metric_time         | customer__country__region   |   transactions |
|:--------------------|:----------------------------|---------------:|
| 2022-03-07 00:00:00 | EU                          |              1 |
| 2022-03-07 00:00:00 | NA                          |              1 |
| 2022-03-08 00:00:00 | NA                          |              2 |
| 2022-03-09 00:00:00 | NA                          |              1 |
| 2022-03-10 00:00:00 | NA                          |              2 |
| 2022-03-11 00:00:00 | NA                          |              1 |
(略）

```


### 週次の粒度で表示する

`metric_time`ディメンションを`metric_time__week`のように変更します。


```
mf query --metrics transactions --dimensions metric_time__week --order metric_time__week
```
```:OUTPUT
✔ Success 🦄 - query completed after 0.49 seconds
| metric_time__week   |   transactions |
|:--------------------|---------------:|
| 2022-03-07 00:00:00 |             10 |
| 2022-03-14 00:00:00 |              8 |
| 2022-03-21 00:00:00 |             15 |
| 2022-03-28 00:00:00 |             16 |
| 2022-04-04 00:00:00 |              1 |
```
週次で出ました。ポストフィックスで表現するのは直感的でいいですね。（？）

### 並び順を変える

`--order`の値の前に`-`をつけます。
` --order metric_time__week`を` --order -metric_time__week`のように

```
> mf query --metrics transactions --dimensions metric_time__week --order -metric_time__week
```

```:OUTPUT
✔ Success 🦄 - query completed after 0.41 seconds
| metric_time__week   |   transactions |
|:--------------------|---------------:|
| 2022-04-04 00:00:00 |              1 |
| 2022-03-28 00:00:00 |             16 |
| 2022-03-21 00:00:00 |             15 |
| 2022-03-14 00:00:00 |              8 |
| 2022-03-07 00:00:00 |             10 |
```

ね、簡単でしょう。

### より複雑な累積とフィルターを使ったメトリック

`transaction_usd_na_l7d`というメトリックを使います。
このメトリックでは、地域が`NA`であるトランザクションの金額について７日間の累計を計算しています。

```
> mf query --metrics transaction_usd_na_l7d --dimensions metric_time --order metric_time
```
```:OUTPUT
✔ Success 🦄 - query completed after 0.56 seconds
| metric_time         |   transaction_usd_na_l7d |
|:--------------------|-------------------------:|
| 2022-03-07 00:00:00 |                   295.03 |
| 2022-03-08 00:00:00 |                   872.03 |
| 2022-03-09 00:00:00 |                   879.69 |
| 2022-03-10 00:00:00 |                  1051.12 |
| 2022-03-11 00:00:00 |                  1259.49 |
(略）
```


ちなみに`transaction_usd_na_l7d`というメトリックは以下のように定義されています。
```yaml:mf_demo_transactions.yaml
metric:
  name: transaction_usd_na_l7d
  owners:
    - support@transformdata.io
  type: cumulative
  type_params:
    measures:
      - transaction_amount_usd
    window: 7 days
  constraint: |
    customer__country__region = 'NA'
```

どのようなSQLになっているか気になる人は`--explain`をつけてみてみましょう。（割愛）

### 複数のメトリクスと`where`を試す

`--where`を追加することで絞り込みをすることができます。

```
> mf query --metrics transactions,transaction_usd_na,transaction_usd_na_l7d --dimensions metric_time,is_large --order metric_time --where "metric_time between '2022-03-20' and '2022-04-01'"
```

```
✔ Success 🦄 - query completed after 0.88 seconds
| metric_time         | is_large   |   transactions |   transaction_usd_na |   transaction_usd_na_l7d |
|:--------------------|:-----------|---------------:|---------------------:|-------------------------:|
| 2022-03-20 00:00:00 | True       |         nan    |               nan    |                  1405.94 |
| 2022-03-21 00:00:00 | True       |           3.00 |               639.87 |                  1866.47 |
| 2022-03-22 00:00:00 | True       |           3.00 |               522.68 |                  2122.40 |
| 2022-03-23 00:00:00 | True       |           2.00 |               215.14 |                  2064.38 |
(略）
```

### チュートリアル用のデータを削除する

以下のコマンドでチュートリアル用のテーブルを削除できます。
```
> mf tutorial --skip-dw --drop-tables
```

# まとめ

dbt セマンティックレイヤーの新しいパッケージとなるMetricFlowについて、公式のチュートリアルをやってみました。
チュートリアルを通してMetricFlowがどのようなものなのか雰囲気が少しわかったような気がします。
PowerBIやTableauのような高性能なBIツールを使ったことがないので、それらと比べてMetrifFlowのメトリック機能が足りているか判断できませんが、結構柔軟にメトリックの計算ができそうだなと感じています。
今回はYamlファイルの内容や記述ルールについて触れられなかったので、また別の機会に深掘りしたいと思います。

少しでも参考になりましましたら、「いいね」「Twitterシェア」をいただけたら嬉しいです🤝

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">記事を投稿しました！ dbtの新しいセマンティックレイヤー「MetricFlow」を触ってみた [データ分析] on <a href="https://twitter.com/hashtag/Qiita?src=hash&amp;ref_src=twsrc%5Etfw">#Qiita</a> <a href="https://t.co/Q4sI347rai">https://t.co/Q4sI347rai</a></p>&mdash; つざき@データエンジニア入門中 (@820zacky) <a href="https://twitter.com/820zacky/status/1662136369953783808?ref_src=twsrc%5Etfw">May 26, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>