Partition射影のinjected型を使ってみた

2021.06.11

データアナリティクス事業本部の鈴木です。

Amazon Athena(以降、Athena)では、パーティション射影(Partition Projection)という機能をサポートしています。パーティション射影を使うと、Athenaはクエリを実行するパーティションを選ぶ際に、パーティション値と場所をGlueデータカタログなどのレポジトリに取りにいくのではなく、テーブル定義で設定したルールに基づいて自分で計算するようになります。これにより、クエリ実行のパフォーマンスの向上が期待できるほか、パーティション管理も自動化できます。

パーティション射影では、パーティションにenum型やinteger型などいくつかの型が設定できますが、そのうちinjected型に興味があったので調査しました。

injected型とは

パーティションが作成されているテーブルにクエリを実行する際、WHERE句でパーティションを指定すると思います。このとき、Athenaに「このパーティションを検索して欲しい!」と単一の値を指定できるパーティション列に使える型です。

詳しくは以下の公式ドキュメントをご確認ください。

パーティション射影のサポートされている型 - Amazon Athena

injected型の利点

ユーザーがSQL内で特定のパーティションを指定できることです。Glueデータカタログなどのレポジトリにパーティション値と場所を格納・参照しないため、パーティションが非常に多くなるときでも、以下のような点を回避することができます。

  • MSCK REPAIR TABLEでパーティション追加する場合に、追加するパーティションの数が非常に多いと、処理がタイムアウトになってしまうことがある。その場合、一部のパーティションがカタログに追加されないので、リトライの必要がある。
  • パーティションが追加できたとしても、レポジトリに登録されるパーティションメタデータの数や、参照回数が非常に多くなってしまう。このとき、データカタログに対するパーティション取得API(GetPartitions)の呼び出しがクエリパフォーマンスのボトルネックになる場合がある。

また、以下の場合にも有効な点で、ほかの射影方法と差別化されています。

  • パーティションがランダムな文字列で、ほかの射影方法を使用して射影できないときにも使える。
  • パーティションが増えても定義を更新する必要がない。

試してみる

injected型のパーティションから、データを検索してみます。

今回は、温度計のようなデバイスから、データが何かしらの方法で送信されてきて、S3に保存される場合を考えてみます。S3に年月日やデバイスIDなどの規則性にしたがってデータが配置され、それをAthenaから検索するような想定です。

データは以下のcsvファイルを手作りしました。

sample.csv

"timestamp","temperature"
2021-05-26 01:00:00,13.6
2021-05-26 02:00:00,12.3
2021-05-26 03:00:00,12.3
2021-05-26 04:00:00,13.5
2021-05-26 05:00:00,13.6
2021-05-26 06:00:00,14.0

このcsvファイルをAthenaから参照したいバケット内に配置しておきます。

ファイルはdevice_id=xxxxxxのようなHive形式で配置してあるとします。device_idはデバイスに割り振られたIDのイメージです。このデバイスIDをパーティションに指定して、機械ごとに送られてきたデータをAthenaから検索したいとします。もし市場に膨大な数のデバイスがあるなら、同じ分だけ膨大な数のパーティションが作られることになります。

device_idの値は適当にUUIDを振っておきます。 サンプルcsvファイル

続いて、テーブルは以下のように定義します。

