Paimon基本概念

301 阅读9分钟

1.文件布局

image.png image.png

案例:

INSERT INTO paimon_table VALUES (1, 10001, 'varchar00001', '20230501');
  • 数据文件data/data-00001.parquet(存储数据记录,在bucket下面)。
  • Manifest 文件manifest/manifest-xxxx.avro(记录数据文件路径、行数、主键范围)。
  • Snapshot 文件_snapshots/snapshot-1.json(记录快照信息)

(0) Schema

schema存储的是表结构元数据

{
  "version" : 3,
  "id" : 0,
  "fields" : [ {
    "id" : 0,
    "name" : "dt",
    "type" : "STRING NOT NULL"
  }, {
    "id" : 1,
    "name" : "id",
    "type" : "BIGINT NOT NULL"
  }, {
    "id" : 2,
    "name" : "sat",
    "type" : "INT NOT NULL"
  }, {
    "id" : 3,
    "name" : "st",
    "type" : "INT NOT NULL"
  }, {
    "id" : 4,
    "name" : "vs",
    "type" : "BIGINT"
  }, {
    "id" : 5,
    "name" : "cs",
    "type" : "BIGINT"
  }, {
    "id" : 6,
    "name" : "ct",
    "type" : "DOUBLE"
  } ],
  "highestFieldId" : 6,
  "partitionKeys" : [ "dt" ],
  "primaryKeys" : [ "dt", "id", "sat", "st" ],
  "options" : {
    "fields.vs.aggregate-function" : "sum",
    "merge-engine" : "aggregation",
    "fields.ct.aggregate-function" : "sum",
    "changelog-producer" : "lookup",
    "file.format" : "orc",
    "write-buffer-size" : "128mb",
    "fields.cs.aggregate-function" : "sum",
    "sink.parallelism" : "2"
  },
  "comment" : "",
  "timeMillis" : 1761042845056
}

(1) Snapshot

关键1:离线读Snapshot

paimon可以快照捕获在某个时间点的状态。可以去访问指定时间版本时候的表情况。 snapshot存储的是元数据链,不是数据,存的都是manifest list 所有快照文件都存储在快照目录中。

snapshot文件是一个 JSON 文件,包含有关此快照的信息,包括:

  1. id:快照的唯一ID
  2. 正在使用的Schema文件id
  3. 包含此快照的所有更改的清单列表(manifest list 会产生两个list,具体介绍如下)
    • baseManifestList:记录本次快照是以哪些manifest file以为基础的(历史)。
    • deltaManifestList:记录本次快照是新增了哪些manifest file(新增)。
  4. changelogManifestList: 指向变更日志(Changelog)的清单列表,用于支持流式消费。
  5. commitKind: 提交类型,如 APPENDCOMPACTOVERWRITE
{
  "version" : 3,
  "id" : 359,
  "schemaId" : 0,
  "baseManifestList" : "manifest-list-de8b039e-7a92-4f76-a98e-bf62ba36bc69-890",
  "deltaManifestList" : "manifest-list-de8b039e-7a92-4f76-a98e-bf62ba36bc69-891",
  "changelogManifestList" : null,
  "indexManifest" : "index-manifest-7ce3f928-fe4b-4b90-b03b-ec8cf67ea5f2-178",
  "commitUser" : "133b0dcb-cd90-4fb7-8cfc-10935295cc96",
  "commitIdentifier" : 179,
  "commitKind" : "APPEND",
  "timeMillis" : 1761101924881,
  "logOffsets" : { },
  "totalRecordCount" : 176981,
  "deltaRecordCount" : 2126,
  "changelogRecordCount" : 0,
  "watermark" : -9223372036854775808
}

(2) Partition和Bucket

分区-->分目录

分桶==>也是分目录,也就是说在分区目录下面还会有分桶目录。桶是读写的最小存储单元桶的数量限制了最大处理的并行度,建议每个桶的数据大小为200M~1GB左右

