暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PolarDB PostgreSQL存储I/O路径分析

PolarDB 2025-02-20
33

PolarDB PostgreSQL存储I/O路径分析

关于 PolarDB PostgreSQL 版

PolarDB PostgreSQL 版是一款阿里云自主研发的云原生关系型数据库产品,100% 兼容 PostgreSQL,高度兼容Oracle语法;采用基于 Shared-Storage 的存储计算分离架构,具有极致弹性、毫秒级延迟、HTAP 、Ganos全空间数据处理能力和高可靠、高可用、弹性扩展等企业级数据库特性。同时,PolarDB PostgreSQL 版具有大规模并行计算能力,可以应对 OLTP 与 OLAP 混合负载。

背景

PostgreSQL中不同类型的数据持久化在不同的路径下,由于这些数据之间存在不同的特征,因此其各自的I/O操作路径也是不同的。

按照I/O路径的不同,可以将PostgreSQL的持久化文件分为以下五类:

  1. 数据文件,主要保存在路径base/、global/下;

  2. WAL日志文件,主要保存在路径pg_wal/下;

  3. 事务文件,主要保存在路径pg_commit_ts/、pg_csnlog/、pg_multixact/、pg_xact/下;

  4. 特殊文件,主要保存在路径global/pg_control、pg_replslot/、pg_twophase/下;

  5. 配置及错误日志文件,主要保存在log/、pg_hba.conf、postgresql.conf中。

PostgreSQL存储I/O路径

image.png
  1. 数据文件,即存储表、索引等数据的文件,是PostgreSQL中用户数据的主要持久化形式。

    1. 由于这部分数据量较大,于是PostgreSQL提供了Buffer Pool缓存池技术来加速这部分数据的读写速度,针对Buffer Pool缓存池的介绍可以参见以往文章《Persistent Buffer Pool》

    2. PostgreSQL为Buffer Pool提供了专门的I/O接口—sgmr(Storage Manager)模块,该模块实现了以下接口函数来执行具体的I/O操作:

extern SMgrRelation smgropen(RelFileNode rnode, BackendId backend);
extern bool smgrexists(SMgrRelation reln, ForkNumber forknum);
extern void smgrsetowner(SMgrRelation *owner, SMgrRelation reln);
extern void smgrclearowner(SMgrRelation *owner, SMgrRelation reln);
extern void smgrclose(SMgrRelation reln);
extern void smgrcloseall(void);
extern void smgrclosenode(RelFileNodeBackend rnode);
extern void smgrcreate(SMgrRelation reln, ForkNumber forknum, bool isRedo);
extern void smgrdosyncall(SMgrRelation *rels, int nrels);
extern void smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo);
extern void smgrextend(SMgrRelation reln, ForkNumber forknum,
        BlockNumber blocknum, char *buffer, bool skipFsync)
;
extern bool smgrprefetch(SMgrRelation reln, ForkNumber forknum,
       BlockNumber blocknum)
;
extern void smgrread(SMgrRelation reln, ForkNumber forknum,
      BlockNumber blocknum, char *buffer)
;
struct PgAioInProgress;
extern void smgrstartread(struct PgAioInProgress *io,
        SMgrRelation reln, ForkNumber forknum,
        BlockNumber blocknum, char *buffer)
;
extern void smgrstartwrite(struct PgAioInProgress *io,
         SMgrRelation reln, ForkNumber forknum,
         BlockNumber blocknum, char *buffer, bool skipFsync)
;
extern void smgrwrite(SMgrRelation reln, ForkNumber forknum,
       BlockNumber blocknum, char *buffer, bool skipFsync)
;
extern void smgrwriteback(SMgrRelation reln, ForkNumber forknum,
        BlockNumber blocknum, BlockNumber nblocks)
;
extern BlockNumber smgrnblocks(SMgrRelation reln, ForkNumber forknum);
extern BlockNumber smgrnblocks_cached(SMgrRelation reln, ForkNumber forknum);
extern void smgrtruncate(SMgrRelation reln, ForkNumber *forknum,
       int nforks, BlockNumber *nblocks)
