Postgresql源码(155)Redo系列CLOG Redo (RM_CLOG_ID = 3)
总结RM_CLOG_ID处理的是PG提交日志的修改。CLOG记录每个事务的最终状态(in_progress/committed/aborted/sub_committed)存储在pg_xact/目录下的 SLRU 文件中。CLOG WAL只有两种:初始化页面(ZEROPAGE)和truncate旧页面(TRUNCATE)。事务提交流程是顺序是 先WAL落盘、在CLOG和数据落盘CLOG可以看做数据redo的时候会用wal覆盖CLOG的状态所以CLOG和数据一样需要后写需要永远落后于WAL。CLOG特殊之处: 事务状态的设置(COMMITTED/ABORTED)不通过 CLOG 自己的 WAL而是通过wal的 commit/abort 记录中的TransactionIdSetTreeStatus()函数直接写入CLOG页面。CLOG自己的wal记录只负责页面生命周期管理(创建销毁)。CommitTransaction() │ ▼ XactLogCommitRecord() │ 写一条 XACT 类型的 WAL 记录 (XLOG_XACT_COMMIT) │ 这条记录包含: 事务ID、提交时间戳、子事务列表等 ▼ TransactionIdCommitTree() │ ▼ TransactionIdSetTreeStatus() │ 直接修改 CLOG 共享内存中的页面 │ 把对应事务的 2-bit 状态从 IN_PROGRESS 改为 COMMITTED ▼ CLOG 页面变脏, 后续由 checkpoint 刷盘clog两类redo总结WAL 类型宏值redo 核心动作触发条件ZEROPAGECLOG_ZEROPAGE0x00ZeroCLOGPage()SimpleLruWritePage()nextXid 跨越到新 CLOG 页面(每 32768 个 xid)TRUNCATECLOG_TRUNCATE0x10AdvanceOldestClogXid()SimpleLruTruncate()VACUUM 清理不再需要的旧事务状态1 WAL Record 类型与结构体事务状态定义/* src/include/access/clog.h *//* * 事务的四种可能状态 -- 注意: 全零(0x00)是初始状态(IN_PROGRESS) * 每个事务占用 2 bit, 一个 8KB CLOG 页面可以存储 8192*4 32768 个事务 */typedefintXidStatus;#defineTRANSACTION_STATUS_IN_PROGRESS0x00/* 进行中 */#defineTRANSACTION_STATUS_COMMITTED0x01/* 已提交 */#defineTRANSACTION_STATUS_ABORTED0x02/* 已中止 */#defineTRANSACTION_STATUS_SUB_COMMITTED0x03/* 子事务已提交(父事务尚未提交) */数据结构/* src/include/access/clog.h *//* * xl_clog_truncate: CLOG truncate操作的 WAL 记录数据 * 当 VACUUM 清理旧事务时, 不再需要的 CLOG 页面可以被truncate */typedefstructxl_clog_truncate{intpageno;/* truncate到的页面编号(保留此页面及之后的) */TransactionId oldestXact;/* 最老的仍需保留的事务 ID */Oid oldestXactDb;/* 该最老事务所属的数据库 OID */}xl_clog_truncate;CLOG_ZEROPAGE 的数据: 仅一个int pageno(不需要结构体直接写)。2 GDB触发 SQL 示例CLOG_ZEROPAGE (0x00) – 新 CLOG 页面初始化-- CLOG_ZEROPAGE 在事务 ID 耗尽当前 CLOG 页面范围时自动触发-- 每个 CLOG 页面覆盖 32768 个事务(8KB * 4 xid/byte)-- 当 nextXid 跨越到需要新页面时, ExtendCLOG() 会零化新页面并写入 WAL-- 通常通过大量事务来触发:-- (在测试中难以直接触发, 因为需要恰好跨越 32K xid 边界)BEGIN;INSERTINTOtVALUES(1);COMMIT;-- ... 重复直到 xid 跨越 CLOG 页面边界CLOG_TRUNCATE (0x10) – VACUUM 清理旧 CLOG 页面-- 当 autovacuum 或手动 VACUUM 运行时, 如果发现很老的事务已不再需要,-- 会调用 TruncateCLOG() truncate过时的 CLOG 段文件VACUUM;-- 也可以通过 pg_controldata 查看当前 oldestXid,-- 确认哪些 CLOG 页面可以安全truncate3 Redo 核心代码 (带中文注释)/* src/backend/access/transam/clog.c:984-1019 *//* * CLOG 资源管理器的 redo 入口函数 */voidclog_redo(XLogReaderState*record){uint8 infoXLogRecGetInfo(record)~XLR_INFO_MASK;/* CLOG 记录不使用 backup block(全页写) */Assert(!XLogRecHasAnyBlockRefs(record));if(infoCLOG_ZEROPAGE){/* * 处理 ZEROPAGE: 零化新 CLOG 页面 * 当事务 ID 增长到需要新 CLOG 页面时, * 必须先将该页面清零(因为 0x00 IN_PROGRESS 是正确的初始状态) */intpageno;intslotno;/* 从 WAL data 中取出页面编号 */memcpy(pageno,XLogRecGetData(record),sizeof(int));/* 获取 SLRU 排他锁, 防止并发访问 */LWLockAcquire(XactSLRULock,LW_EXCLUSIVE);/* 零化指定页面: false 表示不写 WAL(因为我们正在回放 WAL) */slotnoZeroCLOGPage(pageno,false);/* 立即将零化后的页面写入磁盘, 确保持久化 */SimpleLruWritePage(XactCtl,slotno);Assert(!XactCtl-shared-page_dirty[slotno]);LWLockRelease(XactSLRULock);}elseif(infoCLOG_TRUNCATE){/* * 处理 TRUNCATE: truncate旧 CLOG 段文件 * 将不再需要的旧 CLOG 页面从磁盘删除 */xl_clog_truncate xlrec;memcpy(xlrec,XLogRecGetData(record),sizeof(xl_clog_truncate));/* 更新全局的 oldestClogXid, 表示更老的事务不再需要查看 CLOG */AdvanceOldestClogXid(xlrec.oldestXact);/* 执行 SLRU truncate: 删除 pageno 之前的所有段文件 */SimpleLruTruncate(XactCtl,xlrec.pageno);}elseelog(PANIC,clog_redo: unknown op code %u,info);}4 GDB 调试在本次测试中CLOG_ZEROPAGE 未被触发。这是正常的因为 ZEROPAGE 只在事务 ID 增长到需要分配新 CLOG 页面时才会产生每 32768 个事务一次。未触发原因分析:CLOG 每页存储 32768 个事务的状态(8KB page, 每个 xid 占 2 bit)只有当 nextXid 跨越到新页面范围时ExtendCLOG()才会调用ZeroCLOGPage()并写入 CLOG_ZEROPAGE WAL 记录在常规小规模测试中事务 ID 通常不会跨越页面边界如果要在 GDB 中捕获 CLOG_ZEROPAGE可以:# 在 clog_redo 设置断点 break clog_redo # 或者在写入端 break clog.c:ZeroCLOGPage5 官方文档src/backend/access/transam/READMEpg_xact and pg_subtrans are permanent (on-disk) storage of transaction relatedinformation. There is a limited number of pages of each kept in memory, soin many cases there is no need to actually read from disk. However, ifthere’s a long running transaction or a backend sitting idle with an opentransaction, it may be necessary to be able to read and write this informationfrom disk.CLOG (pg_xact) 是事务状态的持久化存储使用 SLRU(简单 LRU)机制管理内存中的有限页面。大部分读取来自内存缓存只有长事务或空闲事务才需要磁盘 I/O。异步提交与 CLOG LSNFirst, for each page of CLOG we must remember the LSN of the latest commitaffecting the page, so that we can enforce the same flush-WAL-before-writerule that we do for ordinary relation pages. Otherwise the record of thecommit might reach disk before the WAL record does.CLOG 页面也遵守 WAL-first 规则。每个 CLOG 页面都有关联的 LSN确保提交记录的 WAL 在 CLOG 数据刷盘之前先持久化。子事务与 CLOG 的两阶段协议The main role of marking transactions as sub-committed is to provide anatomic commit protocol when transaction status is spread across multiple clogpages. Whenever transaction status spreads across multiple pages we must usea two-phase commit protocol: the first phase is to mark the subtransactionsas sub-committed, then we mark the top level transaction and all itssubtransactions committed (in that order).当一个事务树的状态跨越多个 CLOG 页面时使用两阶段提交协议:第一阶段: 将子事务标记为 SUB_COMMITTED (0x03)第二阶段: 将顶层事务和所有子事务标记为 COMMITTED (0x01)这确保了即使在跨页面时事务提交的原子性也能得到保证。Recovery 期间的 CLOG 处理Not all transactional behaviour is emulated, for example we do not inserta transaction entry into the lock table, nor do we maintain the transactionstack in memory. Clog, multixact and commit_ts entries are made normally.在 WAL 恢复期间CLOG 条目的写入与正常运行时完全一致。恢复进程会正常维护 CLOG确保 Hot Standby 查询能获取正确的事务可见性信息。附录redo调用的SimpleLruTruncate函数分析注意: 这里的truncate不是清空文件而是删除文件。CLOG 数据存储在pg_xact/目录下的多个段文件中(如0000,0001,0002…),每个段文件包含 32 个页面(32 × 8KB 256KB)。当 VACUUM 判定某些旧事务的状态不再需要时,SimpleLruTruncate()会扫描目录并unlink(删除) 那些完全落在 cutoffPage 之前的段文件。直观理解: pg_xact/ 目录下的段文件: ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ 0000 │ │ 0001 │ │ 0002 │ │ 0003 │ │ 0004 │ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ page 0-31 32-63 64-95 96-127 128-159 假设 cutoffPage 70 (即页面70之前的都不需要了) 段 0000 (page 0-31): 31 70 → unlink 删除 ✓ 段 0001 (page 32-63): 63 70 → unlink 删除 ✓ 段 0002 (page 64-95): 64 70 但 95 70 → 保留 ✗ (cutoff 落在段内部) 段 0003 (page 96-127): 96 70 → 保留 ✗ 段 0004 (page 128-159):128 70 → 保留 ✗ 结果: 文件 0000 和 0001 被 unlink 删除, 0002 及之后的文件完整保留(不做 ftruncate)源码/* src/backend/access/transam/slru.c */voidSimpleLruTruncate(SlruCtl ctl,intcutoffPage){SlruShared sharedctl-shared;intslotno;/* 更新 truncate 统计计数器 */pgstat_count_slru_truncate(shared-slru_stats_idx);/* * 第一阶段: 清理共享内存中的 SLRU 缓冲区 * * 扫描共享内存中的所有 SLRU buffer slot, * 将属于 cutoffPage 之前的页面从缓冲区中驱逐出去, * 防止后续意外将旧页面重新写回磁盘。 * * (这通常发生在 checkpoint 之后, 脏页应该已经落盘, * 这里只是做额外的安全保障。) */LWLockAcquire(shared-ControlLock,LW_EXCLUSIVE);restart:/* * 安全检查: 最新页面不能被 truncate。 * 如果出现这种情况, 说明可能发生了 XID 回绕异常。 */if(ctl-PagePrecedes(shared-latest_page_number,cutoffPage)){LWLockRelease(shared-ControlLock);ereport(LOG,(errmsg(could not truncate directory \%s\: apparent wraparound,ctl-Dir)));return;}for(slotno0;slotnoshared-num_slots;slotno){if(shared-page_status[slotno]SLRU_PAGE_EMPTY)continue;/* 只处理 cutoffPage 之前的页面 */if(!ctl-PagePrecedes(shared-page_number[slotno],cutoffPage))continue;/* * 如果页面是干净的(VALID且不脏), 直接标记为 EMPTY。 * 这是最常见的情况。 */if(shared-page_status[slotno]SLRU_PAGE_VALID!shared-page_dirty[slotno]){shared-page_status[slotno]SLRU_PAGE_EMPTY;continue;}/* * 如果页面正在进行 I/O 或者是脏页: * - 脏页: 先写回磁盘再驱逐 * - I/O中: 等待 I/O 完成 * 然后重新开始扫描(因为释放锁期间状态可能变化)。 */if(shared-page_status[slotno]SLRU_PAGE_VALID)SlruInternalWritePage(ctl,slotno,NULL);elseSimpleLruWaitIO(ctl,slotno);gotorestart;}LWLockRelease(shared-ControlLock);/* * 第二阶段: 扫描磁盘目录, 删除旧的段文件 * * 扫描 pg_xact/ 目录下的所有文件, * 对每个文件调用 SlruScanDirCbDeleteCutoff 回调, * 判断该段文件是否完全在 cutoffPage 之前, * 如果是则调用 unlink() 删除。 */(void)SlruScanDirectory(ctl,SlruScanDirCbDeleteCutoff,cutoffPage);}函数调用SimpleLruTruncate(ctl, cutoffPage) │ ├── 第一阶段: 清理共享内存缓冲区 │ └── 遍历 shared-page_status[0..num_slots-1] │ ├── 干净页面 → page_status SLRU_PAGE_EMPTY (驱逐) │ ├── 脏页面 → SlruInternalWritePage() → 写回磁盘后驱逐 │ └── I/O中 → SimpleLruWaitIO() → 等待完成后重试 │ └── 第二阶段: 删除磁盘文件 └── SlruScanDirectory(ctl, SlruScanDirCbDeleteCutoff, cutoffPage) │ ├── AllocateDir(ctl-Dir) // 打开 pg_xact/ 目录 │ ├── 对目录中每个合法文件名 (如 0000, 001F): │ │ segno strtol(filename, 16) // 十六进制解析段号 │ │ segpage segno * 32 // 段的首页编号 │ │ │ └── SlruScanDirCbDeleteCutoff(ctl, filename, segpage, cutoffPage) │ │ │ └── SlruMayDeleteSegment(ctl, segpage, cutoffPage) │ │ // 判断条件: 段的首页和尾页都在 cutoffPage 之前 │ │ seg_last_page segpage 31 │ │ return PagePrecedes(segpage, cutoffPage) │ │ PagePrecedes(seg_last_page, cutoffPage) │ │ │ ├── true → SlruInternalDeleteSegment(ctl, segno) │ │ │ │ │ ├── RegisterSyncRequest(SYNC_FORGET_REQUEST) │ │ │ // 取消该段的待 fsync 请求 │ │ │ │ │ └── unlink(path) ← 真正删除文件! │ │ │ └── false → 跳过, 保留该段文件 │ └── FreeDir()SlruInternalDeleteSegment – 最终unlink删除/* src/backend/access/transam/slru.c */staticvoidSlruInternalDeleteSegment(SlruCtl ctl,intsegno){charpath[MAXPGPATH];/* 取消该段的待 fsync 请求(文件都要删了, 不需要再 fsync) */if(ctl-sync_handler!SYNC_HANDLER_NONE){FileTag tag;INIT_SLRUFILETAG(tag,ctl-sync_handler,segno);RegisterSyncRequest(tag,SYNC_FORGET_REQUEST,true);}/* 构造文件路径并调用 unlink() 删除文件 */SlruFileName(ctl,path,segno);ereport(DEBUG2,(errmsg_internal(removing file \%s\,path)));unlink(path);/* ← 这里! 不是 ftruncate, 是 unlink, 直接删除文件 */}

