Feedforce Developer Blog

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

Looker のキャッシュの仕組みを思い出して実装を整理した

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

フィードフォースで Looker を使っているサービスで最近 BigQuery の料金が上がってきました。

本当に使われているのならとても良いことですが、Looker のキャッシュが有効に使われずに BigQuery 料金が増えていたら嫌だなと思い、キャッシュの仕組みを思い出しつつ実装を整理してみました。

Looker のキャッシュおさらい

Looker にはキャッシュの仕組みがあり、LookML 開発者が適切に設定することで、データベースのクエリ実行を減らすことができます。

全く同じ SQL の実行結果がキャッシュされていれば、データベースにクエリ実行せずにキャッシュを返します。

Looker ユーザーとしてはダッシュボードの表示速度向上のメリットがあり、LookML 開発者としては(BigQuery 等であれば)料金削減に繋がります。

キャッシュの有効期限はデフォルトで 1 時間です。これは persist_for (for models) のデフォルト値です。

今回のサービスでのキャッシュ設定

良くも悪くも設定は最小限で、PDT(永続的派生テーブル)の実装箇所だけでした。

例えば Google や Criteo などの広告媒体数値が入ったテーブルを参照する、all_media explore があります。このようなキャッシュ設定でした。

datagroup: cache_all_media {
  # 最新の日付か総レコード数に変化があったら検知する。
  sql_trigger: SELECT CONCAT(MAX(date), '_', COUNT(*)) FROM @{table_all_media} ;;
  max_cache_age: "1 hour"
}

explore: all_media {
  persist_with: cache_all_media # (1)

  # ...
}

view: all_media {
  derived_table: {
    # めちゃめちゃ長い SQL
    sql: SELECT
           ...
         FROM @{table_all_media}
         GROUP BY ... ;;
    datagroup_trigger: cache_all_media # (2)
    partition_keys: ["date"]
  }

  # ...
}

(1) で cache_all_media datagroup と all_media explore が persist_with (for Explores) で紐付くことで、以下の振る舞いが生まれます。

  • sql_trigger によりテーブルの変更が検知されると、キャッシュが破棄される
  • max_cache_age を超えたキャッシュは破棄される

つまりキャッシュの生存期間は最長でも 1 時間です。その前に PDT が再作成されればもっと短くなります。

どちらかと言えば、データベースを優先する設定です。このテーブルは 1 日に 3 回しか更新されないため、最適化の余地がありそうです。

(2) で cache_all_media datagroup と all_media view も datagroup_trigger で紐付いていますが、これは derived_table を永続化するための設定です。sql_trigger によりテーブルの変更が検知されると、PDT が再作成されるとともに、キャッシュが破棄されます。

※ 余談: persist_with を設定していたことをすっかり忘れていたこともあり、調査前は (1) の理解が出来ていませんでした。😇

変更後のキャッシュ設定

紆余曲折あり、max_cache_age を少し増やしただけにしました。

datagroup: cache_all_media {
  # 最新の日付か総レコード数に変化があったら検知する。
  sql_trigger: SELECT CONCAT(MAX(date), '_', COUNT(*)) FROM @{table_all_media} ;;
  max_cache_age: "2 hours"
}

本当は "24 hours" にして「PDT 再作成のタイミングでのみキャッシュを破棄」したかったのですが、他の Explore でも参照される関係で、max_cache_age を思い切って増やせませんでした。

さらなる足掻きからの失敗

max_cache_age を 1 時間から 2 時間に増やした程度では、BigQuery 料金を抑えられたように見えませんでした。

仕方がないので、all_media explore 以外の Explore で cache_all_media datagroup を使うのを止めて、専用 persist_with + datagroup を設定しました。

このような LookML を 40 個書きました。😱

datagroup: cache_corp_xxx {
  sql_trigger:  SELECT
                  STRING_AGG(CONCAT(table, '_', date, '_', count) ORDER BY table)
                FROM (
                  SELECT 'all_media' AS table, MAX(date) AS date, COUNT(*) AS count FROM @{table_all_media} UNION ALL
                  SELECT 'UA_1', MAX(date), COUNT(*) FROM @{table_ga_xxx} UNION ALL
                  SELECT 'UA_2', MAX(date), COUNT(*) FROM @{table_ga_xxx2}
                ) ;;
  max_cache_age: "24 hours" # テーブル更新のタイミングでのみ、キャッシュを破棄する。
}

explore: corp_xxx {
  persist_with: cache_corp_xxx

  # ...
}

結果的には大失敗。40 個の datagroup が 5 分に 1 回トリガーされる関係で、Looker を使わない土日でも BigQuery 料金が変わらなくなってしまいました。

※ 補足: 「可能な限り Explore を少なくする」が Looker のベストプラクティスなので、40 個もの Explore は本来はアンチパターンです。ですが、今回のサービスはお客様にダッシュボードを提供する上に、やむを得ず個別実装が発生することもあります。そのような経緯で結果的にこの数になっています。

もっと足掻いてからの困惑

それならばと、利用頻度が高い Explore だけに、専用 persist_with + datagroup を設定すれば良いのでは?と思いました。System Activity の History Explore から、このようなフィールドを選択すれば利用頻度の高い Explore が分かります。

設定してから一週間。BigQuery 料金はあまり変わりませんでした。😇

Database Performance ダッシュボードの Results from Cache1は 18% から 20% に向上しました。もっと高いほうが良いのか、どうなのか...。

System Activity の History Explore からダッシュボードを作ってもみましたが、設定変更とキャッシュヒット率の相関性は見いだせませんでした。

とはいえ、BigQuery 料金はそこまで増えなかったので、とりあえず続けています。

まとめ

BigQuery 料金が増えたことをきっかけにして、Looker のキャッシュ設定を見直しました。

実装は整理できましたが、BigQuery 料金の変化はなく、キャッシュ難しい...という気持ちです。

さらなるキャッシュへの理解

中の人が翻訳して下さった記事がとても良いです。

クエリのログからキャッシュを理解する | Looker Community


  1. キャッシュから返されたクエリの割合