1.partial update是啥?
先提出一个历史问题:Flink流进行多流JOIN,高强度依赖状态和内存,一旦崩盘,数据没有任何保障,又耗资源,又担心崩溃,那么就没有一种更好的方式去解决吗?比如让每条流的数据对号入座,不需要关联,自己做自己的位置,由一个主键去串起来---Partial Update由此产生
通过指定 ,用户可以通过 多次更新,直到记录完成。这是通过使用 latest 数据。但是,在此过程中不会覆盖 null 值。
通过建表的时候配置'merge-engine' = 'partial-update'
,即可实现部分更新概念
例如,假设 Paimon 收到 3 条记录:
<1, 23.0, 10, NULL>
-<1, NULL, NULL, 'This is a book'>
<1, 25.2, NULL, NULL>
假设第一列是主键,则最终结果为 。<1, 25.2, 10, 'This is a book'>
对于流式查询,merge engine 必须与 changelog producer 一起使用。(还支持 'input' changelog producer, 但仅返回 input records。partial-update``lookup``full-compaction
2.如何解决数据乱序问题呢?
先说一个前知识点,就是sequence field 默认情况下,主键表根据输入顺序确定合并顺序(最后输入的记录将是最后合并的记录)。但是,在分布式计算中, 会有一些情况导致数据混乱,此时可用时间字段作为sequence field
sql
CREATE TABLE my_table (
pk BIGINT PRIMARY KEY NOT ENFORCED,
v1 DOUBLE,
v2 BIGINT,
update_time TIMESTAMP
) WITH (
'sequence.field' = 'update_time'
);
序列字段具有最大值的记录将是最后合并的记录,如果值相同,则输入 order 将用于确定哪一个是最后一个。 支持所有数据类型的字段。sequence.field``sequence.field
您可以定义多个字段,例如 ,将按顺序比较多个字段。sequence.field``'update_time,flag'
用户定义的序列字段与某些特性(如firstrow和first value)存在冲突,可能导致意外的结果。
first_row``first_value
sql
比如建表的时候指定
'merge-engine' = 'partial-update',
'fields.g_1.sequence-group' = 'a,b',
'fields.g_2,g_3.sequence-group' = 'c,d'
这样g_2,g_3只要有一个是null或非严格递增,那么就不会更新数据
到这,我们直到sequence field是专门解决单流数据乱序问题的,但是我们多流join的数据乱序问题,仅仅通过sequence field根本没办法解决,因为完全存在在多流关联的时候,sequence field被另外一个流的最新数据覆盖
因此,我们为 partial-update 表引入了序列组机制。它可以解决:
- 多流更新期间出现混乱。每个流都定义了自己的 sequence-groups。
- 真正的部分更新,而不仅仅是非 null 更新。
sql
CREATE TABLE t
(
k INT,
a INT,
b INT,
g_1 INT,
c INT,
d INT,
g_2 INT,
PRIMARY KEY (k) NOT ENFORCED
) WITH (
'merge-engine' = 'partial-update',
'fields.g_1.sequence-group' = 'a,b', -- 流1的字段是a和b字段,那么决定它的顺序合并的是g_1字段,只有当g_1不为null,且严格递增才会更新a和b的值
'fields.g_2.sequence-group' = 'c,d' -- 流2的字段是c和d字段,那么决定它的顺序合并的是g_2字段
);
INSERT INTO t(k,a,b,g_1,c,d,g_2)
VALUES (1, 1, 1, 1, 1, 1, 1); // 1,1,1,1,1,1,1
-- g_2 is null, c, d should not be updated
INSERT INTO t
VALUES (1, 2, 2, 2, 2, 2, CAST(NULL AS INT)); // 1,2,2,2,1,1,1
SELECT *
FROM t;
-- output 1, 2, 2, 2, 1, 1, 1
-- g_1 is smaller, a, b should not be updated
INSERT INTO t
VALUES (1, 3, 3, 1, 3, 3, 3); // 1,2,2,2,3,3,3
SELECT *
FROM t; -- output 1, 2, 2, 2, 3, 3, 3
并且,sequence-group也是支持aggregation的,案例如下
sql
CREATE TABLE t
(
k INT,
a INT,
b INT,
c INT,
d INT,
PRIMARY KEY (k) NOT ENFORCED
) WITH (
'merge-engine' = 'partial-update',
'fields.a.sequence-group' = 'b',
'fields.c.sequence-group' = 'd',
'fields.default-aggregate-function' = 'last_non_null_value', // 可以为每一个输入的字段机添加aggregate function
// partial-update中默认用的就是这个last_non_null_value
'fields.d.aggregate-function' = 'sum'
);
字段 c 和 d 属于同一个序列组。这意味着在插入或更新操作中,如果 c 有新的非空值,d 的值会根据其聚合函数(这里是sum)
若表中已有记录 (1, 1, 1, 1, 1),执行 INSERT INTO t VALUES (1, NULL, NULL, 2, NULL);
那么c的值为2,d为1,因为d的最新值是null,相当于没有新值
INSERT INTO t(k,a,b,c,d)
VALUES (1, 1, 1, CAST(NULL AS INT), CAST(NULL AS INT));// 1,1,1,null,null
INSERT INTO t
VALUES (1, CAST(NULL AS INT), CAST(NULL AS INT), 1, 1);// 1,1,1,1,1
INSERT INTO t
VALUES (1, 2, 2, CAST(NULL AS INT), CAST(NULL AS INT));// 1,2,2,1,1
INSERT INTO t
VALUES (1, CAST(NULL AS INT), CAST(NULL AS INT), 2, 2);// 1,2,2,2,3
SELECT *
FROM t; -- output 1, 2, 2, 2, 3