相关新闻

【论文】Agentic NL2SQL to Reduce Computational Costs

【论文】Agentic NL2SQL to Reduce Computational Costs

Agentic NL2SQL to Reduce Computational Costs arxiv 下面是在原有框架基础上,按你要求“最开头附上链接并给出文章名”整理后的完整分析。 一. 翻译摘要原文 将自然语言查询转换为SQL查询(NL2SQL 或 Text-to-SQL)最近得益于大型语言模型&…

2026/5/17 11:06:10 阅读更多 →
【量化工具推荐】期货量化交易调试工具推荐:10款工具深度分析

【量化工具推荐】期货量化交易调试工具推荐:10款工具深度分析

一、前言 做期货量化交易,调试是开发过程中不可或缺的环节。好的调试工具能帮你快速定位问题、验证逻辑、优化策略。不同的调试工具有不同的特点——有的集成在IDE中,有的支持断点,有的适合异步和事件驱动,有的专注性能分析。 本…

2026/7/3 0:50:54 阅读更多 →
浙江大学团队:AI实现真人级实时直播互动能力

浙江大学团队:AI实现真人级实时直播互动能力

对话已经成为我们数字生活的核心部分。当你在直播间看到主播娓娓道来,在游戏解说中听到精彩点评,或在学习时获得及时指导,这些看似简单的互动背后,其实隐藏着巨大的技术挑战。现在,一项来自浙江大学、深圳大学、华南理…

