1.写入优化
Paimon的写入性能与检查点密切相关,因此需要更大的写入吞吐量:
- 合理设置桶数:如果使用固定存储桶模式,请重新调整存储桶数量
bucket,生产环境建议(200M~1G),公式:每日增量数据量/每个桶的理想大小=桶数。 - 写入并行度:
sink.parallelism最好<=桶数 - 增加检查点间隔:
execution.checkpointing.interval,根据数据量大小去设置,或者仅使用批处理模式。 - 增加写入缓冲区大小:
write-buffer-size,建议在128~512M间,过大会导致cp超时 - 启用写缓冲区溢出:
write-buffer-spillable为true - 多流写入同一张表:配置表
write-only=true并开启专用压缩任务, - 特殊情况:在write初始化时,bucket的writer需要读取所有历史文件。如果这里出现瓶颈(例如同时写入大量分区),可以使用
write-manifest-cache缓存读取的manifest数据,以加速初始化。 - 缓解写放大严重:
compaction-bytes指标远大于实际写入数据量。可能是compaction-trigger设置过小,导致过于频繁的合并。 - 热点桶、数据倾斜:观察不同 TaskManager 的
write-rows指标,如果存在显著不均,则表明存在数据倾斜。可以考虑使用动态桶,或检查主键和桶键的设计是否合理。
2.读取优化
(1) Full Compaction(相当于大合并)
配置full-compaction.delta-commits在Flink写入中定期执行full-compaction。并且可以确保在写入结束之前分区被完全Compaction。
注意:Paimon 默认处理小文件并提供良好的读取性能。请不要在没有任何要求的情况下配置此Full Compaction选项,因为它会对性能产生重大影响。
(2) 读主键表的优化
对于主键表来说,这是一种“MergeOnRead”技术。读取数据时,会合并多层LSM数据,并行数会受到桶数的限制。虽然Paimon的merge会高效,但是还是赶不上普通的AppendOnly表。
如果你想在某些场景下查询得足够快,但只能找到较旧的数据,你可以:
- 配置
full-compaction.delta-commits:写入数据时(目前只有Flink)会定期进行full Compaction。 - 配置
scan.mode为compacted-full:读取数据时,选择full-compaction的快照和changelog。读取性能良好。
这个读取模式会读取最后一次full compaction的数据
(3) 读仅追加表的优化
小文件会降低读取速度并影响 HDFS 稳定性。默认情况下,当单个存储桶中的小文件超compaction.max.file-num(仅追加表默认50)时,就会触发compaction。
但是当有多个桶时,就会产生很多小文件。
可以使用full-compaction来减少小文件。full-compaction将消除大多数小文件。
(4) 格式
Paimon 对 parquet 读取进行了一些查询优化,因此 parquet 会比 orc 稍快一些。
(5) 采用clustering进行查询提速
效果:查询提速300%+,尤其优化大范围扫描
CREATE TABLE sales WITH (
'sink.clustering.by-columns' = 'product_id', -- 按产品ID聚簇,将相同或相近产品ID的数据在物理存储上尽可能相邻,减少扫表IO了。
'sink.clustering.strategy' = 'zorder' -- Z序空间优化,优化多个不同维度的空间
);
(6) 常见优化
3.存储和压缩优化
(1) 文件存储和压缩
- 设置目标文件大小(解决小文件过多问题):
target-file-size,Paimon在合并的时候会尽量将生成的文件大小接近此值,建议在256M~1G间,必须开compaction;- 增大写入的cp间隔和写入缓冲区的大小
- 合理选择格式:
file.format选择文件格式- 默认是parquet:更高的压缩比和更快的分析查询速度
- orc:以达最大压缩比,但压缩速度会变慢些;
- avro:更高的写入速度。
- 合理选择压缩策略:
file.compression,选择压缩算法,默认是zstd,它提供最快的压缩比和性能平衡;对于avro可以采用lz4,提高写入速度
(2) Snapshot管理
Paimon Writer每次提交都会生成一个或两个快照。每个快照可能会添加一些新的数据文件或将一些旧的数据文件标记为已删除。然而,标记的数据文件并没有真正被删除,因为Paimon还支持时间旅行到更早的快照。文件只有在 Snapshot 过期后才会被真正删除,因此减少文件的第一个方法就是减少 Snapshot 过期的时间。。
目前,Paimon Writer在提交新更改时会自动执行过期操作。通过使旧快照过期,可以删除不再使用的旧数据文件和元数据文件,以释放磁盘空间。
snapshot.time-retained:已完成的快照,最长保留时间,默认是1h,建议1~7d;snapshot.num-retained.min:要保留的已完成快照的最小数量,默认是10,建议200~500;snapshot.num-retained.max:要保留的已完成快照的最大数量,默认是Integer.MAX_VALUE;
注意,保留时间太短或保留数量太少可能会导致如下问题:
- 批量查询找不到该文件。例如,表比较大,批量查询需要10分钟才能读取,但是10分钟前的快照过期了,此时批量查询会读取到已删除的快照。
- 表文件上的流式读取作业(没有外部日志系统)无法重新启动。当作业重新启动时,它记录的快照可能已过期。 (可以使用Consumer Id来保护快照过期的小保留时间内的流式读取)。
(3) Partition管理
DDL的时候可以配置好partition的自动管理,分区会自动过期以减少存储大小
partition.expiration-time:分区的过期时间间隔。如果分区的生命周期超过此值,则该分区将过期。分区时间是从分区值中提取的,根据生产环境决定7~60dpartition.expiration-check-interva:分区过期的检查间隔,默认是1h,建议设为1d,表示1天一检查分区是否过期partition.expiration-strategy:该策略决定如何提取分区时间并将其与当前时间进行比较,默认是values-time,表示数据的值,建议修改为update-time选更新时间为识别生命周期的值
(4) Bucket管理
1)说明
由于总桶数对性能影响很大,Paimon 允许用户通过 ALTER TABLE 命令调整桶数,并通过 INSERT OVERWRITE 重新组织数据布局,而无需重新创建表/分区。当执行覆盖作业时,框架会自动扫描旧桶号的数据,并根据当前桶号对记录进行哈希处理。
-- rescale number of total buckets
ALTER TABLE table_identifier SET ('bucket' = '…')
-- reorganize data layout of table/partition
INSERT OVERWRITE table*identifier [PARTITION (part*spec)]
SELECT … FROM table*identifier [WHERE part*spec]
注意:
- ALTER TABLE 仅修改表的元数据,不会重新组织或重新格式化现有数据。重新组织现有数据必须通过INSERT OVERWRITE来实现。
- 重新缩放桶数不会影响读取和正在运行的写入作业。
- 一旦存储桶编号更改,任何新安排的 INSERT INTO 作业写入未重新组织的现有表/分区将抛出 TableException ,并显示如下类似异常:
对于分区表,不同的分区可以有不同的桶号。例如:
ALTER TABLE my_table SET ('bucket' = '4');
INSERT OVERWRITE my_table PARTITION (dt = '2022-01-01') --这样2022-01-01就是4个桶
SELECT * FROM ...;
ALTER TABLE my_table SET ('bucket' = '8');
INSERT OVERWRITE my_table PARTITION (dt = '2022-01-02') --2022-01-02分区是8个桶
SELECT * FROM ...;
在覆盖期间,确保没有其他作业写入同一表/分区。
注意:对于启用日志系统的表(例如Kafka),请重新调整主题的分区以保持一致性。
重新缩放存储桶有助于处理吞吐量的突然峰值。假设有一个每日流式ETL任务来同步交易数据。该表的DDL和管道如下所示。
2)官方示例:
如下是正在跑的一个作业:
-- 建表
CREATE TABLE verified_orders (
trade_order_id BIGINT,
item_id BIGINT,
item_price DOUBLE,
dt STRING,
PRIMARY KEY (dt, trade_order_id, item_id) NOT ENFORCED
) PARTITIONED BY (dt)
WITH (
'bucket' = '16'
);
-- kafka表
CREATE temporary TABLE raw_orders(
trade_order_id BIGINT,
item_id BIGINT,
item_price BIGINT,
gmt_create STRING,
order_status STRING
) WITH (
'connector' = 'kafka',
'topic' = '...',
'properties.bootstrap.servers' = '...',
'format' = 'csv'
...
);
-- 流式插入16个分桶
INSERT INTO verified_orders
SELECT trade_order_id,
item_id,
item_price,
DATE_FORMAT(gmt_create, 'yyyy-MM-dd') AS dt
FROM raw_orders
WHERE order_status = 'verified';
过去几周运行良好。然而,最近数据量增长很快,作业的延迟不断增加。为了提高数据新鲜度,用户可以执行如下操作缩放分桶:
(1)使用保存点暂停流作业
$ ./bin/flink stop \
--savepointPath /tmp/flink-savepoints \
$JOB_ID
(2)增加桶数
ALTER TABLE verified_orders SET ('bucket' = '32');
(3)切换到批处理模式并覆盖流作业正在写入的当前分区
SET 'execution.runtime-mode' = 'batch';
-- 假设今天是2022-06-22
-- 情况1:没有更新历史分区的延迟事件,因此覆盖今天的分区就足够了
INSERT OVERWRITE verified_orders PARTITION (dt = '2022-06-22')
SELECT trade_order_id,
item_id,
item_price
FROM verified_orders
WHERE dt = '2022-06-22';
- 情况2:有更新历史分区的延迟事件,但范围不超过3天
INSERT OVERWRITE verified_orders
SELECT trade_order_id,
item_id,
item_price,
dt
FROM verified_orders
WHERE dt IN ('2022-06-20', '2022-06-21', '2022-06-22');
(4)覆盖作业完成后,切换回流模式,从保存点恢复(可以增加并行度=新bucket数量)。
SET 'execution.runtime-mode' = 'streaming';
SET 'execution.savepoint.path' = <savepointPath>;
INSERT INTO verified_orders
SELECT trade_order_id,
item_id,
item_price,
DATE_FORMAT(gmt_create, 'yyyy-MM-dd') AS dt
FROM raw_orders
WHERE order_status = 'verified';
(5) compaction
num-sorted-run.compaction-trigger:触发合并的文件数量阈值。默认值为5。- 减小此值会使合并更频繁,有利于控制文件数量和读放大,但会增加写放大和 CPU/IO 消耗,写入性能会下架,读取性能会提升。
- 增大此值则反之。对于写入密集型场景,可以适当增大此值(如
6-8),写入性能会提升,读取性能会下降
num-sorted-run.stop-trigger:暂停写入的文件数量阈值(默认是 'num-sorted-run.compaction-trigger' + 3)。这是一个保护机制,防止文件数量失控。如果写入速度远超合并速度,达到此阈值后写入将会被阻塞。- 启用完全异步压缩:以达到最大吞吐量,则需要配置以下参数
num-sorted-run.stop-trigger = 2147483647
sort-spill-threshold = 10 -- 控制Reader数量(其实就是Sorted Run的数量),防止OOM
lookup-wait = false -- 每次commiter,不需要等待查询的compaction完成
当 num-sorted-run.stop-trigger 变大时,写入停顿将变得不那么频繁,从而提高写入性能。但是,如果该值变得太大,则查询表时将需要更多内存和CPU。如果担心内存 OOM,配置sort-spill-threshold,以控制读取器reader的数量,减少OOM产生
- 启用专用压缩任务(
dedicated-compaction):前提:DDL配置'write-only'='true',然后利用Call开启专用压缩任务,详情看:Dedicated Compaction | Apache Paimon
5.changelog优化
原理 : 当全列数据没有发生变更时(只是ts|seq变更了),可以避免产生变更日志的功能,这样能避免产生无效的增量日志,进而优化下游的计算效率。
比如 : F1-4 字段是我们真实需要检测变更的字段,它变更并不频繁,真正变更频繁的可能是 TS 字段或者 Seq 字段,只要对这些字段配置忽略检测,在真正生成变更日志时,就不会因这些字段的变化而生成新的变更日志了,这对下游无效计算的优化效果是非常明显的。
使用 : 建表时,指定changelog-producer.row-deduplicate = true,可以不对相同的一条数据,产生-U和+U的changelog;且配置changelog-producer.row-deduplicate-ignore-fields,在比较是否是相同的一条数据时,可以忽略这些字段,不进行比较,比较其他的字段。
(注意:请增加 Flink 配置'execution.checkpointing.max-concurrent-checkpoint,这对性能非常重要)。
若lookup开销较大,可采用full-compaction,从而减少开销 主键表是5个文件,但是Append-Only表(桶)可能单个桶里有50个小文件,这是很难接受的。更糟糕的是,不再活动的分区还保留了如此多的小文件。
建议配置Full-Compaction,在Flink写入时配置‘full-compaction.delta-commits’定期进行full-compaction。并且可以确保在写入结束之前分区被full-compaction。
6.Lookup Join的缺点及优化思路
Paimon 支持作为 Lookup 的 source,支持将数据缓存在本地磁盘、按需从表存储中加载数据,还支持将数据缓存在 RocksDB中。然而,它也有一些不足。
(1) Look up的缓存策略
在不同的 Look Join 场景中有部分缓存和全部缓存两种策略,可以提供开发者更好的性能调优选项。
火山引擎为paimon提供了异步同步cache的方法通过在sql中配置 /*+ OPTIONS('lookup.async'='true') */
<1> 部分缓存
部分缓存模式是在 Join 的过程中按需缓存命中的数据文件,如图所示:
通过建表语句中 WITH 参数 'lookup.cache'='auto' 来开启,必须满足以下两种情况:
-
关联表为固定分桶模式的主键表
-
关联表表的主键和 Join Key 一致
此时会自动选择部分缓存(Partial Cache)模式。而不满足这两个条件时会选择全部缓存(Full Cache)模式。
部分缓存能够利用 LSM-Tree 的主键有序性,实现维表缓存数据按需加载,避免全量数据加载,任务初始化更快。
注意:仅支持主键表的主键关联场景,如果关联 Key 不是主键,则无法使用。
<2> 全部缓存
全部缓存会批量将 Paimon 表数据全部 Load 到 RocksDB 中,这样能够在关联 Key 非主键的情况下,能够 Lookup 成功:
通过参数 'lookup.cache'='full' 来开启
全部缓存模式,支持主键表的主键关联和非主键关联两种模式,也支持非主键表的关联。但是劣势是初始化加载时间较长,冷启动现象明显。
(2) Look up 的Bucket Shuffle Join
火山引擎的 Paimon 版本为社区提供了 Bucket Shuffle 功能,极大地提升了 Lookup Join 大规模维表时候的性能。Bucket Shuffle 的原理如下:
没有开启 Bucket Shuffle 功能
开启 Bucket Shuffle 功能
可以看出,在开启 Bucket Shuffle 的 Lookup Join 过程中,主数据会根据 Join Key 进行 Hash 分组处理,这样在每个分组中只要缓存对应 Bucket 数据,大大减少了内存用量,减少了缓存淘汰的概率。可以支持更大规模的维表。开启方法如下,在 hint 中设置 'shuffle' = 'true':
-- 使用 hint 选择最新分区
SELECT /*+ LOOKUP('table'='orders', 'shuffle'='true') */
gen.product_id, gen.product_name, gen.product_category_id,
gen.product_order_id, gen.product_status, gen.create_date,
orders.order_name, orders.order_customer_id, order_status
FROM
datagen_source AS gen
JOIN `paimon_dim`.`dim_test`.`dim_orders`
FOR SYSTEM_TIME AS OF gen.create_date AS orders
ON gen.product_order_id = orders.order_id;
适用场景:
-
固定分桶表:主键表和 Append 表(非主键表)都支持
-
Join Key 要包含所有的 Bucket Key
(3) 本地磁盘性能瓶颈
当数据存储在本地磁盘时,每次 Lookup 操作都会触发磁盘的随机读。这对于使用 HDD 磁盘的计算节点来说,Lookup Join 的性能容易受到限制,因为本地磁盘性能可能成为瓶颈。
**解决方案:用更好的远端磁盘去代替本地磁盘,**把这部分磁盘数据存储到专用的远程的 SSD 上来进行优化,SSD 的性能会比 HDD 要好太多,并且把磁盘的负载转移到远程的 SSD 上之后,本地的任务也不容易受节点上其他任务的影响。
(4) 加载全量数据
当数据更新时,尤其是在进行一次全面的 Compaction 之后,节点在启动时需要加载全部数据,导致启动时间过长,这显然不够高效。
解决方案:
<1> 合理设计Bucket
**合理设计bucket:**通过对数据流采用与 Lookup 表相同的 Bucket 策略,可以有效避免在每个并发操作中加载全表数据的问题。优化后的系统能够自动进行分区,这样每个节点只需加载特定 Bucket 的数据,大幅减少了单个 Lookup 节点的数据加载量。
<2> 用Support Lookup Custom Shuffle
针对无法同步bucket策略的情况,这个接口的本质是允许connector 为维表实现指定事实表的数据 shuffle 方式。有了这个接口后 Paimon 表的维表就能够执行一些特定的操作。
- Fixed Bucket :在作业定义时,而非在 Paimon 表定义时,就已经确定了 Bucket 的数量。本质上这个计算过程是对 Bucket Key 取哈希值,然后再对 Bucket 的总数取模,从而确定数据具体属于哪个 Bucket 。实际上只需要让事实表也按照同样的方式进行 shuffle;例如:在事实表中可以将 K 1 和 K 2 分配到 Lookup 算子上。这个 Lookup 算子知道,它只需要读取 Bucket 1 的数据,并且只需将 Bucket 1 的数据存储在本地即可
- Dynamic Bucket:Paimon 表对于数据的 Bucket 分配是动态的。也就是说随着 Key 的增多,它可能会增加一些 Bucket 。此时对于一条数据来说,无法像之前那样通过一条简单的公式来计算出它属于哪个 Bucket 。可以通过 Custom Shuffle 接口来指定其 Sort of 的方式。这里的分配方式是指根据 Join Key 取一个哈希值,然后在取模时根据下游 Subtask 的数量,即 Lookup Join 的 Subtask 数量来进行。这时每个 Lookup 算子,或者说每个 Lookup 的并发实例,在读取维表时就会知道它可能会接收到哪些与事实表相关的数据。因此它就可以对其存储的缓存进行一些裁剪。比如说,虽然事实表仍然是按照如 K 1 、 K 3 这样的 Key 发送给上游的并发实例,但这些并发实例在读取数据时还是需要全量读取。但是当数据存储到本地时可以进行过滤,只存储与 K 1 和 K 3 相关的数据。因为他知道事实表的分配算法策略是他指定的,所以他可以只存储与 K 1 、 K 3 相关的维表数据。尽管在读取时仍然需要访问全量的数据,但实际上他只需要在本地保存一部分维表数据。
(5) Hot Key问题
当Lookup Join存储了Hot Key的数据,往往会导致性能瓶颈
解决方案:用Skew Join
SELECT /*+ SKEW_JOIN('表别名', '倾斜列', '热点Key列表', 'bucket-num=N') */
...
FROM ...
JOIN ...;
当在 SQL 中使用 /*+ SKEW_JOIN('c', 'id') */ 时(不指定热点 Key 列表和 bucket-num),
Flink 会自适应识别热点,自动打散:
实时监控数据:在作业运行时动态统计 id 列的 Key 分布频率。
自动识别热点:当某个 Key 的处理量超过阈值(如 skew-join.threshold,默认 10000 行)时,标记为热点。
触发打散策略:对热点 Key 自动执行复制分发,无需手动指定具体 Key。
这个优化策略的本质是,如果作业对 Per Key 的顺序没有特定要求,那么就可以启用它。在这种情况下 Paimon 通过 Lookup 自定义分配,会将同一个 Key 的数据随机分发到 N 个并发实例上,其中 N 是用户可以自行指定的。比如说在这个例子中,设定 N 等于 2 ,事实表中的 K 1 可能会被分发到第一个和第二个并发实例上,从而尽可能地将一个 Hot Key 打散。由于需要进行一个类似于复制的操作,因此第一个和第二个 Lookup 并发算子都需要额外读取一个 Bucket 。这实际上是一个 Trade-off 。如果将 N 设置得很大,那么数据被打散得会更加平均,但每个算子需要读取的数据量也会相应增加。
(6) 支持BloomFilter索引
Lookup 上的另一个优化是支持本地 Lookup file 的 BloomFilter 索引。在 RockDB 查找的过程中,通过二分查找定位到文件后,利用 BloomFilter 来判断对应的key是否在文件中。如果判断存在,才会真正读取。而在 Paimon 中,由于也是多层的 LSM Tree,也可以利用这样的优化加速查找。主要场景有两个:一是 Lookup Join, 另一个就是 Lookup Change Producer。
为什么不直接利用远程文件的 BloomFilter 索引的主要原因有两个:
第一,远程文件的 BloomFilter 是一个对应文件格式,内部 SDK 写入,一般是由 BloomFilter 下推的方式判断,并无直接的判断方式,相对不可控 。
第二,远程文件判断需要不断读取文件的 File footer 的。频繁的 Seek 操作对 HDD 磁盘不友好。在最新的 Paimon 0.8 版本中,也开始在 Append 表上支持 File Index,也支持 File BloomFilter 索引,后续可以基于这些文件索引进一步加速文件查找的过程,以后把文件下载到本地这个过程可能都不需要了。因此在读取远程文件、本地 Build 时构建 BloomFilter 索引,后续二分查找命中此文件时,先进行 BloomFilter 索引判断,命中后再进行查找,从而节约了本地查找文件的过程。
(7) 动态分区的优化,提升lookupjoin的性能
问题背景:传统数仓中,分区表(如按天分区)的 Lookup Join 通常只需关联最新分区数据。Paimon 通过 max_pt() 特性自动识别最新分区,避免全表扫描。
1. 动态分区表定义
CREATE TABLE customers (
id INT,
name STRING,
country STRING,
zip STRING,
dt STRING, -- 分区字段(如按天分区)
PRIMARY KEY (id, dt) NOT ENFORCED
) PARTITIONED BY (dt);
2. Lookup Join 动态分区查询
SELECT o.order_id, o.total, c.country, c.zip
FROM orders AS o
JOIN customers /*+
OPTIONS(
'lookup.dynamic-partition'='max_pt()', -- 自动选择最新分区
'lookup.dynamic-partition.refresh-interval'='1 h' -- 每1小时刷新最新分区
) */
FOR SYSTEM_TIME AS OF o.proc_time AS c
ON o.customer_id = c.id;
- 工作原理:
- 自动刷新:每隔
refresh-interval时间,查询服务会检测最新分区(如dt='2023-10-01')。 - 分区剪枝:Lookup Join 仅查询最新分区的数据,减少扫描数据量。
- 自动刷新:每隔
- 适用场景: 维表按时间分区,且仅需关联最新分区数据(如 T+1 更新的用户画像表)。
(8) 高并发LookupJoin,频繁查paimon表性能瓶颈,热点数据缓存困难
问题背景:高并发 Lookup Join 场景下,频繁查询 Paimon 表可能导致性能瓶颈。通过启动 常驻 Flink 流作业 作为查询服务,缓存热数据并加速查询。
1. 启动查询服务
-- 启动并行度为4的查询服务
CALL sys.query*service('my*db.customers', 4);
2. Lookup Join 自动优化
当 Query Service 运行时:
-
优先查询服务:Flink Lookup Join 会优先从 Query Service 的内存缓存中获取数据。
-
缓存热数据:Query Service 会预加载 Paimon 表数据到内存,并定期更新(类似物化视图)。
3. 性能对比
| 场景 | 直接查询 Paimon 表 | 使用 Query Service |
|---|---|---|
| 查询延迟 | 10~100 ms | 1~5 ms |
| 吞吐量 | 1k~10k QPS | 10k~100k QPS |
| 适用场景 | 低并发、冷数据 | 高并发、热数据 |
(9) 各种LookupJoin策略的应用场景
1. 高吞吐低延迟场景
- 异步查询 + Query Service
CALL sys.query_service('my_db.customers', 8);
SELECT /*+
LOOKUP('table'='c', 'output-mode'='allow_unordered') */
o.*, c.*
FROM orders o
JOIN customers /*+ OPTIONS('lookup.async'='true') */
FOR SYSTEM_TIME AS OF o.proc_time AS c
ON o.customer_id = c.id;
2. 维表延迟更新场景
- 异步重试 + Audit Log
CREATE VIEW customers_append AS
SELECT * FROM customers$audit_log WHERE rowkind = '+I';
SELECT /*+
LOOKUP('retry-strategy'='fixed_delay', 'max-attempts'='300') */
o.*, c.*
FROM orders o
JOIN customers_append /*+ OPTIONS('lookup.async'='true') */
FOR SYSTEM_TIME AS OF o.proc_time AS c
ON o.customer_id = c.id;
3. 动态分区场景
- 动态分区 + 定期刷新
SELECT o.order_id, c.country
FROM orders o
JOIN customers /*+
OPTIONS(
'lookup.dynamic-partition'='max_pt()',
'lookup.dynamic-partition.refresh-interval'='5 min'
) */
FOR SYSTEM_TIME AS OF o.proc_time AS c
ON o.customer_id = c.id;
7.starrocks+paimon的优点
(1) 冷热分离
热数据存在OLAP(如starroccks)的物化视图中,冷数据存在远端文件系统(如hdfs、oss),如图 Paimon + StarRocks 冷热分离的例子,如果构建了这样一个冷热分离的 MV 表,当查询到这张表的时候,会自动选择在 StarRocks 上分布的这个热数据和在 Paimon 分布的冷数据。然后对查询结果合并,并返回给用户。
(2) 存算分离
- 可以采用paimon存数据,然后starrocks建立外部表去访问paimon中的数据,进行计算
- 可以指定paimon的catalog为starrcoks,在对接 Paimon 外表时,只需要在 StarRocks 上执行下面这条 Create External Catalog 语句,对 Type 指定为 Paimon,填写上对应的路径之后就可以直接查询 Paimon 中的数据了。---可以实现联邦查询
(3) 读取PK表性能提升--Deletion Vector
采用Deletion Vector,减少读取时候merge的文件,Deletion Vector则是用 Bitmap 来标记,我数据文件中的哪些列是被 Upsert 掉了需要删除的,也就不需要合并,只要跳过就可以了。
(4) 物化视图改写执行计划(透明加速)
我创建了一个 t1 t2 join 的物化视图,之后我执行了一个t1 t2 t3三个表 join 的查询,查的是原表。这种情况下,StarRocks 发现,这个查询中有一个 join 命中了物化视图,然后这一部分的查询就会自动被改写到物化视图上,这个过程用户是不感知的,也就是所谓透明加速。(简单来说:t1和t2的join可以从物化视图拿取,然后在这些拿取的数据上,再去paimon中查询t3的数据进行join)
(5) 统一catalog
有人可能会觉得,我所有湖格式的外表都在一个统一的 Metastore 里,我要给每种湖格式都建一个 catalog,我觉得这还是太麻烦了,怎么办?StarRocks提供了 Unified Catalog 这个功能,专门就针对这种情况,这是一个特殊的外表 catalog,里面什么表都有,只要元数据一样,就都能查。Paimon也是支持了 unified catalog