当前位置: 首页 > news >正文

海山数据库(He3DB)源码详解:He3DB-XLogWrite函数

XLogWrite函数

  • 函数定义
static void 
XLogWrite(XLogwrtRqst WriteRqst, TimeLineID tli, bool flexible)
  • XLogwrtRqst:请求写入位置信息
  • TimeLineID:时间线,表示一个从创建点当前点的完整数据库历史
  • bool flexible:指示写入操作是否可以灵活处理,即不必严格按照WriteRqst指定的位置进行写入,可以在方便的边界(如缓存或日志文件的边界)停止
  • 该函数调用必须处于临界区
Assert(CritSectionCount > 0);
  • 保护共享资源,避免数据竞争
  • CritSectionCount 变量见XLogFlush中开启临界区函数,会使该变量++
  • 更新写入和刷新的位置result
LogwrtResult = XLogCtl->LogwrtResult;
  • 初始化变量
npages = 0;
startidx = 0;
startoffset = 0;
curridx = XLogRecPtrToBufIdx(LogwrtResult.Write);
  • npages:表示可以连续写入磁盘的 WAL 页面数量(待转储的页面数)。由于 WAL 页面在内存中通常是连续分配的,因此可以优化写入操作,通过一次磁盘 I/O 操作写入多个页面,从而减少磁盘操作的次数,提高性能

被初始化为 0,表示还没有找到任何可以连续写入的页面

  • startidx:表示可以连续写入的 WAL 页面序列中第一个页面的缓存块索引。这个索引用于在 WAL 缓存中定位起始页面,以便从那里开始收集可以连续写入的页面。

被初始化为 0 或某个无效值,表示还没有确定起始页面

  • startoffset:表示在 WAL 日志文件中,第一个可以写入的页面应该被写入的位置(即文件偏移量)。这个偏移量允许系统知道从哪里开始写入这些页面,以确保 WAL 日志的完整性和顺序性。

索引startidx和偏移量startoffset就决定了WAL日志写入的位置

  • curridx:代表当前正在考虑的 WAL 缓存页面的索引,该索引由XLogRecPtrToBufIdx(LogwrtResult.Write);返回

该函数将传入的已经写入的末位置转换为缓存页面的索引

  • 开始循环
while (LogwrtResult.Write < WriteRqst.Write)
{}

该循环只能在请求写入的位置大于已经写入位置才能进行


XLogRecPtr EndPtr = XLogCtl->xlblocks[curridx];if (LogwrtResult.Write >= EndPtr)elog(PANIC, "xlog write request %X/%X is past end of log %X/%X",LSN_FORMAT_ARGS(LogwrtResult.Write),LSN_FORMAT_ARGS(EndPtr));
  • 赋值末尾位置给EndPtr,可以理解为从EndPtr开始,要开始写入了,EndPtr是上一个的结尾,即当前缓冲区的结束位置
  • 如果已经写入的位置比将要开始写的位置(或者说上一个结束的位置)要大,则报PANIC日志错误,并输出二者位置信息

LogwrtResult.Write = EndPtr;
ispartialpage = WriteRqst.Write < LogwrtResult.Write;
  • 将已经写入的位置更新为当前缓冲区的结束位置
  • 记录请求写入位置是否小于当前缓冲区结束位置

