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 还会检查快照是否过期,并执行已标记数据文件的物理删除。

相关推荐
苍老流年8 小时前
1. Flink自定义Source
大数据·flink·linq
lzhlizihang18 小时前
Flink一些常用API的使用(Flink中的Source以及Flink中的一些常用算子)
大数据·flink
沐霜枫叶20 小时前
SqlServer Doris Flink SQL 类型映射关系
sql·sqlserver·flink
core5121 天前
flink终止提交给yarn的任务
flink·yarn·任务·终止·停止
java156550579701 天前
Flink WebUI解析(待更新)
大数据·flink
lzhlizihang1 天前
Flink中常用物理分区(区别和用法)算子以及Sink(JDBC Connector、Kafka Connector以及自定义Sink)
大数据·flink·kafka
core5121 天前
flink yarn模式3种提交任务方式
flink·yarn·任务·session·提交·方式·应用模式
静听山水1 天前
Flink CDC
大数据·flink
yala说1 天前
flink-状态
大数据·flink·1024程序员节