新时代多流Join的一个思路----Partial Update

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 表引入了序列组机制。它可以解决:

  1. 多流更新期间出现混乱。每个流都定义了自己的 sequence-groups。
  2. 真正的部分更新,而不仅仅是非 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
相关推荐
考虑考虑18 分钟前
使用jpa中的group by返回一个数组对象
spring boot·后端·spring
GiraKoo27 分钟前
【GiraKoo】C++11的新特性
c++·后端
MO2T31 分钟前
使用 Flask 构建基于 Dify 的企业资金投向与客户分类评估系统
后端·python·语言模型·flask
光溯星河39 分钟前
【实践手记】Git重写已提交代码历史信息
后端·github
PetterHillWater1 小时前
Trae中实现OOP原则工程重构
后端·aigc
圆滚滚肉肉1 小时前
后端MVC(控制器与动作方法的关系)
后端·c#·asp.net·mvc
SimonKing1 小时前
拯救大文件上传:一文彻底彻底搞懂秒传、断点续传以及分片上传
java·后端·架构
深栈解码1 小时前
JUC并发编程 内存布局和对象头
java·后端
37手游后端团队1 小时前
巧妙利用装饰器模式给WebSocket连接新增持久化
后端
编程乐趣1 小时前
C#版本LINQ增强开源库
后端