searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

PostgreSQL——外存管理

2023-08-03 10:08:48
29
0

在pg中,存在一个名为存储管理器的模块,提供了对外存和内存的统一管理,其结构如图1.1所示。内存的管理包括缓冲区、内存上下文、共享和本地内存等部分;而外存的管理则包括虚拟文件描述符(VFD)机制、表和元祖的组织方式、空闲空间映射表(FSM)、可见性映射表(VM)和大数据存储。下面对外存管理的相关内容进行简要的介绍。

图1.1 存储管理器的体系结构

一.虚拟文件描述符(VFD)机制

凡是对存储在磁盘中的表进行磁盘操作(如打开/关闭、读/写等),都由磁盘管理器统一进行处理。但是这样会遇到一个问题,在操作系统中,一个进程能打开的文件数量是有限的,因此对于经常要打开大量文件的数据库进程而言,很容易会超过操作系统所设定的阈值。为解决这个问题,pg中采用了虚拟文件描述符(VFD)机制来解决。这里的VFD是一种数据结构,其中的内容包括操作系统为文件分配的文件描述符(FD)、文件打开时的标志(如只读、只写)等信息。VFD与真实FD和文件之间的关系如图1.2所示,可以看出,这相当于在进程和真实FD中又加了一层,原理类似于连接池。当进程申请打开一个文件时,总能返回一个VFD,对外封装了对应要打开的文件的实际操作。使用了VFD后,一个进程能同时打开的文件的最大数量仍是系统规定的阈值,但进程自身会觉得能打开任意多文件,不再受操作系统的限制。

图1.2 VFD、真实FD与文件的关系

为了管理这些VFD,pg采用空闲链表即FreeList和LRU池来进行管理。每一个进程都拥有其私有的FreeList和LRU池和一系列VFD。

首先是,FreeList是单链表的结构,用于记录所有可分配的VFD。随后是更重要的LRU池,它是双向链表和环的结构,用于管理进程已经打开的VFD。当进程要打开文件时,会从私有的LRU池中申请VFD。LRU池有两种状态,第一种是未满,即进程打开的文件个数未超过系统阈值,就照常申请VFD;当LRU池满时,则使用最近最少(LRU)策略替换。

LRU池结构如图1.3所示,每一个VFD都通过双向指针与左右连接,并且头尾互连。在LRU池中,有一个特殊的VFD,即VFD0和两个特殊指针lruLessRecently和 lruMoreRecently。VFD0单纯起到链表头部作用,不会被实际分配给任何文件。lruLessRecently指针指向最近更不常用的VFD,相反的,lruMoreRecently指针指向更近更常用的。也就是说越靠近VFD0,越是最新打开的文件。

图1.3 LRU池结构

LRU池对VFD的操作主要包括从LRU池中删除VFD、将VFD插入LRU池以及删除LRU池尾的VFD。首先,从LRU池中删除VFD,这对应的情况是进程使用完一个文件后将其关闭,例如要删除VFDa2,在LRU池中,其删除过程和双向链表删除元素的过程相同,即a1和a3直接相连;随后还要对a2的VFD进行处理使其变为空闲,将其插入Freelist中。第二个是将VFD插入LRU池,这对应的操作是打开文件,这需要分配一个VFD,便从FreeList的头部取下,然后打开文件,将相应的文件信息记录到VFD中。随后需要将该VFD插入LRU池中,因为是新打开的,所以插入的位置是VFD0之后,也就是图1.3中的VFD0和VFDa1之间。第三个是删除LRU池尾的VFD,对应的情况是需要打开新文件而LRU池满了的时候。首先需要删除最少使用的VFD,也就是删除池中末尾的VFD,然后插入新的。另外,这里被删除的VFD仅仅只是从LRU池中脱离并关闭了对应的文件,其本身是不做任何修改的,因为进程后面的操作可能会用到该VFD对应的文件。

二.表和元祖的组织方式

