オブジェクトテーブル経由でLooker Studioに画像を表示する際にクォータ超過

記事タイトルとURLをコピーする

G-gen の min です。Looker Studio で Cloud Storage 上の非公開画像を扱う際に、BigQuery の EXTERNAL_OBJECT_TRANSFORM 関数を利用して署名付き URL を生成する方法があります。本記事ではその具体的な手順と、複数の画像を表示しようとした際に発生するレート制限エラーの原因と対処法について解説します。

事象

Looker Studio レポートに、Cloud Storage バケット上に配置した画像ファイルを表示する要件がありました。このバケットおよび画像ファイルは、インターネットに公開していません。

認証を経てレポートに画像を表示するために、BigQuery の EXTERNAL_OBJECT_TRANSFORM 関数を用いて、BigQuery のオブジェクトテーブルから署名付き URL を生成し、この URL を使って Looker Studio レポートに画像を表示することを試みました。

実装してみると、1つの表グラフに複数の画像を表示することは成功しましたが、レポートに2つ以上の表グラフに数十枚の画像を配置して表示・更新すると、BigQuery 側で以下のエラーが発生し、一部の画像が表示されませんでした。

Exceeded rate limits: too many concurrent queries that use EXTERNAL_OBJECT_TRANSFORM table-valued function for this project. For more information, see https://cloud.google.com/bigquery/docs/troubleshoot-quotas

一部画像が表示されない
詳細を表示すると割り当てエラーのメッセージが表示される

背景

今回のように、Looker Studio のレポートで Cloud Storage に保存されている非公開の画像(インターネットに公開せず、アクセスに認証を必要とする画像ファイル)を一部のユーザーにのみ表示するには、いくつかの方法があります。

1つ目は、Cloud Storage オブジェクトの「認証済み URL」を使用する方法です。オブジェクトが持つ認証済み URL を Looker Studio の IMAGE 関数に渡すことで、レポートに表示します。

上記の方法では、画像を表示するにはレポートの閲覧者が Cloud Storage バケットもしくは画像オブジェクトに対して閲覧権限を持っている必要があります。しかし今回は、Looker Studio のレポートに対して閲覧権限を持っている人であれば、Cloud Storage バケットやオブジェクトに対して閲覧権限を持っていないなくても、画像を表示できるようにしたい要件がありましたので、この方法は適切ではありません。

そこで今回は、2つ目の方法である BigQuery のオブジェクトテーブルから署名付き URL(Signed URL)を生成する EXTERNAL_OBJECT_TRANSFORM 関数を用いる方法を用いました。この関数は、オブジェクトテーブルの管理対象となっている Cloud Storage オブジェクトの署名付き URL を生成します。

この方法では、Looker Studio レポートのデータソースとして、BigQuery のオブジェクトテーブルを指定します。その上で Looker Studio のカスタムクエリにおいて EXTERNAL_OBJECT_TRANSFORM 関数を使用することで、当該テーブルへのクエリ権限を持っている人(データソースへの認証を「レポートのオーナー」または「サービスアカウント」にしている場合は、レポートへのアクセス権限を持っている人)であれば、署名付き URL を取得でき、同 URL 経由で画像へアクセスして表示することができます。

原因

公式ドキュメントによると、このエラーは、EXTERNAL_OBJECT_TRANSFORM 関数を含むクエリの同時実行数が、プロジェクトに設定された割り当て(クォータ)を超過したことが原因です。

EXTERNAL_OBJECT_TRANSFORM 関数の呼び出しの同時実行数(リモート関数を含む同時実行クエリ)は、プロジェクトあたり 10 と定められています。したがって、11個以上のクエリが発行されるような画像を一度に表示しようとすると、エラーが発生します。

今回、レポート上には表グラフが4つのみ配置されていました。Looker Studio では、表グラフごとに BigQuery に対してクエリを発行しているため、ドキュメント上の上限である 10 クエリを下回っています。しかし、実際にはクォータエラーが発生しました。これは、Looker Studio の内部的な動作により、レポート上のコンポーネント数以上にクエリが発行された、あるいは非常に短い時間間隔でのクエリ集中を BigQuery 側が同時実行と見なしたことなどが原因として考えられます。

この現象から、根本的な問題は Looker Studio のカスタムクエリから EXTERNAL_OBJECT_TRANSFORM 関数を直接、かつ複数同時に呼び出していることにあると推測できます。以降で説明する対処法は、この直接呼び出しを避けるアプローチです。

実装手順

1. Cloud Storage バケットの準備

画像ファイルをアップロードするための Cloud Storage バケットを作成し、ファイルをアップロードします。

2. BigQuery Connection API の有効化

gcloud コマンドを使用して、オブジェクトテーブルの作成に必要な BigQuery Connection API を有効化します。

gcloud services enable bigqueryconnection.googleapis.com

3. 接続の作成

BigQuery が Cloud Storage にアクセスするための接続(Connection)を作成します。これは Google Cloud リソース接続 とも呼ばれます。

