PostgreSQL启动过程中的那些事十六:启动进程三:CheckPointGuts刷出共享内存里所有数据 ...

 

       话说启动进程调用 StartupXLOG 启动 xlog ,根据情况,如果需要就排除系统故障引起的数据库不一致状态,做相应的 REDO UNDO ,然后创建一个检查点,把所有共享内存磁盘缓冲和提交数据缓冲写并文件同步到磁盘、把检查点插入 xlog 文件、更新控制文件,使数据库达到一种状态

这节接着讨论启动进程在创建检查点时调用的 CheckPointGuts 方法(在创建重启点时也会调用这个方法)。 CheckPointGuts 方法功能是刷出所有共享内存中的数据到磁盘并做文件同步,共享内存中的数据包括 clog subtrans multixact predicate relationmap buffer (数据文件)和 twophase 相关数据。 CheckPointGuts 方法定义和“ CheckPointGuts 方法调用序列图 ”见下面。

 

static void

CheckPointGuts(XLogRecPtr checkPointRedo, int flags)

{

    CheckPointCLOG();

    CheckPointSUBTRANS();

    CheckPointMultiXact();

    CheckPointPredicate();

    CheckPointRelationMap();

    CheckPointBuffers(flags);   /* performs all required fsyncs */

    /* We deliberately delay 2PC checkpointing as long as possible */

    CheckPointTwoPhase(checkPointRedo);

}

 

PostgreSQL启动过程中的那些事十六:启动进程三:CheckPointGuts刷出共享内存里所有数据 ...

CheckPointGuts 方法调用序列图

 

CheckPointGuts 方法主要是通过调用提交事务日志管理器的方法 CheckPointClog ,子事务日志管理器的方法 CheckPointSUBTRANS ,多事务日志管理器的方法 CheckPointMultiXact ,支持序列化事务隔离级别的谓词锁模块的方法 CheckPointPredicate ,目录 / 系统表到文件节点映射模块的方法 CheckPointRelationMap ,缓存管理器的方法 CheckPointBuffers ,两阶段提交模块的方法 CheckPointTwoPhase 把共享内存里的数据刷出并文件同步到磁盘。

其中 提交事务日志管理器的方法 CheckPointClog 子事务日志管理器的方法 CheckPointSUBTRANS 、多事务日志管理器的方法 CheckPointMultiXact 、多事务日志管理器的方法 CheckPointMultiXact 、支持序列化事务隔离级别的谓词锁模块的方法 CheckPointPredicate 最后都调用了 SLRU 模块的 SimpleLruFlush 方法,把相关共享内存数据写到磁盘,并调用 pg_fsync 方法把相关内容文件同步到磁盘上对应文件。

在缓存管理器的方法 CheckPointBuffers ,两阶段提交模块的方法 CheckPointTwoPhase 里,因为没有使用 SLRU 算法,直接调用 pg_fsync 方法把相关内容文件同步到磁盘上对应文件。

在目录 / 系统表到文件节点映射模块的方法 CheckPointRelationMap 里,在释放 RelationMappingLock 时,会完成共享内存里相关系统表和对应物理文件映射的文件同步到磁盘工作。

我们看一下各种日志管理,日志对数据库是至关重要的一部分,出现系统故障时,数据库通过重放日志恢复数据,保证数据库一致性和完整性。

Pg 里有 XLOG CLOG SUBTRANS LOG MultiXactID LOG 四种事务日志, XLOG 是事务日志,就是平时常说的 REDOLOG ,记录了事务操作数据库的过程信息和事务最终状态; CLOG XLOG 里事务的提交状态日志; SUBTRANS 是子事务日志, 为每一个事务存储父事务 ID 。这是嵌套事务实现的基础部分, SUBTRANS 仅需要为当前打开的事务记住信息,没有必要在崩溃并重启后保留数据; MultiXactID 是组合事务日志,由一组事务 ID 组成, 是共享行锁 shared-row-lock 实现的基础部分,共享锁锁住的元组在其 Xmax 字段存储 MultiXactId 。各种日志都存放在对应的日志文件里。

有了文件就有了 I/O ,为了降低 I/O 开销, pg 设置了各种日志的缓存区,由对应的日志管理器管理日志的写、文件同步和读等日志维护工作。 Pg 使用简单最近最少使用( SLRU )算法来管理事务日志。使用轻量锁 LWLock ControlLock 锁保护整个缓冲区,其中的每个缓冲块(默认 8K )还有一个 LWLock 锁保护,以控制并发操作。 SLRU 及事务日志的部分相关数据结构在下面。

 

CLOG 控制链接到共享内存数据结构

static SlruCtlData ClogCtlData;

#define ClogCtl (&ClogCtlData)

 

SUBTRANS 控制链接到共享内存数据结构

static SlruCtlData SubTransCtlData;

