Amazon Data FirehoseでApache Icebergテーブルに配信する際の「一意のキー設定」と「JSONQuery式(JQ式)」の違いについて
お疲れさまです。とーちです。
前回、Firehoseを使ったAmazon S3 Tablesへのレコード配信の記事を書きました。今回は、その続編として、Amazon Data Firehose(以下、Firehose)でApache Icebergテーブルにデータを配信する際の設定の中の「一意のキー設定」と「JQ式」の違いについて詳しく解説したいと思います。この2つの設定は似ているようで役割が異なり、使い分けや併用のポイントを押さえておくことが重要です。
JQ式と一意キー設定の関係
JQ式と一意キー設定の関係を簡単に書くと以下のようになります。
- JQ式:どのデータベース、どのテーブルに、どの操作(insert/update/delete)を行うかを決定します
- 一意キー設定:更新・削除操作時に、対象レコードを特定するためのキーを定義します
また、これらは併用可能である点がポイントです。それぞれの設定についてもう少し細かく説明していきましょう。
ルーティング情報のインライン解析(JQ式)
JQ式に基づきFirehoseに入ってきたレコードを処理する方法で、データベース式(どのデータベース)、テーブル式(どのテーブル)、オペレーション式(どういう処理)の3つを指定します。
公式ドキュメントでは、受信レコードを異なる Iceberg テーブルにルーティングする場合の設定方法として説明されていますが、JQ式を使って単一のテーブルに配信することも可能です。
特徴としては、受信レコードの内容を元に動的にレコード挿入先等を指定できるという点です(静的な指定も可能です)。
JQ式については前回の記事でも解説していますのでこちらもご参照ください。
一意のキー設定
テーブル上で更新をする際には既存のレコードの値を書き換える必要があります。既存のレコードを見つけるためにはそのレコードを一意に特定するためのキーが必要です。この設定はそのキーを指定するための設定になっています。
上記がこの設定の本質的な部分ですが、それに加えてFirehoseから単一のテーブルにのみ書き込めれば良いという場合は、JQ式を設定せずにこちらの設定でデータベース名とテーブル名を指定するだけでも設定が可能になっています。具体的には以下のような書き方です。
[
{
"DestinationDatabaseName": "my_rsclink",
"DestinationTableName": "firehosetest"
}
]
単一のテーブルにinsertだけすればよいという場合は私としてはこちらの設定をおすすめします。個人的にはJQ式のほうが分かりやすいのですが、こちらの設定の場合、(少なくともAWSマネジメントコンソールから設定した場合は)指定したテーブルが存在するかどうかをちゃんとチェックします。これによって、不要なトラブルシューティングの手間が減ります。
ルーティング情報のインライン解析(JQ式)と一意のキー設定を併用する
JQ式と一意のキー設定は併用することも可能です。この場合、一意のキー設定は特定のテーブルで更新と削除を実行する場合に処理対象レコードを見分けるための一意キーの設定として機能します。挿入(insert)には関係してこない点に注意してください。insertは常に新しいレコードを挿入するという操作になるので、重複している一意キーのレコードが既に存在しているかどうかはチェックしません。
JQ式によって、どのデータベース、どのテーブルに、どの処理を行うかを決め、一意のキー設定で更新と削除をする際にどのキーを一意とみなすかを指定するといった具合です。
そのため、単一のテーブルにレコードを書き込む場合でも、対象のテーブルに対してFirehoseから更新や削除をさせたい場合は、JQ式と一意のキー設定は併用する必要があります。
操作タイプごとに一意キーが必要かどうかをまとめたのが以下の表になります。
操作 | 一意キーの必要性 | 動作 |
---|---|---|
挿入(insert) | 不要 | 常に新しいレコードを作成 |
更新(update) | 必要 | 既存レコードを更新、存在しない場合は挿入 |
削除(delete) | 必要 | 既存レコードを削除 |
更新・削除の際の注意事項
Firehoseを使用してApache Icebergテーブルのデータを更新・削除する場合、いくつかの重要な注意点があります。
一意キーの設定は必須
更新・削除操作を行うには、対象レコードを一意に特定するためのキーの設定が必要です。一意キーが設定されていない場合、更新・削除操作は失敗します。
この一意のキー設定ですが、Firehoseストリーム作成時に「一意のキー設定」で設定する他に、Icebergテーブル自体にidentifier-field-ids
というプロパティを設定することでも可能なようです。(ご参考)
複数テーブルの場合の一意キー設定
JQ式を使って書き込み先テーブルを動的に指定した場合、一つのFirehoseストリームから複数のテーブルに書き込むことになるので、更新・削除を行いたい各テーブルに対して、個別に一意キーを設定する必要があります。具体的には以下のように書きます。
[
{
"DestinationTableName": "furniture",
"DestinationDatabaseName": "my_rsclink",
"UniqueKeys": [
"sale_date"
]
},
{
"DestinationTableName": "electronics",
"DestinationDatabaseName": "my_rsclink",
"UniqueKeys": [
"sale_date"
]
}
]
更新操作の特性
更新操作では、テーブルに該当レコードが存在しない場合、自動的に新規レコードとして挿入されます。これは、UPSERTのような動作と考えることができます。
その他の注意
テーブル名の制約
動的にテーブル名を指定できるということで、日付をテーブル名にしたいという方もいるかもしれませんが、Firehoseでのレコード配信時やAmazon Athenaで Amazon S3 Tablesにテーブルを作成する際には以下の制約があります。
- 許可される文字:
- 小文字のアルファベット(lowercase letters)
- 数字(numbers)
- アンダースコア(underscore)
実際に2025-03-25という値が入った列をテーブル名として記録しようとするとFirehoseの配信エラーとして以下が出ます。
"Database and/or table name validation failed. Name should only contain lowercase letters, numbers, underscore. Table: my_rsclink.2025-03-25"
そもそも日付ごとに別々のテーブルを作成するというアプローチ自体はあまり推奨されません。PARTITIONED BY
句を使って適切にパーティショニングしたうえで、WHERE
句で日付指定でクエリするのが良いでしょう。
存在しないテーブルへのレコード追加
テーブルが自動的に作成されるかどうか気になる方もいるかもしれませんが、そういった機能はありません。
作成されていないテーブルに対して動的なJQ式でinsertしようとすると以下のようなエラーになります。
"errorMessage":"The specified table could not be found in the catalog. Table: my_rsclink.electronics"
そのためJQ式で動的にテーブル名を指定する場合でも、事前にテーブルは作成しておく必要があります。
新規で作成したテーブルへの権限付与
JQ式でレコード挿入先テーブルを動的に選択する場合は事前にテーブルを作成しておく必要があるのは上記の通りですが、もう一つ陥りがちなポイントとして、テーブルを新規で作成した場合は、Firehoseストリームも新規で作り直す必要がある場合があります。
実際に私が遭遇した事象を以下に記載します。
Firehoseストリームでは下記のようにレコード挿入先テーブルを動的に指定する設定としています。
この例ではFirehoseに入ってきたレコードのproduct_category
の値をテーブル名としてレコードを挿入することになるので、例えば、"product_category":"electronics"
というレコードなら、electronics
というテーブルにデータが挿入されることになります。
Amazon Lake Formationでは、Firehoseが使用するIAMロールに以下の権限がついています。すべてのテーブルに対して、すべての操作を許可しているという状態です。
この状態で以下のSQLでテーブルを新規作成します。
CREATE TABLE `my_s3_namespace`.`electronics` (
sale_date date,
product_category string,
sales_amount double)
PARTITIONED BY (month(sale_date))
TBLPROPERTIES ('table_type' = 'iceberg')
上記の状態で、JQ式に.product_category
で動的にテーブルを指定して、Firehoseにレコードをputすると以下のエラーになりました。
"errorCode":"S3.AccessDenied","errorMessage":"Access was denied. Ensure that the trust policy for the provided IAM role allows Firehose to assume the role, and the access policy allows access to the S3 bucket. Table: my_rsclink.electronics"
権限がないとのことなので、一度Amazon Lake Formationでつけていた上記の権限をRevokeして、再度上記と同様にFirehoseが使用するIAMロールにすべてのテーブルに対して、すべての操作を許可するよう権限付与してみました。
しかし、それでも先ほどと同様のエラーになります。
結局、Firehoseストリームから作り直したところ、ようやく目的のテーブル(electronics)にputされるようになりました。このケースは実際に運用する中でも遭遇しそうな気がするので気をつけたいですね。
実際に試してみる
上記のJQ式、一意のキー設定、JQ式と一意のキー設定の併用の設定をした際にそれぞれどのような動作になるのかを実際に確認してみましょう。
一意のキー設定のみ指定
まずは単純に一意のキー設定のみを指定するパターンを試してみます。
このパターンではJQ式を指定していません。そのためレコードは必ずinsert
として処理されます。
以下のコマンドで同じレコードを3回、Firehoseストリームにputします。
aws firehose put-record \
--delivery-stream-name PUT-ICE-oxwdt \
--record '{"Data":"eyJzYWxlX2RhdGUiOiIyMDI1LTAzLTIyIiwicHJvZHVjdF9jYXRlZ29yeSI6ImVsZWN0cm9uaWNzIiwic2FsZXNfYW1vdW50Ijo1OTkuOTl9"}'
# 挿入レコードの内容は、{"sale_date":"2025-03-22","product_category":"electronics","sales_amount":599.99}
SELECT * FROM my_s3_namespace.firehosetest
で確認すると、結果は以下の通り、同じレコードが3つ入っています。
JQ式のみ指定
次はJQ式のみを指定するパターンです。テーブル名だけ動的に指定し、オペレーションはinsertで固定します(対応するテーブルは作成済み)。
先ほどと同様のコマンドを使って、今度は以下のレコードを3回ずつ、上記のFirehoseストリームにputします。
{"sale_date":"2025-03-22","product_category":"electronics","sales_amount":599.99}
{"sale_date":"2024-03-20","product_category":"furniture","sales_amount":349.95}
今回は動的にテーブルを指定したので、electronicsテーブルには以下のように3つのレコードが入りました。
furnitureテーブルには以下の3つのレコードが挿入されました。
JQ式と一意のキー設定の併用
最後にJQ式と一意のキー設定の併用をupdateのパターンで試してみます。
DELETE FROM my_s3_namespace.<テーブル名>;
で一度データを全削除してから、先程と同じコマンドを使って以下のレコードを3回ずつ、上記のFirehoseストリームにputします。
{"sale_date":"2025-03-22","product_category":"electronics","sales_amount":599.99}
{"sale_date":"2024-03-20","product_category":"furniture","sales_amount":349.95}
すると今回はupdateを指定しており、かつ一意キーとしてsale_dateを指定したため、結果は以下のように各テーブルに1レコードだけ入った状態となりました。
まとめ
以上、Firehoseを使ったAmazon S3 Tablesへのレコード配信設定の中の「一意のキー設定」と「JQ式」の違いについて解説しました。
両者の使い分けのポイントは
- 単一テーブルへのinsertのみなら「一意のキー設定」だけでシンプルに設定できる
- 動的にテーブルを選択したい場合は「JQ式」を使う
- 更新や削除操作を行いたい場合は「一意のキー設定」が必須
- 動的テーブル選択と更新・削除を両方行いたい場合は両方を併用する
また、新しいテーブルを作成した場合はFirehoseストリームの再作成が必要になる可能性があるなど、いくつかの注意点もありますので、実装時には気をつけましょう。
この記事がどなたかの参考になれば幸いです。
以上、とーちでした。