在pg中,共有4种类型的表(堆)文件:普通表、临时表、序列和TOAST表。但究其根本,这四种表所对应的结构是相似的。每个表文件由多个文件块组成,默认大小为8KB。其磁盘中的存储形式如图1.4所示,可以看出,文件块由PageHeaderData、行指针、Freespace等部分组成。其中,PageHeaderData用于存储文件块的一般信息,例如空闲空间的开始和结束位置、Special space的开始位置、行指针的开始位置以及一些标志信息,如是否所有元组可见等。行指针则用于指向对应的行(元组);Freespace为空闲空间,代表未分配的空间;Speical space为特殊空间,用于存放与索引方法相关的特定数据。另外,图1.4中右半部分为元组的组织形式,包括头部header和数据value。其中header包括各种元组字段,例如t_cmin、t_cmax分别表示插入和删除命令id,可用于判断同一个事务内不同命令导致行版本的变化是否可见;t_xmin、t_xmax则为插入和删除事务id,可用于多版本并发控制,即MVCC中。

图1.4 表和元祖的组织形式

与MySQL不同的是,pg并未采用undo log记录旧数据,而是采用多版本技术存储,各版本之间形成版本链,存储在表文件中,每个版本的数据都有相应版本的索引记录,因此这会造成存储空间的浪费。为解决此问题,pg采用Heap-Only Tuple(HOT)技术进行解决。但是该技术需要满足两个条件,第一个是所有的索引属性未被修改,第二个是更新的元组和旧版本要在同一文件块内。当更新元组时,新版本的元组照常插入,但对应的索引不会更新。当通过索引获取最新元组时,首先会找到最老版本的数据,然后顺着版本链查找。HOT技术消除了拥有完全相同键值的索引记录,减小了索引大小。另外,删除元组时,pg采用标记删除方式,通过设置相应的删除标记实现快速删除,而非立即回收元组所占用的空间。

三.空闲空间映射表(FSM)

同样,与MySQL另一个不同的地方是,pg对表文件还另外附加了空闲空间映射表FSM和可见性映射表VM。下面来首先介绍一下空闲空间映射表FSM。

FSM是在创建每个表文件的时候同时创建的,命名方式为“表OID_fsm”,用于记录该表中各文件块的空闲空间大小。FSM表中记录的并非准确空间大小值,而是用一个字节来表示文件块的空闲空间范围。例如,当字节为0时,表示空闲空间在0-31个字节;为1时表示存在32-63个字节的空闲空间。如下图1.5所示,FSM表采用三层树结构,本质上是最大堆二叉树,第0和1层为辅助层,用于快速定位到满足空间需求的表块所属的第2层FSM块;第2层的叶子结点用于存放各表块的空闲空间值。值得注意的是,第2层的叶子结点从左至右分别对应表文件的每个表块。也就是说,在最左边的fsm块中,左边第一个叶子结点对应表文件中的第一块,第2个对应第2块,以此类推。另外,每个fsm块的根节点都是该块中的最大值。因此,在插入数据的时候,就从第0层开始往第2层找,直到找到符合的空闲空间,如果找不到的话就申请新的表块来插入。

图1.5 FSM组织形式

每个FSM块的默认大小为8KB,除去必要的文件块的头部信息和构成最大堆二叉树的辅助节点,每个叶子结点用一个字节记录,大约可以保存4000个叶节点(表块),因此总的可保存40003个,这远远超过了pg中单个表的最大块数,232个。FSM块以深度优先遍历的方式进行编号,其中第0层为0号,第1层从左到右分别为第1、4001+1、4001*2+1等。剩下的编号都在第二层,序号从左至右递增,其中最左侧为第2号,记录了文件表中第0到4000-1号表块的空闲空间值;第3号FSM块对应文件表中第4000-4000*2-1号表块的空闲空间值。

四.可见性映射表(VM)

同样,对于VM来说,每一个表都对应一个VM,命名方式为“表OID_vm”,用于加快VACUUM查找包含无效元组的文件块的过程。VM中为表的每个文件块都设置了一位,用来表示该文件块是否存在无效元组。VM文件块结构如图1.6所示,除了表头header外,剩余部分的每一位都对应一个表块。当标志位bit为1时,表示没有无效元组,块中所有的元组对于当前事务都是可见的,VACUUM清理会忽略对应的块;bit为0时,表示有无效元组,块中的元组经过了更新或删除。

