postgresql 内核源码分析 clog机制流程 commit log文件格式,分离的原因,分组优化及leader更新机制

clog 介绍

专栏内容

  • postgresql内核源码分析
  • 手写数据库toadb
  • 并发编程

开源贡献

  • toadb开源库

个人主页:我的主页
管理社区:开源数据库
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.

文章目录

  • clog 介绍
  • 前言
  • 概述
  • 文件格式
    • 事务状态
    • 文件内部格式
    • 文件命名
  • clog缓存
  • 事务状态记录
  • 缓存刷到磁盘
    • 缓冲区置换
    • checkpoint时
    • 服务启动、停止时
  • 回收clog段文件
    • truncate段文件
    • 删除段文件
  • 并发控制
    • LRU共享内存锁
    • 写操作
    • 读操作
  • 结尾

前言

PostgreSQL是一种开源的关系型数据库管理系统,其内核源码的分析对于深入理解其工作原理、性能优化以及定制开发等方面都具有重要意义。

PostgreSQL的历史可以追溯到1986年,当时Michael Stonebraker和Eugene Wu在加州大学伯克利分校开始了POSTGRES项目的开发。该项目旨在开发一种具有可扩展性和可靠性的关系型数据库管理系统,以满足日益增长的数据库应用需求。在1994年,POSTGRES被发布为开源软件,并更名为PostgreSQL。

PostgreSQL的特点包括支持ACID事务、支持全文搜索、支持存储过程、支持触发器、支持多版本并发控制(MVCC)等。此外,PostgreSQL还支持多种数据类型、支持多种平台、支持多种编程语言接口等。

对于PostgreSQL内核源码的分析,其目的和意义主要体现在以下几个方面:

  1. 理解工作原理:通过分析内核源码,可以深入理解PostgreSQL的工作原理,包括查询优化、事务管理、并发控制、存储管理等方面的实现细节。
  2. 性能优化:通过分析内核源码,可以找出性能瓶颈,进行针对性的优化,提高数据库的性能和响应速度。
  3. 定制开发:通过分析内核源码,可以根据特定需求进行定制开发,例如实现新的数据类型、实现新的查询优化策略等。
  4. 安全性:通过分析内核源码,可以找出潜在的安全漏洞,进行修复和加固,提高数据库的安全性。
  5. 可靠性:通过分析内核源码,可以深入理解PostgreSQL的可靠性机制,例如备份与恢复、容错处理等方面的实现细节。

PostgreSQL内核源码的分析是一项重要的任务,对于提高数据库的性能、可靠性、安全性和定制开发能力都具有重要意义。

概述

postgresql数据库中,将事务的状态单独存储在文件中,也就是被称为commit log的文件;文件位置clog目录中,通常是在PostgreSQL数据库安装时创建的,其路径可以在postgresql.conf配置文件中找到。默认情况下,clog目录通常位于PostgreSQL数据库安装目录的“data_directory”参数所指定的目录下,其命名可能因版本而异(例如,在PostgreSQL 16版本中,它被命名为“pg_xact”)。

事务状态在数据库运行过程中,会被用来作为MVCC机制的一部分,判断数据的可见性,所以是非常频繁被读取,同时在大量事务运行时,会有大量事务状态的更新;为了高效的存储和查询,将事务状态采用一种简洁的方式存储,可以很快加载到内存,同时又能快速查找到对应事务的状态,这种方式就非常关键。

本文将从以下几方面进行分享:

  • clog文件格式
  • clog缓存
  • 事务状态记录
  • clog的刷盘
  • clog文件回收
  • 并发控制

文件格式

用什么样的文件格式来记录事务状态呢?
首先来看一下事务有那些状态,再分析文件格式如何组织。

事务状态

对于每个xid对应的状态,一般有运行中running,提交commit,中止abort三种;而对于事务而言,还有子事务的存在,也就是嵌套事务,子事务也同样存在这三种状态;

这组合起来就多了,在postgresql中是这样组织的,父子事务的关系,由另一个文件进行记录,而对于clog文件来讲,只是记录事务号与状态信息;那这样就简单了。

事务状态分为四种:

  • 运行状态
  • 已经提交
  • 中止状态
  • 子事务已提交

