[Redash] Redshift で一時テーブルを利用していると、スキーマ情報を取得できない場合がある


動作環境

現象

Redshift をデータソースに登録後、クエリを作成(または編集)するダッシュボードで、本来は左ペインに表示されるはずのスキーマ情報が表示されず、ページ右下に次のエラーメッセージが表示されることがあります。
※勿論いくら待ってもスキーマは表示されません。

原因調査

Redash Scheduler のログを確認すると、次の Exception error が吐かれていました。

6d6321902c Failed refreshing schema for the data source: DS_Redshift
Traceback (most recent call last):
  File "/app/redash/tasks/queries.py", line 238, in refresh_schema
    ds.get_schema(refresh=True)
  File "/app/redash/models/__init__.py", line 157, in get_schema
    schema = sorted(query_runner.get_schema(get_stats=refresh), key=lambda t: t['name'])
  File "/app/redash/query_runner/__init__.py", line 134, in get_schema
    self._get_tables(schema_dict)
  File "/app/redash/query_runner/pg.py", line 289, in _get_tables
    self._get_definitions(schema, query)
  File "/app/redash/query_runner/pg.py", line 113, in _get_definitions
    raise Exception("Failed getting schema.")
Exception: Failed getting schema.

どうやらスキーマ情報を取得するスクリプト /app/redash/query_runner/pg.py で問題が発生しているようです。

そこで /app/redash/query_runner/pg.py の113行目辺りを確認すると、

/app/redash/query_runner/pg.py(109-113行目辺り)
    def _get_definitions(self, schema, query):
        results, error = self.run_query(query, None)

        if error is not None:
            raise Exception("Failed getting schema.")

単に引数で指定されたクエリを実行しているだけです。更にこのメソッドを呼び出している箇所を中心に調べてみると、次のクエリを渡していることが分かりました。

/app/redash/query_runner/pg.py(269-287行目辺り)
        query = """
        WITH tables AS (
            SELECT DISTINCT table_name,
                            table_schema,
                            column_name,
                            ordinal_position AS pos
            FROM svv_columns
            WHERE table_schema NOT IN ('pg_internal','pg_catalog','information_schema')
        )
        SELECT table_name, table_schema, column_name
        FROM tables
        WHERE
            HAS_SCHEMA_PRIVILEGE(table_schema, 'USAGE') AND
            (
                table_schema IN (SELECT schemaname FROM SVV_EXTERNAL_SCHEMAS) OR
                HAS_TABLE_PRIVILEGE('"' || table_schema || '"."' || table_name || '"', 'SELECT')
            )
        ORDER BY table_name, pos
        """

試しに手動で上記クエリを Redshift に投げると、次のエラーが発生しました。

ERROR:  schema "pg_temp_25" does not exist

ちなみに pg_temp_25 はタイミングによって pg_temp_8 だったり pg_temp_10 だったりしますが、要は pg_temp で始まる名前のスキーマが見つからない場合があるようです。

実はこの pg_temp で始まるスキーマは、CREATE TEMPORARY TABLE で一時テーブルを作成した際、同時に作成されるようです。
Redshift の仕様では、一時テーブルはセッションが切断された際に自動で削除されるようですが、一時スキーマは自動で削除されず、また手動で削除する術が用意されていないようです。(参考: https://qiita.com/kusanoiskuzuno/items/6e2b4103758cea7287bc)

尚、当社では Tableau からも Redshift を利用しており、そちらで作成された一時テーブルや一時スキーマが大量に存在していました。

対策

そもそも一時テーブルや一時スキーマを Redash で利用する必要がないので、スキーマ取得クエリに pg_temp から始まる一時スキーマを除外するよう変更を加えることにします。

今回は次のような Patch、Dockerfile を作成、ビルドし直すことで対応しました。

pg.py.patch
--- pg.py.org   2019-10-29 17:21:07.837000000 +0900
+++ pg.py   2019-10-29 17:21:41.357782805 +0900
@@ -274,6 +274,7 @@
                             ordinal_position AS pos
             FROM svv_columns
             WHERE table_schema NOT IN ('pg_internal','pg_catalog','information_schema')
+              AND table_schema NOT LIKE 'pg_temp_%'
         )
         SELECT table_name, table_schema, column_name
         FROM tables
Dockerfile
FROM redash/redash:7.0.0.b18042

ADD pg.py.patch /tmp/pg.py.patch

RUN patch -u /app/redash/query_runner/pg.py </tmp/pg.py.patch && \
    rm -f /tmp/pg.py.patch