注意:

  1. 对于两个同时修改表的writer,只要不修改同一个bucket,他们的提交是不会冲突的,任务不会报错;
  2. 但如果修改的是同一个bucket,则底层会将先来的进行compact,后来的就会报错,重试,重新获取元数据然后写入到compact完成之后的bucket
  3. 若不希望冲突的时候进行任务报错重启,就需要单独配置专业压缩任务,并设置表为'write-only'='true'

(3) Manifest Files

所有清单列表(manifest list)和清单文件(manifest file)都存储在清单(manifest)目录中。

  1. 清单列表(manifest list)是清单文件名(manifest file)的列表。
  2. 清单文件(manifest file)是包含有关 LSM 数据文件和更改日志文件的文件信息。例如对应快照中创建了哪个LSM数据文件、删除了哪个文件。

与manifest list不同,manifest file,并不都是本次快照产生的,可能是以前的产生的,但是被本次快照继承了下来,一般base关联的都是旧的,delta关联的都是新产生的,上面已经讲过了。

每个manifest file都关联了一个或多个具体的数据文件。

manifest file本身没有任何数据,只是数据的描述,记录哪些数据文件包含了哪些数据等信息,其核心字段包括:

  • kind:文件状态,ADD 或 DELETE
  • partition:文件所属的分区。
  • bucket:文件所属的桶。
  • totalBuckets:表的总桶数。
  • file:数据文件的详细元数据,包括文件名、大小、行数、最小/最大主键值、列级别的统计信息等。

其中,列级别的统计信息(Min/Max 值),是 Paimon 实现高效数据跳过(Data Skipping)和谓词下推(Predicate Pushdown)的关键。

(4) Data Files

数据文件按分区和存储桶分组。每个存储桶目录都包含一个 LSM 树及其变更日志文件changelog。目前,Paimon 支持使用 orc(1.0以前默认)、parquet(1.0及以后默认)和 avro 作为数据文件格式。 Paimon中采用的是LSM树的Sorted Run存储 格式如下: image.png LSM 树将数据文件组织成多个Sorted Run。Sorted Run由一个或多个数据文件组成,并且每个数据文件恰好属于一个Sorted Run。

数据文件中的记录按其主键排序

  1. 同一个Sorted Run不同的数据文件的主键范围永远不会重叠;如上图df#1和df#2在不存在主键相同的数据
  2. 不同的Sorted Run可能具有重叠的主键范围,甚至可能包含相同的主键;如上图df#1和df#3中都有主键为3的数据,一个是apple,一个是banana
  3. L0(Level 0)层只是内存刷盘,不存在合并,L1及更高才是合并后的,因此,L0层的每个文件就是一个Sorted Run,L1及以上层的每个Sorted Run包含一个或多个不同的数据文件
    • 每个 L0 文件单独算作一个 Sorted Run,这是 L0 层的特殊处理方式
    • 每个 L0 文件自身内部有序
    • 不同 L0 文件间主键范围可能重叠(不违反 Sorted Run 定义,因为它们属于不同的 Sorted Run)
  4. L1+ 层:更高层级(L1, L2, ...)的文件是通过后台的合并(Compaction)操作生成的。在 L1 及以上层级,同一层内的所有文件,其主键范围是互不重叠的

问题来了,这样读的话,如果不处理,就读出两条,一个是3-apple;一个是3-banana,这样肯定不行,怎么办呢?

解决:查询LSM树时,必须合并(compaction)所有Sorted Run,并且必须根据用户指定的合并引擎和每条记录的时间戳来合并具有相同主键的所有记录。

写入LSM树的新记录将首先缓存在内存中。当内存缓冲区满时,内存中的所有记录将被排序并刷新到磁盘。(类似HBase,HBase也是基于LSM树的)
如 L0 文件数超过 `num-sorted-run.compaction-trigger`,就会触发合并

读的时候也会先合并,执行compaction(将bucket合并)会生成新的bucket和snapshot,每五个sorted-run就会产生一个小压缩

当越来越多的记录写入LSM树时,Sorted Run的数量将会增加。由于查询LSM树需要将所有Sorted Run合并起来,太多Sorted Run将导致查询性能较差,甚至内存不足。为了限制Sorted Run的数量,我们必须按照一定规则将多个Sorted Run合并为一个大的Sorted Run。这个过程称为Compaction。