这里是不是又有点晕呢? 子事务只记录提交状态,而没有abort状态;
其实子事务的abort状态,并没有区分子事务和父事务,都共用abort状态;这里其实是postgresql做了一点优化,因为只有子事务的提交状态时,还需要再进一步确认父事务状态;而子事务的abort状态,直接就可以确定不可见;所以这里只对子事务的提交单独加了状态,其它同普通事务一样记录状态即可;

文件内部格式

clog的文件结构非常简单,每个文件以8KB为单位组成page;每个page中,以2个bit位为单位表示一个事务的状态;这样一个字节可以表示4个事务,按事务号0,1,… 顺序存储每个事务号的状态;

这样的结构对于修改和查找事务状态,就非常高效,只需要找到事务号对应的偏移就可以了。

#define TransactionIdToPage(xid)	((xid) / (TransactionId) CLOG_XACTS_PER_PAGE)
#define TransactionIdToPgIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE)
#define TransactionIdToByte(xid)	(TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE)
#define TransactionIdToBIndex(xid)	((xid) % (TransactionId) CLOG_XACTS_PER_BYTE)

可以看到page就是 xid 除以 每个page有多少个事务号;而对应的状态就是取余操作;

文件命名

在postgresql中事务号xid是32位的正整数,不断再循环使用,说明事务号是有限的;

clog文件为了与缓存有映射关系,按缓存大小将事务号划分到不同的文件段中,那么文件命名就按划分后的顺序来命名,0001,0002依次;

取值算法如下:

0xFFFFFFFF/CLOG_XACTS_PER_PAGE/SLRU_PAGES_PER_SEGMENT #define SlruFileName(ctl, path, seg) \snprintf(path, MAXPGPATH, "%s/%04X", (ctl)->Dir, seg)

可以看到clog文件也是循环使用,根据事务号的回收,截断不再使用的事务号状态;

clog缓存

事务状态需要在数据库服务重启后还能使用,所以必须记录在磁盘文件中,这就带来一个问题,读写磁盘效率非常低,常规办法就是增加缓冲区;

clog文件并不大,所以采用了SLRU(simple LRU)算法的缓冲区,大小定义如下,单位为block,也就是page大小;

#define SLRU_PAGES_PER_SEGMENT	32

事务状态记录

事务号状态写入,先是计算对应的page,然后看是否已经加载到缓冲区中,如果没有,则先加载到缓冲区中;

    /* 获取缓存区的序号,如果不在缓存区中,这里会进行加载 */slotno = SimpleLruReadPage(XactCtl, pageno, XLogRecPtrIsInvalid(lsn), xid);

在缓冲区中找到对应事务号的偏移,进行位操作即可;

static void
TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, int slotno)
{int			byteno = TransactionIdToByte(xid);int			bshift = TransactionIdToBIndex(xid) * CLOG_BITS_PER_XACT;char	   *byteptr;char		byteval;char		curval;byteptr = XactCtl->shared->page_buffer[slotno] + byteno;curval = (*byteptr >> bshift) & CLOG_XACT_BITMASK;if (InRecovery && status == TRANSACTION_STATUS_SUB_COMMITTED &&curval == TRANSACTION_STATUS_COMMITTED)return;byteval = *byteptr;byteval &= ~(((1 << CLOG_BITS_PER_XACT) - 1) << bshift);byteval |= (status << bshift);*byteptr = byteval;if (!XLogRecPtrIsInvalid(lsn)){int			lsnindex = GetLSNIndex(slotno, xid);if (XactCtl->shared->group_lsn[lsnindex] < lsn)XactCtl->shared->group_lsn[lsnindex] = lsn;}
}

缓存刷到磁盘

事务号的更新都是在缓冲区中进行的,什么时候刷到磁盘呢?数据会丢失吗?
对于有缓冲区设计的程序,我们总会提出这两个问题,下面我们来看postgresql中如何处理clog缓冲区;

主要有以下几个时机,会进行刷盘操作;

缓冲区置换

当缓冲区都满时,又需要加载一个page时,就需要置换一个缓冲区出去,此时如果为脏时,就需要刷盘;
刷盘时调用如下接口进行;

static void SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata);

checkpoint时

