列存数据维护
列存数据加载
列存数据加载是指首次根据行存数据生成该表对应的列存数据。
在启用异步加载模式后,通过DDL命令为表创建IMCV时,系统仅会建立列存的元数据结构,并不会立刻生成实际的列存数据。
系统提供两种列存数据的加载方式:
- 后台HTAP Worker自动加载
系统会在后台调度HTAP Worker,对目标表自动完成列存数据的构建。
该方式无需人工干预,但其执行时间可能受到后台HTAP Worker的线程数量(由GUC参数htap_flush_workers决定)、调度情况的影响。
- 手动触发加载
用户可直接调用gs_imcv_flush()接口,立即开始对指定表进行列存数据加载。
当后台加载未及时完成时,用户可主动执行该接口加速加载进程。加载效率与参数设置的并行度有关。
- HTAP Worker数量由GUC参数htap_flush_workers决定,设置方法请参见《参考》中“数据库运行参数说明 > GUC参数说明 > HTAP混合负载”章节。
- gs_imcv_flush是用于列存数据加载/重建的系统函数,使用方法请参见《参考》中“SQL参考 > 函数和操作符 > 系统管理函数 > HTAP系统函数”章节。
自动加载示例:
-- 创建普通表并插入数据。
gaussdb=# DROP TABLE IF EXISTS htap_gs_imcv_flush_nopart;
gaussdb=# CREATE TABLE htap_gs_imcv_flush_nopart(
c1 bigint,
c2 varchar(256)
) WITH (STORAGE_TYPE=USTORE);
CREATE TABLE
htapdb=# INSERT INTO htap_gs_imcv_flush_nopart (c1, c2)
SELECT
g AS c1,
'data_' || g AS c2
FROM generate_series(1, 256000) AS g;
INSERT 0 256000
-- 创建二级分区表并插入数据。
gaussdb=# DROP TABLE IF EXISTS htap_gs_imcv_flush_subpart;
gaussdb=# CREATE TABLE htap_gs_imcv_flush_subpart (
c1 bigint,
c2 bigint,
c3 varchar(256)
)
PARTITION BY RANGE (c1)
SUBPARTITION BY LIST (c2)
(
PARTITION htap_gs_imcv_flush_subpart_p1 VALUES LESS THAN (16001)
(
SUBPARTITION htap_gs_imcv_flush_subpart_p1_sp1 VALUES (1),
SUBPARTITION htap_gs_imcv_flush_subpart_p1_sp2 VALUES (2)
),
PARTITION htap_gs_imcv_flush_subpart_p2 VALUES LESS THAN (32001)
(
SUBPARTITION htap_gs_imcv_flush_subpart_p2_sp3 VALUES (3),
SUBPARTITION htap_gs_imcv_flush_subpart_p2_sp4 VALUES (4)
)
);
CREATE TABLE
-- 子分区 p1_sp1: c1 < 16001, c2 = 1。
gaussdb=# INSERT INTO htap_gs_imcv_flush_subpart
SELECT
10000 AS c1,
1 AS c2,
'data_for_p1_sp1_' || generate_series::text AS c3
FROM generate_series(1, 240000);
INSERT 0 240000
-- 子分区 p1_sp2: c1 < 16001, c2 = 2。
gaussdb=# INSERT INTO htap_gs_imcv_flush_subpart
SELECT
10000 AS c1,
2 AS c2,
'data_for_p1_sp2_' || generate_series::text AS c3
FROM generate_series(1, 240000);
INSERT 0 240000
-- 子分区 p2_sp3: 16001 <= c1 < 32001, c2 = 3。
gaussdb=# INSERT INTO htap_gs_imcv_flush_subpart
SELECT
20000 AS c1,
3 AS c2,
'data_for_p2_sp3_' || generate_series::text AS c3
FROM generate_series(1, 240000);
INSERT 0 240000
-- 子分区 p2_sp4: 16001 <= c1 < 32001, c2 = 4。
gaussdb=# INSERT INTO htap_gs_imcv_flush_subpart
SELECT
20000 AS c1,
4 AS c2,
'data_for_p2_sp4_' || generate_series::text AS c3
FROM generate_series(1, 240000);
INSERT 0 240000
-- 为非分区表和分区表创建IMCV。
gaussdb=# ALTER TABLE htap_gs_imcv_flush_nopart COLVIEW;
ALTER TABLE
gaussdb=# ALTER TABLE htap_gs_imcv_flush_subpart COLVIEW;
ALTER TABLE
-- 查看IMCV信息(其中,rowgroup_num为表的实际行组数量,populated_rowgroup_num为表已完成加载的行组数量)。
gaussdb=# SELECT rel_name, partition_name, subpartition_name, rowgroup_num, populated_rowgroup_num FROM gs_htap_imcv_info();
rel_name | partition_name | subpartition_name | rowgroup_num | populated_rowgroup_num
----------------------------+-------------------------------+-----------------------------------+--------------+------------------------
htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p1 | htap_gs_imcv_flush_subpart_p1_sp1 | 2 | 0
htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p1 | htap_gs_imcv_flush_subpart_p1_sp2 | 2 | 0
htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p1 | | 0 | 0
htap_gs_imcv_flush_subpart | | | 0 | 0
htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p2 | htap_gs_imcv_flush_subpart_p2_sp3 | 2 | 0
htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p2 | htap_gs_imcv_flush_subpart_p2_sp4 | 2 | 0
htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p2 | | 0 | 0
htap_gs_imcv_flush_nopart | | | 2 | 0
-- 等待一段时间后查看IMCV信息(等待后台加载完成)。
gaussdb=# SELECT rel_name, partition_name, subpartition_name, rowgroup_num, populated_rowgroup_num FROM gs_htap_imcv_info();
rel_name | partition_name | subpartition_name | rowgroup_num | populated_rowgroup_num
----------------------------+-------------------------------+-----------------------------------+--------------+------------------------
htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p1 | htap_gs_imcv_flush_subpart_p1_sp1 | 2 | 2
htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p1 | htap_gs_imcv_flush_subpart_p1_sp2 | 2 | 2
htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p1 | | 0 | 0
htap_gs_imcv_flush_subpart | | | 0 | 0
htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p2 | htap_gs_imcv_flush_subpart_p2_sp3 | 2 | 2
htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p2 | htap_gs_imcv_flush_subpart_p2_sp4 | 2 | 2
htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p2 | | 0 | 0
htap_gs_imcv_flush_nopart | | | 2 | 2 手动加载示例:
-- 重新创建非分区表htap_gs_imcv_flush_nopart与二级分区表htap_gs_imcv_flush_subpart并插入数据,创建方式与上文示例相同。
-- 1.指定关系加载列存数据。
-- (1)指定非分区表加载列存数据。
gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_nopart');
total | success | error | skip
-------+---------+-------+------
2 | 2 | 0 | 0
(1 row)
-- (2)指定分区表加载列存数据。
-- (2.1)指定二级分区表加载列存数据。
gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_subpart', 'htap_gs_imcv_flush_subpart_p1_sp1');
total | success| error | skip
-------+--------+-------+------
2 | 2 | 0 | 0
(1 row)
-- (2.2)指定一级分区表加载列存数据(sp2加载成功,sp1已加载过,本次加载跳过)。
gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_subpart', 'htap_gs_imcv_flush_subpart_p1');
total | success| error | skip
-------+--------+-------+------
4 | 2 | 0 | 2
(1 row)
-- (2.3)指定基表加载列存数据(p2加载成功,p1已加载过,本次加载跳过)。
gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_subpart', '');
total| success| error | skip
------+--------+-------+------
8 | 4 | 0 | 4
(1 row)
-- 2.指定加载列存数据的并行度。
gaussdb=# ALTER TABLE htap_gs_imcv_flush_subpart NOCOLVIEW;
ALTER TABLE
gaussdb=# ALTER TABLE htap_gs_imcv_flush_subpart COLVIEW;
ALTER TABLE
gaussdb=# \timing on
Timing is on.
gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_subpart', '', 1);
total| success| error | skip
------+--------+-------+------
8 | 8 | 0 | 0
(1 row)
Time: 406.391 ms
gaussdb=# \time off
Timing is off.
gaussdb=# ALTER TABLE htap_gs_imcv_flush_subpart NOCOLVIEW;
ALTER TABLE
gaussdb=# ALTER TABLE htap_gs_imcv_flush_subpart COLVIEW;
ALTER TABLE
gaussdb=# \timing on
Timing is on.
gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_subpart', '', 2);
total| success| error | skip
------+--------+-------+------
8 | 8 | 0 | 0
(1 row)
Time: 192.194 ms
gaussdb=# \time off
Timing is off. 列存数据重建
在完成列存加载之后,后续所有的DML(INSERT/UPDATE/DELETE)都会产生新的增量数据。这些增量由实时事务单元TMU维护,但不会即时同步到已经加载的列存数据中。
当这些增量积累过多时,列存数据会逐渐落后于实际行存内容,从而影响列存扫描性能。此时需要进行列存数据重建,把累积的增量同步到列存中,保持列存数据的新鲜度和查询性能。
系统同样提供两种重建方式:
- 后台HTAP Worker自动重建
当增量数据达到一定规模,或者距离上一次重建一定时间后,系统在后台自动触发列存重建任务。
重建耗时同样会受到后台线程数量和调度的影响。
后台自动重建的触发条件:
- 增量数据所涉及的页面数达到一定比例,由GUC参数htap_flush_pagenum控制。
- 距离上次重建达到一定的时间间隔,由GUC参数htap_flush_interval控制。
设置方法请参见《参考》中“数据库运行参数说明 > GUC参数说明 > HTAP混合负载”章节。
- 手动触发重建
如果后台重建未及时执行,或者用户希望立即更新列存内容,可直接调用gs_imcv_flush()接口手动触发重建。
重建效率与参数设置的并行度有关。
自动重建示例:
-- 查看同步时间间隔和脏页阈值。
gaussdb=# SHOW htap_flush_interval;
htap_flush_interval
---------------------
5min
(1 row)
gaussdb=# SHOW htap_flush_pagenum;
htap_flush_pagenum
--------------------
512
(1 row)
-- 更新子分区p1_sp1中的前10行(c2 = 1, c1 = 10000)。
gaussdb=# UPDATE htap_gs_imcv_flush_subpart
SET c3 = c3 || '_updated'
WHERE c1 = 10000
AND c2 = 1
LIMIT 10;
UPDATE 10
-- 查看脏页数(未达重建条件的脏页阈值)。
gaussdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_htap_tmu_chunk_meta('htap_gs_imcv_flush_subpart','',0) GROUP BY rel_name;
rel_name | dirty_page_count
----------------------------+------------------
htap_gs_imcv_flush_subpart | 1
(1 row)
-- 等待5min后查看脏页数,此时后台重建完成(达到设置的重建间隔后触发后台重建完成)。
gaussdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_htap_tmu_chunk_meta('htap_gs_imcv_flush_subpart','',0) GROUP BY rel_name;
rel_name | dirty_page_count
----------------------------+------------------
htap_gs_imcv_flush_subpart | 0
(1 row)
-- 向二级分区表htap_gs_imcv_flush_subpart中插入足够数据(大于脏页阈值)。
gaussdb=# INSERT INTO htap_gs_imcv_flush_subpart (c1, c2, c3)
SELECT
10000 AS c1, -- 属于 p1 ( < 16001 )
1 AS c2, -- 属于 sp1
'data_p1_sp1_' || g::text AS c3
FROM generate_series(1, 400000) AS g;
INSERT 0 400000
-- 查看脏页数。
gaussdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_htap_tmu_chunk_meta('htap_gs_imcv_flush_subpart','',0) GROUP BY rel_name;
rel_name | dirty_page_count
----------------------------+------------------
htap_gs_imcv_flush_subpart | 2719
(1 row)
-- 等待后台自动重建完成后再次查看脏页数。
gaussdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_htap_tmu_chunk_meta('htap_gs_imcv_flush_subpart','',0) GROUP BY rel_name;
rel_name | dirty_page_count
----------------------------+------------------
htap_gs_imcv_flush_subpart | 0
(1 row) 手动重建示例:
-- 使用gs_imcv_flush()接口手动触发重建的方式与手动触发加载的方式相同。
-- 向二级分区表htap_gs_imcv_flush_subpart中插入数据。
-- 更新子分区p1_sp1中的前 10 行(c2 = 1, c1 = 10000)。
gaussdb=# UPDATE htap_gs_imcv_flush_subpart
SET c3 = c3 || '_updated2'
WHERE c1 = 10000
AND c2 = 1
LIMIT 10;
UPDATE 10
-- 手动触发非强制全表重建(第四个参数缺省或为0,只重建有脏页标记的rowgroup)。
gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_subpart', '', 2);
total | success | error | skip
--------+---------+-------+------
11 | 1 | 0 | 10
-- 手动触发强制全表重建(第四个参数为1,基于行存重新构建全部rowgroup)。
gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_subpart', '', 2, 1);
total | success | error | skip
--------+---------+-------+------
11 | 11 | 0 | 0
(1 row)
gaussdb=# DROP TABLE htap_gs_imcv_flush_nopart;
DROP TABLE
gaussdb=# DROP TABLE htap_gs_imcv_flush_subpart;
DROP TABLE