Lookerのfilterパラメーターを理解して、Looker Serverのパフォーマンスを最適化する

フィルター機能たくさん用意してあげたから、よしなに使っていいよ! (君たちはフィルターから逃れることはできないのだ〜〜)
2020.05.01

Lookerのパフォーマンスを最適化する

Optimize Looker Server Performance

LookMLで開発をするにあたり、最適解として使えるベストプラクティスが以下の記事にまとめられています。

記事の後半に、Optimize Looker Server Performanceという項があり、その中でconditionally_filtersql_always_whereを使用しすることでメモリをくわないようにする方法がベストプラクティスの一つとして推奨されています。

概要

ビジネスユーザーにフィルターをかけた状態でSQLを実行させる、という機能を持つパラメータがLookerでは、4つ用意されています。各場面で適材適所に配置するのには、各パラメーターの特徴を理解しておく必要があるので、それぞれのパラメータのできることとできないことを以下の表にまとめました。

conditionally_filter always_filter sql_always_where access_filter
フィルターする列の上書き × × ×
フィルターする値の上書き × ×
適用するユーザー 全ユーザー 全ユーザー 全ユーザー ユーザー毎

それでは、それぞれのパラメーターの詳細を確認します。

conditionally_filter

公式ドキュメント

概要

ユーザーに2択を迫ります:filters: [・・・]に記載されている値まで決められているフィルターを使うか、然もなくばunless:[・・・]に記載されている値はユーザが設定できるフィルターを使うんだ!

This parameter is typically used to prevent users from accidentally creating very large queries that may be too expensive to run on your database.

と公式ドキュメントにもあるとおり、ユーザーがうっかり大きなクエリーを走らせてしまうことを防ぎます。

使ってみよう

まずは、modelファイルにconditionally_filterを定義します。

