
# 列存数据维护
#### 列存数据加载
列存数据加载是指首次根据行存数据生成该表对应的列存数据。在启用异步加载模式后，通过DDL命令为表创建IMCV时，系统仅会建立列存的元数据结构，并不会立刻生成实际的列存数据。
系统提供两种列存数据的加载方式：
- 后台HTAP Worker自动加载 系统会在后台调度HTAP Worker，对目标表自动完成列存数据的构建。该方式无需人工干预，但其执行时间可能受到后台HTAP Worker的线程数量（由GUC参数htap_flush_workers决定）、系统负载以及调度策略的影响。
  
- 手动触发加载 用户可直接调用gs_imcv_flush()接口，立即开始对指定表进行列存数据加载。当后台加载未及时完成时，用户可主动执行该接口加速加载进程。加载效率与参数设置的并行度有关。
  
![](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/public_sys-resources/note_3.0-zh-cn.png)
- HTAP Worker数量由GUC参数htap_flush_workers决定，设置方法请参见《参考》中"数据库运行参数说明 \> GUC参数说明 \> HTAP混合负载"章节。
- gs_imcv_flush是用于列存数据加载/重建的系统函数，使用方法请参见《参考》中"SQL参考 \> 函数和操作符 \> 系统管理函数 \> HTAP系统函数"章节。
 
自动加载示例：
```
-- 创建普通表并插入数据。
htapdb=# DROP TABLE IF EXISTS htap_gs_imcv_flush_nopart;
htapdb=# CREATE TABLE htap_gs_imcv_flush_nopart(
    c1 bigint,
    c2 varchar(256)
) WITH (STORAGE_TYPE=USTORE) DISTRIBUTE BY HASH(c1);
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
-- 创建一级分区表并插入数据。
htapdb=# DROP TABLE IF EXISTS htap_gs_imcv_flush_part;
htapdb=# CREATE TABLE htap_gs_imcv_flush_part (
    c1 bigint,
    c2 bigint,
    c3 varchar(256)
) DISTRIBUTE BY HASH(c1)
PARTITION BY RANGE (c1)(
    PARTITION htap_gs_imcv_flush_part_p1 VALUES LESS THAN (2),
    PARTITION htap_gs_imcv_flush_part_p2 VALUES LESS THAN (3)
);
CREATE TABLE
htapdb=# INSERT INTO htap_gs_imcv_flush_part (c1, c2, c3)
SELECT
    CASE
        WHEN g <= 256000 THEN 1
        ELSE 2
    END AS c1,
    g AS c2,
    'data_' || g AS c3
FROM generate_series(1, 512000) AS g;
-- 为非分区表和分区表创建IMCV。
htapdb=# ALTER TABLE htap_gs_imcv_flush_nopart COLVIEW;
ALTER TABLE
htapdb=# ALTER TABLE htap_gs_imcv_flush_part COLVIEW;
ALTER TABLE
-- DN节点查看IMCV信息（其中，rowgroup_num为表的实际行组数量，populated_rowgroup_num为表已完成加载的行组数量）。
htapdb=# SELECT rel_name, partition_name, rowgroup_num, populated_rowgroup_num FROM gs_htap_imcv_info();
          rel_name         |        partition_name      | rowgroup_num | populated_rowgroup_num 
---------------------------+----------------------------+--------------+------------------------
 htap_gs_imcv_flush_part   | htap_gs_imcv_flush_part_p1 |           2 |                     0
 htap_gs_imcv_flush_part   |                            |            0 |                      0
 htap_gs_imcv_flush_part   | htap_gs_imcv_flush_part_p2 |           2 |                      0
 htap_gs_imcv_flush_nopart |                            |            2 |                      2
-- 等待一段时间后查看IMCV信息（等待后台加载完成）。
htapdb=# SELECT rel_name, partition_name, rowgroup_num, populated_rowgroup_num FROM gs_htap_imcv_info();
          rel_name         |        partition_name      | rowgroup_num | populated_rowgroup_num 
---------------------------+----------------------------+--------------+------------------------
 htap_gs_imcv_flush_part   | htap_gs_imcv_flush_part_p1 |           2 |                     2
 htap_gs_imcv_flush_part   |                            |            0 |                      0
 htap_gs_imcv_flush_part   | htap_gs_imcv_flush_part_p2 |           2 |                     2
 htap_gs_imcv_flush_nopart |                            |            2 |                      2
```
手动加载示例：
```
-- 重新创建非分区表htap_gs_imcv_flush_nopart与一级分区表htap_gs_imcv_flush_part并插入数据，创建方式与上文示例相同。
-- 1.指定关系加载列存数据。
-- (1)指定非分区表加载列存数据。
htapdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_nopart');
 total | success | error | skip 
-------+---------+-------+------
 2     | 2       | 0     | 0
(1 row)
-- (2)指定分区表加载列存数据。
-- (2.1)指定一级分区表加载列存数据。
htapdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_part', 'htap_gs_imcv_flush_part_p1');
 total | success | error | skip 
-------+---------+-------+------
 2    | 2      | 0     | 0
(1 row)
-- (2.2)指定基表加载列存数据（p2加载成功，p1已加载过，本次加载跳过）。
htapdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_part', '');
 total | success | error | skip 
-------+---------+-------+------
 4    | 2      | 0     | 2
(1 row)
-- 2.指定加载列存数据的并行度。
htapdb=# ALTER TABLE htap_gs_imcv_flush_part NOCOLVIEW;
ALTER TABLE
htapdb=# ALTER TABLE htap_gs_imcv_flush_part COLVIEW;
ALTER TABLE
htapdb=# \timing on
Timing is on.
htapdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_part', '', 2);
 total | success | error | skip 
-------+---------+-------+------
 4    | 4      | 0     | 0
(1 row)
Time: 406.391 ms
htapdb=# \time off
Timing is off.
htapdb=# ALTER TABLE htap_gs_imcv_flush_part NOCOLVIEW;
ALTER TABLE
htapdb=# ALTER TABLE htap_gs_imcv_flush_part COLVIEW;
ALTER TABLE
htapdb=# \timing on
Timing is on.
htapdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_part', '', 2);
 total | success | error | skip 
-------+---------+-------+------
 4    | 4      | 0     | 0
(1 row)
Time: 332.194 ms
htapdb=# \time off
Timing is off.
```
#### 列存数据重建
在完成列存加载之后，后续所有的DML（INSERT/UPDATE/DELETE）都会产生新的增量数据。这些增量由实时事务单元TMU维护，但不会即时同步到已经加载的列存数据中。
当这些增量积累过多时，列存数据会逐渐落后于实际行存内容，从而影响列存扫描性能。此时需要进行列存数据重建，把累积的增量同步到列存中，保持列存数据的新鲜度和查询性能。
系统同样提供两种重建方式：
- 后台HTAP Worker自动重建 当增量数据达到一定规模，或者距离上一次重建一定时间后，系统在后台自动触发列存重建任务。重建耗时同样会受到后台线程数量和调度的影响。
  ![](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/public_sys-resources/note_3.0-zh-cn.png)
  后台自动重建的触发条件：
  - 增量数据所涉及的页面数达到一定比例，由GUC参数htap_flush_pagenum控制。
  
  - 距离上次重建达到一定的时间间隔，由 GUC 参数htap_flush_interval控制。
  
  
  设置方法请参见《参考》中"数据库运行参数说明 \> GUC参数说明 \> HTAP混合负载"章节。
  
