PostgreSQL一贴一图

xiaobu 1月前 123

数据文件的格式


数据文件是数据库中保存真正数据的地方,它的体积是最大的。在PG中,每个表就是磁盘上的一个文件。这个文件被划分成了大小一样的数据块(data block)。数据块的大小是由常量BLCKSZ来规定的,这个是在编译源代码时就确定的,通常BLCKSZ的值为8192,最大值可以是32KB(32 * 1024)。

我们可以使用下图来表示PG的数据文件的概念:

一个表一个文件,这个文件被称为逻辑的文件(logical data file)。该文件是由BLCKSZ大小的数据块组成,每个数据块有一个“块号”,从0开始。块号是由4个字节来表示的,所以一个表最多可以有2^32个数据块,即42亿多个数据块。在每个数据块的大小是8K的情况下,一个表的体积的最大值是4GB * 8KB = 32TB。在数据块的大小是32KB的情况下,一个表的体积是128TB。所以如果面试时,主考官问你:在PG中,一个表的最大体积是多少,这个问题你应该可以回答出来。

 

管理一个32TB的文件在目前的计算机工程领域是比较费事的,所以PG把这个逻辑文件分割成若干更加容易管理的小文件,这些小文件被称为“段”(segment)文件。在缺省情况下,PG的段文件的体积是1GB,你可以查阅源代码中的RELSEG_SIZE和BLCKSZ这两个常量,它们的乘积就是1GB。

假设一个表的第一个段文件的名字是16389,则第二个段文件的名字叫做16389.1,第三个段文件的名字叫做16389.2,依次类推。

 

 

 

 

 

 

