[Redshift] 外部テーブルはクラスタの外部にあるんだよ!な話

外部テーブルはクラスタの中ではなく心の中にあるのじゃ。Glueデータカタログという名の心の中に。
2021.07.12

Spectrumを使用してS3のファイルをRedshiftで見る場合、 外部スキーマと外部テーブルを作成する必要があります。 というか、外部スキーマと外部テーブルさえ作成してしまえば、 基本的にはそれだけでS3ファイルをSpectrumで見れるようになります。 Spectrum便利!

Spectrum経由でSelect文を流すときにはこんな感じです。

select count(*) from spectrum.table_name

普通のテーブルにクエリをするのと全く同一です。 このspectrumtable_nameがたまたま外部スキーマ、外部テーブルだというだけです。

さて、外部スキーマと外部テーブルを新規に作成したりするだけであれば、 上記の理解だけで十分です。 しかし、複数のクラスタを使用したりすると、 上記の理解だけではどうにも解せない状況が発生してしまいます。 そのベースの部分を理解しておらず、 あれ??となってしまったので、今後のためにまとめておきたいと思います。

スキーマとテーブルの概念図

まず普通のスキーマとテーブルはこんな形ですね。(データベースの層は省略しています)

ありがちな勘違い

ここに新しくRedshiftSpectrumでS3ファイルが参照できるように外部スキーマ、外部テーブルを作るとします。 するとどうなるでしょうか? こんな形を想像してしまいませんか?

私は、まさにこんなイメージをしていました。 しかしこれは見事にハズレです。

正しいSpectrumの姿

正しくはこんな感じになっています。

まず外部スキーマにはテーブルは入っておらず、 中に保有されているのは「IAMロール」「外部データベース名」です。 また、外部スキーマは「外部」と言っていますが、Redshiftの中に入っています。 (図だとちょっとはみ出してますが、これは外とのコネクタという意味で、Redshiftクラスタの中に入っているとお考えください)

そして「Glueデータカタログ」が出てきました。 Redshiftだけを触っているとすっごく不思議なんですが、外部テーブルの実体はGlueデータカタログというもので、 ここに外部データベース、そしてその中に外部テーブルが格納されています。 Glueの方からデータカタログを確認してみるとこんな感じに見えます。

外部スキーマは、この外部データベースへの紐付けに使われる存在というわけです。

外部スキーマ、外部テーブル作成クエリを見てみる

外部スキーマと外部テーブルの関係性がある程度見えてきたところで、 Redshiftにおいてそれらを作成するクエリについてみてみます。

外部スキーマの作成

外部スキーマを作るクエリは下記のようになります。

-- spectrumスキーマ作成
CREATE EXTERNAL SCHEMA spectrum
FROM DATA CATALOG
DATABASE 'spectrumdb'
IAM_ROLE 'arn:aws:iam::123456789012:role/RedshiftIamRole'
CREATE EXTERNAL DATABASE IF NOT EXISTS;

やっていることは、

  • spectrumという外部スキーマをRedshiftクラスタに作る
  • arn:aws:iam::123456789012:role/RedshiftIamRoleというIAMロールをアタッチする
  • spectrumdbという外部データベースをGlueデータカタログに作る

となります。 Glueデータカタログに「外部データベース」も作っているのがポイントですね。

外部テーブルの作成

続いて外部テーブルの作成です。