;
extern void smgrimmedsync(SMgrRelation reln, ForkNumber forknum);

复制
  1. smgr模块缓存了上层模块打开的表信息,结构为SMgrRelationData,主要保存的是已打开的fd句柄信息、缓存的文件大小nblocks信息、表文件节点信息RelFileNode。这些信息都缓存到了进程私有内存中,使用哈希表SMgrRelationHash执行快速的查找。

  2. smgr模块提供了一层表信息缓存机制,以减少一些重复性的I/O操作,比如:open、lseek、close等。smgr模块本身并不关心底层存储设备的类型,因此,提供了一个结构体f_smgr来提供具体的I/O操作函数句柄(如下所示),目前PostgreSQL中实现了一套md_*接口用于执行具体的I/O操作。

typedef struct f_smgr
{

void  (*smgr_init) (void); /* may be NULL */
void  (*smgr_shutdown) (void); /* may be NULL */
void  (*smgr_open) (SMgrRelation reln);
void  (*smgr_close) (SMgrRelation reln, ForkNumber forknum);
void  (*smgr_create) (SMgrRelation reln, ForkNumber forknum,
        bool isRedo);
bool  (*smgr_exists) (SMgrRelation reln, ForkNumber forknum);
void  (*smgr_unlink) (RelFileNodeBackend rnode, ForkNumber forknum,
        bool isRedo);
void  (*smgr_extend) (SMgrRelation reln, ForkNumber forknum,
        BlockNumber blocknum, char *buffer, bool skipFsync);
bool  (*smgr_prefetch) (SMgrRelation reln, ForkNumber forknum,
          BlockNumber blocknum);
void  (*smgr_read) (SMgrRelation reln, ForkNumber forknum,
         BlockNumber blocknum, char *buffer);
void  (*smgr_startread) (struct PgAioInProgress *io,
           SMgrRelation reln, ForkNumber forknum,
           BlockNumber blocknum, char *buffer);
void  (*smgr_startwrite) (struct PgAioInProgress *io,
         SMgrRelation reln, ForkNumber forknum,
         BlockNumber blocknum, char *buffer,
         bool skipFsync);
void  (*smgr_write) (SMgrRelation reln, ForkNumber forknum,
          BlockNumber blocknum, char *buffer, bool skipFsync);
void  (*smgr_writeback) (SMgrRelation reln, ForkNumber forknum,
           BlockNumber blocknum, BlockNumber nblocks);
 BlockNumber (*smgr_nblocks) (SMgrRelation reln, ForkNumber forknum);
void  (*smgr_truncate) (SMgrRelation reln, ForkNumber forknum,
          BlockNumber nblocks);
void  (*smgr_immedsync) (SMgrRelation reln, ForkNumber forknum);
int   (*smgr_fd) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, uint32 *off);
/* POLAR: bulk read */
void  (*polar_smgr_bulkread) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
          int blockCount, char *buffer);
/* POLAR: bulk write */
void  (*polar_smgr_bulkwrite) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
           int blockCount, char *buffer, bool skipFsync);
/* POLAR: bulk extend */
void  (*polar_smgr_bulkextend) (SMgrRelation reln, ForkNumber forknum,
            BlockNumber blocknum, int blockCount, char *buffer, bool skipFsync,
            ExtendBatchType extendtype, bool created_in_current_xact);
/* POLAR end */
} f_smgr;

