数据库内核月报

数据库内核月报 - 2018 / 02

PgSQL · 源码分析 · AutoVacuum机制之autovacuum worker

Author: 卓刀

背景

根据之前月报的分析,PostgreSQL数据库为了定时清理因为MVCC 引入的垃圾数据,实现了自动清理机制。其中涉及到了两种辅助进程:

其中,autovacuum launcher 主要负责调度autovacuum worker,autovacuum worker进程进行具体的自动清理工作。本文主要是对autovacuum worker进行分析。

相关参数

除了之前月报提到的参数track_counts,autovacuum,autovacuum_max_workers,autovacuum_naptime,autovacuum_vacuum_cost_limit,autovacuum_vacuum_cost_delay,autovacuum_freeze_max_age,autovacuum_multixact_freeze_max_age之外,autovacuum worker还涉及到以下参数:

其中,autovacuum_vacuum_threshold和autovacuum_vacuum_scale_factor参数配置会决定VACUUM 的频繁程度。因为autovacuum会消耗一定的资源,设置的不合适,有可能会影响用户的其他正常的查询。对PostgreSQL使用者来说,一般有2种方案:

为了降低对并发正常查询的影响,autovacuum引入了vacuum_cost_delay,vacuum_cost_page_hit,vacuum_cost_page_miss,vacuum_cost_page_dirty,vacuum_cost_limit参数。在VACUUM和ANALYZE命令的执行过程中,系统维护着一个内部计数器来跟踪各种被执行的I/O操作的估算开销。当累计的代价达到一个阈值(vacuum_cost_limit),执行这些操作的进程将按照vacuum_cost_delay所指定的休眠一小段时间。然后它将重置计数器并继续执行,这样就大大降低了这些命令对并发的数据库活动产生的I/O影响。

autovacuum worker 的启动

根据之前月报的分析,autovacuum launcher 在选取合适的database 之后会向Postmaster 守护进程发送PMSIGNAL_START_AUTOVAC_WORKER信号。Postmaster 接受信号会调用StartAutovacuumWorker函数:

StartAutoVacWorker 函数调用AutoVacWorkerMain 函数启动worker 进程:

do_autovacuum 函数的具体流程

do_autovacuum 函数会去遍历选中数据库的所有relation对象,进行自动清理工作,具体过程如下:

可以看出,do_autovacuum中利用共享内存AutoVacuumShmem 获取当前其他worker 的运行情况,避免并行worker 造成冲突。在此过程中调用函数autovacuum_do_vac_analyze 时会传递autovac_table 为参数,其定义如下:

/* struct to keep track of tables to vacuum and/or analyze, after rechecking */
typedef struct autovac_table
{
	Oid			at_relid;
	int			at_vacoptions;	/* bitmask of VacuumOption */
	VacuumParams at_params;
	int			at_vacuum_cost_delay;
	int			at_vacuum_cost_limit;
	bool		at_dobalance;
	bool		at_sharedrel;
	char	   *at_relname;
	char	   *at_nspname;
	char	   *at_datname;
} autovac_table;

其中at_vacoptions指示vacuum的类型,具体如下:

typedef enum VacuumOption
{
	VACOPT_VACUUM = 1 << 0,		/* do VACUUM */
	VACOPT_ANALYZE = 1 << 1,	/* do ANALYZE */
	VACOPT_VERBOSE = 1 << 2,	/* print progress info */
	VACOPT_FREEZE = 1 << 3,		/* FREEZE option */
	VACOPT_FULL = 1 << 4,		/* FULL (non-concurrent) vacuum */
	VACOPT_NOWAIT = 1 << 5,		/* don't wait to get lock (autovacuum only) */
	VACOPT_SKIPTOAST = 1 << 6,	/* don't process the TOAST table, if any */
	VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7	/* don't skip any pages */
} VacuumOption;

在autovacuum中,只涉及到VACOPT_SKIPTOAST,VACOPT_VACUUM,VACOPT_ANALYZE,VACOPT_NOWAIT。其中默认有VACOPT_SKIPTOAST选项,即会自动跳过TOAST表,关于TOAST表的autovacuum,我们在之后的月报详细分析。而VACOPT_VACUUM,VACOPT_ANALYZE,VACOPT_NOWAIT对应上文的vacuum,analyze,wraparound。

at_params存储vacuum的相关参数,其结构VacuumParams定义如下:

/*
 * Parameters customizing behavior of VACUUM and ANALYZE.
 */
typedef struct VacuumParams
{
	int			freeze_min_age; /* min freeze age, -1 to use default */
	int			freeze_table_age;	/* age at which to scan whole table */
	int			multixact_freeze_min_age;	/* min multixact freeze age, -1 to
											 * use default */
	int			multixact_freeze_table_age; /* multixact age at which to scan
											 * whole table */
	bool		is_wraparound;	/* force a for-wraparound vacuum */
	int			log_min_duration;	/* minimum execution threshold in ms at
									 * which  verbose logs are activated, -1
									 * to use default */
} VacuumParams;

vacuum函数的具体流程

vacuum 函数会根据传递的at_vacoptions 参数和at_params 参数对对应的对象进行VACUUM,既可以被autovacuum调用,又被用户手动执行VACUUM命令调用。所以这里的对象可以是relation,也可以是一个database,如果是database则会默认去vacuum该数据库所有relation 对象。autovacuum 调用vacuum函数时,这里的对象是具体的某个relation,其过程如下:

vacuum_rel函数具体去做VACUUM,这里根据at_vacoptions 参数的不同可以分为:

autovacuum 是调用的LAZY vacuum。对于不断增长的表来说,LAZY vacuum显然是更合适的,LAZY vacuum主要完成:

LAZY vacuum该过程的调用函数关系为vacuum_rel—>lazy_scan_heap—>lazy_vacuum_heap—>lazy_vacuum_page,整个过程我们可以简单概括为:

总结

至此,我们得到的database 就是已经经过自动清理后的database。不过本文中还有很多问题没有涉及到:

我们会在之后的月报中一一进行分析,敬请期待。