我们知道,WAL日志并不是实时刷盘的,pg在共享内存中分配了XLOG BUFFER缓存日志页。当要写入日志记录时,会先写入XLOG BUFFER,而在这之前,首先要干的是先为事务日志申请共享内存,以及一些重要结构体的初始化。

本节主要内容如下:

  • 计算XLOG需要申请多少共享内存:XLOGShmemSize函数
  • 两个重要结构体:XLogCtlData和XLogCtlInsert
  • XLOG初始化共享内存:XLOGShmemInit函数

一、 XLOGShmemSize函数

用户可以通过wal_buffers参数来指定XLOG BUFFER中缓存页面的数量。wal_buffers默认值为-1,表示pg通过启发式算法(XLOGChooseNumBuffers函数)计算出需要缓存页面的数量。

除了XLOG BUFFER,还需要申请一部分共享内存保存XLOG的控制信息,因此在函数中可以看到size是几部分相加的结果。

/** 计算XLOG需要申请多少共享内存
*/
Size XLOGShmemSize(void)
{Size       size;/** 判断wal_buffers是否为-1,如果是,则调用XLOGChooseNumBuffers函数获取值*/if (XLOGbuffers == -1){char       buf[32];snprintf(buf, sizeof(buf), "%d", XLOGChooseNumBuffers());SetConfigOption("wal_buffers", buf, PGC_POSTMASTER, PGC_S_OVERRIDE);}// wal_buffers必须大于0,因为是缓存的页面数Assert(XLOGbuffers > 0);/* XLogCtl,XLog控制信息的大小,后面会具体了解这个结构体 */size = sizeof(XLogCtlData);/* WAL insertion locks, plus alignment,日志记录插入时使用的轻量锁大小 */size = add_size(size, mul_size(sizeof(WALInsertLockPadded), NUM_XLOGINSERT_LOCKS + 1));/* xlblocks array,记录每个XLog Block的起始LSN */size = add_size(size, mul_size(sizeof(XLogRecPtr), XLOGbuffers));/* extra alignment padding for XLOG I/O buffers,额外使用一个块大小,保持字节对齐 */size = add_size(size, XLOG_BLCKSZ);/* and the buffers themselves,XLOG BUFFER的大小 */size = add_size(size, mul_size(XLOG_BLCKSZ, XLOGbuffers));// 注意:本函数不计算ControlFileData的大小return size;
}

从上述代码中可用看出共享缓存被分了5个部分:

  • 第一部分:XLogCtl
  • 第二部分:LSN数组,数组元素个数和log buffer的页面数一致(XLOGbuffers)
  • 第三部分:WALInsertLockPadded数组,数组元素个数为NUM_XLOGINSERT_LOCKS
  • 第四部分:对齐位
  • 第五部分:log buffer,数组元素个数为XLOGbuffers

二、 两个重要结构体

1. XLogCtlData

XLogCtlData中保存当前WAL的写入状态、刷盘状态、buffer页面的状态信息等。