CREATE EXTERNAL TABLE spectrum.table_name( "time" bigint
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES ( 'field.delim'='\t','serialization.format'='\t')
STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat' OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://bucket_name/table_name/'
;

色々オプションはあるものの、やっていることは

  • spectrum.table_nameを作る
  • 見るS3パスはs3://bucket_name/table_name/とする

です。 あえて「spectrum.table_nameを作る」とお茶を濁しましたが、これは

「外部スキーマspectrumを経由して、外部データベース内に外部テーブルtable_nameを作る」

ということになります。 このCREATE EXTERNAL TABLE文では、外部データベースの存在は表に現れないようになっているんですね。 この部分が、Redshiftを単純に運用しているとうまく隠蔽できてるけど、 別クラスタとか立ててごにょごにょしだすとアレ?ってなる部分です。

いくつかのパターンを考える

クラスタを復元させる場合

Redshiftはスナップショットから別のクラスタを作成することができます。 スナップショットからの復元を行った場合、 Spectrumについてはどの部分までが復元されるのかということを考えてみます。

先程の図に示した通り、クラスタには外部スキーマが含まれます。 一方外部データベース、外部テーブルは含まれません。 スナップショットから復元したクラスタは外部テーブルとの接続窓口を持っているので、 紐づける外部データベースが同一なので、 特に何の変更もなく新しいクラスタからも外部テーブルを参照することができます。

新しいクラスタで同じ外部テーブルを見たい場合

新しくRedshiftクラスタを立て、そこから同じ外部テーブルを見たい場合を考えます。 先程の図を思い出せばすぐわかることですが、 外部データベースと外部テーブルはRedshiftクラスタからは独立したところに存在していますので、 同じ外部データベースを指定する外部スキーマを作れば良いだけです。

別アカウントでクラスタ復元する場合

Redshiftのスクリーンショットを使うことで、 別アカウントにも同一のクラスタを作成することが可能です。 このときSpectrum関連がどうなるか考えてみます。 これこそが私がハマったパターンなんですが、 本番環境のクラスタから検証環境のクラスタをアカウントまたぎで作成することを考えてみます。

本番環境と検証環境という関係なので、 同じGlueデータカタログには同じ名前の外部データベース、外部テーブルが作成されていました。 この状況で本番クラスタから検証クラスタを復元するとこんな感じです。

当然2つの環境は同じです(検証環境だから当然)。 もちろん外部スキーマに格納されている外部データベース名は、 きちんと検証環境のGlueデータカタログに存在する外部データベースを指しています。 しかし、この状態でテーブルを見ようとすると、なぜか失敗してしまいました。

error:  Invalid IAM Role ARN: arn:aws:iam::210987654321:role/RedshiftIamRole. Role needs to be owned by the same account with cluster
(意訳: 同じアカウントのIAMロールじゃなきゃダメだよ)

IAMロールのことを指摘されたので、 クラスタに紐付けてあるIAMロールを確認してみますが、こちらは問題ありませんでした(検証環境のものが紐づいている)。

さて、原因は、しれっと書いてある「外部スキーマのIAMロール」です。 外部スキーマを作成する際にIAMロールを指定しましたが、この IAMロールは外部スキーマに紐づいている のです。 よって、クラスタを復元した場合、外部スキーマに紐づけられているIAMロールもばっちり復元されます (勝手に書き換えられても怖いし当然ですが)。 なので、この検証環境クラスタは この外部スキーマを利用するときだけ、別アカウントのIAMロールを使用しようとする ことになるわけです。 ちょっと怖いですね。 解決方法としては至極簡単で、IAMロールを検証環境の想定しているIAMロールに付け替えるだけです。

当時はこれが外部テーブルだから起こることなのか、切り分けができていませんでした。 多くのクエリは問題ないのにある一部のクエリだけが失敗する、という風に見えていて、 クラスタのIAMロールも問題ないので全然原因が特定できませんでした...。 やっぱり裏側の仕組みを理解することは大事ですね!

まとめ

RedshiftSpectrumを使用する際の 外部テーブルはRedshiftクラスタの外部にあるよ! ということをひたすら連呼する記事でした。 ただ、外部スキーマにはクラスタとは独立してIAMロールが設定されているという点は盲点でした。

Redshiftだけを使っているとこの辺の仕組みを意識することはないです。 むしろ、意識しないで済むように作られていると言えます。 裏ではこんな仕組みでやっているのだとわかると、 新しいクラスタを作成したりする場合にも理屈が分かって安心ですね。