entry-header-eye-catch.html
entry-title-container.html

entry-header-author-info.html
Article by

Lookerの埋め込みダッシュボードを社外提供する上での課題と解決策

アドプラットフォーム事業部 アドプロダクト部 データチームでアナリティクスエンジニアをしているucchi-です。普段は主に、pixiv Adsという広告ネットワークのデータ周りを開発しています。

ads.pixiv.net 今回は、pixiv Adsで採用している、Lookerの埋め込みダッシュボードについて紹介します。

はじめに

ピクシブでは全社的にLookerを使用しています。Lookerを使うことで、BigQueryに保存されている品質の高いデータを気軽に分析することができ、社内の仮説検証や意思決定に役立っています。

一方で、Lookerは社内の分析用途だけではなく、社外へのデータ提供にも活用できます。
具体的な手段はいくつかありますが、pixiv Adsでは、「シングルサインオン(SSO)組み込み」という形式で、クライアント向けの広告管理画面にLookerのダッシュボードを埋め込んでいます。
これにより、クライアントは管理画面経由で、BigQueryにあるデータを直接参照できます。広告配信実績を、好きなタイミングで、多様な切り口で、分析することができます。
また、埋め込みダッシュボードを利用することには開発目線でもメリットがあります。Lookerの機能を活かすことで、複雑なダッシュボード要件を素早く満たし、アプリケーションの機能開発速度を高めることができます。

しかし、埋め込みダッシュボードをクライアントへ提供するには、乗り越えなければならない壁がいくつか存在します。 そこで、pixiv Adsが提供する埋め込みダッシュボードにおける課題と、その解決策を紹介します。

  • 社外へのデータ提供に耐えうるようなデータ品質の担保
    →Lookerのlinerの1つであるLAMSを適用
  • クライアント毎に参照可能なデータ範囲の制御
    →user_attributesを用いた制御
  • ダッシュボードの開発環境の分離
    →本番をLookML、開発をユーザー定義ダッシュボードに。LookerAPIで状態を自動で同期

LAMSを用いたデータ品質の担保

まず、データ品質の担保についてです。
Lookerが提供するValidatorは、基本的な文法エラーを自動で検知してくれますが、テーブル結合におけるキー指定やリレーションの誤りといったよくある設定ミスを検知してくれません。
これらの誤設定は、結果としてファントラップ(テーブルの結合ミスによるファクトの重複集計)などの集計ミスを引き起こしてしまいます。
特に、pixiv Adsの場合は、クライアントの広告出稿金額など、1円のずれも許されないデータを提供する必要があります。一般的な社内向けのダッシュボードと比べて求められるデータ品質が極めて高いので、品質をさらに高めるための工夫が必要です。

そこで、LAMSというLookerのLinterを導入することで、これらの誤設定を出来る限り未然に防ぐようにしました。
特に便利だと感じたのはK1E2というルールです。まず、K1で、全てのテーブルに”pk{n}_{key_name}”というPrimary Keyの設定を強制します。

view: users {
  label: "ユーザー"
  dimension pk1_user_id { # user_id という1種類のPKが存在する
    label: "ユーザーID"
    primary_key: yes
    hidden: yes
    sql: {TABLE}.id
  }
}

そして、E2で、1対1もしくは1対多における”1”のテーブルをPKで結合するよう強制します。

view: sales {
  label: "販売"
  dimension: user_id{}
}

explore sales_explore {
  view_name: sales
  join: users {
    relationship: many_to_one # 右辺のテーブルは必ず"pk{n}_"を繋がなければならない
    sql_on: sales.user_id = users.pk1_user_id
  }
}

全てのテーブルにPKが存在し、常にPKを用いて結合することにより、ファントラップの発生をある程度防ぐことができます。また、普段からPKを意識したviewの定義を行うことで、誤ったkeyの定義や結合を減らすことができます。

LAMSによるlintチェック

LAMSは.gitlab-ci.ymlの中にstageとして定義します。LAMSのチェックが通った場合のみ、masterへのマージが可能になります。

app:lint:
  image: node:18-slim
  stage: lint
  script:
    - npm install -g @looker/look-at-me-sideways@2
    - lams --reporting=no --source="**/{*.model,*.explore,*.view,manifest}.lkml"
  only:
    refs:
      - merge_requests
      - master

ユーザー属性を用いたソースデータの参照範囲制御

次に、クライアント毎に参照可能なデータ範囲の制御についてです。
クライアントがダッシュボードを操作する上で、競合するクライアントの広告の配信状況が見えてしまうことは絶対にあってはなりません。また、開発環境のクライアントは、本番環境ではなく開発環境の広告配信サーバーによる広告配信実績が見えるべきです。

そこで、user_attributesを使って、Lookerを閲覧するユーザー属性に応じて参照可能なデータ範囲を制御します。

ユーザーIDに基づく絞り込み

埋め込みダッシュボードでは、クライアントがダッシュボードを初めて開くタイミングで、Lookerのアカウントが発行されます。pixiv AdsのユーザーIDの値を、発行されたLookerアカウントに”user_id”属性として付与し、その値に応じてアクセスフィルターを適用します。

