こんにちは。自称 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
-
キャッシュから返されたクエリの割合↩