CREATE EXTERNAL TABLE IF NOT EXISTS cm_suzuki_nayuta.device_data ( 
  timestamp STRING, 
  temperature FLOAT
) 
PARTITIONED BY (device_id string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.OpenCSVSerde'
LOCATION 's3://cm-nayuta-athena-practice/device_data/'
TBLPROPERTIES (
     "skip.header.line.count"="1",
     "projection.enabled" = "true",
     "projection.device_id.type" = "injected",
     "storage.location.template" = "s3://cm-nayuta-athena-practice/device_data/device_id=${device_id}"
  )

TBLPROPERTIESでパーティション射影を有効化し、device_idをinjected型に指定しています。

参照するcsvファイルの場所は、Amazon S3パステンプレートで設定します。具体的には"storage.location.template"の値に、csvファイルがある場所を記載します。このとき、パーティション値が入る箇所は${device_id}のようにプレースホルダにしておきます。

テーブルを作成したら、早速、以下のクエリを使って検索してみます。

SELECT *
  FROM device_data
 WHERE device_id='6deea860-95b1-b5ec-8b41-de4fa6r6ab2e';

このように、検索することができました。 サンプルの検索結果

injected型の制限

injected型は大変便利ですが、制限事項もあります。

記事執筆時点(2021/6/11)では、公式ドキュメントに以下の記載があります。

It is important to keep in mind the following points:

・Queries on injected columns fail if a filter expression is not provided for each injected column.

・Queries on an injected column fail if a filter expression on the column allows multiple values.

・Only columns of string type are supported.

ちょっと内容が難しかったのですが、私は以下のように理解しました。

  • injected型に指定された列に対してWHERE句で指定がない場合、クエリが失敗する。
  • injected型に指定された列に対してWHERE句で複数の値が指定される場合、クエリが失敗する。
  • injected型に指定された列は、string型でないといけない。

文面だけだと少しイメージが付きにくかったので、実際に検証してみました。

injected型の制限の検証

WHERE句で指定がない場合

先ほど作成したテーブルに対して、以下の、WHERE句で指定がないクエリを実行してみました。

SELECT *
  FROM device_data

すると、「CONSTRAINT_VIOLATION: Injected projected partition column device_id must have exactly one value provided in the WHERE clause!」というエラーが出て失敗しました。 Athenaのエラー

device_id列には、WHERE句で指定された値が1つだけ必要とのエラーなので、なにも指定しないとクエリが失敗することが分かりました。

WHERE句で複数の値が指定される場合

続いて、device_id列に対して、WHERE句で2つの値を指定してみました。

準備として、別のdevice_idに対応するデータをアップロードしておきます。オブジェクトキーに含まれるUUIDが異なるだけで、csvファイルは全く同じものです。 異なるデバイスIDのデータをあげたとき

以下のようにWHERE句で複数のdevice_idを指定してSQLを実行しました。

SELECT *
  FROM device_data
 WHERE device_id='6deea860-95b1-b5ec-8b41-de4fa6r6ab2e' AND device_id='7deaa860-95a1-b5dc-8b41-de4fa666abce';

同じく、「CONSTRAINT_VIOLATION: Injected projected partition column device_id must have exactly one value provided in the WHERE clause!」というエラーが出て失敗しました。

injected型に指定した列がstring型ではない場合

最後に、injected型に指定したdevice_id列がstring型ではなかったケースを試してみます。 検証のため、以下のSQLを実行して、追加のテーブルを作成します。このテーブルと、先に作ったテーブルの違いは、PARTITIONED BYで指定したdevice_idの型がstringではなく、intであることです。

CREATE EXTERNAL TABLE IF NOT EXISTS cm_suzuki_nayuta.device_data_invalid ( 
  timestamp STRING, 
  temperature FLOAT
) 
PARTITIONED BY (device_id INT)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.OpenCSVSerde'
LOCATION 's3://cm-nayuta-athena-practice/device_data/'
TBLPROPERTIES (
      "skip.header.line.count"="1",
      "projection.enabled" = "true",
     "projection.device_id.type" = "injected",
     "storage.location.template" = "s3://cm-nayuta-athena-practice/device_data/device_id=${device_id}"
  )

S3にcsvファイルをアップロードします。device_idは11111のようにしておきます。

INT型のパーティションキーを指定するときの準備

準備ができたら、以下のクエリを実行してみます。

SELECT *
  FROM device_data_invalid
 WHERE device_id=11111

これも「CONSTRAINT_VIOLATION: Injected projected partition column device_id must have exactly one value provided in the WHERE clause!」というエラーが出て失敗することが分かりました。

ちなみに、string型で定義したテーブルでは、正常に動作しました。 STRING型だと成功する例

最後に

ご紹介した例のように、パーティションの数がとても多いケースに出会うことは、しばしばあると思います。パーティション射影のinjected型は、そんなときにも強力に助けてくれます。

また、今回はinjected型のみを紹介しましたが、もちろんほかの型と組み合わせて使用することが可能です。ほかの型については、参考にあげた資料をご確認ください。injected型ならではの利点も制限もありますが、ほかの型との組み合わせつつ、Partition射影を使いこなしていきましょう。

参考