Qiita社ではデータの分析や閲覧にRedashを使っています。
適切な権限管理をしつつ、BigQueryやAthenaのデータを取り扱っています。
その中で、今回アップグレードした際に引っかかった部分があるので共有します。
何が起きたか
- Redashを古いバージョン(v8系)から最新の26系へアップグレード
- 厳密には8-> 10.1、10.1 -> 26.3と上げた
- クエリエディタでデータソースをBigQueryにした時、左側のスキーマ一覧(テーブル名・カラム名が表示される)が空になった
- アップグレード前は出ていた
- テーブル名の推薦などもできない
- クエリの実行は問題なくできる
ログで切り分け
Redashはスキーマ情報を、バックグラウンドジョブ(refresh_schemas)でデータソースに問い合わせて取得し、キャッシュしています。ここのログを確認しました。
- スケジューラは
refresh_schemasを30分間隔でちゃんと積んでいる - 実行側のジョブも動いているが、データソースごとの
refresh_schemaが失敗している - エラーの内容は以下
403 PERMISSION_DENIED
Access Denied: Table my-project:restricted_dataset.INFORMATION_SCHEMA.TABLE_OPTIONS:
User does not have permission to query table
my-project:restricted_dataset.INFORMATION_SCHEMA.TABLE_OPTIONS, or perhaps it does not exist.
スキーマ取得が、アクセス権のないデータセットの INFORMATION_SCHEMA を引こうとして弾かれていました。
原因: 全データセットを一括クエリして、1つの失敗で全滅する
Redash 26系の get_schema(redash/query_runner/big_query.py)は、ざっくり次の流れで動きます。
-
datasets().list()でプロジェクト内の全データセットを列挙する - それらを
UNION ALLでまとめた1本のクエリを、各データセットのINFORMATION_SCHEMA.COLUMN_FIELD_PATHS(カラム)とINFORMATION_SCHEMA.TABLE_OPTIONS(説明)に投げる - どこか1つでもエラーが返ると、例外を投げて
get_schema全体が中断する
問題は3番です。BigQueryをデータセット単位で権限分離している環境だと、サービスアカウントがアクセスできないデータセットがプロジェクト内に存在します。
それが UNION ALL に混ざると、そのデータセットだけで403になり、本来見たいデータセットのスキーマまで含めて、まるごと取得が失敗します。結果、スキーマ一覧が空になる、というわけです。
該当箇所はこんな形でした。
なぜ古いバージョンでは平気だったのか
この一括クエリ方式は、パフォーマンス最適化のために導入されたものでした(getredash/redash#5632)。
それ以前はテーブルごとにメタデータAPIを叩いていて、テーブルが大量にある環境では数分かかっていました。これを INFORMATION_SCHEMA への一括クエリにして数秒に短縮したのが #5632 です。
副作用として、スキーマ取得が all-or-nothing になりました。古い方式はアクセスできないデータセットを取りこぼしても全体は動いていたのが、新方式では1つの失敗が全体を巻き込みます。アップグレードで顕在化したのはこのためです。
一応回避策として getredash/redash#7289 が入ってますがこちらはロケーションが分かれている場合のアドホックな修正で、同一ロケーションで閲覧権限が分かれている場合難しいです。
対処の選択肢
整理すると、現実的な選択肢は2つでした。
- 案A: Redashのイメージを派生させて
get_schemaをパッチする。データセット単位でクエリし、失敗したものはスキップする。最小権限の設計を維持できるが、パッチが本家に取り込まれて新バージョンが出るまでイメージのメンテが増える - 案B: サービスアカウントにメタデータ閲覧権限(
roles/bigquery.metadataViewer)を付与する。即効だが、スキーマブラウザに全データセットのテーブル名・カラム名が並ぶようになる
ロケーションで絞る案は前述の通り使えませんでした。
採った応急処置: metadataViewer の付与
まずは早く直したかったので、応急処置として案Bを選びました。各サービスアカウントにプロジェクトレベルで metadataViewer を付与します。
(以下のコードはterraformでの付与例)
resource "google_project_iam_member" "redash_metadata_viewer" {
project = var.project_id
role = "roles/bigquery.metadataViewer"
member = "serviceAccount:${google_service_account.redash.email}"
}
トレードオフは正しく理解しておく必要があります。
-
metadataViewerが許すのはメタデータの閲覧だけです。テーブル名・カラム名・型・説明は見えるようになります - 行データの読み取り(
tables.getData)は含みません。権限のないデータセットをSELECTで叩いても、実行時に403のままです - つまりデータ本体のアクセス分離は維持されますが、テーブル構造(カラム名など)は全データソースの利用者に見えるようになります
カラム名そのものが業務情報になりうる環境では、ここが許容できるかが判断ポイントになります。今回は許容できると判断しました。
恒久対応
応急処置はメタデータの分離を緩めるので、本筋は get_schema 側をデータセット単位の失敗に強くすることです。
upstreamにこの「権限のないデータセットをスキップする」処理は無かったので、Issueを立てました(getredash/redash#7760)。PRも送る予定です。
直し方の方針はシンプルです。
- まず今まで通り一括クエリを投げる(全データセットにアクセスできる通常ケースは1発で速いまま)
- 一括が失敗したときだけ、データセット単位のクエリにフォールバックして、失敗したものだけスキップする
データセット単位でも、クエリ本数はデータセット数で頭打ちです。テーブル単位ではないので、#5632 が嫌った遅さには戻らないでしょう。
まとめ
- Redash 26系のBigQueryスキーマ取得は、プロジェクト内の全データセットを一括でクエリする
- 権限分離している環境では、アクセスできないデータセットが1つあるだけでスキーマ取得が全滅する
- クエリ実行自体は無事。壊れるのは補完用のスキーマキャッシュだけ
- 応急処置は
metadataViewerの付与。ただしテーブル構造が見えるようになるトレードオフがある - 恒久対応はスキーマ取得をデータセット単位の失敗に強くすること
アップデートの際の参考になれば幸いです。