- 手动触发重建 如果后台重建未及时执行，或者用户希望立即更新列存内容，可直接调用gs_imcv_flush()接口手动触发重建。重建效率与参数设置的并行度有关。
  
自动重建示例：
```
-- 查看同步时间间隔和脏页阈值。
htapdb=# SHOW htap_flush_interval;
 htap_flush_interval 
---------------------
 5min
(1 row)
htapdb=# SHOW htap_flush_pagenum;
 htap_flush_pagenum 
--------------------
 512
(1 row)
-- 向一级分区表htap_gs_imcv_flush_part中更新数据。
htapdb=# UPDATE htap_gs_imcv_flush_part SET c3 = 'abcd' WHERE c2 IN (2, 20002);
UPDATE 2
-- 查看脏页数（未达重建条件的脏页阈值）。
htapdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_global_htap_tmu_chunk_meta('htap_gs_imcv_flush_part','',0) GROUP BY rel_name;
          rel_name       | dirty_page_count 
-------------------------+------------------
 htap_gs_imcv_flush_part |               2
(1 row)
-- 等待5min后查看脏页数，此时后台重建完成（达到设置的重建间隔后触发后台重建完成）。
htapdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_global_htap_tmu_chunk_meta('htap_gs_imcv_flush_part','',0) GROUP BY rel_name;
          rel_name       | dirty_page_count 
-------------------------+------------------
 htap_gs_imcv_flush_part |                0
(1 row)
-- 向一级分区表htap_gs_imcv_flush_part中插入足够数据（大于脏页阈值）。
htapdb=# INSERT INTO htap_gs_imcv_flush_part (c1, c2, c3)
SELECT 1, 1, 'c3' FROM generate_series(1, 120000);
INSERT 120000
-- 查看脏页数。
htapdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_htap_tmu_chunk_meta('htap_gs_imcv_flush_part','',0) GROUP BY rel_name;
          rel_name       | dirty_page_count 
-------------------------+------------------
 htap_gs_imcv_flush_part |             557
(1 row)
-- 等待后台自动重建完成后再次查看脏页数。
htapdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_htap_tmu_chunk_meta('htap_gs_imcv_flush_part','',0) GROUP BY rel_name;
          rel_name       | dirty_page_count 
-------------------------+------------------
 htap_gs_imcv_flush_part |                0
(1 row)
```
手动重建示例：
```
-- 使用gs_imcv_flush()接口手动触发重建的方式与手动触发加载的方式相同。
-- 向一级级分区表htap_gs_imcv_flush_part中插入数据。
htapdb=# UPDATE htap_gs_imcv_flush_part SET c3 = 'efgh' WHERE c1=1;
UPDATE 24
-- 手动触发非强制全表重建（第四个参数缺省或为0，只重建有脏页标记的rowgroup）。
htapdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_part', '', 2);
 total | success | error | skip 
-------+---------+-------+------
 4    | 1       | 0     | 3
-- 手动触发强制全表重建（第四个参数为1，基于行存重新构建全部rowgroup）。
htapdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_part', '', 2,1);
 total | success  | error | skip 
-------+----------+-------+------
 4    | 4      | 0     | 0
(1 row)
htapdb=# DROP TABLE htap_gs_imcv_flush_nopart;
DROP TABLE
htapdb=# DROP TABLE htap_gs_imcv_flush_part;
DROP TABLE
```