explore sales_explore {
  view_name: sales
  # クライアントがダッシュボードを閲覧する際に、常にWHERE句の絞り込みが事前に適用される
  # つまり、 sales.user_id = user_id となるファクトのみが集計対象になる
  access_filter: {
    field: sales.user_id
    user_attribute: user_id # ユーザー属性「user_id」を参照する
  }
}

すると、ダッシュボード上で集計が行われる前に、ユーザーIDに基づいてデータが絞り込まれるので、他のクライアントのデータが見えてしまうのを防ぐことができます。

本番環境/開発環境による参照テーブルの切り替え

同様に、「本番」「開発」といった環境情報を、Lookerアカウントに”environment”属性として付与し、その値に応じてviewが参照するBigQueryのテーブルを動的に切り替えます。

view users {
  label: "ユーザー"
  sql_table_name:
    # 本番ユーザーなら本番データセットのユーザーテーブル、
    # 開発ユーザーなら開発データセットのユーザーテーブルを参照する
    {% if _user_attributes['environment'] == "production" %}
      `production.users`
    {% elsif _user_attributes['environment'] == "staging" %}
      `staging.users`
    {% else %}
      `staging.users`
    {% endif %};;
}

これにより、データソースが本番環境と開発環境のどちらを向いているかを意識せずとも、ダッシュボードの開発を行えます。

ダッシュボードの開発環境の分離

最後に、ダッシュボードの開発環境の分離についてです。

なぜ開発環境用にダッシュボードを分ける必要があるか

本番環境の広告管理画面は、特定のLookMLダッシュボードを参照しています。
Lookerには開発モード(Development Mode)があります。これをオンにすると、ダッシュボードのLookMLに改修を加えた結果を、開発者自身が手元で事前に検証することができます。
しかし、開発環境の管理画面に、開発モードのダッシュボードを埋め込むことはできません。ステークホルダーが手元で開発環境の管理画面に入り、開発中の埋め込みダッシュボードを触ってレビューするためには、開発環境用に異なるダッシュボードを作成する必要があります。

LookerAPIを用いたダッシュボードのコピー

そこで、pixiv Adsでは開発環境の管理画面に、本番環境用のLookMLをコピーしたユーザー定義ダッシュボードを埋め込んでいます。
ダッシュボードのコピーはLookerAPIを用いて行います。LookMLダッシュボードの状態を、対応するユーザー定義ダッシュボードに同期します。

import looker_sdk

sdk = looker_sdk.init40()

dashboard_lookups = {
    "本番環境のURL": "紐付く開発環境のURL"
}

print("LookMLダッシュボードの定義に基づいて、リンクされたユーザー定義ダッシュボードに状態を同期します")
for prod_lookml_dashboard, stg_user_dashboard in dashboard_lookups.items():
    # update処理を都度実行する必要はないが、ダッシュボードの紐付けをコード上で明記するために残してある
    sdk.update_dashboard(
        dashboard_id=stg_user_dashboard,
        body={"lookml_link_id": f"{prod_lookml_dashboard}"},
    )
    synced_user_dashboard_ids = sdk.sync_lookml_dashboard(
        lookml_dashboard_id=f"{prod_lookml_dashboard}",
        body={},
    )
    for id in synced_user_dashboard_ids:
        print(f"{prod_lookml_dashboard} の状態を {stg_user_dashboard} に同期しました")
print("同期が完了しました")

pixiv Adsでは、MRをmasterにマージする際に状態を同期します。ダッシュボードの見た目を改修しながらLookMLファイルを変更したい場合もあるので、同期処理は手動で実行します。

app:sync-dashboards:
  image: python:3.11-slim
  stage: sync-dashboards
  when: manual # 手動実行
  variables:
    LOOKERSDK_BASE_URL: "URL"
    LOOKERSDK_VERIFY_SSL: "true"
    LOOKERSDK_CLIENT_ID: $LOOKERSDK_CLIENT_ID
    LOOKERSDK_CLIENT_SECRET: $LOOKERSDK_CLIENT_SECRET
  script:
    - pip install looker-sdk
    - python3 scripts/sync_dashboards.py
  only:
    refs:
      - master

開発環境用のダッシュボードをユーザー定義にする理由

開発環境用のダッシュボードをユーザー定義とすることで、GUIを用いた見た目の試行錯誤を簡単に行えます。ダッシュボードが分かれているので、本番環境に影響が波及することはありません。試行錯誤が終われば、改修後のダッシュボードをLookML形式で吐き出して、本番環境用のLookMLダッシュボードに変更を反映させます。

実装した結果

専用のダッシュボードを1から作るのではなく、Lookerの埋め込みダッシュボードを提供したことで、開発期間を2ヶ月以上短縮し、(当時は)専任のフロントエンジニアがいないにも関わらずクライアントに価値を提供できました。現在では100名以上のクライアントがこのダッシュボードを利用しています。また、Lookerはダッシュボード要件の変更耐性が高いので、広告管理システムの機能追加を素早く行えています。
Google広告やTwitter広告が提供する専用のダッシュボードほどの柔軟性はありませんが、現状のpixiv Adsの規模感や開発体制を踏まえると、十分な役割を果たしていると評価しています。

icon
ucchi-
アド・プロダクト部のデータチームでアナリティクスエンジニアとして働いています。百合とデータ分析と美味しいご飯が趣味。