if (!XLByteInPrevSeg(LogwrtResult.Write, openLogSegNo, wal_segment_size))
{Assert(npages == 0);if (openLogFile >= 0)XLogFileClose();XLByteToPrevSeg(LogwrtResult.Write, openLogSegNo, wal_segment_size);openLogTLI = tli;/* create/use new log file */openLogFile = XLogFileInit(openLogSegNo, tli);ReserveExternalFD();
}
  • XLByteInPrevSeg函数:判断当前已经写入的位置(LogwrtResult.Write)是否在当前打开的日志分段(openLogSegNo)内,并且考虑日志分段的大小(wal_segment_size

如果该函数返回false,表示当前的写入操作已经超出了当前日志分段的范围,因此需要切换到新的日志分段。

  • Assert(npages == 0); 若条件为false,表示有连续写入的页数,会触发错误。即要切换新分段之前,要确保清空所有待处理的数据才能进行
  • if (openLogFile >= 0)如果之前已经打开日志文件了,调用XLogFileClose();关闭
  • XLByteToPrevSeg(LogwrtResult.Write, openLogSegNo, wal_segment_size);更新日志分段(logSegNoopenLogSegNo)的位置

注意:XLByteInPrevSeg函数和XLByteToPrevSeg函数的区别

#define XLByteInPrevSeg(xlrp, logSegNo, wal_segsz_bytes) \((((xlrp) - 1) / (wal_segsz_bytes)) == (logSegNo))#define XLByteToPrevSeg(xlrp, logSegNo, wal_segsz_bytes) \logSegNo = ((xlrp) - 1) / (wal_segsz_bytes)
  • openLogTLI = tli; 更新时间线,为了让新的日志分段能正确反应当前时间线
  • openLogFile = XLogFileInit(openLogSegNo, tli);创建并初始化日志文件
  • ReserveExternalFD();在系统中为外部使用预留一个文件描述符(FD)。文件描述符:它是一个索引值,指向内核中每个进程打开文件的记录表。当打开一个文件(或设备、管道等)时,内核会向进程返回一个文件描述符。这个文件描述符随后被用于后续的读、写或其他文件操作。

  • 如果没有打开文件,则打开当前日志文件
if (openLogFile < 0)
{XLByteToPrevSeg(LogwrtResult.Write, openLogSegNo, wal_segment_size);openLogTLI = tli;openLogFile = XLogFileOpen(openLogSegNo, tli);ReserveExternalFD();
}

if (npages == 0)
{startidx = curridx;startoffset = XLogSegmentOffset(LogwrtResult.Write - XLOG_BLCKSZ, wal_segment_size);
}
npages++;
  • 如果待转储的页面数为0,这意味着当前页面是这一组待转储页面中的第一个。则更新索引和偏移量
  • 不管是不是第一页,待转储的页面数都+1。这表示当前页面已经被添加到待转储的页面集合中。

last_iteration = WriteRqst.Write <= LogwrtResult.Write;finishing_seg = !ispartialpage &&(startoffset + npages * XLOG_BLCKSZ) >= wal_segment_size;
  • 如果请求写入的位置小于等于已经写入的位置,则表明这是最后一次循环迭代last_iteration
  • 检查是否正在完成一个段的写入

if (last_iteration ||curridx == XLogCtl->XLogCacheBlck ||finishing_seg)
{}
  • 条件判断:如果是最后一次迭代;或者当前索引是缓存块索引、或正在完成一个WAL段的写入

如果满足条件,则进行下一步:
定义变量,并赋值

from = XLogCtl->pages + startidx * (Size) XLOG_BLCKSZ;
nbytes = npages * (Size) XLOG_BLCKSZ;
nleft = nbytes;
  • from 计算要写入的起始位置
  • nbytes 总字节数
  • nleft 剩余未写入字节数,初始化剩余字节数为总字节数

开始循环,目的是将nleft减到0,即完成所有数据的写入

do  
{  ...  
} while (nleft > 0);
errno = 0;if (track_wal_io_timing)INSTR_TIME_SET_CURRENT(start);
  • 在循环内部,首先设置errno为0
  • 如果启用了WAL I/O时间跟踪,则测量I/O操作的开始时间
pgstat_report_wait_start(WAIT_EVENT_WAL_WRITE);
written = pg_pwrite(openLogFile, from, nleft, startoffset);
pgstat_report_wait_end();
  • 报告WAL写入的等待开始
  • pg_pwrite 写入函数,用于将nleft字节从from指向的位置写入到openLogFile指定的文件中,从startoffset偏移量开始
  • 报告等待结束
if (track_wal_io_timing)
{instr_time	duration;INSTR_TIME_SET_CURRENT(duration);INSTR_TIME_SUBTRACT(duration, start);PendingWalStats.wal_write_time += INSTR_TIME_GET_MICROSEC(duration);
}
  • 如果启用了WAL I/O时间跟踪
  • 获取当前时间
  • 计算从start到当前时间的差值,并将结果存储在duration
  • 将计算出的时间差(以微秒为单位)加到PendingWalStats.wal_write_time
if (written <= 0)
{char		xlogfname[MAXFNAMELEN];int			save_errno;if (errno == EINTR)continue;save_errno = errno;XLogFileName(xlogfname, tli, openLogSegNo,wal_segment_size);errno = save_errno;ereport(PANIC,(errcode_for_file_access(),errmsg("could not write to log file %s ""at offset %u, length %zu: %m",xlogfname, startoffset, nleft)));
}

如果写入错误written <= 0

  • 检查errno是否为EINTREINTR是一个特殊的错误代码,表示系统调用被信号中断
  • 记录并保存errnosave_errno
  • 生成WAL文件名:XLogFileName函数根据给定的时间线ID(tli)、打开的日志段号(openLogSegNo)和WAL段大小(wal_segment_size),生成WAL文件的名称,并存储在xlogfname数组中。这个文件名将用于错误报告中,以帮助诊断问题。
  • 更新errno
  • 报告错误
nleft -= written;
from += written;
startoffset += written;

如果没有写入错误,则更新对应的剩余写入字符、起始位置指针以及偏移量

npages = 0;
剩余字节数都写完后,即该do while循环结束,则将待转储页面置为0

if (finishing_seg)
{issue_xlog_fsync(openLogFile, openLogSegNo, tli);WalSndWakeupRequest();LogwrtResult.Flush = LogwrtResult.Write;	/* end of page */if (XLogArchivingActive())XLogArchiveNotifySeg(openLogSegNo, tli);XLogCtl->lastSegSwitchTime = (pg_time_t) time(NULL);XLogCtl->lastSegSwitchLSN = LogwrtResult.Flush;if (IsUnderPostmaster && XLogCheckpointNeeded(openLogSegNo)){(void) GetRedoRecPtr();if (XLogCheckpointNeeded(openLogSegNo))RequestCheckpoint(CHECKPOINT_CAUSE_XLOG);}
}

如果完成对一个段的写入:

  • 调用issue_xlog_fsync(openLogFile, openLogSegNo, tli);来立即同步这个文件段到磁盘
  • 唤醒WAL发送者。因为上面已经同步到磁盘了,可以通过流复制,发送到节点
  • 更新已完成flush的位置。将日志写入结果中的Flush更新为Write的值,表示当前页面已经被完全写入并准备同步
  • 通知归档器:如果WAL归档功能XLogArchivingActive()是激活的,代码会调用XLogArchiveNotifySeg(openLogSegNo, tli);来通知归档器这个新的日志文件段已经准备好被复制到归档存储中。
  • 更新最后切换时间和LSN
  • 如果处于Postmaster下且需要检查点
  • 更新重做记录指针
  • 再次检查是否需要检查点

请求检查点

检查点是数据库用来减少恢复时间的一种机制,它确保了数据库可以从一系列固定的点快速恢复

if (ispartialpage)
{LogwrtResult.Write = WriteRqst.Write;break;
}
curridx = NextBufIdx(curridx);if (flexible && npages == 0)break;
  • ispartialpage为真,表示当前请求只要求写入一个部分页面,不是整页。则设置已经写入的位置值为请求写入的位置值,并跳出循环
  • 更新当前索引
  • 如果写入操作是灵活的(即可以根据实际情况提前结束),并且到目前为止还没有写入任何页面,那么就可以停止写入过程
    循环结束

  • 条件判断
if (LogwrtResult.Flush < WriteRqst.Flush && LogwrtResult.Flush < LogwrtResult.Write)
{}

如果已经刷新的日志位置同时小于请求刷新的位置以及已经完成写入的位置(这表明有部分数据已经写入但未刷新到磁盘,且这部分是请求要求刷新的部分)

  • 再次判断
if (sync_method != SYNC_METHOD_OPEN &&sync_method != SYNC_METHOD_OPEN_DSYNC)
{if (openLogFile >= 0 &&!XLByteInPrevSeg(LogwrtResult.Write, openLogSegNo,wal_segment_size))XLogFileClose();if (openLogFile < 0){XLByteToPrevSeg(LogwrtResult.Write, openLogSegNo,wal_segment_size);openLogTLI = tli;openLogFile = XLogFileOpen(openLogSegNo, tli);ReserveExternalFD();}issue_xlog_fsync(openLogFile, openLogSegNo, tli);
}

作用是用于判断是否需要进行文件操作

  1. 如果openLogFile有效,并且当前写入位置LogwrtResult.Write不在当前打开的日志文件段内(通过XLByteInPrevSeg函数检查),则关闭当前文件
  2. 如果openLogFile无效(即小于0),则根据LogwrtResult.Write计算应该打开哪个日志文件段(XLByteToPrevSeg),然后打开该文件(XLogFileOpen),并保留一个外部文件描述符(ReserveExternalFD)。

无论是否进行了文件关闭和重新打开操作,都会调用issue_xlog_fsync函数来刷新当前打开的日志文件段到磁盘。这个函数负责将文件描述符openLogFile指定的文件段同步到磁盘。

  • 唤醒发送者。因为刷新操作可能意味着有新的WAL数据可供流复制
  • 更新已经刷新的日志位置

{SpinLockAcquire(&XLogCtl->info_lck);XLogCtl->LogwrtResult = LogwrtResult;if (XLogCtl->LogwrtRqst.Write < LogwrtResult.Write)XLogCtl->LogwrtRqst.Write = LogwrtResult.Write;if (XLogCtl->LogwrtRqst.Flush < LogwrtResult.Flush)XLogCtl->LogwrtRqst.Flush = LogwrtResult.Flush;SpinLockRelease(&XLogCtl->info_lck);
}
  • 更新共享内存的状态
  1. 上自旋锁
  2. LogwrtResult中最新的日志写入和刷新状态更新到XLogCtl->LogwrtResult
  3. 分别更新XLogCtl中请求写入和刷新的位置
  4. 释放自旋锁

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 揭秘CAAC、AOPA、ALPA、ASFC和UTC无人机执照的差别及实用价值
  • MySQL的延迟复制
  • MySQL存储过程详细讲解和常见问题及性能优化
  • QT opencv(显示图片和视频)
  • 抢单源码修正版,带教程,自动抓取订单,十几种语言可自动切换
  • [数据集][目标检测]电力场景输电线防震锤检测数据集VOC+YOLO格式2721张2类别
  • 在AES加密中,设主密钥为“2B 7E 15 16 28 AE D2 A6 AB F7 15 88 09 CF 4F 3C”,试计算迭代第1轮使用的轮密钥。
  • 合合信息文档解析Coze插件发布,PDF转Markdown功能便捷集成
  • 云计算实训32——roles基本用法、使用剧本安装nginx、使用roles实现lnmp
  • AI入门指南(四):分类问题、回归问题、监督、半监督、无监督学习是什么?
  • 【3】AT32F437 OpenHarmony轻量系统第一个程序:点灯
  • C#与其它编程语言有什么区别,以及相关优势有哪些
  • Java 事务管理:确保数据一致性
  • FPGA开发——DS18B20读取温度并且在数码管上显示
  • 【达梦数据库】锁超时的处理方法-错误码[-6407]
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • CentOS 7 修改主机名
  • ES6简单总结(搭配简单的讲解和小案例)
  • Flex布局到底解决了什么问题
  • HTML中设置input等文本框为不可操作
  • JavaScript服务器推送技术之 WebSocket
  • Java应用性能调优
  • Laravel 菜鸟晋级之路
  • Phpstorm怎样批量删除空行?
  • puppeteer stop redirect 的正确姿势及 net::ERR_FAILED 的解决
  • SegmentFault 2015 Top Rank
  • 成为一名优秀的Developer的书单
  • 复习Javascript专题(四):js中的深浅拷贝
  • 高性能JavaScript阅读简记(三)
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 全栈开发——Linux
  • 如何学习JavaEE,项目又该如何做?
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • TPG领衔财团投资轻奢珠宝品牌APM Monaco
  • ​LeetCode解法汇总2696. 删除子串后的字符串最小长度
  • ​十个常见的 Python 脚本 (详细介绍 + 代码举例)
  • ​学习一下,什么是预包装食品?​
  • !!Dom4j 学习笔记
  • #define 用法
  • #java学习笔记(面向对象)----(未完结)
  • $GOPATH/go.mod exists but should not goland
  • (3) cmake编译多个cpp文件
  • (7)摄像机和云台
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (NO.00004)iOS实现打砖块游戏(九):游戏中小球与反弹棒的碰撞
  • (补充):java各种进制、原码、反码、补码和文本、图像、音频在计算机中的存储方式
  • (第一天)包装对象、作用域、创建对象
  • (二)fiber的基本认识
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (黑客游戏)HackTheGame1.21 过关攻略
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • (译)2019年前端性能优化清单 — 下篇
  • (转)关于多人操作数据的处理策略
  • (转)一些感悟
  • (转)总结使用Unity 3D优化游戏运行性能的经验