最新回复 (6)
  • xiaobu 1月前
    引用 2

    数据块的结构


    缺省情况下,每个数据块的大小是8192字节,数据块的最大值是32KB。无论它是表的数据块,还是索引的数据块。所有的数据块都具备同样的布局结构,如下图所示:

     

    在8192字节的开始有24字节的”块头“,紧挨着块头是记录指针item,每条记录一个指针,里面包含了这个记录的偏移量和长度。记录指针是4字节。

    从后往前数,首先是一个特殊区域(special area),然后就是一条条记录。在PG中,记录被称为tuple,表被称为relation。注意记录是从后往前排列的。

     

    在24字节的块头,我们要注意三个指针,pd_lower、pd_upper和pd_special。它们都是2字节,表示偏移量。pd_lower表示空闲空间的下限,pd_upper表示空闲空间的上限。两个指针相减后的值就是这个数据块的空闲空间的大小。当我们想插入一个大小为n字节的记录到该数据块时,我们要保证该数据块的空间空间大于n,这是很显然的道理。

     

    PG中所有的表都是堆表(heap table),就是插入的记录存放的位置和插入的顺序,该记录的数据无关,找到足够大的地方就插入。堆表的特殊区域是不存在的,所以在数据块的大小为8192字节时,pd_special = 8192。

    特殊区域仅仅供各种类型的索引使用。

     

    上述图是一个非常重要的图,它是我们理解PG各种技术的基础概念,所以大家要牢记在心。

     

     

     

     

  • xiaobu 1月前
    引用 3

    PG中关于文件FORK的概念


    PG中,一张表对应一个数据文件,这个数据文件被称为主数据文件。除了主数据文件以外,还有两种辅助的数据文件,一个是FSM文件,一个是VM文件。假设一个表的主数据文件是16389,则对应的FSM文件的文件名是16389_fsm,对应的VM文件名是16389_vm。这三种文件的关系可以用下图来表示:

    FSM是free space management的缩写。当我们想往一个表中插入一条记录时,我们首先要找到一个数据块,其上的空闲空间足够大,足够容纳这条新插入的记录。一个数据块的空闲空间 = pd_upper - pd_lower。如果一个表中有上百万个数据块,如何在这么庞大的数据块中找个一个空闲空间足够大的数据块,就是一个难题。很显然我们不能逐一扫描这些数据块,而是要有更加快速的查找方式。FSM就是为了解决这个挑战而设计的。它就是一个字典,用一个字节表示一个数据块上的空闲空间,而且它的组织方式是树形结构,查找速度非常快。FSM文件上的信息是自动维护的,不需要用户操心。

    VM是visibility mapping的缩写,它用2个比特表示一个数据块的状态。其用途主要是VACUUM过程中帮助VACUUM进程快速决定一个数据块上是否有死亡记录。

    大致了解了这两种文件类型后,我们就容易理解FORK这个概念。FSM和VM文件也是按照BLCKSZ划分成一样大小的数据块,这些数据块也会被加载到共享池(shared buffer)中,和主要的数据页混在一起。为了区分共享池中一个数据页的类型,PG引入了FORK的概念,FORK就是数据页的类型,分为MAIN,FSM和VM三种,还有一种类型我们不常用。

    如果我们要指定一个数据块,我们需要五元组(a, b, c, d, e),a表示这个数据块所在的表空间的编号,b表示这个数据块所在的数据库的编号,c表示这个数据块所在的表的编号,d表示数据块的编号,e表示这个数据块的FORK,就是这个块是MAIN,还是FSM,还是VM。

    当你安装好pg_buffercache插件后,这个插件提供的系统表中有一列就记录FORM的类型。

     

     

     

     

  • xiaobu 1月前
    引用 4

    数据指针的结构


    在一个数据块上,开始的24字节是块头,紧挨着块头就是每条记录的指针,被称为“记录指针”。每个记录指针的大小是4字节,包含对应记录在本数据块中的偏移量,这条记录的长度信息。其结构可以用下图来表示:

    4字节共计32比特,分为15 + 15 + 2的结构,其中15比特表示该记录指针对应的记录在本数据块上的偏移量,另外15比特表示这条记录的长度,单位是字节。还有2比特表示该记录的四种状态,如:正常记录,已经被删除的记录等等。

     

    根据这个设计,我们可以知道,PG数据块的最大尺寸是32KB,即2^15。

  • xiaobu 1月前
    引用 5

    WAL文件的分割


    从理论上讲,WAL文件只有一个,对它的写入只有在文件尾部增加这一个操作。因为LSN是8字节,而LSN表示WAL文件上某个字节的位置,或者偏移量,所以这个WAL文件的体积是非常巨大的,是16EB(2^64)字节这么大。在这个文件中,第一个字节的LSN是0,最后一个字节的LSN是0xFFFFFFFFFFFFFFFF。为了更便于阅读,我们在表示LSN时,会在高4字节和低4字节的分割处加上一个”/“符号,所以最后一个字节的LSN是FFFFFFFF/FFFFFFFF。

    这么巨大的一个WAL文件,已目前的计算机工程水平,没有任何一块磁盘可以存放下,所以我们可以把它进行分割。分割的方式是把它划分为4GB大小的一个文件,很显然,这样的文件一共有4GB个,因为2^64 / 2^32 = 2^32,如下图所示:

    这种4GB大小的文件被称为”逻辑WAL文件“(logicall WAL file),如果我们给它们编号,则编号正好是所有LSN的高4字节。譬如一个LSN为ABC/DEF12345,则这个LSN位置所表示的字节x被保存在编号为0xABC的WAL文件中。WAL文件的编号范围从0到0xFFFFFFFF,共计2^32个。这个编号被称为逻辑WAL文件编号。

     

    但是4GB大小的划分方案依然显得很庞大,PG进一步对逻辑文件进行划分,最小按照1MB的尺寸,最大按照1GB的尺寸,划分成的WAL文件被称为”段“(segment)文件。这些段文件就是我们在pg_wal目录下看到的真实的文件。我们提到”WAL文件“这个概念,基本上都是指WAL段文件。

     

    当我们按照1GB的体积划分是,4GB的逻辑文件被分为4个,编号是0、1、2和3。当我们按照1MB的体积划分时,4GB的逻辑文件被分为4096个,编号是0到4095。在缺省情况下,我们按照16MB来划分,那么一个4GB的逻辑文件就被分成256个,编号是0到255,如下图所示:

    至此,我们有两个编号,一个是逻辑文件的编号,其范围从0到0xFFFFFFFF,第二个是段文件的编号,其范围不定。如果按照缺省16MB的划分方案,段文件的编号范围从0到255。这两个编号形成二级编号,即x.y,x是逻辑文件的编号,y是段文件的编号。

    理解了这些知识后,我们就容易理解WAL段文件的文件名的规则,它包含时间线,逻辑文件编号和段文件编号三个信息,如下图所示:

    大家很容易看到一个规律,就是因为在16MB的划分方式下,段文件的编号取值范围是0到255,用一个字节就可以表示,所以WAL文件的文件名,从后往前数,跳过2位后,有连续的6个0是不会改变的。

     

    两级编号也可以变成一级编号。在WAL文件的大小是16MB的情况下,编号为0的逻辑文件被分割为256个段文件,它们的编号是0.0, 0.1, 0.2, ..., 0.255。编号为1的逻辑文件也被分割为256个段文件,它们的编号是1.0, 1.1, 1.2, ..., 1.255。如果把这些编号变成一维的,则0.0的编号为0, 0.1的编号为1, 0.255的编号为255, 1.0的编号为256, 1.1的编号为257, 1.255的编号是511。依次类推。

  • xiaobu 1月前
    引用 6

    什么是LSN?


    在PG中,LSN是Log Sequence Number的缩写,它是一个非常基础性的概念,随处可见。所以对这个概念的正确理解与否,会影响你对更多PG技术的理解。

     

    其实这个概念是非常简单的:当某个用户对PG数据库进行修改后,描述修改的信息被按照某种格式组成一条WAL记录,该WAL记录会被追加到一个WAL文件的尾部,这个WAL文件处于最大体积时的状态被称为”WAL空间“。WAL空间中,每个字节的地址由8字节来记录,所以这个WAL空间的体积是2^64个字节,如下图所示:

    我们对这个WAL空间上每个字节进行编号,第一个字节的编号是0,最后一个字节的编号自然是0xFFFFFFFFFFFFFFFF。大家注意:编号是8字节。一个8字节的数字用16进制来表示,要有16个字符,因为一个字节可以用两位十六进制的数字来表示。为了更加清晰地显示,我们在高四字节和低四字节中间加上一个”/“除号,这个分隔符仅仅是为了更容易阅读而设计的,并没有其它更多的含义。

    了解了以上知识后,我们就容易理解什么是LSN。所谓LSN,就是在WAL空间上每个字节的位置编号,或者叫地址,或者叫偏移量,就是该字节的LSN。每个字节都有一个LSN。

     

    由于WAL记录是由n个字节组成,n的最小值是24,所以一条WAL记录会有n个LSN,因为每个字节都有一个LSN嘛。我们把第一个字节的LSN作为这条WAL记录的LSN。

     

    WAL记录无论多么复杂,它开始的24字节是固定的,其中头4字节表示这条WAL记录的总长度,包括开始的24字节,所以当我们拿到一个WAL记录的LSN后,在WAL文件上从这个位置读取4字节,就知道这条WAL记录的长度了,从而可以完整地把这条WAL记录从WAL文件中提取出来。

     

    LSN就是编号,LSN就是编号,LSN就是编号,重要的事情说三遍。LSN并不神秘,它是一个单一且巨大的WAL文件中每个字节的编号。在这个文件中每个字节都有一个LSN。无非是我们说LSN,通常指的是WAL记录的LSN,即这条WAL记录的第一个字节的LSN。每条WAL记录的开始四字节表示长度。 

     

     

  • xiaobu 1月前
    引用 7

    WAL文件的结构


    这里的”WAL文件“指的是段文件(segment),就是你在pg_wal目录下能够看到的WAL文件。它的结构可用下图来表示:

    不出意外,WAL文件依然是按照8KB来划分的,一样的配方,熟悉的味道。但是因为WAL记录是可变长的,所以它的整体结构和数据文件一样,但是每个数据块内部是不同的。

    每个数据块的开始是一个叫做XLogPageHeaderData的数据结构,但是第一个数据块的开始有点特殊,它的结构叫做XLogLongPageHeaderData,多了16字节。在这16字节中,记录了“系统标识符”,system identifier。当我们使用initdb创建数据库集群时,initdb会根据某种算法,产生一个8字节的随机数,作为这个数据库集群的唯一标识。这个系统标识被记录在控制文件中。

    在WAL文件的开始部分记录这个系统标识符,其目的是防止未来在恢复数据库时用错了WAL文件。譬如你可能会B数据库集群产生的WAL文件来恢复A数据库集群。因为PG在每个WAL文件的开始部分都记录了这个系统标识符,所以在恢复之前,用它和控制文件中的系统标识符对比一下,就知道是否搞错了。

     

    WAL记录的格式虽然复杂,但是每条WAL记录的开始部分是24字节的固定头,其中开始的4字节表示这条WAL记录的总长度,包括24字节。所以一条WAL记录的最小尺寸是24字节,XLOG_SWITCH类型的WAL记录就是24字节,它仅仅表示在它被插入到WAL文件中时,PG进行了一次WAL文件的切换。

     

    我们要区分“WAL记录”和“WAL文件”两个概念,WAL文件是磁盘上的文件,所有的WAL文件的体积都是一样的,文件名用户不能随便更改。WAL记录是WAL文件中的n个连续的字节,n的最小值是24。每条WAL记录都有一个LSN,即这条WAL记录的第一个字节在WAL空间中的位置编号。

     

返回
发新帖