图1.6 VM组织形式

另外,设置标志位bit时,要对对应的VM进行加锁,这是为了避免在VACUUM判断该块是否对所有事务可见的同时,其他进程修改该块,从而在VACUUM清理时忽略。VM文件适用于Lazy VACUUM的方式,能够加快其效率。而对于FULL VACUUM,由于其需要对整个表文件进行扫描,因此作用不大。

五.大数据存储

在平时的存储过程中,难免会保存一些特别大的数据,为解决此问题。pg提供了TOAST机制和大对象机制进行处理。

  1. TOAST机制

TOAST机制是基于数据压缩和线外存储(数据存储其他表中,即TOAST表)的方式来实现。目前,只支持可变长的数据类型,例如TEXT。其触发条件为存储数据超过BLCKSZ/4,通常为2KB。另外,该技术保障了元组大小足以存放在一个文件块中,故TOAST为系统自动处理机制,用户无法控制。

该机制识别四种不同的存储数据的策略,分别为:PLAIN,避免压缩或者线外存储;EXTENDED,允许压缩和线外存储,这是默认选择的策略,TOAST机制首先会对数据进行压缩,如果仍然较大,则采用线外存储的方式;EXTERNAL,不允许压缩但允许线外存储;MAIN,允许压缩但不允许线外存储。

  1. 大对象机制

大对象机制使用一个专门的系统表pg_largeobject来存储,该表的结构如1.7下图所示。在该表中,每个元组也会被称为页面,每个页面默认大小为2KB。表中共有三个字段:loid是pg当中该对象的唯一标识,这是在大对象创建时候分配的;pageno则用于表示本页在大对象数据中的页码也就是位置;data则用于存储大对象中的实际数据。

图1.7 pg_largeobject表结构

大对象机制支持的数据类型包括二进制大对象(BLOB)、字符大对象(CLOB)和双字节字符大对象(DBCLOB)。并且,该机制可以容忍页面的丢失,丢失的部分读取时记为0。例如存储某个大对象占据了6个页面,编号为0-5,由于某些原因,第1、2号页面丢失,此时pg_largeobject表并不会直接清掉这两个页面,而是在之后读到这两个页面的时候,将他们以全0的形式展现出来。另外,该机制并不是系统自动触发的,而是由用户主动进行控制。

  1. 对比

下面对这两种机制进行将要的比较。首先,TOAST机制只支持可变长类型,并且是系统自动触发,即需要达到2KB的阈值;而大对象则是用户手动调用。第二,TOAST机制不能丢失数据,否则报错;而大对象则能够容忍,丢失的用0代替。第三,大对象比TOAST更适合存储文件,如果用TOAST机制保存的话,需要将文件以二进制形式读出,然后将二进制作为字符串保存到变长数据类型的属性中,读取时,则要将数据读取出来再转换为文件;而大对象直接将文件视为一个对象进行存储,更加快捷。最后,尽管TOAST和大对象都是将数据切片然后存储至表中,但TOAST提供了线外存储和压缩这两种机制;而大对象不会对数据做处理,而是直接存储。

六.文件存放布局

在介绍完前面的内容后,再来看一下建立的数据库、表在磁盘上存储的目录形式是怎么样的。pg存储数据的地方被称为PGDATA目录,一般默认在pg的安装目录的data文件夹中。PGDATA目录中存放的数据类型总体上可分为两类,目录和文件。在目录中,包括base目录,用于存放数据库及包含的表;global目录,用于存放全局对象、pg_wal目录,用于存放预写日志,即WAL日志,等等;而文件则包括一些配置文件,例如postgresql.conf等。

base目录中的情况,其结构如图1.8所示。首先,base中的每个文件夹代表一个数据库,其名称为数据库的oid;然后在数据库文件夹中则包含表的相关文件,比如以表oid为命名的用于存储表格数据的主数据文件以及对应的FSM和VM文件。另外, 在pg部署完成之后,会默认包含template0和template1这两个模板数据库以及postgres数据库。

图1.8 base目录

 

0条评论
0 / 1000
z****n
4文章数
0粉丝数
z****n
4 文章 | 0 粉丝
z****n
4文章数
0粉丝数
z****n
4 文章 | 0 粉丝
原创

