Unique モデルにおける複数ストリームからの更新
業務上の利用場面と課題
データウェアハウスでワイドテーブルを使用する場合、同じテーブルの列が異なるデータソースや異なる処理パイプラインに由来することがよくあります。代表的な例は次のとおりです。
- リアルタイムストリーム: テーブルの一部のフィールドを継続的に更新します(ユーザー行動に関する列など)。
- オフラインストリーム(またはオンデマンドストリーム): テーブルの別のフィールドを特定の時点で更新します(ユーザープロファイルやタグに関する列など)。
この場合、複数のデータストリームが同じ主キーテーブルへ同時に書き込むため、主に次の二つの問題が生じます。
| 課題 | 説明 |
|---|---|
| 同時更新による上書き | データストリームごとに到着時刻が異なります。単純な REPLACE 集約では、各ストリームのデータがバージョン順に更新されることを保証できません。 |
| データ結合のコストが高い | すべての列のデータがそろうまで書き込みを待つことは、ほぼ不可能です。待機時間が非常に長くなる可能性があり、業務上許容できません。 |
Doris の Unique モデルは Sequence 列に基づくバージョン管理を標準でサポートします。ただし、定義できるグローバル Sequence 列は一つだけなので、異なるデータストリームから更新される列ごとに、更新バージョンを独立して制御することはできません。この問題に対応するため、Doris は Sequence Mapping 機能を提供します。
解決方法: Sequence Mapping
Sequence Mapping は、Doris の Unique モデルがサポートする、複数ストリームからの同時更新の仕組みです。データ列ごとに異なる Sequence 列を指定することで、各データストリームのバージョンを相互に干渉させず、独立して管理できます。
動作の仕組み
次の列を持つ Unique テーブルを考えます。
| 列 | A | B | C | D | E | s1 | s2 |
|---|---|---|---|---|---|---|---|
| 役割 | Key | Key | Value | Value | Value | Sequence | Sequence |
- データストリーム 1 は列
A, B, C, Dに書き込み、s1をバージョン管理列として使用します。 - データストリーム 2 は列
A, B, Eに書き込み、s2をバージョン管理列として使用します。
このマッピングでは、次のように動作します。
s1は列C, Dのバージョンを制御します。新しいデータのs1が既存データのs1より大きい場合にのみ、C, D, s1が更新されます。s2は列Eのバージョンを制御します。新しいデータのs2が既存データのs2より大きい場合にのみ、E, s2が更新されます。- 二つのストリームは互いに干渉せず、クエリではすべての列の最新データを読み取れます。
使用例
1. Sequence Mapping をサポートするテーブルを作成する
sequence_mapping.<sequence_col> プロパティを PROPERTIES で使用し、Sequence 列と、その列が制御する Value 列の対応関係を宣言します。
次の例では、列 c, d のバージョンを s1 で、列 e のバージョンを s2 で管理する Unique テーブルを作成します。
CREATE TABLE `upsert_test` (
`a` bigint(20) NULL COMMENT "",
`b` int(11) NULL COMMENT "",
`c` int(11) NULL COMMENT "",
`d` int(11) NULL COMMENT "",
`e` int(11) NULL COMMENT "",
`s1` int(11) NULL COMMENT "",
`s2` int(11) NULL COMMENT ""
) ENGINE=OLAP
UNIQUE KEY(`a`, `b`)
COMMENT "OLAP"
DISTRIBUTED BY HASH(`a`, `b`) BUCKETS 1
PROPERTIES (
"enable_unique_key_merge_on_write" = "false",
"light_schema_change" = "true",
"replication_num" = "1",
"sequence_mapping.s1" = "c,d",
"sequence_mapping.s2" = "e"
);
Sequence 列でサポートされる型は、整数型、
DATE、DATETIME、TIMESTAMP_NSです。列の作成後に型を変更することはできません。
作成後のテーブルスキーマは次のとおりです。
MySQL > desc upsert_test;
+-------+--------+------+-------+---------+---------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------+------+-------+---------+---------+
| a | bigint | Yes | true | NULL | |
| b | int | Yes | true | NULL | |
| c | int | Yes | false | NULL | REPLACE |
| d | int | Yes | false | NULL | REPLACE |
| e | int | Yes | false | NULL | REPLACE |
| s1 | int | Yes | false | NULL | REPLACE |
| s2 | int | Yes | false | NULL | REPLACE |
+-------+--------+------+-------+---------+---------+
2. データを書き込み、クエリする
次の五つの挿入操作では、Sequence 値の違いが各列の更新にどう影響するかを示します。
挿入 1: c, d, s1 のみに書き込み、e, s2 には書き込みません。
MySQL > insert into upsert_test(a, b, c, d, s1) values (1,1,2,2,2);
Query OK, 1 row affected (0.080 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | e | s1 | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 2 | 2 | NULL | 2 | NULL |
+------+------+------+------+------+------+------+
挿入 2: s1=1 は既存の s1=2 より小さいため、c, d, s1 は更新されません。
MySQL > insert into upsert_test(a, b, c, d, s1) values (1,1,1,1,1);
Query OK, 1 row affected (0.048 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | e | s1 | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 2 | 2 | NULL | 2 | NULL |
+------+------+------+------+------+------+------+
挿入 3: e, s2 に書き込みます。直前の s2 は NULL(最小値として扱われる)だったため、e, s2 が更新されます。
MySQL > insert into upsert_test(a, b, e, s2) values (1,1,2,2);
Query OK, 1 row affected (0.043 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | e | s1 | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 2 | 2 | 2 | 2 | 2 |
+------+------+------+------+------+------+------+
挿入 4: s1=3 は既存の s1=2 より大きいため、c, d, s1 が更新されます。e, s2 には影響しません。
MySQL > insert into upsert_test(a, b, c, d, s1) values (1,1,3,3,3);
Query OK, 1 row affected (0.049 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | e | s1 | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 3 | 3 | 2 | 3 | 2 |
+------+------+------+------+------+------+------+
挿入 5: 両方の列グループに書き込みます。s1=4 と s2=4 はどちらも既存の値より大きいため、すべての列が更新されます。
MySQL > insert into upsert_test(a, b, c, d, s1, e, s2) values (1,1,5,5,4,5,4);
Query OK, 1 row affected (0.050 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | e | s1 | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 5 | 5 | 5 | 4 | 4 |
+------+------+------+------+------+------+------+
更新結果をまとめると、次のとおりです。
| 操作 | 更新条件 | 更新された列 |
|---|---|---|
| 挿入 1 | c, d, s1 への最初の書き込み | c, d, s1 |
| 挿入 2 | 新しい s1 < 既存の s1 | なし |
| 挿入 3 | e, s2 への最初の書き込み | e, s2 |
| 挿入 4 | 新しい s1 > 既存の s1 | c, d, s1 |
| 挿入 5 | 新しい s1 と s2 がどちらも既存の値より大きい | c, d, s1, e, s2 |
3. 列を追加または削除する
Sequence Mapping を使用するテーブルでは、ALTER TABLE によって列とマッピングを動的に追加または削除できます。
最初のテーブル作成: s1 -> c,d というマッピングのみを含みます。
CREATE TABLE `upsert_test` (
`a` bigint(20) NULL COMMENT "",
`b` int(11) NULL COMMENT "",
`c` int(11) NULL COMMENT "",
`d` int(11) NULL COMMENT "",
`s1` int(11) NULL COMMENT ""
) ENGINE=OLAP
UNIQUE KEY(`a`, `b`)
COMMENT "OLAP"
DISTRIBUTED BY HASH(`a`, `b`) BUCKETS 1
PROPERTIES (
"enable_unique_key_merge_on_write" = "false",
"light_schema_change" = "true",
"replication_num" = "1",
"sequence_mapping.s1" = "c,d"
);
初期データの書き込み:
MySQL > insert into upsert_test(a, b, c, d, s1) values (1,1,1,1,1),(1,1,3,3,3),(1,1,2,2,2);
Query OK, 3 rows affected (0.101 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+
| a | b | c | d | s1 |
+------+------+------+------+------+
| 1 | 1 | 3 | 3 | 3 |
+------+------+------+------+------+
列とマッピングの追加: ALTER TABLE を使用して列 e, s2 を追加すると同時に、マッピング s2 -> e を宣言します。
MySQL > alter table upsert_test add column (e int(11) NULL, s2 bigint) PROPERTIES('sequence_mapping.s2' = 'e');
Query OK, 0 rows affected (0.011 sec)
MySQL > desc upsert_test;
+-------+--------+------+-------+---------+---------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------+------+-------+---------+---------+
| a | bigint | Yes | true | NULL | |
| b | int | Yes | true | NULL | |
| c | int | Yes | false | NULL | REPLACE |
| d | int | Yes | false | NULL | REPLACE |
| s1 | int | Yes | false | NULL | REPLACE |
| e | int | Yes | false | NULL | REPLACE |
| s2 | bigint | Yes | false | NULL | REPLACE |
+-------+--------+------+-------+---------+---------+
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | s1 | e | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 3 | 3 | 3 | NULL | NULL |
+------+------+------+------+------+------+------+
新しいマッピングが有効になった後の書き込み:
MySQL > insert into upsert_test(a, b, e, s2) values (1,1,2,2);
Query OK, 1 row affected (0.052 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | s1 | e | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 3 | 3 | 3 | 2 | 2 |
+------+------+------+------+------+------+------+
MySQL > insert into upsert_test(a, b, c, d, s1, e, s2) values (1,1,5,5,4,5,4);
Query OK, 1 row affected (0.050 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | s1 | e | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 5 | 5 | 4 | 5 | 4 |
+------+------+------+------+------+------+------+
列の削除: Value 列 e を削除すると、対応するマッピングは無効になります。その後、Sequence 列 s2 を削除します。
MySQL > alter table upsert_test drop column e;
Query OK, 0 rows affected (0.006 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+
| a | b | c | d | s1 | s2 |
+------+------+------+------+------+------+
| 1 | 1 | 5 | 5 | 4 | 4 |
+------+------+------+------+------+------+
MySQL > alter table upsert_test drop column s2;
Query OK, 0 rows affected (0.005 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+
| a | b | c | d | s1 |
+------+------+------+------+------+
| 1 | 1 | 5 | 5 | 4 |
+------+------+------+------+------+
使用上の制約
Sequence Mapping を使用する際は、次の制約に注意してください。
| 項目 | 制約 |
|---|---|
| テーブル作成時の設定 | light_schema_change を有効にする必要があります。テーブル作成時に sequence_mapping プロパティを宣言しなかった場合、後から有効にすることはできません。 |
| 列の型 | Sequence 列でサポートされる型は整数型と日時型(DATE、DATETIME、TIMESTAMP_NS)のみで、作成後に型を変更することはできません。 |
| 列の役割 | Sequence 列もマッピング対象の列も Key 列にはできません。すべての非 Key 列は、いずれかの Sequence 列にマッピングする必要があります。 |
| マッピング関係 | 異なる Sequence 列にマッピングされる列は重複できません(例えば、d を s1 と s2 の両方にマッピングすることはできません)。マッピングを設定した後は変更できません(例えば、s1 にマッピング済みの列を s2 に再マッピングすることはできません)。 |
| DDL の制限 | 列名の変更はサポートされません。Rollup の作成はサポートされません。 |
| ストレージモード | 現在サポートされるのは MOR(Merge-on-Read)テーブルのみです。グローバル Sequence 列との併用はサポートされません。バッチ削除操作もサポートされません。 |
| ロード時の動作 | ロード時に含まれないフィールドには、デフォルト値または NULL が自動的に設定されます。Sequence 列の比較では、NULL は最小値として扱われます。 |