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 Group
先说一个前知识点,就是sequence field
默认情况下,主键表根据输入顺序确定合并顺序(最后输入的记录将是最后合并的记录)。但是,在分布式计算中, 会有一些情况导致数据混乱,此时可用时间字段作为sequence field
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和firstvalue)存在冲突,可能导致意外的结果。
first_row``first_value
比如建表的时候指定
'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 更新。
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 也就是说g_1不能比表中当前的g_1小,只有>=,才可以更新a和b的值,但如果a和b有一个是聚合操作,那么无论你g_1的值是什么,都会聚合指标
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的,案例如下
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
INSERT INTO t
VALUES (1, CAST(NULL AS INT), CAST(NULL AS INT), 2, 4);// 1,2,2,2,7
INSERT INTO t
VALUES (1, CAST(NULL AS INT), CAST(NULL AS INT), 1, 7);// 1,2,2,2,14
SELECT *
FROM t; -- output 1, 2, 2, 2, 3
到这里,肯定有很多异想天开的操作,下面是测试遇到的坑,供参考
-- 坑1:同一个字段不可以被不同的sequence-group都绑定
CREATE TABLE t2
(
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,s', -- a绑定s
'fields.c.sequence-group' : 's,d,e', -- c还绑定s
'fields.default-aggregate-function' : 'last_non_null_value',
'fields.s.aggregate-function' : 'sum',
'fields.d.aggregate-function' : 'sum'
);
报错:Failed to commit table test_db.t2: Field s is defined repeatedly by multiple groups: [[c], [a]]..
若一定要对s进行sum,那么方案就是:释放s,不和任何绑定,那么,就任何一条数据来了,都会对s字段进行sum聚合
-- 坑2:被sequence-group绑定的字段,无法再单独绑定其他字段
CREATE TABLE t3
(
k INT,
a INT,
b INT,
c INT,
d INT,
e INT,
d1 INT,
d2 INT
PRIMARY KEY (k) NOT ENFORCED
) WITH (
'merge-engine' = 'partial-update',
'snapshot.num-retained.max'='150',
'fields.a.sequence-group' = 'b',
'fields.c.sequence-group' = 'd,e',
'fields.d.sequence-group' = 'd1,d2',
'fields.default-aggregate-function' = 'last_non_null_value',
'fields.d2.aggregate-function' = 'sum'
);
报错:SQL 执行异常 : Failed to commit table test_db.t4: Field d is defined repeatedly by multiple groups: fields.c.sequence-group.
-- 坑3.坑2的补充
CREATE TABLE t5
(
k INT,
a INT,
b INT,
c INT,
e INT,
e1 INT,
e2 INT
PRIMARY KEY (k) NOT ENFORCED
) WITH (
'merge-engine' = 'partial-update',
'fields.a.sequence-group' = 'b',
'fields.c.sequence-group' = 'e',
'fields.e.sequence-group' = 'e1,e2',
'fields.default-aggregate-function' = 'last_non_null_value',
'fields.e.aggregate-function' = 'sum', -- 这里对绑定的e进行了sum操作
'fields.e2.aggregate-function' = 'sum',
);
报错:SQL 执行异常 : Failed to commit table test_db.t5: Should not defined aggregation function on sequence group: [e].
总结:
- a.sequence-group = b:表示a和b字段绑定,只有a的字段单调递增(=也可),才会更新当前insert语句中的b字段的值。
- 默认的default-aggregate-function' = 'last_non_null_value':以1中的案例来说,只有当前insert语句中的a的值>=表中现在a的值,才会覆盖更新b的值。
- 若配置了a.sequence-group = b且b.aggregate-function' = 'sum':那么,无论当前insert中a的值如何,都会对b进行sum,但是a的值只有当a>表中的a才会更新。
partial update的原理:compaction
疑问:多个FlinkJob多条流同时写入到同一张Paimon表,会不会触发compaction冲突呢,触发了又怎么解决呢? 详情请看:湖仓相关场景---持续更新