在checkpoint时,会将所有缓存区刷到磁盘上,调用以下接口,内部也是按page进行刷盘;

void 
SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied);

服务启动、停止时

同checkpoint一样,将所有缓存区刷盘;

事务状态数据,并没有随着事务提交一起刷盘,可能会有丢失的情况,如果这种情况发生,也会存在redo,此时可以从WAL恢复事务状态,所以数据完整性和一致性是得到保障的;

回收clog段文件

随着事务号的增加和回卷,有些clog段文件就不再需要,需要进行删除回收;

clog模块通过删除和truncate操作来进行回收段文件;

truncate段文件

在进行checkpoint时,会根据事务号进行判断段文件是否有效,在目录下查找无效的段文件进行truncate;
另外在vacuum时,会更新frozenxid,那么更新后,就会有一些事务号不再使用,也会对整个目录中的段文件进行truncate;

void
SimpleLruTruncate(SlruCtl ctl, int cutoffPage);

当然这里的truncate,不是对文件的truncate,而是对目录中段文件,基实是对无效段文件的删除;

truncate时也会记录一条WAL日志,调用以下接口;

static void
WriteTruncateXlogRec(int pageno, TransactionId oldestXact, Oid oldestXactDb);

删除段文件

当清理multiXact时,如果存在较早的clog,就会将它们删除;或者上面进行trancate时,会批量将旧的clog进行删除;

void
SlruDeleteSegment(SlruCtl ctl, int segno);

并发控制

无论是对于共享缓存区,还是对于clog文件的操作,都会存在多任务同时访问,所以需要一定的并发控制;

对于缓冲区SLRU,会有一个总的controllock,每次需要修改共享缓冲区时,都需要先加此锁;

而对于缓冲区块的操作,每一个缓冲区块会有一个独立的锁,读写缓冲区块时获得此锁即可;

LRU共享内存锁

对于SLRU结构的内容的修改,或者缓冲区替换,段文件操作等,都必须先获取此锁,它在SlruSharedData中定义;

```c
typedef struct SlruSharedData
{LWLock	   *ControlLock;bool	   *page_dirty;int		   *page_number;int		   *page_lru_count;LWLockPadded *buffer_locks;} SlruSharedData;

LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);


## 缓冲区块锁
对于每个缓冲区块buffer的操作,每个buffer都定义了一把锁 buffer_locks;```c/* Initialize LWLocks */shared->buffer_locks = (LWLockPadded *) (ptr + offset);offset += MAXALIGN(nslots * sizeof(LWLockPadded));

为了避免死锁,它的获取前,必须先要加上LRU共享控制锁,然后再获取,获取到时,就可以释放LRU控制锁;

如下所示:

	LWLockRelease(shared->ControlLock);LWLockAcquire(&shared->buffer_locks[slotno].lock, LW_SHARED);LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);LWLockRelease(&shared->buffer_locks[slotno].lock);

写操作

而对于只读操作时,只需要加ControlLock的share锁即可;

int
SimpleLruReadPage_ReadOnly(SlruCtl ctl, int pageno, TransactionId xid)
{SlruShared	shared = ctl->shared;int			slotno;/* Try to find the page while holding only shared lock */LWLockAcquire(shared->ControlLock, LW_SHARED);/* See if page is already in a buffer */for (slotno = 0; slotno < shared->num_slots; slotno++){if (shared->page_number[slotno] == pageno &&shared->page_status[slotno] != SLRU_PAGE_EMPTY &&shared->page_status[slotno] != SLRU_PAGE_READ_IN_PROGRESS){/* See comments for SlruRecentlyUsed macro */SlruRecentlyUsed(shared, slotno);/* update the stats counter of pages found in the SLRU */pgstat_count_slru_page_hit(shared->slru_stats_idx);return slotno;}}/* No luck, so switch to normal exclusive lock and do regular read */LWLockRelease(shared->ControlLock);LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);return SimpleLruReadPage(ctl, pageno, true, xid);
}XidStatus
TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
{int			pageno = TransactionIdToPage(xid);int			byteno = TransactionIdToByte(xid);int			bshift = TransactionIdToBIndex(xid) * CLOG_BITS_PER_XACT;int			slotno;int			lsnindex;char	   *byteptr;XidStatus	status;/* lock is acquired by SimpleLruReadPage_ReadOnly */slotno = SimpleLruReadPage_ReadOnly(XactCtl, pageno, xid);byteptr = XactCtl->shared->page_buffer[slotno] + byteno;status = (*byteptr >> bshift) & CLOG_XACT_BITMASK;lsnindex = GetLSNIndex(slotno, xid);*lsn = XactCtl->shared->group_lsn[lsnindex];LWLockRelease(XactSLRULock);return status;
}

