Feedforce Developer Blog

フィードフォース開発者ブログ

Looker のレイヤー化を本番環境に導入してみた

こんにちは。自称 Looker エバンジェリストid:masutaka26 です。

この記事は Looker Advent Calendar 2022 の 5 日目の記事です。

qiita.com

昨日と一昨日は記事がなくて、その前は BASE 永野さん (@glassmonekey) の「アジリティを保ってデータ基盤を作る取り組み」でした。使用した技術そのものよりも、どのように合意形成してこのような体制に出来たのかが気になりました。👀

今回は奇しくも去年の Advent Calendar その後の話になります。全然狙っていませんでした。たまたまです。

developer.feedforce.jp

LookML のレイヤー化とは

LookML には Refinements という Extends に似た機能があり、explore や view の定義を分散させたり、元のコードを変更せずに機能を追加することが出来ます。

この Refinements を応用することで LookML に「レイヤー化」という概念を導入することが出来ます。まずはこちらの記事をどうぞ。

community.looker.com

このような責務だと理解しました。

  • /_base.layer.lkml
    • スキーマと Explore の定義に徹する。Join はしない
  • /_basic.layer.lkml
    • データ構造に着目した Join と、primary_key の定義に徹する。ビジネスロジックは定義しない
  • /layers/*.layer.lkml
    • ビジネスロジックの論理レイヤーをここに定義する

💭 _base_basic はもっと良い命名はあるかもしれません。

導入戦略

既存のプロジェクトを全部書き換えるのはリスクが大きいので、新規で Join する view からレイヤー化を試すことにしました。

ただ、責務はこのように再定義しました。特に /_basic.layer.lkml を変えました。

  • /_base.layer.lkml
    • スキーマ定義に徹する
  • /_basic.layer.lkml
    • primary_key のテストを書ける定義を書く。Join はしない。ビジネスロジックは知らない
  • /layers/*.layer.lkml
    • Join 含めて、ビジネスロジックの論理レイヤーをここに定義する

というのも、今回は「データ構造に着目した Join」と同じ Join を論理レイヤーにも定義する必要があり、実装が分散するからです。後述します。

実際の導入方法

/_base.layer.lkml

このようなスキーマ定義を、1 つのファイルに書いていきます。

view: ad_budget {
  sql_table_name: @{table_ad_budget} ;;

  dimension: month        { type: string }
  dimension: client_name  { type: string }
  dimension: site_name    { type: string }
  dimension: media        { type: string }
  dimension: budget_gross { type: number }
}

💡 sql: ${TABLE}.date ;; 等がなくて違和感があるかも知れませんが、以下のとおり問題ありません。

🔗 sql (for fields) > Definition > sql for Dimensions

If sql is left unspecified, then Looker assumes that there is a column in the underlying table with the same name as the field. For example, selecting a field called city without a sql parameter would be equivalent to specifying sql: ${TABLE}.city.

sql が指定されていない場合、Looker はそのフィールドと同じ名前のカラムが基礎となるテーブルに存在すると仮定します。例えば、sql パラメータを指定せずに city というフィールドを選択すると、sql: ${TABLE}.city と指定したのと同じことになります。

/_basic.layer.lkml

このような定義を書きます。

include: "/_base.layer"

# Define for tests
explore: ad_budget {
  required_access_grants: [can_view_explores_for_tests]
  hidden: yes
}

view: +ad_budget {
  dimension: pk {
    primary_key: yes
    type: string
    sql: CONCAT(${month}, ${client_name}, ${site_name}, ${media}) ;;
    hidden: yes
  }

  measure: count {
    type: count
    description: "Row Count"
    filters: {
      field: "pk"
      value: "-NULL"
    }
  }
}

💡 ad_budget view は直接使われず、他の Explore から Join して使うので、ad_budget explore はテストだけで使うように隠しています。

関連記事です。

developer.feedforce.jp

/layers/*.layer.lkml

ad_budget view を 2 つの Explore から Join します。join 内容はほとんど同じで、sql_on の view 名が違うだけです。

似た実装を 1 つにまとめられるメリットがあります。corp_name1 という名前から推測できるとおり、corp_nameN explore は複数存在しており、その数数十にもなります。

include: "/_basic.layer"
include: "/explores/all_media.explore"

explore: +all_media {
  join: ad_budget {
    view_label: "広告予算"
    relationship: many_to_many
    sql_on: ${all_media.date_month} = ${ad_budget.month}
            AND ${all_media.corporate_name} = ${ad_budget.client_name}
            AND ${all_media.proposition_name} = ${ad_budget.site_name} ;;
  }
}

explore: +corp_name1 {
  join: ad_budget {
    view_label: "広告予算"
    relationship: many_to_many
    sql_on: ${corp_name1.date_month} = ${ad_budget.month}
            AND ${corp_name1.corporate_name} = ${ad_budget.client_name}
            AND ${corp_name1.proposition_name} = ${ad_budget.site_name} ;;
  }
}

view: +ad_budget {
  dimension: budget_gross { hidden: yes }

  measure: total_budget_gross {
    type: sum
    sql: ${budget_gross} ;;
    label: "広告予算 (Gross)"
    value_format_name: jpy_0
  }
}

/*.model.lkml

/model_name.model.lkml のようなファイルに、レイヤーファイルの include を追加しました。

include: "/layers/**/*.layer"

