前言
在 postgresql 数据库里,数据都会以表的形式组织起来。表的数据会被持久化到底层的磁盘里。负责与底层的磁盘交互,就是 postgresql 的文件存储层。本篇博客会依次介绍表的文件构成、分片机制和存储接口。
表的文件类型
postgresql 使用 RelFileNode 来标识一张表
typedef struct RelFileNode
{
Oid spcNode; /* tablespace */
Oid dbNode; /* database */
Oid relNode; /* relation */
} RelFileNode;
数据表都有下面三种文件
- main 文件存储着实际的数据,
- fsm 文件记录着 main 文件块的空闲大小,当有数据插入时,会优先从空闲位置写入。
- vm 文件记录了过期的数据,这在 vaccum 清洗过程中有用到。
ForkNumber 枚举定义文件类型
typedef enum ForkNumber
{
InvalidForkNumber = -1,
MAIN_FORKNUM = 0, // 存储着实际的数据,文件名没有后缀
FSM_FORKNUM, // 存储着空闲位置,文件名的后缀为fsm
VISIBILITYMAP_FORKNUM, // 文件名的后缀为vm
INIT_FORKNUM // 用于数据库初始化,文件名的后缀为init
} ForkNumber;
#define MAX_FORKNUM INIT_FORKNUM // 等于3
文件分片
因为有些文件系统对单个文件的数目大小有限制,postgresql 数据库为了良好的移植性,将上述文件进行切片,每个切片对应着一个文件,通过在文件名添加数字后缀来区分(注意第一个分片没有数字后缀)。每个文件切片由MdfdVec表示
typedef struct _MdfdVec
{
File mdfd_vfd; // 文件描述符
BlockNumber mdfd_segno; // 分片索引
} MdfdVec;
块存储
postgresql 存储数据并不是一条一条的存储,而是将数据合并成一个小块,每个块的大小都相同,默认为8KB。之所以这样设计,是因为磁盘的读写速度太慢了,尤其是随机读写,通过增加单次的吞吐量,来提高读写性能。
每个块都有一个唯一的标识,叫做 block number。它们依次递增,连续的存储在文件里。每个文件分片包含的块数目是相同的。下面展示了根据 block number 找到对应的文件切片,
// blkno是块的唯一标识,RELSEG_SIZE是切片包含的块数目
targetseg = blkno / ((BlockNumber) RELSEG_SIZE);
动态哈希表
postgresql 使用 SMgrRelationData来表示单个表的文件结构,下面展示了主要成员
typedef struct SMgrRelationData
{
RelFileNodeBackend smgr_rnode; // 表的标识
BlockNumber smgr_targblock; // 准备写的blocknum
BlockNumber smgr_fsm_nblocks; // 最大的fsm文件的fsm block number,用于快速判断用户传递的blocknum是否超过最大值,需要扩充
BlockNumber smgr_vm_nblocks; /* last known size of vm fork */
int smgr_which; // 选择哪个存储接口,目前只有一种实现,默认为0
int md_num_open_segs[MAX_FORKNUM + 1]; // 每种类型文件的分片数目
struct _MdfdVec *md_seg_fds[MAX_FORKNUM + 1]; // 每种类型对应的分片数组
} SMgrRelationData;
注意到和分片相关的两个数组md_num_open_segs和md_seg_fds,它们的数组长度都是文件类型的数目。前者是一个一维数组,表示打开的分片文件的数目。后者是一个二维数组,存储着每种类型文件的的分片 。
这里还额外说下RelFileNodeBackend的定义,它与RelFileNode的区别仅仅是多了一个BackendId
typedef struct RelFileNodeBackend
{
RelFileNode node;<

最低0.47元/天 解锁文章
179

被折叠的 条评论
为什么被折叠?