PostgreSQL——外存管理

2023-08-03 10:08:48
29
0

在pg中,存在一个名为存储管理器的模块,提供了对外存和内存的统一管理,其结构如图1.1所示。内存的管理包括缓冲区、内存上下文、共享和本地内存等部分;而外存的管理则包括虚拟文件描述符(VFD)机制、表和元祖的组织方式、空闲空间映射表(FSM)、可见性映射表(VM)和大数据存储。下面对外存管理的相关内容进行简要的介绍。

图1.1 存储管理器的体系结构

一.虚拟文件描述符(VFD)机制

凡是对存储在磁盘中的表进行磁盘操作(如打开/关闭、读/写等),都由磁盘管理器统一进行处理。但是这样会遇到一个问题,在操作系统中,一个进程能打开的文件数量是有限的,因此对于经常要打开大量文件的数据库进程而言,很容易会超过操作系统所设定的阈值。为解决这个问题,pg中采用了虚拟文件描述符(VFD)机制来解决。这里的VFD是一种数据结构,其中的内容包括操作系统为文件分配的文件描述符(FD)、文件打开时的标志(如只读、只写)等信息。VFD与真实FD和文件之间的关系如图1.2所示,可以看出,这相当于在进程和真实FD中又加了一层,原理类似于连接池。当进程申请打开一个文件时,总能返回一个VFD,对外封装了对应要打开的文件的实际操作。使用了VFD后,一个进程能同时打开的文件的最大数量仍是系统规定的阈值,但进程自身会觉得能打开任意多文件,不再受操作系统的限制。

图1.2 VFD、真实FD与文件的关系

为了管理这些VFD,pg采用空闲链表即FreeList和LRU池来进行管理。每一个进程都拥有其私有的FreeList和LRU池和一系列VFD。

首先是,FreeList是单链表的结构,用于记录所有可分配的VFD。随后是更重要的LRU池,它是双向链表和环的结构,用于管理进程已经打开的VFD。当进程要打开文件时,会从私有的LRU池中申请VFD。LRU池有两种状态,第一种是未满,即进程打开的文件个数未超过系统阈值,就照常申请VFD;当LRU池满时,则使用最近最少(LRU)策略替换。

LRU池结构如图1.3所示,每一个VFD都通过双向指针与左右连接,并且头尾互连。在LRU池中,有一个特殊的VFD,即VFD0和两个特殊指针lruLessRecently和 lruMoreRecently。VFD0单纯起到链表头部作用,不会被实际分配给任何文件。lruLessRecently指针指向最近更不常用的VFD,相反的,lruMoreRecently指针指向更近更常用的。也就是说越靠近VFD0,越是最新打开的文件。

图1.3 LRU池结构

LRU池对VFD的操作主要包括从LRU池中删除VFD、将VFD插入LRU池以及删除LRU池尾的VFD。首先,从LRU池中删除VFD,这对应的情况是进程使用完一个文件后将其关闭,例如要删除VFDa2,在LRU池中,其删除过程和双向链表删除元素的过程相同,即a1和a3直接相连;随后还要对a2的VFD进行处理使其变为空闲,将其插入Freelist中。第二个是将VFD插入LRU池,这对应的操作是打开文件,这需要分配一个VFD,便从FreeList的头部取下,然后打开文件,将相应的文件信息记录到VFD中。随后需要将该VFD插入LRU池中,因为是新打开的,所以插入的位置是VFD0之后,也就是图1.3中的VFD0和VFDa1之间。第三个是删除LRU池尾的VFD,对应的情况是需要打开新文件而LRU池满了的时候。首先需要删除最少使用的VFD,也就是删除池中末尾的VFD,然后插入新的。另外,这里被删除的VFD仅仅只是从LRU池中脱离并关闭了对应的文件,其本身是不做任何修改的,因为进程后面的操作可能会用到该VFD对应的文件。

二.表和元祖的组织方式

