ストリーミングデータの各項目は、外部のディメンションテーブルデータソースと結合できます。Realtime Compute for Apache Flink はこの機能を利用して、ストリームをディメンションデータでエンリッチします。
ディメンションテーブル JOIN の概要
ディメンションテーブル JOIN (ルックアップ結合とも呼ばれます) は、処理時間にストリームデータの各項目に対して外部のディメンションテーブルをクエリし、メインストリームをディメンション列でエンリッチします。一般的に、イベントストリームに辞書情報やディメンション情報を追加するために使用されます。
ディメンションテーブル JOIN の主な側面は次のとおりです。
-
構文:
FOR SYSTEM_TIME AS OF PROCTIME()を使用して、メインストリームの各行がスナップショットではなく、処理時間時点のディメンションテーブルの現在のデータをクエリすることを示します。 -
キャッシュ戦略:コネクタ層でディメンションデータをキャッシュして、外部システムへの負荷を軽減します。
-
ルックアップの動作:LOOKUP ヒントを使用して、同期/非同期モード、バッファ容量、リトライ戦略などを設定します。
-
物理的な結合戦略:SHUFFLE_HASH などのヒントを使用して、シャッフルの動作を制御し、データスキューを緩和します。
-
ジョブレベルの設定:SET コマンドを使用して、
table.exec.*系のグローバルパラメーターを調整します。
ディメンションテーブル JOIN の構文
SELECT column-names
FROM table1 [AS <alias1>]
[LEFT] JOIN table2 FOR SYSTEM_TIME AS OF PROCTIME() [AS <alias2>]
ON table1.column-name1 = table2.key-name1;
-
FOR SYSTEM_TIME AS OF PROCTIME()は必須です。これは、メインストリームの各行を、処理時間時点のディメンションテーブルのスナップショットと結合するようにエンジンに指示します。 -
ON 句には、ディメンションテーブルでランダムルックアップが可能な列の等価条件を含める必要があります。
-
ON 句では、ソーステーブルの列に CAST などの型変換関数を適用できます。ソース列とディメンション列の型が一致しない場合は、ソース列をキャストしてディメンション列の型に合わせます。
制限と考慮事項
-
ディメンションテーブル JOIN は、ディメンションテーブルの現在のスナップショットとの結合のみをサポートします。
-
ディメンションテーブルは INNER JOIN と LEFT JOIN のみをサポートします。RIGHT JOIN と FULL JOIN はサポートされていません。
-
1対1の結合の場合、結合条件にディメンションテーブル内で一意な列の等価述語が含まれていることを確認してください。
-
ストリーミングデータの各項目では、結合は処理時間時点のディメンションテーブルの最新バージョンのみを参照します。結合が実行された後、その後のディメンションデータへの変更 (挿入、更新、または削除) は、すでに結合された行には伝播されません。コネクタごとの動作については、対応するコネクタのドキュメントをご参照ください。
ディメンションテーブルのキャッシュ戦略
ほとんどのコネクタは、ディメンションテーブル JOIN 用のキャッシュ戦略をサポートしています。サポート内容はコネクタによって若干異なります。詳細については、対応するコネクタのドキュメントをご参照ください。一般的なキャッシュ戦略は次のとおりです。
|
戦略 |
動作 |
|
None (デフォルト) |
キャッシュなし。 |
|
LRU |
ディメンションテーブルのサブセットをキャッシュします。ソースストリームの各行は、まずキャッシュをルックアップします。キャッシュミスした場合は、エンジンは物理的なディメンションテーブルをクエリします。 |
|
ALL |
ディメンションテーブル全体をキャッシュします。ジョブが開始される前に、エンジンはすべての行をキャッシュにロードします。その後のすべてのルックアップはキャッシュから提供され、キャッシュミスはキーが存在しないことを意味します。コネクタ固有のオプションで、ディメンションテーブルの定期的またはスケジュールされた再ロードを制御します。この戦略は、頻繁な更新を必要とせず、最大限のスループットが求められる小さなディメンションテーブルに適しています。 |
-
ビジネス要件に基づいて、鮮度とパフォーマンスのどちらを優先するかを選択してください。鮮度が重要な場合は、キャッシュを無効にしてディメンションテーブルから直接読み取ることができます。
-
キャッシュを有効にする場合は、LRU と TTL を組み合わせて、キャッシュされたデータを適度に新鮮な状態に保つことができます。TTL は、数秒から数十秒などの短い値に設定できるため、キャッシュはソースから定期的に更新されます。
-
ALL 戦略を使用する場合は、OOM を避けるためにノードのメモリを注意深く監視してください。
-
ALL 戦略では、エンジンはディメンションデータを非同期にロードするため、ディメンション JOIN ノードのメモリを増やす必要があります。目安として、リモートのディメンションテーブルデータのサイズの 2 倍分を増やしてください。
ディメンションテーブル JOIN のチューニング
チューニングパラメータのカテゴリと渡し方
ディメンションテーブル JOIN のチューニングには、3 つのカテゴリのパラメータが関係します。各カテゴリに固有の渡し方があり、渡し方を間違えると、ヒントは警告なしに無視されます。
|
カテゴリ |
代表的なパラメータ |
渡し方 |
スコープ |
|
コネクタの WITH オプション |
|
テーブル作成時に |
単一テーブル |
|
LOOKUP ヒントのオプション |
|
|
単一の結合操作 |
|
Flink ジョブレベルの TableConfig |
キーが |
ジョブの開始時に |
ジョブ全体 |
-
OPTIONS()ヒントは、コネクタが WITH 句で受け入れるオプションのみを上書きします。これはtable.exec.*TableConfig キーを受け入れません。無効なキーは暗黙的に無視されます。 -
LOOKUP()ヒントは、上記の表に記載されている固定キーのみを認識します。これは、完全な Flink 設定キーを受け付けません。たとえば、非同期ルックアップバッファを調整するには'capacity'='100'と記述する必要があります。'table.exec.async-lookup.buffer-capacity'='100'と記述しても効果はありません。 -
どのヒントでも公開されていないグローバルパラメータを変更するには、
SET 'xxx' = 'yyy';を使用します。
OPTIONS ヒントによるコネクタオプションの上書き
OPTIONS() ヒントを使用すると、CREATE TABLE ステートメントを変更することなく、クエリ内で直接テーブルの WITH オプションを上書きできます。この方法は一般的にキャッシュの動作をチューニングするために使用されます。
SELECT t.id, t.name, w.phoneNumber
FROM kafka_input AS t
LEFT JOIN phoneNumber /*+ OPTIONS(
'lookup.cache' = 'PARTIAL',
'lookup.partial-cache.max-rows' = '1000'
) */ FOR SYSTEM_TIME AS OF PROCTIME() AS w
ON t.name = w.name;
OPTIONS 内のすべてのキーは、コネクタが実際にサポートする WITH オプションでなければなりません。
上記で示されている lookup.cache および lookup.partial-cache.max-rows オプションは、Flink の汎用 LookupCache インターフェース (FLIP-221) に属しており、このインターフェースを実装する新しいコネクタ (Fluss や JDBC など) にのみ適用されます。「ディメンションテーブルのキャッシュ戦略」セクションで説明されている None/LRU/ALL 戦略は、レガシーコネクタに対応しており、これらのコネクタは異なる WITH オプションを通じてキャッシュの動作を提供します。対象のコネクタのドキュメントを参照して、どのキャッシュオプションと戦略をサポートしているかを確認し、コネクタが認識しないパラメータの使用を避けてください。
LOOKUP ヒントによるルックアップ動作の設定
LOOKUP ヒントは、Apache Flink コミュニティ版と一貫した動作をします。これは、単一の結合操作の同期/非同期モード、リトライ、シャッフルの動作を設定します。詳細については、「Apache Flink Lookup Hint」をご参照ください。
-
LOOKUP ヒントは、VVR 8.0 以降で利用可能です。
-
'shuffle' = 'true'オプションは、VVR 8.0.8 以降で利用可能です。 -
エイリアスは VVR 8.0 以降でサポートされています。ディメンションテーブルがエイリアスで参照される場合、ヒントはそのエイリアスを使用する必要があります。
サポートされるオプション
|
オプション |
意味 |
値 |
|
|
ヒントが適用されるディメンションテーブル名またはエイリアス。 |
テーブル名またはエイリアスの文字列。 |
|
|
非同期ルックアップを有効にするかどうか。 |
|
|
|
非同期ルックアップの出力順序。 |
|
|
|
非同期ルックアップのバッファキュー容量。 |
整数。 |
|
|
非同期ルックアップのタイムアウト。 |
期間 (例: |
|
|
リトライをトリガーする条件。 |
現在は、 |
|
|
リトライ戦略。 |
現在は、 |
|
|
リトライ間の固定間隔。 |
期間 (例: |
|
|
最大リトライ試行回数。 |
整数。 |
|
|
ディメンションテーブルの結合前にシャッフルするかどうか。 |
|
shuffle オプションの動作
shuffle オプションは、ディメンションテーブル結合のシャッフル戦略に影響します。各シナリオでの実際の動作は次のとおりです。
|
シナリオ |
結合戦略 |
|
|
エンジンのデフォルトのシャッフル戦略が使用されます。 |
|
|
エンジンのデフォルトのシャッフル戦略が使用されます。 |
|
|
デフォルトで SHUFFLE_HASH が使用されます。詳細は、後述の SHUFFLE_HASH のセクションをご参照ください。 |
|
|
コネクタによって提供されるカスタムのシャッフル戦略が使用されます。 |
現時点では、Streaming Lakehouse Paimon コネクタのみがカスタムのシャッフル戦略を提供しています。結合列がすべてのバケット列をカバーしている場合、バケットに基づいてシャッフルします。
コード例
-- shuffle 戦略をディメンションテーブル dim1 にのみ適用します。
SELECT /*+ LOOKUP('table'='dim1', 'shuffle' = 'true') */ ...
FROM src AS T
LEFT JOIN dim1 FOR SYSTEM_TIME AS OF PROCTIME() ON T.a = dim1.a
LEFT JOIN dim2 FOR SYSTEM_TIME AS OF PROCTIME() ON T.b = dim2.b;
-- shuffle 戦略を dim1 と dim2 の両方に適用します。
SELECT /*+ LOOKUP('table'='dim1', 'shuffle' = 'true'), LOOKUP('table'='dim2', 'shuffle' = 'true') */ ...
FROM src AS T
LEFT JOIN dim1 FOR SYSTEM_TIME AS OF PROCTIME() ON T.a = dim1.a
LEFT JOIN dim2 FOR SYSTEM_TIME AS OF PROCTIME() ON T.b = dim2.b;
-- dim1 がエイリアス D1 で参照される場合、ヒントはエイリアスを使用する必要があります。
SELECT /*+ LOOKUP('table'='D1', 'shuffle' = 'true') */ ...
FROM src AS T
LEFT JOIN dim1 FOR SYSTEM_TIME AS OF PROCTIME() AS D1 ON T.a = D1.a
LEFT JOIN dim2 FOR SYSTEM_TIME AS OF PROCTIME() AS D2 ON T.b = D2.b;
-- エイリアスを使用して、shuffle 戦略を dim1 と dim2 の両方に適用します。
SELECT /*+ LOOKUP('table'='D1', 'shuffle' = 'true'), LOOKUP('table'='D2', 'shuffle' = 'true') */ ...
FROM src AS T
LEFT JOIN dim1 FOR SYSTEM_TIME AS OF PROCTIME() AS D1 ON T.a = D1.a
LEFT JOIN dim2 FOR SYSTEM_TIME AS OF PROCTIME() AS D2 ON T.b = D2.b;
SET を使用したジョブレベルの TableConfig 設定
table.exec.* および類似の TableConfig キーは、ジョブレベルのグローバル設定です。これらは SET を使用して渡す必要があり、LOOKUP ヒントも OPTIONS ヒントも受け付けません。
ディメンションテーブル JOIN でよく使われる TableConfig キー:
|
キー |
意味 |
デフォルト |
|
|
非同期ルックアップのバッファキュー容量 (ジョブレベルのデフォルト)。 |
100 |
|
|
非同期ルックアップのタイムアウト (ジョブレベルのデフォルト)。 |
3 min |
|
|
非同期ルックアップの出力順序 (ジョブレベルのデフォルト)。 |
|
-- ジョブの開始時にジョブレベルのデフォルトを設定します。
SET 'table.exec.async-lookup.buffer-capacity' = '3072';
SET 'table.exec.async-lookup.timeout' = '180s';
INSERT INTO sink_table
SELECT ...
FROM src_table
LEFT JOIN dim_table FOR SYSTEM_TIME AS OF PROCTIME() AS d
ON src_table.key = d.key;
SET と LOOKUP ヒントが同じパラメータを設定する場合、個々の結合では LOOKUP ヒントが優先されます。
結合戦略ヒントによるシャッフルの制御
結合戦略ヒントは、ディメンションテーブル結合のシャッフル動作を制御します。利用可能なヒントは SHUFFLE_HASH、REPLICATED_SHUFFLE_HASH、SKEW です。以下の表は、どの結合戦略がどのキャッシュ戦略に適しているかを示しています。
|
キャッシュ戦略 |
SHUFFLE_HASH |
REPLICATED_SHUFFLE_HASH (SKEW と同等) |
|
None |
この戦略は推奨されません。メインストリームに余分なネットワークオーバーヘッドが発生します。 |
この戦略は推奨されません。メインストリームに余分なネットワークオーバーヘッドが発生します。 |
|
LRU |
ディメンションルックアップの IO がボトルネックになる場合にこの戦略を検討します。メインストリームが結合キーに時間的局所性を持つ場合、キャッシュヒット率が向上し、IO リクエストが減少し、全体的なスループットが向上します。 重要
メインストリームに余分なネットワークオーバーヘッドが発生します。メインストリームが結合キーに偏りがあり、パフォーマンスのボトルネックに達した場合は、代わりに REPLICATED_SHUFFLE_HASH を検討してください。 |
ディメンションルックアップの IO がボトルネックであり、メインストリームが結合キーに偏りがある場合にこの戦略を検討します。メインストリームが結合キーに時間的局所性を持つ場合、キャッシュヒット率が向上し、IO リクエストが減少し、全体的なスループットが向上します。 |
|
ALL |
ディメンションテーブルのメモリがボトルネックになる場合に推奨されます。メモリ使用量は並列度分の 1 にまで減少する可能性があります。 重要
メインストリームに余分なネットワークオーバーヘッドが発生します。メインストリームが結合キーに偏りがあり、パフォーマンスのボトルネックに達した場合は、代わりに REPLICATED_SHUFFLE_HASH を検討してください。 |
ディメンションテーブルのメモリがボトルネックになり、メインストリームが結合キーに偏りがある場合に推奨されます。メモリ使用量は並列度分のバケット数にまで減少します。 |
-
LOOKUP ヒントの
shuffleオプションは、SHUFFLE_HASH の機能をすでに網羅しています。両方が存在する場合、LOOKUP ヒントのshuffleオプションが優先されます。 -
LOOKUP ヒントの
shuffleオプションは、まだデータスキューに対応していません。REPLICATED_SHUFFLE_HASH または SKEW と組み合わせると、REPLICATED_SHUFFLE_HASH または SKEW で指定された戦略が優先されます。
SHUFFLE_HASH
効果:ディメンションテーブル JOIN でシャッフルハッシュ戦略を使用すると、結合前にメインストリームが結合キーでシャッフルされます。LRU キャッシュ戦略と組み合わせることで、キャッシュヒット率が向上し、IO リクエストが減少します。ALL キャッシュ戦略と組み合わせることで、メモリ使用量が削減されます。単一の SHUFFLE_HASH ヒントで複数のディメンションテーブルを指定できます。
制限:SHUFFLE_HASH はメモリ使用量を削減しますが、上流のデータを結合キーでシャッフルする必要があり、これにより余分なネットワークオーバーヘッドが発生します。次の 2 つのケースでは使用を避けてください。
-
メインストリームが結合キーに著しく偏っている場合。SHUFFLE_HASH を使用すると、結合ノードがボトルネックになり、ストリーミングジョブで深刻なバックプレッシャーが発生したり、バッチジョブでロングテール問題が発生したりします。代わりに REPLICATED_SHUFFLE_HASH を使用してください。
-
ディメンションテーブルが小さく、ALL キャッシュ戦略にメモリのボトルネックがない場合。この場合、SHUFFLE_HASH によって節約されるメモリは、余分なネットワークオーバーヘッドに見合いません。
コード例:
-- dim1 にのみ SHUFFLE_HASH を有効にします。
SELECT /*+ SHUFFLE_HASH(dim1) */ ...
-- dim1 と dim2 の両方に SHUFFLE_HASH を有効にします。
SELECT /*+ SHUFFLE_HASH(dim1, dim2) */ ...
-- dim1 がエイリアス D1 で参照される場合、ヒントはエイリアスを使用する必要があります。
SELECT /*+ SHUFFLE_HASH(D1) */ ...
REPLICATED_SHUFFLE_HASH
効果:REPLICATED_SHUFFLE_HASH は、基本的に SHUFFLE_HASH と同様に動作しますが、同じキーを持つメインストリームの行を N 個の並列インスタンスにランダムに分散する点が異なります。これにより、データスキューによって引き起こされるパフォーマンスのボトルネックに対処します。単一の REPLICATED_SHUFFLE_HASH ヒントで複数のディメンションテーブルを指定できます。
制限:
-
偏りのあるデータのバケット数を
table.exec.skew-join.replicate-num(デフォルト:16) で設定します。その値は、ディメンション結合ノードの並列度を超えることはできません。詳細については、「ジョブレベルの SQL チューニング」をご参照ください。 -
更新ストリームはサポートされていません。メインストリームが更新ストリームの場合にこのヒントを使用すると、エラーが発生します。
コード例:
SELECT /*+ REPLICATED_SHUFFLE_HASH(dim1) */ ...
SKEW
効果:指定されたテーブルに偏りがあることがわかっている場合、オプティマイザは replicated-shuffle-hash 戦略を適用します。SKEW は糖衣構文であり、内部的には replicated shuffle hash で実装されています。
制限:
-
各 SKEW ヒントは 1 つのテーブルしか指定できません。
-
テーブル名は、ディメンションテーブルではなく、偏りのあるメインテーブルを参照する必要があります。
-
更新ストリームはサポートされていません。メインストリームが更新ストリームの場合にこのヒントを使用すると、エラーが発生します。
コード例:
SELECT /*+ SKEW(src) */ ...
例
例 1:基本的なディメンションテーブル JOIN
最も基本的な例:Kafka ストリームを MySQL ディメンションテーブルでエンリッチします。チューニングヒントは使用されません。
CREATE TEMPORARY TABLE kafka_input (
id BIGINT,
name VARCHAR,
age BIGINT
) WITH (
'connector' = 'kafka',
'topic' = '<yourTopic>',
'properties.bootstrap.servers' = '<yourKafkaBrokers>',
'properties.group.id' = '<yourKafkaConsumerGroupId>',
'format' = 'csv'
);
CREATE TEMPORARY TABLE phoneNumber (
name VARCHAR,
phoneNumber BIGINT,
PRIMARY KEY (name) NOT ENFORCED
) WITH (
'connector' = 'mysql',
'hostname' = '<yourHostname>',
'port' = '3306',
'username' = '<yourUsername>',
'password' = '<yourPassword>',
'database-name' = '<yourDatabaseName>',
'table-name' = '<yourTableName>'
);
CREATE TEMPORARY TABLE result_infor (
id BIGINT,
phoneNumber BIGINT,
name VARCHAR
) WITH (
'connector' = 'blackhole'
);
INSERT INTO result_infor
SELECT
t.id,
w.phoneNumber,
t.name
FROM kafka_input AS t
JOIN phoneNumber FOR SYSTEM_TIME AS OF PROCTIME() AS w
ON t.name = w.name;
例 2:OPTIONS ヒントでキャッシュを有効にし、LOOKUP ヒントで非同期ルックアップを設定
チューニングシナリオ:ディメンションテーブルの QPS が不十分です。このシナリオでは、ディメンションテーブルのパーシャルキャッシュを有効にし、より大きなバッファキューで非同期ルックアップを有効にします。
INSERT INTO user_behavior_wide
SELECT /*+ LOOKUP('table' = 't2', 'async' = 'true', 'capacity' = '3072') */
t1.member_id AS member_id,
t2.tag AS tag
FROM user_behavior_datagen AS t1
LEFT JOIN fluss.fluss.user_active_info /*+ OPTIONS(
'lookup.cache' = 'PARTIAL',
'lookup.partial-cache.max-rows' = '1000'
) */
FOR SYSTEM_TIME AS OF PROCTIME() AS t2
ON t1.member_id = t2.member_id;
-
コネクタオプション (
lookup.cache、lookup.partial-cache.max-rows) は、ディメンションテーブル参照の直後に配置されたOPTIONS()ヒントを使用して渡されます。これにより、クエリ時に対応する WITH オプションが上書きされます。 -
ルックアップの動作 (
async、capacity) は、SELECT の後に配置されたLOOKUP()ヒントを使用して設定されます。OPTIONS と LOOKUP は 2 つの異なるヒントタイプであり、別々に記述する必要があります。これらを組み合わせると、ヒントは警告なしに無視されます。
例 3:SET を使用したジョブレベルの非同期ルックアップバッファのチューニング
チューニングシナリオ:同じジョブ内の複数のディメンションテーブル JOIN で、より大きな非同期ルックアップバッファが必要な状況です。この変更をグローバルに適用し、LOOKUP ヒントを使用して非同期を有効にします。
SET 'table.exec.async-lookup.buffer-capacity' = '3072';
SET 'table.exec.async-lookup.timeout' = '180s';
INSERT INTO user_behavior_wide
SELECT /*+ LOOKUP('table' = 't2', 'async' = 'true') */
t1.member_id,
t2.tag
FROM user_behavior_datagen AS t1
LEFT JOIN fluss.fluss.user_active_info
FOR SYSTEM_TIME AS OF PROCTIME() AS t2
ON t1.member_id = t2.member_id;
-
table.exec.async-lookup.buffer-capacityおよび同様のパラメータは、ジョブレベルの TableConfig キーであり、SET でのみ設定できます。OPTIONS または LOOKUP 内に配置しても効果はありません。 -
LOOKUP ヒントが
capacityを指定しない場合、SET で設定したグローバル値が使用されます。両方が指定されている場合、対象の結合では LOOKUP ヒントが優先されます。