/** Total shared-memory state for XLOG.*/
typedef struct XLogCtlData
{XLogCtlInsert Insert;  //其中包含XLogCtlInsert结构体/* Protected by info_lck: 由info_lck保护 */XLogwrtRqst LogwrtRqst;        /* 日志需要写入和刷入的LSN */XLogRecPtr  RedoRecPtr;     /* a recent copy of Insert->RedoRecPtr,冗余保存Insert->RedoRecPtr,表示接近checkpoint的Redo LSN */FullTransactionId ckptFullXid;  /* nextXid of latest checkpoint,最近一次checkpoint对应的下一个事务id */XLogRecPtr    asyncXactLSN;   /* LSN of newest async commit/abort,最新异步提交/回滚操作的LSN */XLogRecPtr replicationSlotMinLSN;  /* oldest LSN needed by any slot,复制槽需要的最旧LSN */XLogSegNo lastRemovedSegNo;   /* latest removed/recycled XLOG segment,最近一次删除/回收的日志段ID *//* Fake LSN counter, for unlogged relations. Protected by ulsn_lck. 假的LSN计数器,用于不需记录日志的表,由ulsn_lck保护,目前只有GiST使用 */XLogRecPtr   unloggedLSN;slock_t     ulsn_lck;/* Time and LSN of last xlog segment switch. Protected by WALWriteLock. WAL日志切换时,记录当前时间和刷入日志的LSN,由WALWriteLock保护 */pg_time_t lastSegSwitchTime;XLogRecPtr    lastSegSwitchLSN;/** Protected by info_lck and WALWriteLock (you must hold either lock to* read it, but both to update) 日志已经写入和刷入的LSN。由info_lck和WALWriteLock保护,获得其中一个时可以读取它,两者都获得时才能更新它*/XLogwrtResult LogwrtResult;/** Latest initialized page in the cache (last byte position + 1).* 当前XLOG BUFFER分配的页面中,最后一个页面的LSN*/XLogRecPtr    InitializedUpTo;/** These values do not change after startup, although the pointed-to pages* and xlblocks values certainly do.  xlblocks values are protected by* WALBufMappingLock. XLOG BUFFER中的页面及页面编号。以下值在db启动后就不会改变,虽然它们指向页和xlblocks确实会变。Xlblocks的值由WALBufMappingLock保护*/char      *pages;          /* buffers for unwritten XLOG pages,指向XLOG BUFFER中尚未写入XLOG的页的指针 */XLogRecPtr *xlblocks;      /* 1st byte ptr-s + XLOG_BLCKSZ */int          XLogCacheBlck;  /* highest allocated xlog buffer index,最大已分配的XLOG BUFFER索引。存放最大的log buffer页面下标,也就是页面数量-1 *//** 时间线信息*/TimeLineID  ThisTimeLineID; // 当前的时间线TimeLineID PrevTimeLineID; // 之前的时间线,如果没有出现过分支,二者是相等的/** SharedRecoveryState indicates if we're still in crash or archive* recovery.  Protected by info_lck.恢复状态标志,表示我们是否在crash或archive恢复中,由info_lck保护*/RecoveryState SharedRecoveryState;/** SharedHotStandbyActive indicates if we allow hot standby queries to be* run.  Protected by info_lck.是否允许从库执行查询,由info_lck保护*/bool     SharedHotStandbyActive;/** SharedPromoteIsTriggered indicates if a standby promotion has been* triggered.  Protected by info_lck. 是否已触发从库提升为主库操作,由info_lck保护*/bool       SharedPromoteIsTriggered;/** WalWriterSleeping indicates whether the WAL writer is currently in* low-power mode (and hence should be nudged if an async commit occurs).* Protected by info_lck. WAL writer当前是否为low-power模式(允许异步提交),由info_lck保护*/bool       WalWriterSleeping;/** recoveryWakeupLatch is used to wake up the startup process to continue* WAL replay, if it is waiting for WAL to arrive or failover trigger file* to appear. 用于唤醒启动进程继续执行WAL replay操作,如果当前在等待WAL进行归档或者出现failover触发器文件**/Latch       recoveryWakeupLatch;/** During recovery, we keep a copy of the latest checkpoint record here.在recovery期间,我们会保存最近的checkpoint记录的复制*/XLogRecPtr lastCheckPointRecPtr; //指向checkpoint记录的开头位置XLogRecPtr   lastCheckPointEndPtr; //指向checkpoint记录的结束位置(end+1),当checkpointer需要创建restartpoint时使用CheckPoint lastCheckPoint;       // 最近的checkpoint记录的复制/** lastReplayedEndRecPtr points to end+1 of the last record successfully* replayed. When we're currently replaying a record, ie. in a redo* function, replayEndRecPtr points to the end+1 of the record being* replayed, otherwise it's equal to lastReplayedEndRecPtr.
* lastReplayedEndRecPtr指向最后一个成功回放的记录的end+1位置。* 如果正处于redo函数回放记录期间,replayEndRecPtr指向正在恢复的记录的end+1位置,否则replayEndRecPtr = lastReplayedEndRecPtr*/XLogRecPtr    lastReplayedEndRecPtr;TimeLineID    lastReplayedTLI;XLogRecPtr  replayEndRecPtr;TimeLineID  replayEndTLI;/* timestamp of last COMMIT/ABORT record replayed (or being replayed),最后的提交/回滚记录回放(或正在回放)的时间 */TimestampTz recoveryLastXTime;/** timestamp of when we started replaying the current chunk of WAL data,* only relevant for replication or archive recovery,开始回放当前的WAL chunk的时间(仅与复制或归档恢复相关)*/TimestampTz currentChunkStartTime;/* Recovery pause state,Recovery暂停状态 */RecoveryPauseState recoveryPauseState;ConditionVariable recoveryNotPausedCV;/** lastFpwDisableRecPtr points to the start of the last replayed* XLOG_FPW_CHANGE record that instructs full_page_writes is disabled.
* 指向最后已回放的XLOG_FPW_CHANGE记录(禁用全页写)的起始点.*/XLogRecPtr   lastFpwDisableRecPtr;slock_t        info_lck;       /* locks shared variables shown above,前面提到的一个锁共享变量 */
} XLogCtlData;// 定义对应指针,初始值为空
static XLogCtlData *XLogCtl = NULL;