在pg中,共有4种类型的表(堆)文件:普通表、临时表、序列和TOAST表。但究其根本,这四种表所对应的结构是相似的。每个表文件由多个文件块组成,默认大小为8KB。其磁盘中的存储形式如图1.4所示,可以看出,文件块由PageHeaderData、行指针、Freespace等部分组成。其中,PageHeaderData用于存储文件块的一般信息,例如空闲空间的开始和结束位置、Special space的开始位置、行指针的开始位置以及一些标志信息,如是否所有元组可见等。行指针则用于指向对应的行(元组);Freespace为空闲空间,代表未分配的空间;Speical space为特殊空间,用于存放与索引方法相关的特定数据。另外,图1.4中右半部分为元组的组织形式,包括头部header和数据value。其中header包括各种元组字段,例如t_cmin、t_cmax分别表示插入和删除命令id,可用于判断同一个事务内不同命令导致行版本的变化是否可见;t_xmin、t_xmax则为插入和删除事务id,可用于多版本并发控制,即MVCC中。

图1.4 表和元祖的组织形式

与MySQL不同的是,pg并未采用undo log记录旧数据,而是采用多版本技术存储,各版本之间形成版本链,存储在表文件中,每个版本的数据都有相应版本的索引记录,因此这会造成存储空间的浪费。为解决此问题,pg采用Heap-Only Tuple(HOT)技术进行解决。但是该技术需要满足两个条件,第一个是所有的索引属性未被修改,第二个是更新的元组和旧版本要在同一文件块内。当更新元组时,新版本的元组照常插入,但对应的索引不会更新。当通过索引获取最新元组时,首先会找到最老版本的数据,然后顺着版本链查找。HOT技术消除了拥有完全相同键值的索引记录,减小了索引大小。另外,删除元组时,pg采用标记删除方式,通过设置相应的删除标记实现快速删除,而非立即回收元组所占用的空间。

三.空闲空间映射表(FSM)

同样,与MySQL另一个不同的地方是,pg对表文件还另外附加了空闲空间映射表FSM和可见性映射表VM。下面来首先介绍一下空闲空间映射表FSM。

FSM是在创建每个表文件的时候同时创建的,命名方式为“表OID_fsm”,用于记录该表中各文件块的空闲空间大小。FSM表中记录的并非准确空间大小值,而是用一个字节来表示文件块的空闲空间范围。例如,当字节为0时,表示空闲空间在0-31个字节;为1时表示存在32-63个字节的空闲空间。如下图1.5所示,FSM表采用三层树结构,本质上是最大堆二叉树,第0和1层为辅助层,用于快速定位到满足空间需求的表块所属的第2层FSM块;第2层的叶子结点用于存放各表块的空闲空间值。值得注意的是,第2层的叶子结点从左至右分别对应表文件的每个表块。也就是说,在最左边的fsm块中,左边第一个叶子结点对应表文件中的第一块,第2个对应第2块,以此类推。另外,每个fsm块的根节点都是该块中的最大值。因此,在插入数据的时候,就从第0层开始往第2层找,直到找到符合的空闲空间,如果找不到的话就申请新的表块来插入。

图1.5 FSM组织形式

每个FSM块的默认大小为8KB,除去必要的文件块的头部信息和构成最大堆二叉树的辅助节点,每个叶子结点用一个字节记录,大约可以保存4000个叶节点(表块),因此总的可保存40003个,这远远超过了pg中单个表的最大块数,232个。FSM块以深度优先遍历的方式进行编号,其中第0层为0号,第1层从左到右分别为第1、4001+1、4001*2+1等。剩下的编号都在第二层,序号从左至右递增,其中最左侧为第2号,记录了文件表中第0到4000-1号表块的空闲空间值;第3号FSM块对应文件表中第4000-4000*2-1号表块的空闲空间值。

四.可见性映射表(VM)

同样,对于VM来说,每一个表都对应一个VM,命名方式为“表OID_vm”,用于加快VACUUM查找包含无效元组的文件块的过程。VM中为表的每个文件块都设置了一位,用来表示该文件块是否存在无效元组。VM文件块结构如图1.6所示,除了表头header外,剩余部分的每一位都对应一个表块。当标志位bit为1时,表示没有无效元组,块中所有的元组对于当前事务都是可见的,VACUUM清理会忽略对应的块;bit为0时,表示有无效元组,块中的元组经过了更新或删除。

