ワークロードに適した設定を選択できるよう、Data Lake Formation (DLF) で管理される Paimon プライマリキー テーブルのコア設定 (バケット化、マージエンジン、チェンジログプロデューサー、DLF ストレージの最適化モードを含む) について説明します。
クイックリファレンス
カテゴリ | シナリオ | 推奨設定 |
バケット化 |
| 遅延バケット (デフォルト) |
ストリーミングジョブでコミット後すぐにデータの可視性が必要な場合。 |
| |
プライマリキーがパーティションキーを完全には含まず、かつテーブルサイズが 100 GB 未満の場合。 | 動的バケット ( | |
削除ベクトル | StarRocks または Hologres による高性能クエリを行う場合。 |
|
マージエンジン | 新しいデータが古いデータを完全に置き換える場合。 |
|
|
| |
データ集約が必要な場合。 |
| |
プライマリキーごとに先頭のレコードのみを保持する必要がある場合。 |
| |
変更ログプロデューサー | ダウンストリームがストリームを消費しない場合。 |
|
|
| |
|
| |
アップストリームが完全な変更ログ (例:データベース binlog) を提供し、 |
| |
上記以外で、ダウンストリームが完全な変更ログを必要とするすべてのシナリオ。 |
| |
ストレージ最適化モード | 通常 1~3 分、場合によって 5~10 分のレイテンシーを許容できる場合。 | リソース・レイテンシーバランス (デフォルト) |
リソース調整によるレイテンシーを低減するために、より多くのリソースを使用する場合。 | レイテンシー優先 | |
約 30 分のレイテンシーを許容でき、リソースの節約を優先する場合。 | リソース優先 |
プライマリキーテーブルの作成
プライマリキーが定義された Paimon テーブルは、Paimon プライマリキーテーブルです。 次の SQL は、パーティションキーに dt を、プライマリキー列に dt、shop_id、および user_id を指定して、パーティション化されたプライマリキーテーブルを作成します。
Flink SQL
CREATE TABLE T (
dt STRING,
shop_id BIGINT,
user_id BIGINT,
num_orders INT,
total_amount INT,
PRIMARY KEY (dt, shop_id, user_id) NOT ENFORCED
) PARTITIONED BY (dt);Spark SQL
CREATE TABLE T (
dt STRING,
shop_id BIGINT,
user_id BIGINT,
num_orders INT,
total_amount INT
) PARTITIONED BY (dt) TBLPROPERTIES (
'primary-key' = 'dt,shop_id,user_id'
);Paimon プライマリキーテーブルの各行には、一意のプライマリキーがあります。同じプライマリキーを持つ複数のレコードが書き込まれると、設定されたマージエンジンに従ってマージされます。
以降のセクションでは、バケット化、削除ベクトル、マージエンジン、変更ログプロデューサーといった各コア機能の設定と使用法について説明します。
バケット化
バケットは、Paimon プライマリキーテーブルにおける読み取りおよび書き込み操作の最小単位です。非パーティション化テーブルのデータ、またはパーティションテーブルの各パーティション内のデータは、並列での読み書きを可能にするために複数のバケットに分割されます。
遅延バケット (デフォルト)
遅延バケット化を使用するテーブルを作成するには、bucket プロパティを省略するか、'bucket' = '-2' と設定します。
データはまずパーティションの bucket-postpone ステージングディレクトリに書き込まれます。その後、Data Lake Formation (DLF) はデータ量に基づいて各パーティションの最適なバケット数を決定し、データをターゲットのバケットディレクトリに移動して、小さなファイルをコンパクションします。
遅延バケット化は DLF の拡張機能であり、以下の利点があります。
自動バケット化:バケット数はデータ量とスループットに基づいて自動的に計算され、手動での設定は不要です。
動的調整:データの特性が大幅に変化した場合、バケット数は自動的に調整されます。
パーティションレベルのバケット化:異なるパーティションで異なるバケット数を使用できるため、さまざまなパーティションサイズに対応できます。
同時書き込み:複数のジョブが競合することなく同時に書き込みを行えます。
データ分散
デフォルトでは、各レコードはプライマリキーのハッシュ値に基づいてバケットに割り当てられます。
データ可視性
遅延バケット化テーブルのデータ可視性は、書き込み方法によって異なります。DLF は、以下の条件が満たされた場合に固定バケットへのバッチ書き込みを有効にし、コミット直後にデータが可視になるようにします。
削除ベクトルが有効になっておらず、データが Flink バッチジョブまたは
INSERT INTOもしくはINSERT OVERWRITEを使用する Spark ジョブによって書き込まれる場合。削除ベクトルが有効になっており、データが Flink バッチジョブまたは
INSERT OVERWRITEを使用する Spark ジョブによって書き込まれる場合。
それ以外の場合、データは DLF によって処理された後に可視になり、通常は 1~3 分かかります。DLF がリソースやバケット数の調整を行っている場合、可視になるまでに 5~10 分かかることがあります。特定のストレージ最適化モードを選択して、調整の頻度を減らすことができます。詳細については、「ストレージ最適化モード」をご参照ください。
固定バケットへのバッチ書き込みが有効な場合、DLF は bucket-postpone の一時ディレクトリをバイパスし、データを直接固定バケットに書き込みます。
制御パラメーター:
'postpone.batch-write-fixed-bucket'。trueに設定すると、この機能が有効になります。サポートされるエンジンバージョン:Realtime Compute for Apache Flink VVR 11.4 以降、EMR Serverless Spark esr-4.7.0 以降。
新しいパーティションのバケット数:固定バケットへのバッチ書き込みが有効で、新しいパーティションに書き込む場合、パーティションのバケット数は自動的に Flink ジョブの並列度または Spark パーティション数 (最大 2048) に設定されます。DLF は後でこれを調整することがあります。
既存パーティションの書き込み競合:固定バケットへのバッチ書き込みが有効で、既存のパーティションに書き込む場合、書き込み中に DLF がパーティションのバケット数を調整すると、競合により書き込みジョブは失敗します。この機能を無効にして競合を防ぐには、
'postpone.batch-write-fixed-bucket' = 'false'と設定します。削除ベクトルテーブル: 削除ベクトルが有効になっているテーブルで Flink バッチまたは Spark ジョブを使用して
INSERT INTO操作を実行する場合は、'postpone.batch-write-fixed-bucket'を無効にしてください。
データ一貫性
単一ジョブの書き込み:同じジョブによって書き込まれたデータの場合、DLF は書き込み順序でレコードを処理し、シーケンシャル一貫性を提供します。
説明並列度が変更されていないチェックポイントまたはセーブポイントから再開されたジョブは、同じジョブとして扱われます。
複数ジョブの書き込み:異なるジョブによって書き込まれたデータの場合、最終的なテーブルの状態には両方のジョブの結果が混在する可能性がありますが、データは失われず、スナップショット分離の一貫性が提供されます。マージの順序を制御するには、シーケンスフィールドを設定します。詳細については、「順序が乱れたデータの処理」をご参照ください。
固定バケット
固定バケット数を指定するには、'bucket' = '<num>' と設定します。<num> は正の整数である必要があります。
バケット数が少なすぎると、ジョブの並列度が制限され、バケットあたりのデータが過剰になり、読み書きのパフォーマンスが低下します。一方、バケット数が多すぎると、過剰な数の小さなファイルが作成されます。バケットあたり約 1 GB を目標にすることを推奨します。
データ分散
遅延バケット化と同じです。詳細については、「データ分散」をご参照ください。
データ可視性
可視性は、削除ベクトルが有効かどうかによって異なります。
削除ベクトルなし:データはコミット直後に可視になります。
削除ベクトルあり:データは DLF 最適化ジョブによって処理された後に可視になり、通常は 1~3 分かかります。
動的バケット
動的バケットを有効にするには、'bucket' = '-1' と設定します。
制限事項:
動的バケットモードは、複数ジョブによる同時書き込みをサポートしていません。
100 GB を超えるテーブルの場合、動的バケット化は大きなオーバーヘッドを伴うため、推奨されません。
データ分散
動的バケット化テーブルは、既存のバケットにデータを書き込み、バケットが容量を超えると自動的に新しいバケットを作成します。以下のプロパティが動作を制御します。
プロパティ | タイプ | デフォルト | 説明 |
|
|
| バケットあたりの最大行数。 |
|
| - | 初期バケット数。設定されていない場合、デフォルトで書き込みオペレーターの並列度になります。 |
動的バケットが設定されている場合、更新のパフォーマンスとリソース使用量は、プライマリキーがパーティションキーを完全に含むかどうかによって異なります。
プライマリキーがパーティションキーを含む場合:更新は同じパーティション内にとどまり、ヒープメモリを使用します。
プライマリキーがパーティションキーを完全に含まない場合:更新はパーティションをまたぐ可能性があり、RocksDB を必要とし、パフォーマンスが低下する可能性があります。
非クロスパーティション更新
プライマリキーがパーティションキーを完全に含む場合、DLF は各キーのパーティションを特定できますが、バケットは特定できません。そのため、キーをバケット ID にマッピングするインメモリインデックスを維持します。これにより、プライマリキー 1 億個あたり約 1 GB のヒープメモリが消費されます。アクティブに書き込まれているパーティションのみがヒープメモリを消費します。
ヒープメモリの使用を除けば、非クロスパーティションの動的バケット化は、他のバケット化モードと比べて大きなパフォーマンスの違いはありません。
クロスパーティション更新
プライマリキーがパーティションキーを完全に含まない場合、DLF は RocksDB を使用して、キーからパーティションおよびバケット ID へのマッピングを維持します。これにより、大規模なテーブルでは顕著なパフォーマンスオーバーヘッドが発生する可能性があり、完全なインデックスの読み込みによりジョブの起動が遅くなります。
さらに、マージエンジンはクロスパーティション更新の動作に影響を与えます。
deduplicate:データは古いパーティションから削除され、新しいパーティションに挿入されます。aggregationおよびpartial-update:新しいパーティションキーは無視され、古いパーティションのデータが更新されます。first-row:同じプライマリキーを持つレコードが既に存在する場合、新しいデータは破棄されます。
データ可視性
固定バケットと同じです。詳細については、「データ可視性」をご参照ください。
削除ベクトル
Paimon のプライマリキーテーブルを作成する際に、'deletion-vectors.enabled' = 'true' を設定します。
削除ベクトルは、スモールファイルコンパクション中に生成され、プライマリキーテーブルのクエリパフォーマンスを向上させます。この設定は、クエリ性能が重視される、または読み取りヘビーなワークロードに最適です。
削除ベクトルの設定は、テーブルの作成後に変更できません。
削除ベクトルは、データの可視性に影響します。詳細については、「遅延バケット化:データの可視性」および「固定バケット:データの可視性」をご参照ください。
削除ベクトルは、Data Lake Formation (DLF) の最適化ジョブにおけるリソース消費に影響します。詳細については、「ストレージ最適化モード」をご参照ください。
マージエンジン
同じプライマリキーを持つ複数のレコードが書き込まれた場合、Paimon は merge-engine プロパティに基づいてそれらをマージします。サポートされている値は、deduplicate (デフォルト)、partial-update、aggregation、および first-row です。
順序が正しくないデータの処理
デフォルトでは、Paimon は入力順を使用してマージの優先順位を決定し、最後に書き込まれたレコードが最新として扱われます。順不同のデータの場合は、'sequence.field' = '<column-name>' を設定することで、指定した列の昇順でレコードをマージできます。
サポートされるシーケンスフィールドの型:TINYINT、SMALLINT、INTEGER、BIGINT、TIMESTAMP、TIMESTAMP_LTZ。
deduplicate (デフォルト)
'merge-engine' = 'deduplicate' では、プライマリキーごとに最新のレコードのみが保持されます。 最新のレコードが delete メッセージの場合、そのキーを持つすべてのレコードが削除されます。
例:
CREATE TABLE T (
k INT,
v1 DOUBLE,
v2 STRING,
PRIMARY KEY (k) NOT ENFORCED
) WITH (
'merge-engine' = 'deduplicate' -- deduplicate はデフォルトのため省略可能です
);以下のレコードを順に書き込みます。
+I(1, 2.0, 'apple')+I(1, 4.0, 'banana')+I(1, 8.0, 'cherry')
SELECT * FROM T WHERE k = 1は(1, 8.0, 'cherry')を返します。
以下のレコードを順に書き込みます。
+I(1, 2.0, 'apple')+I(1, 4.0, 'banana')-D(1, 4.0, 'banana')
SELECT * FROM T WHERE k = 1 は行を返しません。
集計
'merge-engine' = 'aggregation' の場合、同じプライマリーキーを持つレコードは指定された関数を使用して集約されます。キー以外の各列には fields.<field-name>.aggregate-function が必要です。デフォルトは last_non_null_value です。
例:
CREATE TABLE T (
product_id BIGINT,
price DOUBLE,
sales BIGINT,
PRIMARY KEY (product_id) NOT ENFORCED
) WITH (
'merge-engine' = 'aggregation',
'fields.price.aggregate-function' = 'max',
'fields.sales.aggregate-function' = 'sum'
);以下のレコードを順に書き込みます。
+I(1, 23.0, 15)+I(1, 30.2, 20)
SELECT * FROM T WHERE product_id = 1 は (1, 30.2, 35) を返します。
サポートされる集計関数
sum: 合計を算出します。型: DECIMAL, TINYINT, SMALLINT, INTEGER, BIGINT, FLOAT, DOUBLE。product: 積を計算します。型: DECIMAL、TINYINT、SMALLINT、INTEGER、BIGINT、FLOAT、DOUBLE。max: 最大値を返します。型: CHAR, VARCHAR, DECIMAL, TINYINT, SMALLINT, INTEGER, BIGINT, FLOAT, DOUBLE, DATE, TIME, TIMESTAMP, TIMESTAMP_LTZ。min: 最小値を返します。maxと同じ型です。first_value: 最初の入力値 (null を含む) を返します。すべての型。first_not_null_value: 最初の NULL でない入力値を返します。すべての型。last_value: 最新の入力値 (null を含む) を返します。すべての型に対応します。last_non_null_value(デフォルト):最新の非 null の入力値を返します。すべての型。listagg: 文字列をカンマで連結します。型: STRING。bool_and/bool_or: 論理 AND / 論理 OR。型: BOOLEAN。rbm32/rbm64: 32 ビット / 64 ビットの RoaringBitmaps をマージします。 型: VARBINARY。nested_update: 行を配列に集約します。'fields.<field-name>.nested-key' = 'pk0,pk1,...'を設定して、ネストされたキーによって重複を排除します (最後の項目が保持されます)。nested_partial_update:nested_updateと同様ですが、ネストされたキーによってARRAY内で列の部分的な更新を実行します。collect: 複数の ARRAY を 1 つに結合します。'fields.<field-name>.distinct' = 'true'を設定すると、重複を排除できます。merge_map:複数の MAP をマージし、重複するキーについては最後の値を保持します。
sum、product、last_value、last_non_null_value、nested_update、collect、および merge_map のみが、リトラクトメッセージ (update_before および delete) をサポートします。列でリトラクションを無視するには、'fields.<field-name>.ignore-retract' = 'true' を設定します。
例
複数のサブオーダーを集約するには、nested_update を使用します:
-- メインの orders テーブル
CREATE TABLE orders (
order_id BIGINT PRIMARY KEY NOT ENFORCED,
user_name STRING,
address STRING
);
-- サブオーダーテーブル
CREATE TABLE sub_orders (
order_id BIGINT,
sub_order_id INT,
product_name STRING,
price BIGINT,
PRIMARY KEY (order_id, sub_order_id) NOT ENFORCED
);
-- ワイドテーブル
-- サブオーダーを集計
-- sub_order_id に基づいてサブオーダーを重複排除
CREATE TABLE order_wide (
order_id BIGINT PRIMARY KEY NOT ENFORCED,
user_name STRING,
address STRING,
sub_orders ARRAY<ROW<sub_order_id BIGINT, product_name STRING, price BIGINT>>
) WITH (
'merge-engine' = 'aggregation',
'fields.sub_orders.aggregate-function' = 'nested_update',
'fields.sub_orders.nested-key' = 'sub_order_id'
);
INSERT INTO order_wide
SELECT
order_id,
user_name,
address,
CAST (NULL AS ARRAY<ROW<sub_order_id BIGINT, product_name STRING, price BIGINT>>)
FROM orders
UNION ALL
SELECT
order_id,
CAST (NULL AS STRING),
CAST (NULL AS STRING),
ARRAY[ROW(sub_order_id, product_name, price)]
FROM sub_orders;orders テーブルにデータがあるとします:
(1, 'Alice', 'add1'),
(2, 'Bob', 'add2')
sub_orders テーブルにはデータがあります:
(1, 11, 'apple', 10),
(1, 12, 'banana', 20),
(2, 21, 'cherry', 30),
(2, 22, 'peach', 40),
(2, 21, 'cherry', 50)
order_wide テーブルには次のデータが格納されます。
(1, 'Alice', 'add1', [(11, 'apple', 10), (12, 'banana', 20)])
(2, 'Bob', 'add2', [(21, 'cherry', 50), (22, 'peach', 40)])
partial-update
'merge-engine' = 'partial-update' を使用すると、複数のメッセージを介してレコードをインクリメンタルに更新できます。同じプライマリーキーの場合、新しいデータが古いデータを上書きしますが、新しいデータに含まれる null 値は既存の値を上書きしません。
partial-update は delete または update_before メッセージを処理できません。 それらを無視するには、'ignore-delete' = 'true' を設定します。
例:
CREATE TABLE T (
k INT,
v1 DOUBLE,
v2 BIGINT,
v3 STRING,
PRIMARY KEY (k) NOT ENFORCED
) WITH (
'merge-engine' = 'partial-update'
);以下のレコードを順に書き込みます。
+I(1, 23.0, 10, NULL)+I(1, NULL, NULL, 'これは本です')+I(1, 25.2, NULL, NULL)
SELECT * FROM T WHERE k = 1 は (1, 25.2, 10, 'This is a book') を返します。
シーケンスグループ
グローバルな シーケンスフィールド に加えて、シーケンスグループを使用して、異なる列グループに対して独立したマージ順序を指定できます。これは、複数のソーステーブルを持つテーブルのワイド化シナリオで役立ちます。
例:
CREATE TABLE T (
k INT,
a STRING,
b STRING,
g_1 INT,
c STRING,
d STRING,
g_2 INT,
PRIMARY KEY (k) NOT ENFORCED
) WITH (
'merge-engine' = 'partial-update',
'fields.g_1.sequence-group' = 'a,b',
'fields.g_2.sequence-group' = 'c,d'
);列 a と b はマージシーケンスとして g_1 を使用し (値が大きいほどデータが新しいことを意味します)、列 c と d はマージシーケ-ケンスとして g_2 を使用します。
集計によるテーブルのワイド化
部分更新では、fields.<field-name>.aggregate-function を設定することで、特定のカラムに集計関数を適用することもできます。
<field-name>列は、シーケンスグループに属している必要があります。集計マージエンジンのすべての関数が利用可能です。「サポートされる集計関数」を参照してください。
例:
CREATE TABLE T (
k INT,
a STRING,
b INT,
g_1 INT,
c STRING,
d INT,
g_2 INT,
PRIMARY KEY (k) NOT ENFORCED
) WITH (
'merge-engine' = 'partial-update',
'fields.g_1.sequence-group' = 'a,b',
'fields.b.aggregate-function' = 'max',
'fields.g_2.sequence-group' = 'c,d',
'fields.d.aggregate-function' = 'sum'
);列 a と b は、マージシーケンスとして g_1 を使用します (値が大きいほどデータが新しいことを意味します)。a は最新の null でない値 (デフォルトは last_non_null_value) を保持し、b は最大値を保持します。列 c と d は、マージシーケンスとして g_2 を使用します。c は最新の null でない値を保持し、d は合計を計算します。
first-row
'merge-engine' = 'first-row' では、DLF はプライマリキーごとに最初のレコードのみを保持します。 deduplicate と比較して、first-row は insert タイプの変更ログのみを、より効率的に生成します。
制限事項:
first-row は delete または update_before メッセージを処理できません。それらを無視するには、
'ignore-delete' = 'true'を設定します。first-rowはシーケンスフィールドをサポートしていません。
例:
CREATE TABLE T (
k INT,
v1 DOUBLE,
v2 STRING,
PRIMARY KEY (k) NOT ENFORCED
) WITH (
'merge-engine' = 'first-row'
);以下のレコードを順に書き込みます。
+I(1, 2.0, 'apple')+I(1, 4.0, 'banana')+I(1, 8.0, 'cherry')
SELECT * FROM T WHERE k = 1 は (1, 2.0, 'apple') を返します。
チェンジログプロデューサー
changelog-producer プロパティは、Paimon が下流のストリーミングコンシューマー向けのチェンジログを生成する方法を制御します。一般的な値は、none (デフォルト)、input、lookup です。
none (デフォルト)
下流でストリームが消費されない場合や、増分データまたは最新の状態のみを必要とする場合に適しています。
'changelog-producer' = 'none' を設定すると、下流では各 Paimon スナップショットから増分データが読み取られます。同じプライマリキーが複数回出現する可能性があります。
'merge-engine' = 'deduplicate':下流では完全な行が読み取られます。'merge-engine' = 'partial-update':下流では更新された列のみが読み取られ、その他の列は null になります。'merge-engine' = 'aggregation':下流では更新された列の差分値のみが読み取られ、その他の列は null になります。
'changelog-producer' = 'none' でストリーミングする場合は、'scan.remove-normalize' = 'true' を設定して、高コストな下流の Flink 正規化オペレーターを削除してください。完全なチェンジログが必要な場合は、代わりに 'changelog-producer' = 'input' または 'changelog-producer' = 'lookup' を使用してください。
input
'merge-engine' = 'deduplicate' を使用し、かつ下流で完全なチェンジログが必要な場合に適しています。
'changelog-producer' = 'input' を設定すると、Paimon は入力メッセージをそのままチェンジログとして渡します。これは、入力ストリームがすでに完全なチェンジログ (データベースのバイナリログなど) である場合にのみ機能します。
input メカニズムは追加の計算を伴わないため、ルックアップよりも効率的です。
lookup
下流で完全なチェンジログを必要とするすべてのシナリオに適しています。
'changelog-producer' = 'lookup' を設定すると、最適化ジョブは各コミット前にバッチポイントルックアップを実行し、コンパクションをトリガーして、結果から完全なチェンジログを生成します。入力ストリームが完全なチェンジログであるかどうかに関係なく機能します。
ルックアップメカニズムは追加の計算を必要とするため、input よりも効率は劣りますが、適用範囲がより広くなっています。
不要なチェンジログの削減
デフォルトでは、Paimon は更新された値が以前の値と同一である場合でもチェンジログを生成します。'changelog-producer.row-deduplicate' = 'true' を設定して、このような no-op チェンジログを排除します。これはルックアップメカニズムにのみ適用されます。
no-op チェンジログが頻繁に発生する場合にのみ使用してください。追加の比較オーバーヘッドが発生するためです。
ストレージ最適化モード
DLF は、データの可視性とリソース消費量のバランスを取るために、複数のストレージ最適化モードを提供します。この設定は DLF コンソールで構成します。詳細については、「ストレージ最適化戦略の表示と構成」をご参照ください。DLF は以下のストレージ最適化モードをサポートしています。
動的リソース - バランス型 (デフォルト):遅延バケット化テーブルの場合、レイテンシーは通常 1~3 分です。リソースまたはバケットの調整中は 5~10 分に達することがあります。削除ベクトルを持つ他のテーブルの場合、レイテンシーは通常 1~3 分です。
動的リソース - 低レイテンシー:バランス型モードの約 2 倍のリソースを使用して、最適化ジョブの速度を向上させ、リソース調整を最小限に抑えます。
動的リソース - 低リソース使用量:データバックプレッシャーによって駆動されます。バランス型モードのリソースの 1/3~1/2 を使用しますが、レイテンシーは 30 分に達することがあります。
固定リソース:最適化ジョブは調整なしで継続的に実行されます。遅延バケット化テーブルのリソース割り当て、コンパクション間隔、およびバケット数を構成できます。動的モードのレイテンシーが高い場合のデータキャッチアップにのみ推奨され、日常的な使用には推奨されません。
説明コンパクション間隔は、DLF 最適化ジョブのコミット間隔を指します。ジョブは継続して実行され、スケジュールモードには切り替わりません。
遅延バケット化テーブルのバケット数を設定した後、新しいパーティションは調整なしでその数を直接使用します。