KingbaseES垃圾回收参数优化之maintenance_work_mem
maintenance_work_mem 参数说明与vacuum过程
maintenance_work_mem , autovacuum_work_mem。
如果没有设置autovacuum_work_mem,默认值是-1,则使用maintenance_work_mem的设置值。
首先这部分内存最大的用处是用于vacuum进程操作。过程如下:
(1) 从指定的表中获取每个表。
(2) 获取表的ShareUpdateExclusiveLock锁。该锁允许从其他事务中读取。
(3) 扫描所有页面以获取所有死元组,必要时冻结旧元组。
(4) 如果存在死元组,则删除指向相应死元组的索引元组。
(5) 对表的每一页执行以下任务,步骤 (6) 和 (7)。
(6) 移除死元组并重新分配页面中的活元组。
(7) 更新目标表各自的FSM和VM。
(8) 通过 index_vacuum_cleanup() 函数清理索引。
(9) 如果最后一页没有任何元组,则截断最后一页。
(10) 更新与目标表的vacuum处理相关的统计信息和系统目录。
(11) 更新与vacuum处理相关的统计和系统目录。
(12) 如果可能,删除不必要的文件和阻塞页面。
首先,数据库扫描一个目标表以构建一个死元组列表,并在可能的情况下冻结旧元组。该列表存储在本地内存中的autovacuum_work_mem或maintenance_work_mem中。冻结处理又分慵懒模式和饥饿模式,这里不做赘述。
扫描后,数据库通过引用死元组列表来删除索引元组。 此过程在内部称为“清理阶段”。不用说,这个过程是昂贵的。在KingbaseESV8R6 或更高版本中,如果目标索引为 B-tree,则是否执行清理阶段由配置参数Vacuum_cleanup_index_scale_factor决定。
当maintenance_work_mem已满且扫描未完成时,数据库进行步骤(4)至(7);然后返回步骤(3)并进行余数扫描。也就是开始INDEX的扫描,当所有索引都扫描并清理了一遍后,继续从刚才的内存满的点开始扫描表。如此反复,当多次maintenance_work_mem满后,会出现索引扫描多次。
如图:vacuum扫描索引,删除索引的过程。
(8)至(10)步骤在删除索引后执行清理,并更新与每个目标表的vacuum处理相关的统计信息和系统目录。
此外,如果最后一页没有元组,则将其从表文件中截断。
显然,步骤中看出重要问题是autovacuum_work_mem太小,INDEX会被多次扫描,非常浪费资源,时间。对于一个大表,多次扫描索引的代价非常昂贵,特别是在表中有很多索引的情况下。如果maintenance_work_mem设置太低,甚至需要多次索引扫描。那将非常考验性能。因此,需要将maintenance_work_mem设置得足够高,这样就避免多次索引扫描。VACUUM VERBOSE TABLE 可以看到详情索引被扫描了几次。
关联重要提示:
1)客户现场遇到的问题是autovacuum_work_mem或maintenance_work_mem内存太小会导致autovacuum进程忙不过来,直观感受就是对于大表有很多dead_tuple的情况,last_autovacuum为空或者很久不触发autovacuum进程工作。当调大maintenance_work_mem,问题得到解决。
2)maintenance_work_mem的总内存消耗等于maintenance_work_mem*autovacuum_max_workers。当然理论是不会达到内存上限。
这部分内存是随用随分配,并不是直接全部用掉maintenance_work_mem或autovacuum_work_mem设置的全部内存。也就是说maintenance_work_mem设置的是实际上限值。这部分内存不包括你可能手工发起的VACUUM或 CREATE INDEX操作所需的内存。那么就要求maintenance_work_mem足够高。注意autovacuum_max_workers进程的数量和表的数量相对应,如果autovacuum_max_workers设置3,那么只能同时并行vacuum3张表,对于分区表将以子分区为单位进行vacuum。至此,有没有发现大的服务器内存和ssd存储对于vacuum性能至关重要。
maintenance_work_mem的另外一个用途,它控制构建索引时使用的最大内存量。构建B树索引时,必须对数据排序,如果要排序的数据在maintenance_work_mem内存中放置不下,它将会溢出到磁盘中。在执行CREATE INDEX命令之前,你可以在本地会话中使用SET命令临时增加该值。``
maintenance_work_mem的内存空间优化
之前发了一篇文章讨论没有及时autovacuum的原因,现在补充一条原因,这是近期的发现。有时候即使该表触及到了20%的阈值,autovacuum进程可能也没有启动,比如它正在忙于处理其他一些表,由于受到autovacuum_max_workers进程的影响,清理进程可能在该表触及20%阈值之前就开始工作了,没有多余的进程或者内存空间再去执行后续触及阈值的表的清理工作。
如果你确实有非常大的表,可以做一件事,将autovacuum_vacuum_scale_factor减少到一个更小的值。你可以在死亡元组到达表大小的1%时就清理,甚至是表大小的0.1%。还可以分别针对每个表进行配置,这样可以规避vacuum造成的IO高峰的问题,与设置大的触发阈值相比,更频繁地清理表并每次只扫描一遍索引,与较少扫描表但每次扫描多遍索引相比,并不一定会节省I/O时间。然而,我们需要考虑对前台进程的影响。但是别忘了特别是对于非常大的表,频繁清理往往会显著提升查询性能,因为每次顺序扫描扫的是全表,包括死亡元组的行。
建议将log_autovacuum_min_duration设置为非默认值,因为它将告诉你autovacuum进程正在做什么工作,这对故障诊断很有用。把这个参数值设置为0,但会记录autovacuum进程执行的每个操作,但是很可能生成巨大的日志量,不建议这样设置。建议把它设置成表级参数,这样不仅能排查问题,还有效规避了日志量巨大的问题。日志消息中,如果看到“index scans: 2”或更大的值,则表示maintenance_work_mem内存快用完了,应该考虑增加该配置值。
maintenance_work_mem的内存计算
设置maintenance_work_mem=1GB,最多有1GB的内存,用于记录一次vacuum时,一次可存储的垃圾tuple的tupleid,tupleid为6字节长度。
1G可存储约1.7亿条dead tuple的tupleid。
test=# select 1024*1024*1024/6;
?column?
-----------
178956970
(1 row)
默认死亡元组约等于表大小的20%,触发垃圾回收。
那么1G总共能存下多大的含死亡元组的表呢?约8.9亿条记录的表。
test=# select 1024*1024*1024/6/0.2;
?column?
--------------------
894784850.00000000
(1 row)
总结
1、log_autovacuum_min_duration=0,表示记录所有表autovacuum的统计信息。这会使日志量巨大,建议对大表单独设置。
2、autovacuum_vacuum_scale_factor=0.01,表示1%的垃圾时,触发自动垃圾回收。这会使垃圾回收提前量,前面提到了,如果多表同时发生autovauum,会受到垃圾回收最大进程的影响,所以最好在表级别设置此参数,以达到频繁的运行autovacuum进程并不会发生io拥挤现象。最有效的作用是频繁vacuum帮助顺序扫描避免扫描过多死亡元组。
3、autovacuum_work_mem,maintenance_work_mem根据实际情况设定,确保不出现垃圾回收时多次INDEX SCAN的数值.可以表级别设置log_autovacuum_min_duration,在日志中观察INDEX SCAN的数量。或者手工vacuum verbose table也可看到。
4、当触发垃圾数据阈值的表很多,尤其是大表的情况,建议增加autovacuum_max_workers,避免有的表autovacuum不及时,但要注意总内存消耗。
5、当出现了index scans: 超过1的情况:
5.1、需要增加autovacuum_work_mem,或maintenance_work_mem增加到当前autovacuum_work_mem乘以index scans数量即可。
5.2、或者调低autovacuum_vacuum_scale_factor到当前值除以index scans即可,目的是让autovacuum尽可能早的工作。