2026/5/17 6:55:52 阅读更多 →

最新新闻

抓包、TLS 指纹、UA 一致性分析工具

抓包、TLS 指纹、UA 一致性分析工具

TLSFOWARD:一款集抓包、TLS指纹分析与UA一致性验证于一体的专业工具 在接口调试、浏览器环境分析、爬虫环境排查以及测试排查等场景中,抓包是一项非常基础且常见的操作。 然而,仅仅查看 HTTP 请求往往是不够的。因为 User-Agent 可以被修改&a…

2026/7/3 3:48:58 阅读更多 →
继承、重载与多态

继承、重载与多态

继承是C中的一个重要特性&#xff0c;它可以让我们从一个类的部分成员继承并新建立一个类&#xff0c;class <派生类名> : <继承方式(public/protected/private)> <基类名>例如&#xff1a;//基类 class Animal{eat(); sleep(); }//派生类 class Dog : publi…

2026/7/3 3:46:58 阅读更多 →
2026年AI网站设计公司排名,品牌视觉定制企业盘点

2026年AI网站设计公司排名,品牌视觉定制企业盘点

2026年AI网站设计公司排名&#xff0c;品牌视觉定制企业盘点一、品牌视觉定制市场的需求变化2026年&#xff0c;企业官网已经从“有就行”升级到了“好看且好用”。据艾瑞咨询联合IDC发布的《2026年中国企业数字化建站行业白皮书》显示&#xff0c;2026年中国网站建设行业整体市…