2. XLogCtlInsert

XLogCtlInsert中保存日志记录中写入buffer时所需的各种变量。

/** Shared state data for WAL insertion.*/
typedef struct XLogCtlInsert
{slock_t        insertpos_lck;  /* protects CurrBytePos and PrevBytePos,保护CurrBytePos和PrevBytePos */uint64       CurrBytePos; // 新记录写入的位置uint64      PrevBytePos; // 新记录需要记录前一条日志的LSN/** 确保上面的变量(会被频繁修改)在同一个cache line,下面的变量(较少修改)在另一个cache line。分到不同的cache line可以避免上面变量频繁修改导致cache line失效,影响下面变量的读取效率*/char       pad[PG_CACHE_LINE_SIZE];/** 全页写相关的变量*/XLogRecPtr    RedoRecPtr;     /* current redo point for insertions,插入时的当前redo point */bool     forcePageWrites;    /* forcing full-page writes for PITR? 为PITR强制执行全页写? */bool      fullPageWrites;  //是否全页写?/** 在线备份功能 pg_start_backup/pg_stop_backup*/ExclusiveBackupState exclusiveBackupState; //排他备份的状态int          nonExclusiveBackups;          //非排他备份变量是一个计数器,指示当前正在进行的流基础备份(streaming base backups)的数量XLogRecPtr  lastBackupStart; //用作在线备份起点的最新检查点的重做位置/** WAL insertion locks.日志插入时的锁。为提高日志记录写入buffer的并发度,这里会分配NUM_XLOGINSERT_LOCKS个锁,Backends进程按照MyProc->pgprocno依次申请,直到获得锁*/WALInsertLockPadded *WALInsertLocks;
} XLogCtlInsert;

三、 XLOGShmemInit函数

用于XLOG初始化共享内存