staticconst f_smgr smgrsw[] = {
/* magnetic disk */
 {
  .smgr_init = mdinit,
  .smgr_shutdown = NULL,
  .smgr_open = mdopen,
  .smgr_close = mdclose,
  .smgr_create = mdcreate,
  .smgr_exists = mdexists,
  .smgr_unlink = mdunlink,
  .smgr_extend = mdextend,
  .smgr_prefetch = mdprefetch,
  .smgr_read = mdread,
  .smgr_startread = mdstartread,
  .smgr_write = mdwrite,
  .smgr_startwrite = mdstartwrite,
  .smgr_writeback = mdwriteback,
  .smgr_nblocks = mdnblocks,
  .smgr_truncate = mdtruncate,
  .smgr_immedsync = mdimmedsync,
  .smgr_fd = mdfd,
/* POLAR: bulk read */
  .polar_smgr_bulkread = polar_mdbulkread,
/* POLAR: bulk write */
  .polar_smgr_bulkwrite = polar_mdbulkwrite,
/* POLAR: extend batch */
  .polar_smgr_bulkextend = polar_mdbulkextend,
/* POLAR end */
 }
};

复制
  1. md_*接口包装了PostgreSQL实现的虚拟文件描述符Vfd模块,向上层smgr模块提供具体的I/O操作实现。md_*接口主要对Vfd模块接口的返回结果做了错误处理、优化了I/O操作执行的时机、抽象了上层逻辑的表对象和底层存储文件之间的映射关系。

  2. Vfd(Virtual File Descriptor)模块包装了底层glibc库实现的open、read、write等读写接口,兼容了这些接口返回的错误码,并提供了VfdCache缓存当前已经打开的文件句柄信息。

  3. WAL日志文件,即存储PostgreSQL产生的数据修改记录,主要用于崩溃恢复、备份、备库数据同步等。

  4. PostgreSQL提供了WAL Buffer机制来加速WAL日志文件的读写,具体可以参见文章《PostgreSQL WALBuffer并发机制》。

  5. WAL日志文件的I/O操作,是直接通过调用Vfd模块实现的接口执行的,没有复杂的缓存机制,因为WAL日志文件的I/O操作比较简单:Append-only的追加写、同一时刻只能有一个进程写文件、多进程并发写入机制由WAL Buffer保证。

  6. 事务文件,存储了CLOG信息、事务提交时间、组合事务信息、CSN事务信息等内容。

  7. 事务文件存在这样的特征:事务是递增分配的,而事务状态在持久化文件层面是连续存储的、一一对应的关系,可以通过简单的线性公式将事务ID与持久化存储的文件位置一一关联起来。另外,事务文件与事务状态是一对多的映射关系。因此,事务文件的读写操作一般情况下出现这样的规律:读写操作大多数发生在同一个文件block上,于是PostgreSQL提供了SLRU(Simple LRU buffering)机制来加速该类文件的读写操作。

  8. SLRU模块将事务文件的读写频率最小化,其真正执行I/O操作是通过调用Vfd模块的接口实现的。

  9. 特殊文件,包含controlfile文件读写、replslot复制槽、两阶段事务文件pg_twophase,这些文件的读写操作都是直接调用Vfd模块提供的接口实现的。

  10. 配置及错误日志文件,包含postgresql.conf配置文件、pg_hba.conf配置文件、错误日志文件log等都是通过直接调用glibc提供的接口实现的。

PolarDB PostgreSQL存储I/O路径

image.png
  1. PolarDB PostgreSQL实现了polar_vfs模块,让Vfd模块调用polar_vfs模块实现的polar_open、polar_read、polar_write等接口,polar_vfs模块自动判断文件是否位于共享存储设备上,如果是的话,polar_vfs模块将I/O操作调用PolarFS库提供的接口,将I/O操作转发给外部共享存储设备,比如:PolarStore存储设备。

  2. 关于polar_vfs模块的详情内容介绍,见以往文章《PolarDB PostgreSQL版 VFS 模块介绍》

总结

本文介绍了PostgreSQL中各类文件的I/O操作路径实现原理,涉及了多个模块的实现。同时,介绍了PolarDB PostgreSQL在I/O操作路径上做的演进—VFS模块的实现理念,从I/O路径的模块层次关系上看,VFS模块的实现是一种比较好的设计方案。


文章转载自PolarDB,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论