图1.6 VM组织形式

另外,设置标志位bit时,要对对应的VM进行加锁,这是为了避免在VACUUM判断该块是否对所有事务可见的同时,其他进程修改该块,从而在VACUUM清理时忽略。VM文件适用于Lazy VACUUM的方式,能够加快其效率。而对于FULL VACUUM,由于其需要对整个表文件进行扫描,因此作用不大。

五.大数据存储

在平时的存储过程中,难免会保存一些特别大的数据,为解决此问题。pg提供了TOAST机制和大对象机制进行处理。

  1. TOAST机制

TOAST机制是基于数据压缩和线外存储(数据存储其他表中,即TOAST表)的方式来实现。目前,只支持可变长的数据类型,例如TEXT。其触发条件为存储数据超过BLCKSZ/4,通常为2KB。另外,该技术保障了元组大小足以存放在一个文件块中,故TOAST为系统自动处理机制,用户无法控制。

该机制识别四种不同的存储数据的策略,分别为:PLAIN,避免压缩或者线外存储;EXTENDED,允许压缩和线外存储,这是默认选择的策略,TOAST机制首先会对数据进行压缩,如果仍然较大,则采用线外存储的方式;EXTERNAL,不允许压缩但允许线外存储;MAIN,允许压缩但不允许线外存储。

  1. 大对象机制

大对象机制使用一个专门的系统表pg_largeobject来存储,该表的结构如1.7下图所示。在该表中,每个元组也会被称为页面,每个页面默认大小为2KB。表中共有三个字段:loid是pg当中该对象的唯一标识,这是在大对象创建时候分配的;pageno则用于表示本页在大对象数据中的页码也就是位置;data则用于存储大对象中的实际数据。

图1.7 pg_largeobject表结构

大对象机制支持的数据类型包括二进制大对象(BLOB)、字符大对象(CLOB)和双字节字符大对象(DBCLOB)。并且,该机制可以容忍页面的丢失,丢失的部分读取时记为0。例如存储某个大对象占据了6个页面,编号为0-5,由于某些原因,第1、2号页面丢失,此时pg_largeobject表并不会直接清掉这两个页面,而是在之后读到这两个页面的时候,将他们以全0的形式展现出来。另外,该机制并不是系统自动触发的,而是由用户主动进行控制。

  1. 对比

下面对这两种机制进行将要的比较。首先,TOAST机制只支持可变长类型,并且是系统自动触发,即需要达到2KB的阈值;而大对象则是用户手动调用。第二,TOAST机制不能丢失数据,否则报错;而大对象则能够容忍,丢失的用0代替。第三,大对象比TOAST更适合存储文件,如果用TOAST机制保存的话,需要将文件以二进制形式读出,然后将二进制作为字符串保存到变长数据类型的属性中,读取时,则要将数据读取出来再转换为文件;而大对象直接将文件视为一个对象进行存储,更加快捷。最后,尽管TOAST和大对象都是将数据切片然后存储至表中,但TOAST提供了线外存储和压缩这两种机制;而大对象不会对数据做处理,而是直接存储。

六.文件存放布局

在介绍完前面的内容后,再来看一下建立的数据库、表在磁盘上存储的目录形式是怎么样的。pg存储数据的地方被称为PGDATA目录,一般默认在pg的安装目录的data文件夹中。PGDATA目录中存放的数据类型总体上可分为两类,目录和文件。在目录中,包括base目录,用于存放数据库及包含的表;global目录,用于存放全局对象、pg_wal目录,用于存放预写日志,即WAL日志,等等;而文件则包括一些配置文件,例如postgresql.conf等。

base目录中的情况,其结构如图1.8所示。首先,base中的每个文件夹代表一个数据库,其名称为数据库的oid;然后在数据库文件夹中则包含表的相关文件,比如以表oid为命名的用于存储表格数据的主数据文件以及对应的FSM和VM文件。另外, 在pg部署完成之后,会默认包含template0和template1这两个模板数据库以及postgres数据库。

图1.8 base目录

 

文章来自个人专栏
postgresql
4 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
1