#define SubTransCtl  (&SubTransCtlData)

 

MultiXact 控制链接到共享内存数据结构

static SlruCtlData MultiXactOffsetCtlData;

static SlruCtlData MultiXactMemberCtlData;

#define MultiXactOffsetCtl   (&MultiXactOffsetCtlData)

#define MultiXactMemberCtl  (&MultiXactMemberCtlData)

 

 

typedef SlruCtlData * SlruCtl ;

 

/* SlruCtlData 是指向共享内存里的活跃信息的非共享结构 */

typedef struct SlruCtlData

{

    SlruShared shared ;

 

    /* 这个标志告诉 是否文件同步写( pg_clog multixact 成员是 true,pg_subtrans pg_notify false */

    bool        do_fsync ;

 

    /* 为截断目的决定两个页号哪一个是更旧的。为了用 包裹 XID 算法( with wraparound XID arithmetic )做正确的事,这儿我们需要用事务 ID 比较 */

    bool        (* PagePrecedes ) ( int , int );

 

    /* SimpleLruInit 期间目录被设置,并且从那以后不变。因为它总是相同的,它不必放到共享内存里。 */

    char        Dir [64];

} SlruCtlData ;

 

共享内存状态

typedef struct SlruSharedData

{

    LWLockId    ControlLock ;

 

    /* 由这个 SLRU 结构管理的缓存块号 */

    int         num_slots ;

 

    /* 持有每一个缓存槽信息的数组。当状态是 EMPTY 缓存页 / 块号是未定义的,当作

page_lru_count */

    char      ** page_buffer ;

    SlruPageStatus * page_status ;

    bool       * page_dirty ;

    int        * page_number ;

    int        * page_lru_count ;

    LWLockId    * buffer_locks ;

 

    /* SLRU / 块里的相关条目的 WAL 刷出 LSN 的可选数组。如果不是 0/NULL ,在写缓存页 / 块前 我们必须刷出 WAL pg_clog true multixact pg_subtrans pg_notify false )。 Group_lsn[] 每缓存页 / 块槽有 lsn_groups_per_page 条目,在这个槽的缓存页 / 块上 SLRU 条目的一个临近组 每一个缓存页 / 块槽 包含最高已知 LSN */

    XLogRecPtr * group_lsn ;

    int         lsn_groups_per_page ;

 

    /* 我们通过设置 page_lru_count[slotno ] = ++cur_lru_count 标记页“最近使用”;最老旧页因此是有表达式 cur_lru_count - page_lru_count[slotno ] 值最高 / 大的那一个。这个数事实上包裹,但这个计算仍然工作 和缓存页 / 块的年龄(超过了 INT_MAX 数)一样长。    */

    int         cur_lru_count ;

 

    /* latest_page_number 是当前日志结尾的页 / 块号;这不是严格的数据,因为我们仅用它避免包裹 swapping 出了最后的页 / 块。 */

    int         latest_page_number ;

} SlruSharedData ;

 

typedef SlruSharedData * SlruShared ;

 

/* 页状态代码。注意这不包含 "dirty" 位。仅在 VALID 或者 WRIT_IN_PROGRESS 状态里 page_dirty 能是 true ;在后面的例子 / 情况里 它暗示 从这次写开始后页又被搞脏 */

typedef enum

{

    SLRU_PAGE_EMPTY ,         /* buffer is not in use */

    SLRU_PAGE_READ_IN_PROGRESS , /* page is being read in */

    SLRU_PAGE_VALID ,         /* page is valid and not being written */

    SLRU_PAGE_WRITE_IN_PROGRESS /* page is being written out */

} SlruPageStatus ;

 

 

SLRU 算法的缓存区操作在下面,其中包括了本节多次调用的 SimpleLruFlush 方法,将缓存数据刷出并文件同步到磁盘。

extern Size SimpleLruShmemSize ( int nslots, int nlsns);

extern void SimpleLruInit ( SlruCtl ctl, const char *name, int nslots, int nlsns,

             LWLockId ctllock, const char *subdir);

extern int SimpleLruZeroPage ( SlruCtl ctl, int pageno);

extern int SimpleLruReadPage ( SlruCtl ctl, int pageno, bool write_ok,

                TransactionId xid);

extern int SimpleLruReadPage_ReadOnly ( SlruCtl ctl, int pageno,

                        TransactionId xid);

extern void SimpleLruWritePage ( SlruCtl ctl, int slotno);

extern void SimpleLruFlush ( SlruCtl ctl, bool checkpoint);

extern void SimpleLruTruncate ( SlruCtl ctl, int cutoffPage);

extern bool SlruScanDirectory ( SlruCtl ctl, int cutoffPage, bool doDeletions);

 

就到这儿吧。

 


------------
转载请著明出处,来自博客:
blog.csdn.net/beiigang
beigang.iteye.com