OpenGauss线程管理-数据页写线程(1)-pagewriter
数据页写线程在openGauss数据库中包含两个线程:PageWriter和BgWriter。
操作系统数据块大小一般是4k,数据库大小一般是8k/16k/32k,openGauss默认是8kb,这样就有可能造成页面断裂问题,一个数据库数据块刷到操作系统的过程中可能发生因宕机而造成块损坏从而导致数据库无法启动的问题。pagewriter线程负责将脏页数据拷贝至双写(double-writer)区域并落盘,然后将脏页转发给bgwriter子线程进行数据下盘操作,这样可以防止该现象的发生,因为如果发生数据页”折断”的问题,就会从双写空间里找到完整的数据页进行恢复。
术语解释
- 死锁:为使用同一资源而产生的无法解决的争用状态。
- 宕(dàng)机:计算机主机出现意外故障而死机,或者一些服务器例如数据库死锁。
- 数据块:数据块是一组或几组按顺序连续排列在一起的记录,是主存储器与输入设备、输出设备或外存储器之间进行传输的数据单位。
- 页面断裂:数据库宕机时,数据库页面只有部分写入磁盘,导致页面出现不一致的情况。
- 脏页面:已经被修改且未写入持久性设备的页面。
- 落盘:将数据写入到磁盘(存储介质)
- 双写(double writer)文件:一个连接的存储空间。所有页面在写入文件系统之前,首先要写入双写文件,并且双写文件以“O_SYNC | O_DIRECT”模式打开,保证同步写入磁盘。
pagewriter线程
路径:openGauss-server/src/gausskernel/process/postmaster/pagewriter.cpp
pagewriter 线程的工作模式,coordinator pagewriter 线程将脏页复制到双写区,然后将脏页分发给子线程将页面刷新到数据文件。
在主线程中的使用
在GaussDbAuxiliaryThreadMain中通过thread_role进入pagewriter线程
代码理解
pagewriter线程组由多个pagewriter线程组成,pagewriter线程组分为主pagewriter线程和子pagewriter线程组。主pagewriter线程只有一个,负责从全局脏页队列数组中批量获取脏页面、将这些脏页批量写入双写文件、推进整个数据库的检查点(故障恢复点)、分发脏页给各个pagewriter线程,以及将分发给自己的那些脏页写入文件系统。子pagewriter线程组包括多个子pagewriter线程,负责将主pagewriter线程分发给自己的那些脏页写入文件系统。
1.ckpt_pagewriter_main——主pagewriter线程
void ckpt_pagewriter_main(void)
{
sigjmp_buf localSigjmpBuf;
MemoryContext pagewriter_context;
char name[MAX_THREAD_NAME_LEN] = {0};
t_thrd.role = PAGEWRITER_THREAD;
SetupPageWriterSignalHook();
/* 我们始终允许 SIGQUIT (quickdie) */
(void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT);
ereport(LOG,
(errmodule(MOD_INCRE_CKPT),
errmsg("pagewriter started, thread id is %d", t_thrd.pagewriter_cxt.pagewriter_id)));
g_last_snapshot_ts = GetCurrentTimestamp();
/*
* 创建一个资源所有者来跟踪我们的资源(目前只有缓冲引脚)。
*/
Assert(t_thrd.pagewriter_cxt.pagewriter_id >= 0);
errno_t err_rc = snprintf_s(
name, MAX_THREAD_NAME_LEN, MAX_THREAD_NAME_LEN - 1, "%s%d", "PageWriter", t_thrd.pagewriter_cxt.pagewriter_id);
securec_check_ss(err_rc, "", "");
t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, name,
THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE));
/*
* 创建一个我们将在其中完成所有工作的内存上下文。
* 我们这样做是为了在错误恢复期间重置上下文,从而避免可能的内存泄漏。
* 以前这段代码只是在 TopMemoryContext 中运行,但重置它是一个非常糟糕的主意。
*/
pagewriter_context = AllocSetContextCreate(
TopMemoryContext, name, ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
(void)MemoryContextSwitchTo(pagewriter_context);
on_shmem_exit(pagewriter_kill, (Datum)0);
/*
* 如果遇到异常,则在此处继续处理。
*
* 请参阅 postgres.c 中有关此编码设计的注释。
*/
if (sigsetjmp(localSigjmpBuf, 1) != 0) {
ereport(WARNING, (errmodule(MOD_INCRE_CKPT), errmsg("pagewriter exception occured.")));
ckpt_pagewriter_handle_exception(pagewriter_context);
}
/* 我们现在可以处理 ereport(ERROR) */
t_thrd.log_cxt.PG_exception_stack = &localSigjmpBuf;
/*
* 解除封锁信号(当postmaster fork我们时,它们被封锁了)
*/
gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);
(void)gs_signal_unblock_sigusr2();
/*
* 在恢复期间使用恢复目标时间线 ID
*/
if (RecoveryInProgress()) {
t_thrd.xlog_cxt.ThisTimeLineID = GetRecoveryTargetTLI();
}
pgstat_report_appname("PageWriter");
pgstat_report_activity(STATE_IDLE, NULL);
if (t_thrd.pagewriter_cxt.pagewriter_id == 0) {
g_instance.proc_base->pgwrMainThreadLatch = &t_thrd.proc->procLatch;
g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem->pagewritermain_pid = t_thrd.proc_cxt.MyProcPid;
InitSync();
}
pg_time_t now = (pg_time_t) time(NULL);
t_thrd.pagewriter_cxt.next_flush_time = now + u_sess->attr.attr_storage.pageWriterSleep;
t_thrd.pagewriter_cxt.next_scan_time = now +
MAX(u_sess->attr.attr_storage.BgWriterDelay, u_sess->attr.attr_storage.pageWriterSleep);
/*
* 永远循环
*/
for (;;) {
/*
* 主pagewriter线程需要选择一个batch page flush来双写文件,而不是划分到其他子线程。
*/
if (t_thrd.pagewriter_cxt.pagewriter_id == 0) {
if (!t_thrd.pagewriter_cxt.shutdown_requested) {
logSnapshotForLogicalDecoding();
}
/* 需要生成新版本的单次刷新 dw 文件 */
if (pg_atomic_read_u32(&g_instance.dw_single_cxt.dw_version) < DW_SUPPORT_NEW_SINGLE_FLUSH &&
t_thrd.proc->workingVersionNum >= DW_SUPPORT_NEW_SINGLE_FLUSH) {
dw_upgrade_single();
}
if (pg_atomic_read_u32(&g_instance.dw_batch_cxt.dw_version) < DW_SUPPORT_MULTIFILE_FLUSH &&
t_thrd.proc->workingVersionNum >= DW_SUPPORT_MULTIFILE_FLUSH) {
dw_upgrade_batch();
}
/*
* 当双写被禁用时,pg_dw_meta 将在 dw_file_num = 0 的情况下创建,
* 所以这里用于升级过程。 当 enable_incremetal_checkpoint = on 时,
* pagewrite 将运行。
*/
if (pg_atomic_read_u32(&g_instance.dw_batch_cxt.dw_version) < DW_SUPPORT_REABLE_DOUBLE_WRITE
&& t_thrd.proc->workingVersionNum >= DW_SUPPORT_REABLE_DOUBLE_WRITE) {
dw_upgrade_renable_double_write();
}
ckpt_pagewriter_main_thread_loop();
} else {
ckpt_pagewriter_sub_thread_loop();
}
}
}
2.主pagewriter线程的中断(退出)
static void HandlePageWriterMainInterrupts()
{
if (t_thrd.pagewriter_cxt.got_SIGHUP) {
t_thrd.pagewriter_cxt.got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
}
if (t_thrd.pagewriter_cxt.sync_requested || t_thrd.pagewriter_cxt.sync_retry) {
t_thrd.pagewriter_cxt.sync_requested = false;
t_thrd.pagewriter_cxt.sync_retry = true;
PageWriterSyncWithAbsorption();
t_thrd.pagewriter_cxt.sync_retry = false;
}
/* 主线程应该最后退出。 */
while (t_thrd.pagewriter_cxt.shutdown_requested && g_instance.ckpt_cxt_ctl->page_writer_can_exit) {
if (pg_atomic_read_u32(&g_instance.ckpt_cxt_ctl->current_page_writer_count) == 1) {
ereport(LOG,
(errmodule(MOD_INCRE_CKPT),
errmsg("pagewriter thread shut down, id is %d", t_thrd.pagewriter_cxt.pagewriter_id)));
/*
* 从这里开始,elog(ERROR) 应该以 exit(1) 结束,而不是将控制权发送回上面的 sigsetjmp 块。
*/
u_sess->attr.attr_common.ExitOnAnyError = true;
/* 页面编写器的正常退出在这里 */
proc_exit(0); /* 完毕*/
}
}
return;
}
3.唤醒主pagewriter线程
static void wakeup_pagewriter_main_thread()
{
PageWriterProc *pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[0];
/* 当前候选列表为空,唤醒缓冲区写入器。 */
if (pgwr->proc != NULL) {
SetLatch(&pgwr->proc->procLatch);
}
return;
}