【PGCCC】Postgresql 存储设计

server/2024/11/19 5:01:42/

架构图

在这里插入图片描述
用户查询指定 page 的数据

首先查询该 page 是否在缓存中,通过 hash table 快速查找它在缓存池的位置
如果存在,那么从缓存池读取返回
如果不存在需要从磁盘读取数据,并且放入到缓存池中,然后返回

postgresql__8">postgresql 存储单位

postgresql 底层存储的数据交互是以 Page 为单位的,它会负责将数据持久化到底层的文件系统里。postgresql 的这种设计应该是基于磁盘考虑的,因为我们大部分的存储介质都是磁盘,磁盘都是由固定大小的扇区组成,一般是512字节。文件系统负责与磁盘交互,它为了利用磁盘顺序读写的特点,将数据交互的单位设置为更大的块,一般是4KB。因为每种磁盘和文件系统的交互单位并不一定相等,postgresql 为了更好的跨平台性,将数据单位设置为 Page,默认 8KB。

数据读写安全性

虽然一些磁盘或者文件系统支持一定长度的原子读写,但是长度不一致,而且有些系统并没有实现。如果 postgresql 想支持跨平台,那么它不能依靠底层的原子操作,需要自身保证这些数据的正确写入,防止 part-write 问题(也就是当写入一个 Page 时机器断电了,可能造成 Page 只写了一部分)。

postgresql 为了解决整个问题,它使用了 xlog 存储了每次修改。并且在 page 第一次修改时,还会记录该 page 的全部数据。这样即使发送了 part-write 问题,也可以从 xlog 恢复出来,具体细节可以参考 postgresql checkpoint full-page write。

Buffer Pool

如果每次读写数据都需要从磁盘操作,那么造成读取速度很慢,所以在实现数据库时,都会加上缓存。一般来说,数据库为了更加精细化的管理,不会采用文件系统的自身缓存,而是自身来实现缓存管理,因为数据库对数据更加理解。postgresql 使用了 Buffer Pool 来实现缓存。

当需要取数据时,需要先从 Buffer Pool 中查找,如果没有则需要从磁盘加载到缓存。因为 Buffer Pool 是对外层用户透明的,用户只是传输 Page 地址,为了快速的查找指定 Page 在 Buffer Pool 的位置,postgresql 使用了 hash table 来存储。

Buffer Pool 在 postgresql 的定义如下,它只是一个简单的数组

char	   *BufferBlocks;            // 缓存数组
BufferDescPadded *BufferDescriptors;  // 缓存元数据// 初始化缓存数组
void InitBufferPool(void)
{// 创建一块共享内存,里面存储了 NBuffers 个缓存,每个缓存的大小为 BLCKSZBufferBlocks = (char *) ShmemInitStruct("Buffer Blocks", NBuffers * (Size) BLCKSZ, &foundBufs);// 创建一块共享内存,里面存储了 NBuffers 个缓存元数据BufferDescriptors = (BufferDescPadded *) ShmemInitStruct("Buffer Descriptors", NBuffers * sizeof(BufferDescPadded), &foundDescs);
}

Buffer 存储的数据是和 Page 完全一样的。我们还需要一些描述 Buffer 的信息,比如该 Buffer 对应哪个 Page,这里当持久化该 Buffer 的时候,知道存储到磁盘的哪个位置。还有为了支持并发,需要保存一些并发的信息。这些信息都由 BufferDesc 保存,需要强调下 BufferDesc 信息是不会被持久化的。

typedef struct BufferDesc
{BufferTag	tag;			/* 指定对应的 page 地址 */int			buf_id;			/* 指定的 buffer 在数组中的索引 *//* state of the tag, containing flags, refcount and usagecount */pg_atomic_uint32 state;int			wait_backend_pid;	/* backend PID of pin-count waiter */int			freeNext;		/* link in freelist chain */LWLock		content_lock;	/* 锁,用于并发控制 */
} BufferDesc;

我们查看下 BufferTag的定义,看看 postgresql 是如何表示 page 地址的。postgresql 对于表是单独存储的,每个表除了包含了本身的数据文件,还有其它一些类型的文件。

typedef struct buftag
{RelFileNode rnode;			/* 表标识 */ForkNumber	forkNum;         /* 文件类型 */BlockNumber blockNum;		/* page 在底层文件的索引 */
} BufferTag;

我们仔细看看BufferDesc虽然是数组,但是在创建的时候,却是以BufferDescPadded格式存储的。