对于大并发下的事务状态更新,postgresql 还进行了精细的优化,避免controlLock竞争,增加group的优化,对于在同一个clog page中的事务号更新,只由第一个backend写入clog,其它只是加到grouplist中,利用proc记录进行优化;

读操作

此处获取事务状态,只读操作,如果缓冲区中已经加载了事务号所在的page,此时只加controllock的共享模式;

结尾

非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!

作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。

注:未经同意,不得转载!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/86720.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vis.js教程(一):基础教程

1、Vis.js是什么 一个动态的、基于浏览器的可视化库。 该库的设计易于使用&#xff0c;能够处理大量动态数据&#xff0c;并能够对数据进行操作和交互。 该库由 DataSet、Timeline、Network、Graph2d 和 Graph3d 组件组成。 Vis.js官网&#xff1a;https://visjs.org/ github…

TongWeb8 专用机使用指导

前言 专用机要求软件以deb、rpm安装包形式提供&#xff0c;通过三合一安全管理工具进行安装&#xff0c;否则软件的可执行程序无法运行&#xff0c;所以TongWeb6、7版本的专用机版本遵循此原则。 TongWeb8安装使用方式 TongWeb8除可以提供deb、rpm安装包形式外&#xff0c;还支…

设计模式:备忘录模式

目录 组件代码示例源码中使用优缺点总结 备忘录模式&#xff08;Memento Pattern&#xff09;是一种行为型设计模式&#xff0c;用于在不破坏封装性的前提下&#xff0c;捕获和恢复对象的内部状态。备忘录模式可以将对象的状态保存到备忘录对象中&#xff0c;并在需要时从备忘录…

电脑计算机xinput1_3.dll丢失的解决方法分享,四种修复手段解决问题

日常生活中可能会遇到的问题——xinput1_3.dll丢失的解决方法。我相信&#xff0c;在座的很多朋友都曾遇到过这个问题&#xff0c;那么接下来&#xff0c;我将分享如何解决这个问题的解决方法。 首先&#xff0c;让我们来了解一下xinput1_3.dll文件。xinput1_3.dll是一个动态链…

第1篇 目标检测概述 —(1)目标检测基础知识

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。目标检测是计算机视觉领域中的一项任务&#xff0c;旨在自动识别和定位图像或视频中的特定目标&#xff0c;目标可以是人、车辆、动物、物体等。目标检测的目标是从输入图像中确定目标的位置&#xff0c;并使用边界框将其标…

Go基础语法:map

9 map Go 语言中提供的映射关系容器为 map &#xff0c;其内部使用 散列表&#xff08;hash&#xff09; 实现。它是一种无序的基于 key-value 的数据结构。 Go 语言中的 map 是引用类型&#xff0c;必须初始化之后才能使用。 9.1 map 定义 Go 语言中 map 的定义语法为&…

sql on条件判断是要注意null值

我是因为用了merge into语法&#xff0c;然后on条件中判断的字段是可配置的&#xff0c;这就导致了&#xff0c;有时候判断条件多的情况下&#xff0c;判断的字段会碰到有null值的情况&#xff0c;如果on两边的字段都是null&#xff0c;null和null对比就会导致结果为false&…

安全防御第二次作业

1. 防火墙支持那些NAT技术&#xff0c;主要应用场景是什么&#xff1f; 防火墙支持几乎所有的NAT技术&#xff0c;包括源NAT、目标NAT、双向NAT等&#xff0c;主要应用场景是保护内部网络免受外部网络的攻击 NAT技术可以将IP数据报文头中的IP地址转换为另一个IP地址&#xff…

stc8H驱动并控制三相无刷电机综合项目技术资料综合篇