2026/7/3 3:44:57 阅读更多 →
DeepSeek-V4定价逻辑:隐性成本优化与企业级AI落地新范式

DeepSeek-V4定价逻辑:隐性成本优化与企业级AI落地新范式

1. 这不是“买菜砍价”&#xff0c;而是大模型时代的价格认知重构DeepSeek-V4发布后&#xff0c;朋友圈和开发者群最常刷屏的一句话是&#xff1a;“这价格&#xff0c;是不是标错了&#xff1f;”——不是调侃&#xff0c;是真有人反复刷新官网页面确认。我第一时间拉了三台不…

2026/7/3 3:42:57 阅读更多 →
5分钟掌握VinXiangQi:高效实用的AI象棋连线工具终极指南

5分钟掌握VinXiangQi:高效实用的AI象棋连线工具终极指南

5分钟掌握VinXiangQi&#xff1a;高效实用的AI象棋连线工具终极指南 【免费下载链接】VinXiangQi Xiangqi syncing tool based on Yolov5 / 基于Yolov5的中国象棋连线工具 项目地址: https://gitcode.com/gh_mirrors/vi/VinXiangQi 你是否经常在网上对弈时遇到瓶颈&…

2026/7/3 3:42:56 阅读更多 →
Uniapp上架苹果4.3a被拒?我摸出了躺过的万能公式!

