GaussDB(DWS)表对象VACUUM FULL实践
在长期使用DWS过程中,经常会遇到越跑越慢的情况,这个时候就要考虑是否遇到了数据文件碎片化过于严重的问题,有几个典型应用场景会导致该问题。
一、大量的删改操作
DWS中的表在经过大量的UPDATE/DELETE操作之后,会产生脏页,造成查询时访问过多的无效元组,影响检索性能,建议对脏页率超过30%的表执行清理操作。
二、列存表的小批量入库
众所周知DWS中的列存表的存储单元为CU,每次入库都会在各个datanode上产生CU文件,单个CU默认大小为6万条数据,但是在单次小批量入库场景下,会产生大量的小CU,过于小的CU会导致在查询过程中访问过多小的数据文件,影响检索性能,建议在存在列存表小批量入库场景下定期对小CU文件进行合并操作。
上述清理和合并操作都需要通过VACUUM FULL来实现。
那么如何确认一个表是否达到了需要VACUUM FULL的标准呢,在此分享一下我们在实践中总结的两个方法,供大家参考:
脏页/无效元组检查
DWS中提供了PGXC_GET_STAT_ALL_TABLES视图获取全库所有表的脏页情况,但是由于是该视图是全库统计,耗时很长,效率较低,如果需要对单表查询脏页时,可以借助于如下自定义函数,实现方案是通过封装pg_stat_get_tuples_deleted接口(入参是表oid)来进行分布式查询,统计单表在各个datanode上脏页情况:
CREATE OR REPLACE FUNCTION PUBLIC.check_dead_tuples(table_name varchar(100))
RETURNS BIGINT
LANGUAGE 'plpgsql'
--IMMUTABLE
AS $$
DECLARE
TYPE ARRAY_VARCHAR IS VARRAY(1024) OF VARCHAR;
ARRNAME ARRAY_VARCHAR := ARRAY_VARCHAR();
schemaname varchar default 'public';
tablename varchar;
query_str text;
query_str_nodes text;
table_oid int;
temp bigint := 0;
temp_sum bigint := 0;
row_name record;
BEGIN
ARRNAME.extend(2);
IF ( regexp_like(table_name, '.+\..+') = true )
THEN
ARRNAME := regexp_split_to_array(table_name,'\.');
schemaname := COALESCE(ARRNAME(1),'public');
tablename := ARRNAME(2);
ELSIF ( regexp_like(table_name, '.+\..+') = FALSE ) THEN
schemaname := 'public';
tablename := table_name;
END IF;
query_str_nodes := 'SELECT node_name FROM pgxc_node where node_type = ''D''';
FOR row_name IN EXECUTE(query_str_nodes) LOOP
query_str := 'EXECUTE DIRECT ON (' || row_name.node_name || ') ''select a.oid from pg_class a, pg_namespace b where a.relnamespace=b.oid and a.relname='''''||tablename||''''' and b.nspname='''''||schemaname||'''''''';
EXECUTE immediate query_str into table_oid;
query_str := 'EXECUTE DIRECT ON (' || row_name.node_name || ') ''SELECT pg_stat_get_tuples_deleted('||table_oid||')''';
EXECUTE immediate query_str into temp;
temp_sum := temp_sum + temp;
END LOOP;
return temp_sum;
END $$
;
本地测试效果如下:
postgres=> select count(*) from public.customer;
count
--------
100000
(1 row)
postgres=> delete from public.customer;
DELETE 100000
postgres=> checkpoint;
CHECKPOINT
postgres=> select public.check_dead_tuples('public.customer');
check_deleted
---------------
100000
(1 row)
在获取该表脏数据条数后,可以与总条数作对比,来评估是否需要在业务空窗期做VACUUM FULL清理操作。
列存表小CU检查
实现方案是通过获取在各个datanode统计列存表CU_DESC表中的数据条数,来统计CU个数,另外排除了隐藏列的CU,实现借助于两个自定义函数:
CREATE OR REPLACE FUNCTION public.get_cu_num_dn(table_name character varying)
RETURNS bigint
LANGUAGE plpgsql
--IMMUTABLE
NOT FENCED
AS $function$
DECLARE
TYPE ARRAY_VARCHAR IS VARRAY(1024) OF VARCHAR;
ARRNAME ARRAY_VARCHAR := ARRAY_VARCHAR();
schemaname varchar default 'public';
tablename varchar;
tb_oid oid;
rowcount bigint;
query_str text;
query_str_nodes text;
cudes_name text;
temp bigint := 0;
temp_cu_sum bigint := 0;
temp_sum bigint := 0;
row_name record;
cu_name record;
BEGIN
ARRNAME.extend(2);
IF ( regexp_like(table_name, '.+\..+') = true )
THEN
ARRNAME := regexp_split_to_array(table_name,'\.');
schemaname := COALESCE(ARRNAME(1),'public');
tablename := ARRNAME(2);
ELSIF ( regexp_like(table_name, '.+\..+') = FALSE ) THEN
schemaname := 'public';
tablename := table_name;
END IF;
SELECT a.oid FROM pg_class a, pg_namespace b WHERE a.relname = tablename and a.relnamespace=b.oid and b.nspname=schemaname into tb_oid;
select count(*) from pg_partition where parentid=tb_oid and parttype='p' into rowcount;
if rowcount = 0 then
query_str := 'select (''cstore.''||relname) from pg_class where oid = (select a.relcudescrelid from pg_class a, pg_namespace b where a.relnamespace=b.oid and a.relname='''||tablename||''' and b.nspname='''||schemaname||''')';
EXECUTE immediate query_str into cudes_name;
query_str := 'SELECT count(*) from '||cudes_name||' where col_id > 0';
EXECUTE immediate query_str into temp;
temp_sum := temp_sum + temp;
ELSE
query_str := 'select (''cstore.''||relname) from pg_class where oid in (select c.relcudescrelid from pg_class a, pg_namespace b, pg_partition c where a.relnamespace=b.oid and c.parentid=a.oid and a.relname='''||tablename||''' and b.nspname='''||schemaname||''')';
FOR cu_name in EXECUTE(query_str) LOOP
query_str := 'SELECT count(*) from '||replace(replace(cu_name::text,'(',''),')','')||' where col_id > 0';
EXECUTE immediate query_str into temp;
temp_cu_sum := temp_cu_sum + temp;
END LOOP;
temp_sum := temp_sum + temp_cu_sum;
end if;
return temp_sum;
END $function$
;
CREATE OR REPLACE FUNCTION public.get_cu_num(table_name character varying)
RETURNS bigint
LANGUAGE plpgsql
--IMMUTABLE
NOT FENCED
AS $function$
DECLARE
TYPE ARRAY_VARCHAR IS VARRAY(1024) OF VARCHAR;
ARRNAME ARRAY_VARCHAR := ARRAY_VARCHAR();
schemaname varchar default 'public';
tablename varchar;
tb_oid oid;
rowcount bigint;
query_str text;
query_str_nodes text;
cudes_name text;
temp bigint := 0;
temp_cu_sum bigint := 0;
temp_sum bigint := 0;
row_name record;
cu_name record;
BEGIN
ARRNAME.extend(2);
IF ( regexp_like(table_name, '.+\..+') = true )
THEN
ARRNAME := regexp_split_to_array(table_name,'\.');
schemaname := COALESCE(ARRNAME(1),'public');
tablename := ARRNAME(2);
ELSIF ( regexp_like(table_name, '.+\..+') = FALSE ) THEN
schemaname := 'public';
tablename := table_name;
END IF;
SELECT a.oid FROM pg_class a, pg_namespace b WHERE a.relname = tablename and a.relnamespace=b.oid and b.nspname=schemaname into tb_oid;
select count(*) from pg_partition where parentid=tb_oid and parttype='p' into rowcount;
query_str_nodes := 'SELECT node_name FROM pgxc_node where node_type = ''D''';
FOR row_name IN EXECUTE(query_str_nodes) LOOP
query_str := 'EXECUTE DIRECT ON (' || row_name.node_name || ') ''SELECT public.get_cu_num_dn('''''||table_name||''''')''';
EXECUTE immediate query_str into temp;
temp_sum := temp_sum + temp;
END LOOP;
return temp_sum;
END $function$
;
本地测试效果如下:
postgres=# SELECT public.get_cu_num('public.t_v');
get_cu_num
------------
396
(1 row)
在统计CU个数之后,根据总数据条数,就可以评估小CU个数是否过多了,该自定义函数思路供大家参考,可以在此基础之上再针对需求进行优化。
VACUUM FULL清理
在统计完脏页和小CU后,就可以对表进行VACUUM FULL操作了,由于VACUUM FULL操作是较为重的表对象运维操作,会持有表对象的最高级锁,阻塞所有业务,所以建议在业务空窗期对表进行VACUUM FULL操作,另外对于按时间作分区的分区表来说,建议按分区粒度对最近一个频繁入库和操作的分区,以尽可能缩短维护窗口,降低对业务的影响,目前在DWS中尽管是VACUUM FULL单个分区,也会对整个表加排它锁,这一点建议后续版本中做一些优化,对单个分区加排它锁,降低影响的分区范围,VACUUM FULL操作示例如下:
postgres=# vacuum full t_v;
VACUUM
postgres=# vacuum full t_v partition(p1);
VACUUM
在VACUUM FULL完成后,别忘了对表执行一下ANALYZE统计分析,获取最新的统计信息,在查询中生成最优的执行计划。
postgres=# analyze t_v;
ANALYZE
另外,在DWS中,随着对象的不断创建删除,以及临时表的大量使用,还有增删列操作,系统表也会产生一些无效元组,影响涉及系统表的操作性能,比如DDL,建议也定期对一些系统表进行清理操作,主要涉及的系统表如下:
VACUUM FULL PG_ATTRIBUTE;
VACUUM FULL PG_CLASS;
VACUUM FULL PG_PARTITION;
VACUUM FULL PG_STATISTIC;
VACUUM FULL PG_INDEX;
ANALYZE PG_ATTRIBUTE;
ANALYZE PG_CLASS;
ANALYZE PG_PARTITION;
ANALYZE PG_STATISTIC;
ANALYZE PG_INDEX;
系统表的清理也同样是阻塞业务的,必须在业务空窗期执行,系统表的维护操作频度不用太高,根据实际业务模型建议半年到一年再考虑是否需要清理。
最后,希望我们的分享能够对大家有所帮助,让DWS给您的业务中更加高效,同时希望DWS可以不断改进提升,在数据库的国产化进程中持续发力!
- 点赞
- 收藏
- 关注作者
评论(0)