paimon实战 -- 数据写入和编辑底层数据流转过程解读

数据全生命周期操作

创建表

执行以下创建表的语句将创建一个包含3个字段的Paimon表:

复制代码
CREATE TABLE T (  id BIGINT,  a INT,  b STRING,  dt STRING COMMENT 'timestamp string in format yyyyMMdd',  PRIMARY KEY(id, dt) NOT ENFORCED) PARTITIONED BY (dt);

这将会在路径 /tmp/paimon/default.db/T 下创建一个名为 T 的 Paimon 表,并且其表结构(schema)将存储在 /tmp/paimon/default.db/T/schema/schema-0 中。

写入数据

在 Flink SQL 中执行以下插入语句:

复制代码
INSERT INTO T VALUES (1, 10001, 'varchar00001', '20230501');

一旦 Flink 作业完成,记录会通过一次成功的提交被写入到 Paimon 表中。可以通过执行查询 SELECT * FROM T 来验证这些记录是否可见,该查询将会返回一条记录。提交过程会在路径 /tmp/paimon/default.db/T/snapshot/snapshot-1 处创建一个快照。snapshot-1 的文件布局描述如下:

snapshot-1 的内容包含快照的元数据,例如清单列表(manifest list)和模式ID(schema id):

复制代码
{  "version" : 3,  "id" : 1,  "schemaId" : 0,  "baseManifestList" : "manifest-list-4ccc-c07f-4090-958c-cfe3ce3889e5-0",  "deltaManifestList" : "manifest-list-4ccc-c07f-4090-958c-cfe3ce3889e5-1",  "changelogManifestList" : null,  "commitUser" : "7d758485-981d-4b1a-a0c6-d34c3eb254bf",  "commitIdentifier" : 9223372036854775807,  "commitKind" : "APPEND",  "timeMillis" : 1684155393354,  "logOffsets" : { },  "totalRecordCount" : 1,  "deltaRecordCount" : 1,  "changelogRecordCount" : 0,  "watermark" : -9223372036854775808}

注意,清单列表(manifest list)包含快照的所有更改,baseManifestList 是基础文件,而 deltaManifestList 中的更改是基于此基础文件进行的。首次提交将生成1个清单文件,并创建2个清单列表。

复制代码
./T/manifest:manifest-list-4ccc-c07f-4090-958c-cfe3ce3889e5-1  manifest-list-4ccc-c07f-4090-958c-cfe3ce3889e5-0manifest-2b833ea4-d7dc-4de0-ae0d-ad76eced75cc-0

manifest-2b833ea4-d7dc-4de0-ae0d-ad76eced75cc-0 是清单文件(上图中的 manifest-1-0),它存储了快照中数据文件的信息。

manifest-list-4ccc-c07f-4090-958c-cfe3ce3889e5-0 是基础清单列表(上图中的 manifest-list-1-base),实际上是空的。

manifest-list-4ccc-c07f-4090-958c-cfe3ce3889e5-1 是增量清单列表(上图中的 manifest-list-1-delta),它包含一组对数据文件进行操作的清单条目,在此情形下指的是 manifest-1-0。

现在,让我们插入一批跨不同分区的记录,并看看会发生什么。在 Flink SQL 中,执行以下语句:

复制代码
INSERT INTO T VALUES (2, 10002, 'varchar00002', '20230502'),(3, 10003, 'varchar00003', '20230503'),(4, 10004, 'varchar00004', '20230504'),(5, 10005, 'varchar00005', '20230505'),(6, 10006, 'varchar00006', '20230506'),(7, 10007, 'varchar00007', '20230507'),(8, 10008, 'varchar00008', '20230508'),(9, 10009, 'varchar00009', '20230509'),(10, 10010, 'varchar00010', '20230510');

第二次提交完成后,执行 SELECT * FROM T 将会返回10行记录。一个新的快照被创建,即 snapshot-2,它提供了以下物理文件布局:​​​​​​​

复制代码
% ls -1tR . ./T:dt=20230501dt=20230502  dt=20230503  dt=20230504  dt=20230505  dt=20230506  dt=20230507  dt=20230508  dt=20230509  dt=20230510  snapshotschemamanifest
./T/snapshot:LATESTsnapshot-2EARLIESTsnapshot-1
./T/manifest:manifest-list-9ac2-5e79-4978-a3bc-86c25f1a303f-1 # delta manifest list for snapshot-2manifest-list-9ac2-5e79-4978-a3bc-86c25f1a303f-0 # base manifest list for snapshot-2  manifest-f1267033-e246-4470-a54c-5c27fdbdd074-0   # manifest file for snapshot-2
manifest-list-4ccc-c07f-4090-958c-cfe3ce3889e5-1 # delta manifest list for snapshot-1 manifest-list-4ccc-c07f-4090-958c-cfe3ce3889e5-0 # base manifest list for snapshot-1manifest-2b833ea4-d7dc-4de0-ae0d-ad76eced75cc-0  # manifest file for snapshot-1
./T/dt=20230501/bucket-0:data-b75b7381-7c8b-430f-b7e5-a204cb65843c-0.orc
...# each partition has the data written to bucket-0...
./T/schema:schema-0