bq mk --connection \
 --location=[LOCATION] \
 --connection_type=CLOUD_RESOURCE \
 [CONNECTION_ID]

[LOCATION]には BigQuery のデータセットと同じリージョンを、[CONNECTION_ID]には任意の接続IDを指定します。 コマンドが成功すると、この接続に関連付けられた一意のサービスアカウント ID が払い出されます。この接続で作成されたサービスアカウント ID は、次の手順で使用します。

4. サービスアカウントへの権限付与

手順3で作成されたサービスアカウントに対し、対象バケットへのストレージオブジェクト閲覧者roles/storage.objectViewer)ロールを付与します。

gsutil iam ch \
 serviceAccount:[SERVICE_ACCOUNT_ID]:objectViewer \
 gs://[BUCKET_NAME]

[SERVICE_ACCOUNT_ID]には払い出されたサービスアカウントを、[BUCKET_NAME]には対象のバケット名を指定します。

5. オブジェクトテーブルの作成

Cloud Storage バケット上の画像をメタデータとして参照するオブジェクトテーブルを作成します。

CREATE EXTERNAL TABLE
  `my-project.my_dataset.my_object_table`
WITH CONNECTION `asia-northeast1.my-connection` OPTIONS ( object_metadata = 'SIMPLE',
    uris = ['gs://my-bucket/images/*.jpg'] );

6. Looker Studio での設定

Looker Studio でデータソースとして BigQuery を選択し、「カスタムクエリ」に以下のSQLを記述します。

SELECT
  uri,
  signed_url
FROM
  EXTERNAL_OBJECT_TRANSFORM( TABLE `my-project.my_dataset.my_object_table`,
    ['SIGNED_URL'] );

Looker Studio のデータソースのフィールド編集画面で、IMAGE 関数を使用して新しい計算フィールドを作成します。この関数に signed_url フィールドを渡すことで、URL を画像として表示できます。

IMAGE(signed_url, "代替テキスト")

作成したフィールドをレポートの表などに追加すると、署名付き URL によって画像が表示されます。

対処案1 : レポート側の工夫(緩和策)

1つのレポートページに表示するグラフや画像の数を減らし、同時実行クォータである 10 以下に収まるように調整する方法です。

この方法は手軽ですが、表示したいレポートのレイアウトや画像の数に制約がかかるため、根本的な解決策ではありません。

対処案2 : 署名付き URL をテーブルに保存(推奨)

手順

EXTERNAL_OBJECT_TRANSFORM を含むクエリを定期的に実行し、生成された署名付き URL を別の BigQuery テーブルに保存します。Looker Studio は、この保存先のテーブルを参照するように設定します。

BigQuery のスケジュールされたクエリなどを利用して、以下のクエリを定期的に実行します。このクエリは、署名付き URL を生成し、結果を signed_url_table というテーブルに書き込みます。

CREATE OR REPLACE TABLE
  `my-project.my_dataset.signed_url_table` AS
SELECT
  uri,
  signed_url,
  -- 有効期限を管理しやすくするため、生成時刻を記録
  CURRENT_TIMESTAMP() AS generated_at
FROM
  EXTERNAL_OBJECT_TRANSFORM( TABLE `my-project.my_dataset.my_object_table`,
    ['SIGNED_URL'] );

Looker Studio のデータソースは、この signed_url_table を直接参照するように変更します。これにより、カスタムクエリは不要になります。

ビューの使用について

スケジュールされたクエリなどの定期実行ワークフローを使用しないですむよう、当初はマテリアライズドビューの使用も検討されました。しかし2025年9月現在、マテリアライズドビューでは EXTERNAL_OBJECT_TRANSFORM のようなテーブル関数を FROM 句で直接参照することがサポートされていないため、この手法は実現できません。

また論理ビューについては、クエリ結果が最大24時間キャッシュされるため、署名付き URL の有効期間(6時間)を超過してしまい、URL が無効になる可能性があります。このキャッシュを回避するには、CURRENT_TIMESTAMP() のような非決定性関数をクエリに含める工夫が必要です。また論理ビューの場合、EXTERNAL_OBJECT_TRANSFORM の同時実行数の問題が根本的に解決されるわけではないため、事前の検証が推奨されます。

注意点

EXTERNAL_OBJECT_TRANSFORM で生成される署名付き URL の有効期限は、6 時間です。スケジュールされたクエリの実行間隔は、この有効期限より短く設定する必要があります(例 : 1時間ごとに実行)。

なお、Looker Studio は取得した画像を内部でキャッシュすることがあります。そのため、テーブル上の署名付き URL の有効期限が切れた後も、Looker Studio のキャッシュが有効な間は画像が表示され続ける場合があります。ただし、URL 自体は無効になっているため、リンクへ直接アクセスするとエラーが表示されます。

Looker Studio から有効期間後にリンクへ直接アクセスした場合のエラー例

佐々木 愛美 (min) (記事一覧)

クラウドソリューション部 データアナリティクス課。2024年7月 G-gen にジョイン。G-gen 最南端、沖縄県在住。最近覚えた島言葉は、「マヤー(猫)」。