基础设置
-- 创建catalog/加载catalog,如果这个catalog已经存在就不会创建,自动加载元数据信息
CREATE CATALOG fs_paimon_catalog WITH (
'type' = 'paimon',
'warehouse' = 'hdfs://wsl01:8020/paimon/catalog'
);
-- 使用catalog
use catalog fs_paimon_catalog;
-- sqlClinet使用
-- 设置为批处理模式
SET 'execution.runtime-mode' = 'batch';
-- 设置为流处理模式
SET 'execution.runtime-mode' = 'streaming';
-- 设置查询结果显示方式,sql-clinet 特有
SET 'sql-client.execution.result-mode' = 'tableau';
-- 设置checkpoint,如果使用流模式,必须设置
SET 'execution.checkpointing.interval' = '10 s';
root@wsl01:~/soft/paimon/flink-1.17.0# cat fs_catalog.sql
CREATE CATALOG fs_catalog WITH (
'type'='paimon',
'warehouse'='file:/mnt/d/wsl/soft/paimon/catalog'
-- 'warehouse' = 'hdfs://wsl01:8020/paimon/catalog'
);
use catalog fs_catalog ;
SET 'sql-client.execution.result-mode' = 'tableau';
-- 默认批处理
SET 'execution.runtime-mode' = 'batch';
-- 指定默认启动catalog
bin/sql-client.sh -i fs_catalog.sql
DDL
创建普通表
-- 普通表,没有主键
CREATE TABLE t_sample (
user_id BIGINT,
item_id BIGINT,
behavior STRING,
dt STRING,
hh STRING
);
创建主键表
CREATE TABLE t_pk (
user_id BIGINT,
item_id BIGINT,
behavior STRING,
dt STRING,
hh STRING,
PRIMARY KEY (dt, hh, user_id) NOT ENFORCED
);
创建分区表
-- 分区表的分区字段必须是主键的子集
CREATE TABLE t_partition (
user_id BIGINT,
item_id BIGINT,
behavior STRING,
dt STRING,
hh STRING,
PRIMARY KEY (dt, hh, user_id) NOT ENFORCED
) PARTITIONED BY (dt, hh);
创建临时表
-- 如果进入paimon创建的catalog后,无法创建非paimon类型的表,如果需要借助第三方的表,需要创建临时表来使用
CREATE TEMPORARY TABLE t_temporary (
user_id BIGINT,
item_id BIGINT,
behavior STRING,
dt STRING,
hh STRING,
PRIMARY KEY (dt, hh, user_id) NOT ENFORCED
) WITH (
'connector' = 'filesystem',
'path' = 'file:///mnt/d/wsl/soft/paimon/temp_table.csv',
'format' = 'csv'
);
复制表-AS,create table as
-- create as 创建主键表,
CREATE TABLE t_create_as_pk AS SELECT * FROM t_pk;
show create table t__pk;
show create table t_create_as_pk;
-- create as 创建分区表
show create table t_partition ;
CREATE TABLE t_create_as_partition AS SELECT * FROM t_partition;
show create table t_create_as_partition ;
上述执行结果告诉我们,create as 的表,只保留原表的字段,不保留其他属性信息
-- 通过with 重新指定,关于with的用法,参考flink
CREATE TABLE t_create_as_with with ('primary-key' = 'dt,hh','partition' = 'dt') AS SELECT * FROM t_pk ;
show create table t_create_as_with;
上述执行结果告诉我们,create as 的表可以通过with 重新指定属性信息
复制表-LIKE,create table like
CREATE TABLE t_create_like like t_pk;
show create table t_pk;
show create table t_create_like;
上述执行结果告诉我们,create like 的表,保留全部信息
DML
常用管理语句
desc #{name}
show create table #{name}
show catalogs;
show databases;
show tables;
新增-普通表
insert into t_sample(user_id,item_id,behavior,dt,hh) values(100,100,'sing-sample','1','2');
insert into t_sample values(101,101,'jump-sample','1','2');
insert into t_sample select 102,102,'rap-sample','1','2';
Flink SQL> select * from t_sample;
+---------+---------+-------------+----+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+-------------+----+----+
| 100 | 100 | sing-sample | 1 | 2 |
| 101 | 101 | jump-sample | 1 | 2 |
| 102 | 102 | rap-sample | 1 | 2 |
+---------+---------+-------------+----+----+
3 rows in set
新增-主键表
insert into t_pk values(1,1,'sing','1','2');
insert into t_pk values(2,2,'jump','1','2');
insert into t_pk values(3,3,'rap','1','2');
Flink SQL> select * from t_pk;
+---------+---------+----------+----+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+----------+----+----+
| 1 | 1 | sing | 1 | 2 |
| 2 | 2 | jump | 1 | 2 |
| 3 | 3 | rap | 1 | 2 |
+---------+---------+----------+----+----+
3 rows in set
insert into t_pk values(3,3,'basketball','1','2');
Flink SQL> select * from t_pk;
+---------+---------+------------+----+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+------------+----+----+
| 1 | 1 | sing | 1 | 2 |
| 2 | 2 | jump | 1 | 2 |
| 3 | 3 | basketball | 1 | 2 |
+---------+---------+------------+----+----+
3 rows in set
-- 我们发现,主键表,写入两条相同主键的数据,后者会覆盖前者
-- 主键表有一个默认引擎,默认是就是 'merge-engine' = 'deduplicate',因此才有这个效果
新增-分区表
insert into t_partition partition(dt='2024-10-08',hh='15')(user_id,item_id,behavior) values(1,1,'sing');
insert into t_partition partition(dt='2024-10-08',hh='15')(user_id,item_id,behavior) values(2,2,'jump');
insert into t_partition(user_id,item_id,behavior,dt,hh) values(3,3,'rap','2024-10-08','16');
insert into t_partition values(4,4,'basketball','2024-10-08','16');
Flink SQL> select * from t_partition;
+---------+---------+------------+------------+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+------------+------------+----+
| 1 | 1 | sing | 2024-10-08 | 15 |
| 2 | 2 | jump | 2024-10-08 | 15 |
| 3 | 3 | rap | 2024-10-08 | 16 |
| 4 | 4 | basketball | 2024-10-08 | 16 |
+---------+---------+------------+------------+----+
4 rows in set
insert into t_partition as select * from t_sample;
insert into t_partition partition(dt='2099-10-08',hh='15')(user_id,item_id,behavior) select user_id,item_id,behavior from t_sample;
Flink SQL> select * from t_partition;
+---------+---------+-------------+------------+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+-------------+------------+----+
| 1 | 1 | sing | 2024-10-08 | 15 |
| 2 | 2 | jump | 2024-10-08 | 15 |
| 100 | 100 | sing-sample | 2099-10-08 | 15 |
| 101 | 101 | jump-sample | 2099-10-08 | 15 |
| 102 | 102 | rap-sample | 2099-10-08 | 15 |
| 3 | 3 | rap | 2024-10-08 | 16 |
| 4 | 4 | basketball | 2024-10-08 | 16 |
| 100 | 100 | sing-sample | 1 | 2 |
| 101 | 101 | jump-sample | 1 | 2 |
| 102 | 102 | rap-sample | 1 | 2 |
+---------+---------+-------------+------------+----+
10 rows in set
Flink SQL> insert overwrite t_partition(user_id,item_id,behavior) values(5,5,'non-partition');
[ERROR] Could not execute SQL statement. Reason:
org.apache.calcite.sql.validate.SqlValidatorException: Column 'dt' has no default value and does not allow NULLs
以上多种写入方式都支持,把分区字段当成普通字段用就行,但是分区字段不能为空
新增-覆盖写入
Flink SQL> select * from t_pk;
+---------+---------+------------+----+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+------------+----+----+
| 1 | 1 | sing | 1 | 2 |
| 2 | 2 | jump | 1 | 2 |
| 3 | 3 | basketball | 1 | 2 |
+---------+---------+------------+----+----+
insert into t_pk values(10,10,'overwrite','1','2');
Flink SQL> select * from t_pk;
+---------+---------+-----------+----+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+-----------+----+----+
| 10 | 10 | overwrite | 1 | 2 |
+---------+---------+-----------+----+----+
1 row in set
overwrite 会直接清空表,不会考虑主键
insert into t_partition partition(dt='2024-10-08',hh='15')(user_id,item_id,behavior) values(1,1,'sing');
insert into t_partition partition(dt='2024-10-08',hh='15')(user_id,item_id,behavior) values(2,2,'jump');
insert into t_partition partition(dt='2024-10-09',hh='15')(user_id,item_id,behavior) values(3,3,'rap');
Flink SQL> select * from t_partition;
+---------+---------+----------+------------+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+----------+------------+----+
| 3 | 3 | rap | 2024-10-09 | 15 |
| 1 | 1 | sing | 2024-10-08 | 15 |
| 2 | 2 | jump | 2024-10-08 | 15 |
+---------+---------+----------+------------+----+
3 rows in set
insert overwrite t_partition partition(dt='2024-10-08',hh='15')(user_id,item_id,behavior) values(4,4,'basketball');
Flink SQL> select * from t_partition;
+---------+---------+------------+------------+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+------------+------------+----+
| 3 | 3 | rap | 2024-10-09 | 15 |
| 4 | 4 | basketball | 2024-10-08 | 15 |
+---------+---------+------------+------------+----+
2 rows in set
分区表只会overwrite 当前要写入分区
Flink SQL> select * from t_partition;
+---------+---------+----------+------------+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+----------+------------+----+
| 3 | 3 | rap | 2024-10-09 | 15 |
| 1 | 1 | sing | 2024-10-08 | 15 |
| 2 | 2 | jump | 2024-10-08 | 15 |
+---------+---------+----------+------------+----+
3 rows in set
-- 对指定分区写入空记录,没有效果
INSERT OVERWRITE t_partition PARTITION (`dt` = '2024-10-08', `hh` = '15') SELECT user_id,item_id,behavior FROM t_partition WHERE false;
Flink SQL> select * from t_partition;
+---------+---------+----------+------------+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+----------+------------+----+
| 3 | 3 | rap | 2024-10-09 | 15 |
| 1 | 1 | sing | 2024-10-08 | 15 |
| 2 | 2 | jump | 2024-10-08 | 15 |
+---------+---------+----------+------------+----+
3 rows in set
-- 对指定分区写入空记录,指定 /*+ OPTIONS('dynamic-partition-overwrite'='false') */,会清空指定的分区
INSERT OVERWRITE t_partition /*+ OPTIONS('dynamic-partition-overwrite'='false') */ PARTITION (`dt` = '2024-10-08', `hh` = '15') SELECT user_id,item_id,behavior FROM t_partition WHERE false;
Flink SQL> select * from t_partition;
+---------+---------+----------+------------+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+----------+------------+----+
| 3 | 3 | rap | 2024-10-09 | 15 |
+---------+---------+----------+------------+----+
1 row in set
-- 不指定分区写入空记录,指定 /*+ OPTIONS('dynamic-partition-overwrite'='false') */,会清空所有分区,truncate
INSERT OVERWRITE t_partition /*+ OPTIONS('dynamic-partition-overwrite'='false') */ SELECT * FROM t_partition WHERE false;
Flink SQL> select * from t_partition;
Empty set
/*+ OPTIONS('dynamic-partition-overwrite'='false') */ Flink默认的覆盖模式是动态分区覆盖 (即Paimon只删除覆盖数据中出现的分区)。可以配置动态分区覆盖将其更改为静态覆盖。
paimon 没有truncate,因此我们可以借助overwite+静态覆盖,这个实现truncate
查询
修改
Important table properties setting:
- Only primary key table supports this feature. 表必须有主键
- MergeEngine needs to be deduplicate or partial-update to support this feature. 合并引擎必须为deduplicate,以后会支持partial-update
- Do not support updating primary keys. 不能修改主键
- Flink 版本1.17 及以上版本才支持
- 必须是批处理模式
UPDATE table_identifier SET column1 = value1, column2 = value2, ... WHERE condition;
Flink SQL> select * from t_partition;
+---------+---------+-------------+----+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+-------------+----+----+
| 100 | 100 | sing-sample | 1 | 2 |
| 101 | 101 | jump-sample | 1 | 2 |
| 102 | 102 | rap-sample | 1 | 2 |
+---------+---------+-------------+----+----+
3 rows in set
update t_partition set behavior = 'baskteball-sample' where user_id =100;
Flink SQL> select * from t_partition;
+---------+---------+-------------------+----+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+-------------------+----+----+
| 100 | 100 | baskteball-sample | 1 | 2 |
| 101 | 101 | jump-sample | 1 | 2 |
| 102 | 102 | rap-sample | 1 | 2 |
+---------+---------+-------------------+----+----+
3 rows in set
删除
常用属性
CREATE TABLE my_table (
user_id BIGINT,
item_id BIGINT,
behavior STRING,
dt STRING,
hh STRING,
PRIMARY KEY (dt, hh, user_id) NOT ENFORCED
) PARTITIONED BY (dt, hh);
统计数优化
paimon 会默认为每一列添加3个统计属性:最大值、最小值、null值数量
有四种配置来约束统计属性
- full:为所有数据添加最大值、最小值、null值数量统计
- truncate(length):截断length长度后,为所有数据添加最大值、最小值、null值数量统计,这个是默认值:默认length 16,为了避免长文本字段的统计
- counts:只对null值数量统计
- none:不统计
如果需要修改某个字段的统计属性
- fields.{field_name}.stats-mode,with ( ‘fields.behavior.stats-mode’ = ‘full’ )
官网原文:https://paimon.apache.org/docs/master/flink/sql-ddl/#specify-statistics-mode