stc8H驱动并控制三相无刷电机综合项目技术资料综合篇 🌿相关项目介绍《基于stc8H驱动三相无刷电机开源项目技术专题概要》 🔨停机状态,才能进入设置状态,可以设置调速模式,以及转动方向。 ✨所有的功能基本已经完成调试,目前所想到的功能基本已经都添加和实现。引脚利…

C++入门知识

Hello&#xff0c;今天我们分享一些关于C入门的知识&#xff0c;看完至少让你为后面的类和对象有一定的基础&#xff0c;所以在讲类和对象的时候&#xff0c;我们需要来了解一些关于C入门的知识。 什么是C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对…

【Python从入门到进阶】37、selenium关于phantomjs的基本使用

接上篇《36、Selenium 动作交互》 上一篇我们介绍了selenium操作网页的动作内容。本篇我们来学习有关phantomjs的相关知识。 一、selenium的缺点 在介绍PhantomJS之前&#xff0c;让我们先讨论一下直接使用Selenium的一些缺点。 1、显示浏览器窗口&#xff1a;Selenium通常需…

AndroidUtil - 强大易用的安卓工具类库

官网 https://github.com/Blankj/AndroidUtilCode/blob/master/README-CN.md 项目介绍 AndroidUtilCode &#x1f525; 是一个强大易用的安卓工具类库&#xff0c;它合理地封装了安卓开发中常用的函数&#xff0c;具有完善的 Demo 和单元测试&#xff0c;利用其封装好的 API…

CUDA学习笔记0924

一、nvprof分析线程束和内存读写 &#xff08;1&#xff09;线程束占用率分析 线程束占用率&#xff1a;nvprof --metrics achieved_occupancy &#xff08;2&#xff09;内存读写分析 内核数据读取效率&#xff1a;nvprof --metrics gld_throughput 程序对设备内存带宽利…

《动手学深度学习 Pytorch版》 7.4 含并行连接的网络(GoogLeNet)

import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l7.4.1 Inception块 GoogLNet 中的基本卷积块叫做 Inception 块&#xff08;大概率得名于盗梦空间&#xff09;&#xff0c;由 4 条并行路径组成。 前 3 条路径使用窗口…

合规性管理如何帮助产品团队按时交付?

成功的产品和产品发布背后通常需要经过一个涉及多个监督机构、多功能团队和利益相关者的复杂流程。在组织的治理、风险管理和合规性&#xff08;GRC&#xff09;框架下&#xff0c;产品团队不仅需要追求市场创新&#xff0c;还需要确保符合所有适用的法规、标准和合同要求。由于…

libpcap之socket创建

一、 lipcap回调注册 在libpcap中&#xff0c;最重要的就是打开接口&#xff0c;其中关键函数为pcap_activate。这里只关注Linux平台。 只分析通用平台。 pcap_t * pcap_create(const char *device, char *errbuf) { ... p pcap_create_interface(device_str, errbuf); ... …

【性能测试】JMeter:集合点,同步定时器的应用实例!

一、集合点的定义 在性能测试过程中&#xff0c;为了真实模拟多个用户同时进行操作以度量服务器的处理能力&#xff0c;可以考虑同步虚拟用户以便恰好在同一时刻执行操作或发送请求。 通过插入集合点可以较真实模拟多个用户并发操作。 (注意&#xff1a;虽然通过加入集合点可…

Go内置函数make和new的区别?

首先纠正一下make 和 new 是内置函数&#xff0c;不是关键字。 变量初始化&#xff0c;一般分为2步&#xff0c;变量声明变量内存分配&#xff0c;var 关键字就是用来声明变量的&#xff0c;new和make 函数主要是用来分配内存的。 var 声明值类型的变量时&#xff0c;系统会默…

利用Socks5代理IP加强跨界电商爬虫的网络安全

随着跨界电商的兴起&#xff0c;爬虫技术在这个领域变得越来越重要。然而&#xff0c;网络安全一直是一个值得关注的问题。在本文中&#xff0c;我们将讨论如何利用代理IP和Socks5代理来增强跨界电商爬虫的网络安全&#xff0c;确保稳定和可靠的数据采集&#xff0c;同时避免封…

Leetcode13. 罗马数字转整数

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 罗马数字包含以下七种字符: I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X 10 L 5…