Uniapp上架苹果4.3a被拒?我摸出了躺过的万能公式!

家人们谁懂这种崩溃啊&#x1f62b; 熬了快一个月的Uniapp项目&#xff0c;改了八版交互测了无数遍兼容性&#xff0c;打包完兴冲冲点提交&#xff0c;隔天直接收到苹果爸爸的4.3a拒信大礼包&#xff01;红色警告大字写着“你的App只是网页的简单复制&#xff0c;没有提供足够的…

2026/7/3 3:38:55 阅读更多 →

日新闻

Nginx防御TLS重协商攻击实战:从原理到配置与监控

Nginx防御TLS重协商攻击实战:从原理到配置与监控

1. 项目概述&#xff1a;为什么TLS重协商攻击至今仍需警惕十多年前的CVE-2011-1473&#xff0c;一个关于TLS/SSL协议重协商机制的漏洞&#xff0c;现在提起来还有必要吗&#xff1f;很多运维和开发朋友可能会觉得&#xff0c;这都老掉牙了&#xff0c;现代服务器和客户端不都默…

2026/7/3 0:03:59 阅读更多 →
华为防火墙双通道远程管理实战:Web与SSH配置详解

华为防火墙双通道远程管理实战:Web与SSH配置详解

1. 项目概述&#xff1a;为什么需要双通道远程管理防火墙&#xff1f;在任何一个稍具规模的企业网络里&#xff0c;防火墙都是那个默默守护在边界的关键角色。作为网络工程师&#xff0c;我们不可能每次都跑到机房&#xff0c;插上console线去配置它。远程管理能力&#xff0c;…

2026/7/3 0:03:59 阅读更多 →
AD74413R与PIC18F65K40的高精度工业数据采集方案

AD74413R与PIC18F65K40的高精度工业数据采集方案

1. 项目概述&#xff1a;AD74413R与PIC18F65K40的协同工作在工业自动化和精密测量领域&#xff0c;同时实现高精度模数转换(ADC)和数模转换(DAC)功能是许多复杂系统的核心需求。AD74413R作为一款四通道可配置模拟输入/输出器件&#xff0c;与PIC18F65K40微控制器的组合&#xf…

2026/7/3 0:05:59 阅读更多 →

周新闻

月新闻