截至 snapshot-2 的新文件布局看起来如下图所示:

删除数据

现在我们来删除符合 dt>=20230503 条件的记录。在 Flink SQL 中,执行以下语句:

复制代码
DELETE FROM T WHERE dt >= '20230503';

第三次提交完成后,它生成了 snapshot-3。现在,列出表中的文件,注意到没有任何分区被移除。取而代之的是,为20230503至20230510的分区创建了一个新的数据文件:​​​​​​​

复制代码
./T/dt=20230510/bucket-0:data-b93f468c-b56f-4a93-adc4-b250b3aa3462-0.orc # newer data file created by the delete statement data-0fcacc70-a0cb-4976-8c88-73e92769a762-0.orc # older data file created by the insert statement

这很合理,因为在第二次提交时我们插入了一条记录(表示为 +I[10, 10010, 'varchar00010', '20230510']),然后在第三次提交时删除了这条记录。执行 SELECT * FROM T 将返回2行数据,具体是:​​​​​​​

复制代码
+I[1, 10001, 'varchar00001', '20230501']+I[2, 10002, 'varchar00002', '20230502']

截至 snapshot-3 的新文件布局看起来如下图所示:

注意,manifest-3-0 包含8个添加(ADD)操作类型的清单条目,对应于8个新写入的数据文件。

压缩表

小文件的数量会随着快照的增加而增多,这可能会导致读取性能下降。因此,为了减少小文件的数量,需要执行一次全面压缩(full-compaction)。

现在,让我们触发全面压缩,并通过 flink run 来运行一个专门的压缩作业:​​​​​​​

复制代码
<FLINK_HOME>/bin/flink run \    -D execution.runtime-mode=batch \    /path/to/paimon-flink-action-0.9.0.jar \    compact \    --warehouse <warehouse-path> \    --database <database-name> \     --table <table-name> \    [--partition <partition-name>] \    [--catalog_conf <paimon-catalog-conf> [--catalog_conf <paimon-catalog-conf> ...]] \    [--table_conf <paimon-table-dynamic-conf> [--table_conf <paimon-table-dynamic-conf>] ...]

举例:​​​​​​​

复制代码
./bin/flink run \    ./lib/paimon-flink-action-0.9.0.jar \    compact \    --path file:///tmp/paimon/default.db/T

所有当前的表文件都将被压缩,并且会生成一个新的快照,即 snapshot-4,它包含以下信息:​​​​​​​

复制代码
{  "version" : 3,  "id" : 4,  "schemaId" : 0,  "baseManifestList" : "manifest-list-9be16-82e7-4941-8b0a-7ce1c1d0fa6d-0",  "deltaManifestList" : "manifest-list-9be16-82e7-4941-8b0a-7ce1c1d0fa6d-1",  "changelogManifestList" : null,  "commitUser" : "a3d951d5-aa0e-4071-a5d4-4c72a4233d48",  "commitIdentifier" : 9223372036854775807,  "commitKind" : "COMPACT",  "timeMillis" : 1684163217960,  "logOffsets" : { },  "totalRecordCount" : 38,  "deltaRecordCount" : 20,  "changelogRecordCount" : 0,  "watermark" : -9223372036854775808}

截至 snapshot-4 的新文件布局如下所示:

注意,manifest-4-0 包含20个清单条目(18个 DELETE 操作和2个 ADD 操作):

• 对于20230503至20230510的分区,每个分区有两个数据文件的删除操作。

• 对于20230501至20230502的分区,同一个数据文件有一个删除操作和一个添加操作。

修改表

执行以下语句以配置完整压缩(full-compaction):

复制代码
ALTER TABLE T SET ('full-compaction.delta-commits' = '1');

这将为 Paimon 表创建一个新的模式(schema),即 schema-1,但直到下一次提交之前,没有快照会使用这个新 schema。

快照过期

请注意,标记的数据文件并不会真正被删除,直到快照过期且没有消费者依赖于该快照。