void XLOGShmemInit(void)
{bool       foundCFile,foundXLog;char      *allocptr;int            i;ControlFileData *localControlFile;    // 控制文件内容#ifdef WAL_DEBUG   // 如果启用了WAL_DEBUG参数/** 为WAL debug创建内存上下文,如果内存分配失败,DB可能进入PANIC状态,不过wal_debug本来就不是用于生产环境的参数,所以问题也不大*/if (walDebugCxt == NULL){walDebugCxt = AllocSetContextCreate(TopMemoryContext,"WAL Debug",ALLOCSET_DEFAULT_SIZES);MemoryContextAllowInCriticalSection(walDebugCxt, true);}
#endif// 初始化共享内存结构体XLogCtlData的对象XLogCtlXLogCtl = (XLogCtlData *)ShmemInitStruct("XLOG Ctl", XLOGShmemSize(), &foundXLog);// 初始化共享内存结构体ControlFileData的对象ControlFile,以及localControlFilelocalControlFile = ControlFile;ControlFile = (ControlFileData *)ShmemInitStruct("Control File", sizeof(ControlFileData), &foundCFile);// 如果存在控制文件或XLOG文件if (foundCFile || foundXLog){/* both should be present or neither,两者都存在或都不存在 */Assert(foundCFile && foundXLog);/* Initialize local copy of WALInsertLocks,初始化WALInsertLocks的本地copy */WALInsertLocks = XLogCtl->Insert.WALInsertLocks;/* 如果localControlFile已存在,释放其占用的内存 */if (localControlFile)pfree(localControlFile);return;}// 为XLogCtl分配内存memset(XLogCtl, 0, sizeof(XLogCtlData));/** Already have read control file locally, unless in bootstrap mode. Move* contents into shared memory. 如果本地已读取过控制文件,除非是bootstrap模式,否则将其内容移至共享内存*/if (localControlFile){memcpy(ControlFile, localControlFile, sizeof(ControlFileData));pfree(localControlFile);}/** Since XLogCtlData contains XLogRecPtr fields, its sizeof should be a* multiple of the alignment for same, so no extra alignment padding is* needed here.
* 由于XLogCtlData包含XLogRecPtr字段,它的size应该是多次相同大小的分配,不需要额外的填充字段*/allocptr = ((char *) XLogCtl) + sizeof(XLogCtlData);XLogCtl->xlblocks = (XLogRecPtr *) allocptr;memset(XLogCtl->xlblocks, 0, sizeof(XLogRecPtr) * XLOGbuffers);allocptr += sizeof(XLogRecPtr) * XLOGbuffers;/* WAL insertion locks. Ensure they're aligned to the full padded size,WAL插入锁 */allocptr += sizeof(WALInsertLockPadded) -((uintptr_t) allocptr) % sizeof(WALInsertLockPadded);WALInsertLocks = XLogCtl->Insert.WALInsertLocks =(WALInsertLockPadded *) allocptr;allocptr += sizeof(WALInsertLockPadded) * NUM_XLOGINSERT_LOCKS;for (i = 0; i < NUM_XLOGINSERT_LOCKS; i++){LWLockInitialize(&WALInsertLocks[i].l.lock, LWTRANCHE_WAL_INSERT);WALInsertLocks[i].l.insertingAt = InvalidXLogRecPtr;WALInsertLocks[i].l.lastImportantAt = InvalidXLogRecPtr;}/** Align the start of the page buffers to a full xlog block size boundary.* This simplifies some calculations in XLOG insertion. It is also* required for O_DIRECT.分配从起始缓存页到完整xlog块大小边界的共享内存。这简化了XLOG插入的部分运算,另外需要请求O_DIRECT模式*/allocptr = (char *) TYPEALIGN(XLOG_BLCKSZ, allocptr);XLogCtl->pages = allocptr;memset(XLogCtl->pages, 0, (Size) XLOG_BLCKSZ * XLOGbuffers);/** Do basic initialization of XLogCtl shared data. (StartupXLOG will fill* in additional info.) 初始化 XLogCtl 共享数据,StartupXLOG函数会填充其余的字段*/XLogCtl->XLogCacheBlck = XLOGbuffers - 1;XLogCtl->SharedRecoveryState = RECOVERY_STATE_CRASH;XLogCtl->SharedHotStandbyActive = false;XLogCtl->SharedPromoteIsTriggered = false;XLogCtl->WalWriterSleeping = false;// XLogCtl 中的锁是通过spinlock来实现的SpinLockInit(&XLogCtl->Insert.insertpos_lck);SpinLockInit(&XLogCtl->info_lck);SpinLockInit(&XLogCtl->ulsn_lck);InitSharedLatch(&XLogCtl->recoveryWakeupLatch);ConditionVariableInit(&XLogCtl->recoveryNotPausedCV);
}

参考

《PostgreSQL技术内幕:事务处理深度探索》第4章

https://www.jianshu.com/p/69323c1c9994