字段默认值
paimon表可以设置字段默认值,但是 不能 对主键字段设置默认值
如果需要修改某个字段的统计属性
- fields.{field_name}.default-value
- with ( ‘fields.behavior.default-value’ = ‘sing’ )
官网原文:https://paimon.apache.org/docs/master/flink/sql-ddl/#field-default-value

聚类写入
在批作业中配置该参数可以启用聚类写入功能,使数据在特定列上按大小范围聚集分布,从而提升该列的查询速度。只能在批处理或者append table(流处理)中使用。
多个列名请使用英文逗号(,)进行分隔,例如’col1,col2’。
- sink.clustering.by-columns
- with ( ‘sink.clustering.by-columns’ = ‘user_id,item_id’ )
也可以使用Hints
- INSERT INTO my_table /*+ OPTIONS(‘sink.clustering.by-columns’ = ‘a,b’) */ SELECT * FROM source;
动态覆盖
Flink overwrite模式,在分区表中默认是动态分区覆盖,也就是说在使用overwrite时,只覆盖当前写入分区的数据,写入数据为空时,不进行覆盖,我们可以设置为静态覆盖,当写入数据为空时,也会覆盖。如果写入的分区为空则覆盖所有分区!
Hints,跟在表的后边,也就是声明本次sql的执行策略,dynamic-partition-overwrite=false > 静态覆盖,truncate的替代语法(TRUNCATE TABLE my_table 需要flink 1.18 之后才支持)
INSERT OVERWRITE my_table /*+ OPTIONS(‘dynamic-partition-overwrite’=‘false’) */
批处理时间旅行
在批处理模式下,每一个表,在发生数据变化时会保留一份快照信息,我们可以在元数据目录找到
RESET ‘execution.checkpointing.interval’;
SET ‘execution.runtime-mode’ = ‘batch’;
root@wsl01:~/soft/paimon/catalog/default.db/t_pk/snapshot# ll
total 28
drwxrwxrwx 1 root root 4096 Oct 8 17:13 ./
drwxrwxrwx 1 root root 4096 Oct 8 16:58 ../
-rwxrwxrwx 1 root root 1 Oct 8 16:58 EARLIEST*
-rwxrwxrwx 1 root root 1 Oct 8 17:13 LATEST*
-rwxrwxrwx 1 root root 621 Oct 8 16:58 snapshot-1*
-rwxrwxrwx 1 root root 621 Oct 8 16:58 snapshot-2*
-rwxrwxrwx 1 root root 621 Oct 8 16:58 snapshot-3*
-rwxrwxrwx 1 root root 621 Oct 8 16:59 snapshot-4*
-rwxrwxrwx 1 root root 621 Oct 8 17:12 snapshot-5*
-rwxrwxrwx 1 root root 623 Oct 8 17:12 snapshot-6*
-rwxrwxrwx 1 root root 625 Oct 8 17:13 snapshot-7*
我们可以指定快照版本进行查询,比如 我要查询快照1版本的数据和快照2版本的数据有什么区别
Flink SQL> select * from t_pk /*+ OPTIONS('scan.snapshot-id' = '1') */;
+---------+---------+----------+----+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+----------+----+----+
| 1 | 1 | sing | 1 | 2 |
+---------+---------+----------+----+----+
1 row in set
Flink SQL> select * from t_pk /*+ OPTIONS('scan.snapshot-id' = '2') */;
+---------+---------+----------+----+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+----------+----+----+
| 1 | 1 | sing | 1 | 2 |
| 2 | 2 | jump | 1 | 2 |
+---------+---------+----------+----+----+
通过查询我们可以得知,在1版本写入了一条数据1,在2版本写入了一条数据2
每一个表都有一个快照表,我们可以从快照表中获取所有的快照信息和变化情况
Flink SQL> SELECT * FROM t_pk$snapshots;
+-------------+-----------+--------------------------------+---------------------+-------------+-------------------------+--------------------------------+--------------------------------+-------------------------+--------------------+--------------------+------------------------+----------------------+
| snapshot_id | schema_id | commit_user | commit_identifier | commit_kind | commit_time | base_manifest_list | delta_manifest_list | changelog_manifest_list | total_record_count | delta_record_count | changelog_record_count | watermark |
+-------------+-----------+--------------------------------+---------------------+-------------+-------------------------+--------------------------------+--------------------------------+-------------------------+--------------------+--------------------+------------------------+----------------------+
| 1 | 0 | 1012627f-bc72-4c6f-a270-5ab... | 9223372036854775807 | APPEND | 2024-10-08 16:58:41.049 | manifest-list-2b0d3b30-b03c... | manifest-list-2b0d3b30-b03c... | <NULL> | 1 | 1 | 0 | -9223372036854775808 |
| 2 | 0 | 7d768bf3-2e13-4e2d-b091-130... | 9223372036854775807 | APPEND | 2024-10-08 16:58:46.607 | manifest-list-b8a205f2-f890... | manifest-list-b8a205f2-f890... | <NULL> | 2 | 1 | 0 | -9223372036854775808 |
| 3 | 0 | e2193b18-9491-493a-8b40-88c... | 9223372036854775807 | APPEND | 2024-10-08 16:58:56.868 | manifest-list-045c4b86-b7f5... | manifest-list-045c4b86-b7f5... | <NULL> | 3 | 1 | 0 | -9223372036854775808 |
| 4 | 0 | 548ab8ae-92dd-496f-a066-e57... | 9223372036854775807 | APPEND | 2024-10-08 16:59:25.990 | manifest-list-563fe53f-e48b... | manifest-list-563fe53f-e48b... | <NULL> | 4 | 1 | 0 | -9223372036854775808 |
| 5 | 0 | 2ead5fde-9367-4e09-95cd-aed... | 9223372036854775807 | APPEND | 2024-10-08 17:12:47.720 | manifest-list-b87abf82-80ef... | manifest-list-b87abf82-80ef... | <NULL> | 5 | 1 | 0 | -9223372036854775808 |
| 6 | 0 | 2ead5fde-9367-4e09-95cd-aed... | 9223372036854775807 | COMPACT | 2024-10-08 17:12:48.638 | manifest-list-b87abf82-80ef... | manifest-list-b87abf82-80ef... | <NULL> | 4 | -1 | 0 | -9223372036854775808 |
| 7 | 0 | ec784398-da6c-48e9-8525-ea6... | 9223372036854775807 | OVERWRITE | 2024-10-08 17:13:09.135 | manifest-list-f64997fa-77dc... | manifest-list-f64997fa-77dc... | <NULL> | 1 | -3 | 0 | -9223372036854775808 |
+-------------+-----------+--------------------------------+---------------------+-------------+-------------------------+--------------------------------+--------------------------------+-------------------------+--------------------+--------------------+------------------------+----------------------+
7 rows in set
Flink SQL> select * from t_pk /*+ OPTIONS('scan.snapshot-id' = '4') */;
+---------+---------+------------+----+----+
| user_id | item_id | behavior | dt | hh |
+---------+---------+------------+----+----+
| 1 | 1 | sing | 1 | 2 |
| 2 | 2 | jump | 1 | 2 |
| 3 | 3 | basketball | 1 | 2 |
+---------+---------+------------+----+----+
3 rows in set
上述查询结果告诉我们 快照id为4的时候数据最多,有4条,但是查询结果显示只有3条,那是因为批处理模式下,不显示删除的数据,而快照数据都统计了,我们可以查询audit表查询操作明细
Flink SQL> SELECT * FROM t_pk$audit_log /*+ OPTIONS('scan.snapshot-id' = '3') */;
+---------+---------+---------+----------+----+----+
| rowkind | user_id | item_id | behavior | dt | hh |
+---------+---------+---------+----------+----+----+
| +I | 1 | 1 | sing | 1 | 2 |
| +I | 2 | 2 | jump | 1 | 2 |
| +I | 3 | 3 | rap | 1 | 2 |
+---------+---------+---------+----------+----+----+
3 rows in set
Flink SQL> SELECT * FROM t_pk$audit_log /*+ OPTIONS('scan.snapshot-id' = '4') */;
+---------+---------+---------+------------+----+----+
| rowkind | user_id | item_id | behavior | dt | hh |
+---------+---------+---------+------------+----+----+
| +I | 1 | 1 | sing | 1 | 2 |
| +I | 2 | 2 | jump | 1 | 2 |
| +I | 3 | 3 | basketball | 1 | 2 |
+---------+---------+---------+------------+----+----+
3 rows in set
我们可以看到在 快照4的时候执行了一个 +I id=3的操作,把原来的id=3覆盖掉了,被覆盖掉的那条数据依然会被统计
流处理时间旅行
在流处理模式下,默认情况下,Streaming read 在第一次启动时会生成表上的最新快照,并继续读取最新的更改。
SET ‘execution.checkpointing.interval’=‘30s’;
SET ‘execution.runtime-mode’ = ‘streaming’;
读取最新的数据
SELECT * FROM ws_t /*+ OPTIONS('scan.mode' = 'latest') */
从指定快照id开始读取变更数据
SELECT * FROM ws_t /*+ OPTIONS('scan.snapshot-id' = '1') */;
常用的元数据表
表结构审计
Flink SQL> SELECT * FROM t_pk$schemas;
+-----------+--------------------------------+----------------+-----------------------+---------+---------+-------------------------+
| schema_id | fields | partition_keys | primary_keys | options | comment | update_time |
+-----------+--------------------------------+----------------+-----------------------+---------+---------+-------------------------+
| 0 | [{"id":0,"name":"user_id","... | [] | ["dt","hh","user_id"] | {} | | 2024-10-08 16:33:10.895 |
+-----------+--------------------------------+----------------+-----------------------+---------+---------+-------------------------+
1 row in set
配置审计
Flink SQL> SELECT * FROM t_pk$options;
Empty set
操作审计
Flink SQL> SELECT * FROM t_pk$audit_log;
+---------+---------+---------+-----------+----+----+
| rowkind | user_id | item_id | behavior | dt | hh |
+---------+---------+---------+-----------+----+----+
| +I | 10 | 10 | overwrite | 1 | 2 |
+---------+---------+---------+-----------+----+----+
1 row in set
文件元数据
Flink SQL> SELECT * FROM t_pk$files;
+-----------+--------+--------------------------------+-------------+-----------+-------+--------------+--------------------+------------+------------+--------------------------------+--------------------------------+--------------------------------+---------------------+---------------------+-------------------------+
| partition | bucket | file_path | file_format | schema_id | level | record_count | file_size_in_bytes | min_key | max_key | null_value_counts | min_value_stats | max_value_stats | min_sequence_number | max_sequence_number | creation_time |
+-----------+--------+--------------------------------+-------------+-----------+-------+--------------+--------------------+------------+------------+--------------------------------+--------------------------------+--------------------------------+---------------------+---------------------+-------------------------+
| [] | 0 | data-15fc7fe2-b3e7-485e-b60... | orc | 0 | 0 | 1 | 948 | [1, 2, 10] | [1, 2, 10] | {behavior=0, dt=0, hh=0, it... | {behavior=overwrite, dt=1, ... | {behavior=overwrite, dt=1, ... | 0 | 0 | 2024-10-08 17:13:07.749 |
+-----------+--------+--------------------------------+-------------+-----------+-------+--------------+--------------------+------------+------------+--------------------------------+--------------------------------+--------------------------------+---------------------+---------------------+-------------------------+
1 row in set
标记元数据
Flink SQL> SELECT * FROM t_pk$tags;
Empty set
Paimon LSM Trees
Paimon 采用 LSM 树(日志结构合并树)作为文件存储的数据结构。
Sorted Runs
LSM 树将文件组织成多个Sorted Run。Sorted Run由一个或多个数据文件组成,并且每个数据文件恰好属于一个Sorted Run。
数据文件中的记录按其主键排序。在Sorted Run中,数据文件的主键范围永远不会重叠。
正如您所看到的,不同的Sorted Run可能具有重叠的主键范围,甚至可能包含相同的主键。查询LSM树时,必须合并所有Sorted Run,并且必须根据用户指定的合并引擎和每条记录的时间戳来合并具有相同主键的所有记录。
写入LSM树的新记录将首先缓存在内存中。当内存缓冲区满时,内存中的所有记录将被排序并刷新到磁盘。
Compaction
当越来越多的记录写入LSM树时,Sorted Run的数量将会增加。由于查询LSM树需要将所有Sorted Run合并起来,太多Sorted Run将导致查询性能较差,甚至内存不足。
为了限制Sorted Run的数量,我们必须偶尔将多个Sorted Run合并为一个大的Sorted Run。这个过程称为Compaction。
然而,Compaction是一个资源密集型过程,会消耗一定的CPU时间和磁盘IO,因此过于频繁的Compaction可能会导致写入速度变慢。这是查询和写入性能之间的权衡。 Paimon 目前采用了类似于 Rocksdb 通用压缩的Compaction策略。
默认情况下,当Paimon将记录追加到LSM树时,它也会根据需要执行Compaction。用户还可以选择在“专用Compaction作业”中独立执行所有Compaction。
调优
写入调优
流式写入检查点间隔
- ck检查点间隔在允许的情况下调大,ck过于频繁会导致写入性能下降
- 或者使用批处理模式
- 并发checkpoints改成3 (‘execution.checkpointing.max-concurrent-checkpoints’)
写入缓冲区
- 写入缓冲区缓存大小(write-buffer-size)大小调大,默认256M,默认先放到缓冲区,缓冲区满了之后再写sorted-run
- 开始缓冲区溢出策略(write-buffer-spillable)=true,默认不设置
并行度
- 一个桶1g的数据量,写入并行度(sink.parallelism)要小于等于桶的数量,最好等于,默认等于上游节点并行度
合并策略
- paimon是lsm tree架构,每一个sorted-run 就是一个小的分片,当sorted-run 数量过多时,就会触发compaction,compaction 是一个资源密集型的过程为了防止sorted-run数量无限增长,当sorted-run数量达到写入停止阈值(num.sorted-run.stop-trigger)的时候就会暂停写入,默认值为达到compaction阈值+1(num-sorted-run.compaction-trigger+1),因此提高写入停止阈值就能保证停止写入不那么频繁,但是compaction的频率就会降低,会导致查询需要更多的merge on read,会影响查询的效率


文件格式
- avro文件类型(行存储)可以提高写入吞吐量,但会降低压缩性能
- 缺点:查询分析会变慢,如果该表有100列,但只查询了几列,则不能忽略行存储的IO。此外,压缩效率将降低,存储成本将增加。
- 行存储的统计信息的收集有点贵,所以我建议也关闭统计信息。
- 如果您不想将所有文件都修改为Avro格式,至少可以考虑将前几层的文件修改为Avro格式。您可以使用 ‘file.format.per.level’ = ‘0:avro,1:avro’ 将前两层中的文件指定为Avro格式。
file.format = avro
metadata.stats-mode = none
file.format.per.level = 0:avro,1:avro
压缩比
默认情况下,Paimon 使用 1 级的 zstd,你可以修改压缩算法:
‘file.compression.zstd-level’:默认 zstd 级别为 1。对于更高的压缩率,可以配置为 9,但读写速度会明显降低。
file.compression.zstd-level = 1
压缩稳定性
如果存储桶或资源太少,完全压缩可能会导致 checkpoint 超时,这是 Flink 的默认 检查点超时为 10 分钟。
如果希望在这种情况下也能保持稳定,则可以调高检查点超时,例如:
execution.checkpointing.timeout = 60 min
内存
在 Paimon writer 中,有三个主要地方会占用内存:
- Writer 的内存缓冲区,由单个任务的所有 writer 共享和抢占。此内存值可通过 table 属性进行调整。write-buffer-size
- 合并多个排序运行以进行压缩时消耗的内存。可以通过选项进行调整,以更改要合并的已排序运行数。num-sorted-run.compaction-trigger
- 如果行非常大,在进行 Compaction 时,一次读取过多的数据行会消耗大量内存。减少选项可以减轻此案例的影响。read.batch-size
- 写入列式 ORC 文件所消耗的内存。减少该选项可以减少 ORC 格式的内存消耗。orc.write.batch-size
- 如果文件在写入任务中自动压缩,则某些大列的字典在压缩期间可能会显著消耗内存。
- 要禁用 Parquet 格式的所有字段的字典编码,请将 .‘parquet.enable.dictionary’= ‘false’
要禁用 ORC 格式的所有字段的字典编码 orc.dictionary.key.threshold=‘0’,请将 .此外,设置为禁用特定列的字典编码。orc.column.encoding.direct=‘field1,field2’ - 如果您的 Flink 作业不依赖于状态,请避免使用托管内存,您可以使用以下 Flink 参数来控制托管内存:
taskmanager.memory.managed.size=1m
- 或者,您可以将 Flink 托管内存用于写入缓冲区以避免 OOM,请设置 table 属性:
sink.use-managed-memory-allocator=true
-
如果写入表的数据量特别大,Committer 节点可能会占用较大的内存,这可能会导致 OOM 如果内存太小。在这种情况下,您需要增加 Committer 堆内存,但您可能不想这样做 统一增加 Flink 的 TaskManager 的内存,这可能会导致内存浪费。
-
配置 Flink 配置 。(这是 Flink 1.18 之后的默认设置)cluster.fine-grained-resource-management.enabled: true
-
配置 Paimon Table 选项:,例如 300 MB,具体取决于您的 。 (也支持 )sink.committer-memoryTaskManagersink.committer-cpu
最大写入吞吐量场景
- 如果希望某种模式具有最大写入吞吐量,则可以缓慢而不是匆忙地进行Compaction。可以对表使用以下策略,适合写入过程没有过多查询请求的场景,太多Sorted Run将导致查询性能较差,甚至内存不足
num-sorted-run.stop-trigger = 2147483647
sort-spill-threshold = 10
lookup-wait=false
file.format = avro
metadata.stats-mode = none
3071



被折叠的 条评论
为什么被折叠?