过于频繁的Compaction可能会导致写入速度变慢。这是查询和写入性能之间的权衡。 Paimon 目前采用了类似于 Rocksdb 通用压缩的Compaction策略。

默认情况下,当Paimon将记录追加到LSM树时,它也会根据需要执行Compaction。用户还可以选择在“专用Compaction作业”中独立执行所有Compaction。

Full Compaction 是比Compaction更重的合并,是针对整个表的全部数据进行整理,彻底将所有小文件合并,提高整体存储效率。但是相对的Full compaction的付出的执行代价也较大(时间很长,测试最少也要10min),所以执行的频率也要相对控制。

目前已有主要控制参数为`manifest.full-compaction-threshold-size` 这个是manifest的总大小。

更有效的直接控制是通过`full-compaction.delta-commits` 目前没有默认值,生产上可以设置几十到上百。

2.数据变更--Changelog

先说结论

image.png

关键2:实时读Changelog

目前的changelog的生成方式主要看changelog-producer这个选项:

  • none:只保留snapshot级的变更日志,会丢失一部分中间变化。虽然flink本身也会辅助生成changelog但是开销太大不推荐。可以通过scan.remove-normalize = true来关闭flink自身的补偿日志。
  • input:保留上游输入的变更日志,一般上游输入为CDC时使用(了解数仓分层的大概想到了,ods→dwd|dim可以,但是dwd→dws由于存在聚合,不能用dwd去给dws填充changelog)
  • lookup:在上游没有输入CDC日志时,依据paimon对数据的对比生成新的changelog供下游使用,在上游非cdc任务时,算是最准确的changelog(dwd→dws可以用,专门有一个比较器去记录变化然后加入到log中,缺点性能开销大)
  • full-compaction:相比lookup生成changelog间隔较大,可以通过设置full compaction间隔,在full compaction时产生最新的changelog给下游,性能相对lookup较好,但产生延迟受full compaction影响。如果设置了该类型的changelog-producer,则full-compaction.delta-commits在无默认值自动变为1(缺点:数据延迟大)。

3.元数据--Catalog

关键3:catalog无缝兼容hive

Paimon Catalog可以持久化元数据,当前支持两种类型的metastore:

Ø 文件系统(默认):将元数据和表文件存储在文件系统中。

Ø hive:在 hive metastore中存储元数据。用户可以直接从 Hive 访问paimon表。

catalog可以理解为在database上面的一层

先说本地文件系统

-- 显示所有的catalog
show catalogs;
-- 创建catalog
CREATE CATALOG fs_catalog WITH (
    'type' = 'paimon',
    'warehouse' = 'hdfs://hadoop102:8020/paimon/fs'
);
-- 使用catalog
USE CATALOG fs_catalog;

再使用hive的metastore

1)上传 hive-connector
将flink-sql-connector-hive-3.1.3_2.12-1.17.0.jar上传到Flink的lib目录下
2)重启yarn-session集群
3)启动hive的metastore服务
nohup hive --service metastore &
4)创建Hive Catalog
CREATE CATALOG hive_catalog WITH (
    'type' = 'paimon',
    'metastore' = 'hive',
    'uri' = 'thrift://hadoop102:9083',
    'hive-conf-dir' = '/opt/module/hive/conf',
    'warehouse' = 'hdfs://hadoop102:8020/paimon/hive'
);

USE CATALOG hive_catalog;

5)注意事项
使用hive Catalog通过alter table更改不兼容的列类型时,参见 HIVE-17832。需要配置
vim /opt/module/hive/conf/hive-site.xml;

    <property>
        <name>hive.metastore.disallow.incompatible.col.type.changes</name>
        <value>false</value>
    </property>
上述配置需要在hive-site.xml中配置,且hive metastore服务需要重启。
如果使用的是 Hive3,请禁用 Hive ACID:
hive.strict.managed.tables=false
hive.create.as.insert.only=false
metastore.create.as.acid=false

总结,三个关键点:

  • 关键1:离线读Snapshot
  • 关键2:实时读Changelog
  • 关键3:catalog无缝兼容hive