explore: order_items {
  label: "(1) オーダー、アイテム、ユーザー関連"
  view_name: order_items
  view_label: "オーダー"
  conditionally_filter: {
    filters: [status: "プロセス中"]
    unless: [created_date]
  }

(1) オーダー、アイテム、ユーザー関連のExploreの画面を開くと、既に オーダー ステータス にプロセス中のフィルターが設定されています。

次に、unless要素のcreated_dateをフィルターに設定すると、先ほどはなかった×印が オーダー ステータス フィルターの横に出現します。これで消せるようになりました。

また、filtersで指定しているフィルターする値はユーザーが変更することができます。

always_filter

公式ドキュメント

概要

conditionally_filterのunlessを使わないバージョン。ユーザーはこのフィルルターからも逃れることはできない。

If You Want to Use conditionally_filter without unless, Just Use always_filter Instead

上述の通り、conditionally_filterの公式ドキュメントにunlessを使わないなら、always_filterを使いましょうという記述があります。

使ってみよう

まずは、modelファイルにalways_filterを定義します。

explore: order_items {
  label: "(1) オーダー、アイテム、ユーザー関連"
  view_name: order_items
  view_label: "オーダー"
  always_filter: {
    filters: [created_date: "last 90 days"]   
  }

(1) オーダー、アイテム、ユーザー関連のExploreの画面を開くと オーダー 受注 Date に in the past 90 daysのフィルターが設定されています。

このフィルターは他のfieldをフィルターに加えても、消すことはできません。

ただ、フィルターの値を変えることはできます。

sql_always_filter

公式ドキュメント

概要

ユーザーは消せない、変えられない、逃れられない。

The restriction will be inserted into the WHERE clause of the underlying SQL that Looker generates, for all queries on the Explore where sql_always_where is used.

公式ドキュメントにはこのように記載があったので、always_filterはどうなのだろう?と思い、確認してみました。

sql_always_where

always_filter

どちらもwhere句が生成されていることが確認できます。

sql_always_whereの名前の通り、フィルターではないので、Exploreのフィルターに表示されない(=ユーザーは触ることすらできない)というのが特徴でしょうか。

平等の名の下に、全てのユーザーのクエリに等しく制限をかけたい場合に使用するというのがユースケースでしょうか。

使ってみよう

まずは、modelファイルにsql_always_whereを定義します。

explore: order_items {
  label: "(1) オーダー、アイテム、ユーザー関連"
  description: "あり!"
  view_name: order_items
  view_label: "オーダー"
  sql_always_where: ${status} = 'プロセス中' ;;

(1) オーダー、アイテム、ユーザー関連のExploreの画面を開くと通常通りのExplore画面が表示されます。

フィールドピッカーからいつもの通りfirldを選んで実行すると、

ステータスが「プロセス中」のものが返されます。

access_filter

公式ドキュメント

概要

sql_always_whereと同様、ユーザーは消せない、変えられない、逃れられない。
違いは、where句の条件をユーザー毎に設定できる点。

また、user_attributeの設定が必要な点も特徴的です。

使ってみよう

まず、user_attributeを作成します。

brand_accessというuser_attributeを作成して、自分に「Calvin Klein」というvalueを設定しました。

そして、modelファイルにaccess_filterを定義します。

explore: order_items {
  label: "(1) オーダー、アイテム、ユーザー関連"
  view_name: order_items
  view_label: "オーダー"
  access_filter: {
    field: products.brand
    user_attribute: brand_access
  }

(1) オーダー、アイテム、ユーザー関連のExploreの画面を開くと通常通りのExplore画面が表示されます。(sql_always_whereと同様のため、画像は省略します。)

フィールドピッカーからbrandを選択して、SQLを表示させると、

フィルター部分で何も設定していないのに、brandがCalvin Kleinを返すSQLが生成されています。

この段階では自分にしかuser_attributeのvalueを設定していないので、他人になり代わった場合どのような動きをするのかを確認します。

画面上部の赤いラインが他人になり代わっているというお知らせなのですが、この場合、brandを選択してもwhere句がfalseになっているのがわかります。

access_filterはexploreのサブパラメーターのため、explore単位で定義する必要があります。細かな設定ができる = 細かな設定が必要ということですね。抜けがないようしっかりと設計する必要がありそうです。

検証するぞ

本当に逃げられないのか?諦めるのは早くないか?生きてさえいれば、自由になれるんじゃないか?

というのは、conditionally_filterの公式ドキュメントにはこのような記載があるのです。

There Is a Method to Apply conditionally_filter to a Subset of Users

やり方は、conditionally_filterを設定しているmodelと設定しないmodelの2つを用意して、ユーザー毎にどちらのmodelへアクセスするかを設定するだけ。(結構ややこしい)

まずは、conditionally_filterありmodelのみにアクセスを許可したrole(test_for_conditionally_filter_ari)を作成します。 そして、テスト用のユーザーにtest_for_conditionally_filter_ariのみを付与します。

次に、conditionally_filterを設定しているmodelと設定しないmodelの2つを用意します。

conditionally_filterあり

conditionally_filterなし

どちらがありで、どちらがなしかを判別できるように、descriptionで「あり!」と「なし!」と定義しました。

そして、テスト用のユーザーになり代わってExploreへアクセスします。

あり!のExploreのみが表示され、

filter部分には、見づらいかもしれませんが、Conditionally Requiredと表示されています。

ここで本題、この秘技modelを分けちゃうよ!はconditionally_filterのドキュメントにしかその方法が記載されていないのですが、他のfilterでも同様のことができるのではないでしょうか?

今回は、sql_always_filterで検証してみます。

まずはmodelファイルを書き換えます。(conditionally_filter -> sql_always_where)

explore: order_items {
  label: "(1) オーダー、アイテム、ユーザー関連"
  description: "あり!"
  view_name: order_items
  view_label: "オーダー"
  sql_always_where: ${status} = 'プロセス中' ;;

そして、先ほども使用したこのmodelのみにアクセス権のあるユーザーになり代わってExploreを表示させます。

先ほどと同様選択できるExploreが一つだけですが、filter部分に何も表示がありません。
フィールドピッカーから適当に選択してSQLを覗いてみると、

問題なく強制的にwhere句が出現しました。
この方法を使えば、対象を一部のユーザーにすることができますね。

まとめ

以上、filterパラメーターのご紹介でした。途中のどこかにも書きましたが、細かい設定ができる仕様になっており、権限と合わせて用法容量を正しく理解し、設定していく必要がありそうです。