在快照过期的过程中,首先确定要过期的快照范围,然后标记这些快照中的数据文件以供删除。只有当存在引用特定数据文件的 DELETE 类型清单条目时,数据文件才会被标记为删除。这种标记确保了文件不会被后续快照使用,并可以安全移除。

假设上述图中的所有4个快照即将过期。过期过程如下:

  1. 首先删除所有标记的数据文件,并记录所有更改的桶(buckets)。

  2. 然后删除每一个变更日志文件和相关的清单(manifests)。

  3. 最后,删除快照本身并写入最早的提示文件(hint file)。如果删除过程后有任何目录为空,则这些空目录也会被删除。

假设另一个快照,即 snapshot-5 被创建并且触发了快照过期机制。此时,snapshot-1 到 snapshot-4 将被删除。为了简化说明,我们只关注来自之前快照的文件,在快照过期后的最终布局看起来像:

因此,20230503 至 20230510 的分区在物理上被删除了。

Flinkl流式写入举例

最后,我们将利用 CDC(变更数据捕获)摄入的示例来演示Flink 流写入。这部分内容将涵盖变更数据的捕获与写入到 Paimon 的过程,以及异步压缩和快照提交与过期背后的机制。

首先,让我们先来看一下 CDC 摄入的工作流程,以及各个组件在其中发挥的作用。

  1. MySQL CDC 源统一读取快照数据和增量数据,SnapshotReader 负责读取快照数据,而 BinlogReader 负责读取增量数据。

  2. Paimon Sink 在桶(bucket)级别将数据写入 Paimon 表。其内部的 CompactManager 将异步触发压缩操作。

  3. Committer Operator 是一个单例组件,负责提交和过期快照。

接下来,我们来详细介绍端到端的数据流

MySQL CDC 源读取快照数据和增量数据,并在归一化后将这些数据.emit(发送)到下游。

Paimon Sink 首先在基于堆的 LSM 树中缓冲新记录,当内存缓冲区满时,会将这些记录刷新到磁盘。请注意,每个写入的数据文件都是一个已排序的片段(sorted run)。此时,还没有创建任何清单文件和快照。在 Flink 检查点即将发生之前,Paimon Sink 会刷新所有缓冲的记录,并向下游发送可提交的消息,此消息将在检查点期间由 Committer Operator 读取并提交。

在检查点期间,Committer Operator 会创建一个新的快照,并将其与清单列表关联,从而使该快照包含表中所有数据文件的信息。

稍后可能会进行异步压缩,CompactManager 产生的可提交信息包含关于之前文件和合并文件的信息,使得 Committer Operator 可以构建相应的清单条目。在这种情况下,Committer Operator 在 Flink 检查点期间可能会产生两个快照:一个是用于写入数据的快照(Append 类型),另一个是用于压缩的快照(Compact 类型)。如果在检查点间隔期间没有写入任何数据文件,则只会创建 Compact 类型的快照。Committer Operator 还会检查快照是否过期,并执行已标记数据文件的物理删除。

相关推荐
武子康19 小时前
大数据-126 - Flink一文搞懂有状态计算:State Backend 工作原理与性能差异详解 核心原理与作用
大数据·后端·flink
ApacheSeaTunnel1 天前
新兴数据湖仓手册·从分层架构到数据湖仓架构(2025):数据仓库分层的概念与设计
大数据·数据仓库·开源·数据湖·dataops·白鲸开源·底层技术
武子康2 天前
大数据-125 - Flink 实时流计算中的动态逻辑更新:广播状态(Broadcast State)全解析
大数据·后端·flink
Hello.Reader2 天前
Flink Checkpoint 通用调优方案三种画像 + 配置模板 + 容量估算 + 巡检脚本 + 告警阈值
大数据·flink
yumgpkpm3 天前
CMP平台(类Cloudera CDP7.3)在华为鲲鹏的Aarch64信创环境中的性能表现
大数据·flink·kafka·big data·flume·cloudera
武子康3 天前
大数据-124 - Flink State:Keyed State、Operator State KeyGroups 工作原理 案例解析
大数据·后端·flink
代码匠心4 天前
从零开始学Flink:流批一体的执行模式
java·大数据·后端·flink·大数据处理
鸿儒之观4 天前
dinky提交flink任务报 java.lang.OutOfMemoryError: Direct buffer memory
大数据·flink
武子康4 天前
大数据-123 - Flink 并行度设置优先级讲解 原理、配置与最佳实践 从Kafka到HDFS的案例分析
大数据·后端·flink
Hello.Reader5 天前
Flink 状态模式演进(State Schema Evolution)从原理到落地的一站式指南
python·flink·状态模式