存储引擎源码解析 | 磁盘引擎(16)
4.2.8 日志系统
内存是一种易失性存储介质,在断电等场景下存储在内存介质中的数据会丢失。为了保障数据的可靠性需要将共享缓冲区中的脏页写入磁盘,此即数据的持久化过程。对于最常用的持久化存储介质磁盘,由于每次读写操作都有一个“启动”代价,导致磁盘的读写操作频率有一个上限。即使是超高性能的SSD磁盘,其读写频率也只能达到10000次/秒左右。如果多个磁盘读写请求的数据在磁盘上是相邻的,就可以被合并为一次读写操作。因为合并后可以等效降低读写频率,所以磁盘顺序读写的性能通常要远优于随机读写。由于如上原因,数据库通常都采用顺序追加的预写日志(write ahead log,WAL)来记录用户事务对数据库页面的修改。对于物理表文件所对应的共享内存中的脏页会等待合适的时机再异步、批量地写入磁盘。
日志可以按照用户对数据库不同的操作类型分为以下几类,每种类型日志分别对应一种资源管理器,负责封装该日志的子类、具体结构以及回放逻辑等。如表4-32所示。
openGauss日志文件、页面和日志记录的格式如图4-32所示。
日志文件在逻辑意义上是一个最大长度为64位无符号整数的连续文件。在物理分布上,该逻辑文件按XLOG_SEG_SIZE大小(默认为16MB)切断,每段日志文件的命名规则为“时间线+日志id号+该id内段号”。“时间线”用于表示该日志文件属于数据库的哪个“生命历程”,在时间点恢复功能中使用。“日志id号”从0开始,按每4G大小递增加1。“id内段号”表示该16MB大小的段文件在该4G“日志id号”内是第几段,范围为0至255。上面3个值在日志段文件名中都以16进制方式显示。
每个日志段文件都可以用XLOG_BLCKSZ(默认8kB)为单位,划分为多个页面。每个8kB页面中,起始位置为页面头,如果该页是整个段文件的第一个页面,那么页面头为一个长页头(XLogLongPageHeader),否则为一个正常页头(短页头)(XLogPageHeader)。在页头之后跟着一条或多条日志记录。每个日志记录对应一个数据库的某种操作。为了降低日志记录的大小(日志写入磁盘时延是影响事务时延的主要因素之一),每条日志内部都是紧密排列的。各条日志之间按8字节(64位系统)对齐。一条日志记录可以跨两个及以上的日志页面,其最大长度限制为1G。对于跨页的日志记录,其后续日志页面页头的标志位XLP_FIRST_IS_CONTRECORD会被置为1。
长、短页头结构体的定义如下,其中存储了用于校验的magic信息、页面标志位信息、时间线信息、页面(在整个逻辑日志文件中的)偏移信息、有效长度信息、系统识别号信息、段尺寸信息、页尺寸信息等。
短页头结构体的代码如下:
uint16 xlp_magic; /* 日志magic校验信息 */
uint16 xlp_info; /* 标志位 */
TimeLineID xlp_tli; /* 该页面第一条日志的时间线 */
XLogRecPtr xlp_pageaddr; /* 该页面起始位置的lsn */
uint32 xlp_rem_len; /*如果是跨页记录,本字段描述该跨页记录在本页面内的剩余长度 */
} XLogPageHeaderData;