postgresql源码学习(21)—— 事务日志②-日志初始化相关推荐

  1. postgresql源码学习(27)—— 事务日志⑦-日志落盘上层函数 XLogFlush

    一. 预备知识 1. XLOG什么时候需要落盘 事务commit之前 log buffer被覆盖之前 后台进程定期落盘 2. 两个核心结构体 这两个结构体定义代码在xlog.c,它们在日志落盘过程中非 ...

  2. postgresql源码学习(51)—— 提交日志CLOG 原理 用途 管理函数

    一. CLOG是什么 CLOG(commit log)记录事务的最终状态. 物理上,是$PGDATA/pg_xact目录下的一些文件 逻辑上,是一个数组,下标为事务id,值为事务最终状态 1. 事务最 ...

  3. postgresql源码学习(49)—— MVCC⑤-cmin与cmax 同事务内的可见性判断

    一. 难以理解的场景 postgresql源码学习(十九)-- MVCC④-可见性判断 HeapTupleSatisfiesMVCC函数_Hehuyi_In的博客-CSDN博客 在前篇的可见性判断中有 ...

  4. PostgreSQL源码学习(1)--PG13代码结构

    PostgreSQL源码学习(1)–PG13代码结构 PostgreSQL代码结构 Bootstrap:用于支持Bootstrap运行模式,该模式主要用来创建初始的模板数据库. Main:主程序模块, ...

  5. PostgreSQL源码学习(一)编译安装与GDB入门

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 PostgreSQL源码学习(一)编译安装与GDB入门 前言 一.安装PostgreSQL 1.获取源码 2.配置 3.编译 3.安装 ...

  6. postgresql源码学习(52)—— vacuum①-准备工作与主要流程

    关于vacuum的基础知识,参考,本篇从源码层继续学习 https://blog.csdn.net/Hehuyi_In/article/details/102992065 https://blog.c ...

  7. postgresql源码学习(53)—— vacuum②-lazy vacuum之heap_vacuum_rel函数

    一. table_relation_vacuum函数 1. 函数定义 前篇最后(https://blog.csdn.net/Hehuyi_In/article/details/128749517),我 ...

  8. postgresql源码学习(57)—— pg中的四种动态库加载方法

    一. 基础知识 1. 什么是库 库其实就是一些通用代码,可以在程序中重复使用,比如一些数学函数,可以不需要自己编写,直接调用相关函数即可实现,避免重复造轮子. 在linux中,支持两种类型的库: 1. ...

  9. postgresql源码学习(一)—— 源码编译安装与gdb调试入门

    一. postgresql源码编译安装 因为只是用来调试的测试环境,把基本的软件装好和库建好就可以,一切从简. 1. 创建用户和目录 mkdir -p /data/postgres/base/ mkd ...

  10. 菜鸟学源码之Nacos v1.1.3源码学习-Client模块(1):NacosNamingService初始化

    摘要: 本文是Nacos源码学习的第一篇,基于Nacos v1.1.3版本对Nacos源码进行学习,本片主要从exmaple的App示例入手,切入Nacos客户端NacosNamingService的 ...

最新文章

  1. c# 修饰词public, protected, private,internal,protected的区别
  2. js获取checkbox多选表单
  3. ndroid网络(4):HttpClient必经之路----使用线程安全的单例模式HttpClient,及HttpClient和Application的融合...
  4. 如何选择LoRa产品
  5. mysql 注入 file load_Mysql注入中into outfile和load_file()总结
  6. 关于 m_pszAppName
  7. linux下dns服务器安装,Linux下DNS服务器安装配置方法详细介绍
  8. Oracle 分组拼接字符串
  9. kafka 修改分区_kafka修改分区和副本数
  10. windows qt 使用openssl API
  11. 《Python分布式计算》 第5章 云平台部署Python (Distributed Computing with Python)
  12. View的事件处理流程
  13. iOS导航控制器和Segues
  14. lamp+cacti+ntop+thold+nagios+syslog
  15. golang文件传输工具,支持大文件
  16. 周鸿祎创业史细说漫谈话神秘
  17. vivo oppo 相机权限处理
  18. RSS推送技术——打造自己的今日头条
  19. js函数提升和变量提升_关于在js中提升的真相
  20. springboot整合jett实现模板excel数据导出

热门文章

  1. 推导三次以及四次方程的求根公式
  2. java 找茬_求大神帮忙找茬,就是改不过来错误
  3. GB28181 码流解析( 四 )
  4. 对python有兴趣
  5. 泰山OFFICE技术讲座:字体属性的上标研究3:上标对绘制的影响
  6. android微信电话锁屏,解决 Android 7.0 系统中,微信无法在锁屏画面显示
  7. Java process.waitfor 返回1的原因
  8. deepin linux手工更新系统
  9. 水晶球制作–关于质感表现与光影的细节
  10. Python爬取新闻标题及链接存至 Excel(含源码)