[コラム] 複数レイヤーで共通するロジックの置き場所

少し脱線。複数レイヤーで共通するロジックをどう書けばよいか?の話はあると思います。

今まで通り extends パラメーターを使えば良いです。Refinements と Extends は共存できます。詳しくは Lookml refinements compared to extends をどうぞ。

まだ試行錯誤中ですが、私は /modules/*.module.lkml に共通ロジックをまとめています。

まとめ

LookML のレイヤー化を紹介しました。今回の例以外にもガッツリと使っています。

記事を書いていて、具体的な例を出してもピンと来ないだろうし、抽象的な例にするとボンヤリするしで、いろんな意味で難易度が高かったです。どなたか 1 人にでも参考になれば十分です。

レイヤー化は LookML の到達点ではなく、1 つの選択肢に過ぎません。素朴なデータ構造であれば、/views/explores で実装したほうが良いと思います。

というのも、レイヤー化を導入すると、スキーマ定義と Dimension&Measure 定義の距離が離れるので、その view で何が使えるのか、コードから理解するのが難しくなるからです。

今回紹介した例も、記事を書いていてもっと良い方法があるような気がしました。Explore をうまいこと Extends して Join を 1 つにまとめれば、レイヤー化を使わなくてよいのでは?とか。でも以前検討した時はうまくいかなかったんですよね...。

正解はないと思うので、他の試行錯誤も是非教えて下さい。

🔗 What is the looker recommended folder structure for LookML development ? | Looker Community

I used different approaches in the last few years and found it that I always have to have some kind of a hybrid system. (snip) But to be honest, I am still experimenting with what’s the best physical split versus logical

私は過去数年間、さまざまなアプローチを使用し、私は常にいくつかの種類のハイブリッドシステムを持たなければならないことを発見した。 (省略) しかし、正直なところ、物理的な分割と論理的な分割のどちらが良いのか、まだ試行錯誤の段階です。

以前、Looker Community のこちらのコメントを紹介したことがあります。Dawid さんはよくお見かけする方で、かなりの熟練者のはずです。そんな彼も、未だに試行錯誤しているようです。

始めは /views/explores のような素朴な実装から始めて、必要に応じてレイヤー化を取り入れれば良いと思います。いつか公式から、データの複雑度に応じたベストプラクティスが出てくることを期待します。🙃

Looker Advent Calendar 2022 の明日、明後日は書く人はまだいなくて、その次は @saenakano さんの「System Activity入門」です。お楽しみに。