#define BUFFERDESC_PAD_TO_SIZE	(SIZEOF_VOID_P == 8 ? 64 : 1)typedef union BufferDescPadded
{BufferDesc	bufferdesc;char		pad[BUFFERDESC_PAD_TO_SIZE];
} BufferDescPadded;

为什么需要在BufferDesc添加一个 64 bytes 的空闲数据呢。其实这是 postgresql 针对 cpu cache 做的优化,这里简单说下 cache 原理。如今的 cpu 处理速度越来越快,以至于内存的读取速度已经不能满足了,所以每个 cpu 都会有着独立的 高速 cache。当 cpu 要处理数据时,会先从内存中把数据加载到 cache,每次交互的数据单位大小叫做 cache line size,一般是 64 bytes。当两个 cpu 并行的处理同一块数据,那么就会造成该数据都会被拷贝到两个 cpu 的 cache 里。如果一个 cpu 修改了该 cache,那么会造成另外一个 cpu 的 cache 失效,那么另外的 cpu 就会重新去共享内存或者共享 cache 取数据,这种现象称为 false share。

postgresql 解决这个问题非常粗暴,在 BufferDesc 之间添加了 64bytes 的空闲数据,这样就能够保证在同一个 cache line 是不可能包含多个 BufferDesc,避免了 false share。不过这也造成了内存浪费,假如我们的缓存个数为 1024 个,那么就会造成 64Kb 的空间浪费。
作者:zhmin
链接:https://zhmin.github.io/posts/postgresql-storage-architecture/
#PG证书#PG考试#postgresql初级#postgresql中级#postgresql高级


http://www.ppmy.cn/server/143082.html

相关文章

力扣周赛:第424场周赛

👨‍🎓作者简介:爱好技术和算法的研究生 🌌上期文章:力扣周赛:第422场周赛 📚订阅专栏:力扣周赛 希望文章对你们有所帮助 第一道题模拟题,第二道题经典拆分数组/线段树都…

<项目代码>YOLOv8 番茄识别<目标检测>

YOLOv8是一种单阶段(one-stage)检测算法,它将目标检测问题转化为一个回归问题,能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法(如Faster R-CNN),YOLOv8具有更高的…

3D绘制动态爱心Matlab

代码1:动态爱心 function particleHeart1% 调整背景及比例axgca;hold onax.DataAspectRatio[1,1,1];ax.XLim[-25,25];ax.YLim[-25,20];ax.Color[0,0,0];ax.XColornone;ax.YColornone;set(gcf,Color,[0,0,0]);% 散点位置计算函数及扩散函数tFunc(n) rand([1,n]).*pi…

【计算机网络】TCP网络特点2

断开连接 四次挥手 原因 TCP 四次挥手是为了满足 TCP 连接的全双工特性:两个方向都可以自由传输 保证数据传输的完整性:两方都完成了数据发送和接收并且都同意断开连接 可靠地终止连接以及避免数据混淆和错误等需求:每个方向都需要单独确认导致四次挥手过程 这些…

matlab 读取csv

需要跳过第一行表头等信息 1、读取整个文件 csvread(FILENAME)%文件路径 文件名2、指定起始位置 csvread(FILENAME, R, C)%从文件的第R行和第C列开始读取数据 逗号分开3、指定数据范围 csvread(FILENAME, R, C, [R1 C1 R2 C2])%读取从(R1, C1)到(R2, C2)范围内的数据注意&am…

SpringBoot - Async异步处理

目录 一、定义 1、同步调用 2、异步调用 二、示例 1、同步调用 执行类: 测试用例: 运行结果: 2、异步调用 (1)普通调用 执行类: 测试用例: 运行结果: (2&…

4.2 Android NDK 基础概念

1 JavaVM和JNIEnv JNI 定义了两个关键数据结构,JavaVM和JNIEnv。这两者本质上都是指向函数表指针的指针。(在 C 版本中,它们是具有指向函数表的指针的类,以及指向该表的每个 JNI 函数的成员函数。)JavaVM提供了“调用接…

第九部分 :1.STM32之通信接口《精讲》(USART,I2C,SPI,CAN,USB)

本芯片使用的是STM32F103C8T6型号 STM32F103C8T6是STM32F1系列中的一种较常用的低成本ARM Cortex-M3内核MCU,具有丰富的通信接口,包括USART、SPI、I2C等。下面是该芯片上通信接口的管脚分布、每个接口的工作模式、常用应用场景和注意事项。 1. USART (通…