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

PostgreSQL Xlog回放中的invalid page

2023-09-27 07:36:54
19
0

背景

在研究PG社区日志并行回放方案时,发现为并行进程中设计了invalid page worker。由于回放的目标数据页可能被后续的drop操作删除,导致页面丢失或数据不一致。可是xlog在进行回放时是按序的,为什么后续对页面的操作会影响到当前的回放呢?为了研究这个问题,需要从drop相关的操作入手。

Drop table

drop table的操作主要调用RemoveRelation函数来完成删除。该函数实现了DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW等多种操作。其简化流程如下图所示。

doDeletion中物理删除表时,将要删除的对象添加至pendingDeletes链表中,在事务提交时进行真正的删除。
void
RelationDropStorage(Relation rel)
{
    PendingRelDelete *pending;

    /* Add the relation to the list of stuff to delete at commit */
    pending = (PendingRelDelete *)
        MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
    pending->relnode = rel->rd_node;
    pending->backend = rel->rd_backend;
    pending->atCommit = true;   /* delete if commit */
    pending->nestLevel = GetCurrentTransactionNestLevel();
    pending->next = pendingDeletes;
    pendingDeletes = pending;

    RelationCloseSmgr(rel);
}
事务提交时会通过 smgrdounlinkall 函数对物理文件进行删除。
 

Redo

对涉及数据库页面修改的xlog日志,在进行回放前会通过 XLogReadBufferForRedoExtended 函数获取需要回放的buffer页面。如果之后存在drop等操作,该页面可能为一个invalid page。
考虑一个场景:
  1. insert/update操作,对pageA写入数据。
  2. Drop table,将pageA删除,pageA对应的磁盘blockA也被删除。
  3. 崩溃。

在进行崩溃恢复时,回放insert/update操作,发现需要回放的page无对应的block,标记为invalid page。回放过程中的invalid page使用hash表进行维护。当回放操作发现需要回放的page是invalid page时,则跳过回放。当回放drop操作的事务提交回放时,将该invalid page从hash表中删除。
当数据达到一致性时,invalid page的数量应该为0。

Full page write

当开启full page write时不会出现上述情况。
在尝试读buffer时会获取或创建相关relation的block。并通过指定blocknumber与获取到的blocknumber进行比较。若新建的blocknumber小于指定blocknumber,则认为原指定的block已不存在,在未开启full page write的情况下会被标记为invalid page。
Buffer
XLogReadBufferExtended(RelFileNode rnode, ForkNumber forknum,
                       BlockNumber blkno, ReadBufferMode mode)
{
    BlockNumber lastblock;
    Buffer      buffer;
    SMgrRelation smgr;

    Assert(blkno != P_NEW);

    /* Open the relation at smgr level */
    smgr = smgropen(rnode, InvalidBackendId);
    smgrcreate(smgr, forknum, true);

    lastblock = smgrnblocks(smgr, forknum);

    if (blkno < lastblock)
    {
        /* page exists in file */
        buffer = ReadBufferWithoutRelcache(rnode, forknum, blkno,
                                           mode, NULL);
    }
    else
    {
        /* hm, page doesn't exist in file */
        if (mode == RBM_NORMAL)
        {
            log_invalid_page(rnode, forknum, blkno, false);
            return InvalidBuffer;
        }
        if (mode == RBM_NORMAL_NO_LOG)
            return InvalidBuffer;
如果开启full page write,则会尝试回放整个buffer页面。会不断获取新的buffer页面和block,直到分配到的buffer对应的blocknum等于指定的blocknum。
do
{
    if (buffer != InvalidBuffer)
    {
        if (mode == RBM_ZERO_AND_LOCK || mode == RBM_ZERO_AND_CLEANUP_LOCK)
            LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
        ReleaseBuffer(buffer);
    }
    buffer = ReadBufferWithoutRelcache(rnode, forknum,
                                        P_NEW, mode, NULL);
}
while (BufferGetBlockNumber(buffer) < blkno);

并行回放invalid page worker

回放过程中可能会存在invalid page,并由hash表进行记录。当回放完毕时,invalid page的数量应该保持为0。
原本的回放过程中,回放过程遇到invalid page时会使用 log_invalid_page 对其进行记录;在进行 xact_redo_commit 事务提交重做时,如果事务中涉及到drop table,需对产生invalid page进行清理。
XLogDropRelation(RelFileNode rnode, ForkNumber forknum)
{
    forget_invalid_pages(rnode, forknum, 0);
}
在并行回放方案中,将invalid page的记录和清除交由统一的worker进行处理。
switch(page->cmd)
{
    case PR_LOG:
        PR_log_invalid_page_int(page->node, page->forkno, page->blkno, page->present);
        break;
    case PR_FORGET_PAGES:
        PR_forget_invalid_pages_int(page->node, page->forkno, page->blkno);
        break;
    case PR_FORGET_DB:
        PR_forget_invalid_pages_db_int(page->dboid);
        break;
    case PR_CHECK_INVALID_PAGES:
        PR_XLogCheckInvalidPages_int();
        break;
    default:
        PR_failing();
        elog(PANIC, "Inconsistent internal status.");
    break;
}

 

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

PostgreSQL Xlog回放中的invalid page

2023-09-27 07:36:54
19
0

背景

在研究PG社区日志并行回放方案时,发现为并行进程中设计了invalid page worker。由于回放的目标数据页可能被后续的drop操作删除,导致页面丢失或数据不一致。可是xlog在进行回放时是按序的,为什么后续对页面的操作会影响到当前的回放呢?为了研究这个问题,需要从drop相关的操作入手。

Drop table

drop table的操作主要调用RemoveRelation函数来完成删除。该函数实现了DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW等多种操作。其简化流程如下图所示。

doDeletion中物理删除表时,将要删除的对象添加至pendingDeletes链表中,在事务提交时进行真正的删除。
void
RelationDropStorage(Relation rel)
{
    PendingRelDelete *pending;

    /* Add the relation to the list of stuff to delete at commit */
    pending = (PendingRelDelete *)
        MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
    pending->relnode = rel->rd_node;
    pending->backend = rel->rd_backend;
    pending->atCommit = true;   /* delete if commit */
    pending->nestLevel = GetCurrentTransactionNestLevel();
    pending->next = pendingDeletes;
    pendingDeletes = pending;

    RelationCloseSmgr(rel);
}
事务提交时会通过 smgrdounlinkall 函数对物理文件进行删除。
 

Redo

对涉及数据库页面修改的xlog日志,在进行回放前会通过 XLogReadBufferForRedoExtended 函数获取需要回放的buffer页面。如果之后存在drop等操作,该页面可能为一个invalid page。
考虑一个场景:
  1. insert/update操作,对pageA写入数据。
  2. Drop table,将pageA删除,pageA对应的磁盘blockA也被删除。
  3. 崩溃。

在进行崩溃恢复时,回放insert/update操作,发现需要回放的page无对应的block,标记为invalid page。回放过程中的invalid page使用hash表进行维护。当回放操作发现需要回放的page是invalid page时,则跳过回放。当回放drop操作的事务提交回放时,将该invalid page从hash表中删除。
当数据达到一致性时,invalid page的数量应该为0。

Full page write

当开启full page write时不会出现上述情况。
在尝试读buffer时会获取或创建相关relation的block。并通过指定blocknumber与获取到的blocknumber进行比较。若新建的blocknumber小于指定blocknumber,则认为原指定的block已不存在,在未开启full page write的情况下会被标记为invalid page。
Buffer
XLogReadBufferExtended(RelFileNode rnode, ForkNumber forknum,
                       BlockNumber blkno, ReadBufferMode mode)
{
    BlockNumber lastblock;
    Buffer      buffer;
    SMgrRelation smgr;

    Assert(blkno != P_NEW);

    /* Open the relation at smgr level */
    smgr = smgropen(rnode, InvalidBackendId);
    smgrcreate(smgr, forknum, true);

    lastblock = smgrnblocks(smgr, forknum);

    if (blkno < lastblock)
    {
        /* page exists in file */
        buffer = ReadBufferWithoutRelcache(rnode, forknum, blkno,
                                           mode, NULL);
    }
    else
    {
        /* hm, page doesn't exist in file */
        if (mode == RBM_NORMAL)
        {
            log_invalid_page(rnode, forknum, blkno, false);
            return InvalidBuffer;
        }
        if (mode == RBM_NORMAL_NO_LOG)
            return InvalidBuffer;
如果开启full page write,则会尝试回放整个buffer页面。会不断获取新的buffer页面和block,直到分配到的buffer对应的blocknum等于指定的blocknum。
do
{
    if (buffer != InvalidBuffer)
    {
        if (mode == RBM_ZERO_AND_LOCK || mode == RBM_ZERO_AND_CLEANUP_LOCK)
            LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
        ReleaseBuffer(buffer);
    }
    buffer = ReadBufferWithoutRelcache(rnode, forknum,
                                        P_NEW, mode, NULL);
}
while (BufferGetBlockNumber(buffer) < blkno);

并行回放invalid page worker

回放过程中可能会存在invalid page,并由hash表进行记录。当回放完毕时,invalid page的数量应该保持为0。
原本的回放过程中,回放过程遇到invalid page时会使用 log_invalid_page 对其进行记录;在进行 xact_redo_commit 事务提交重做时,如果事务中涉及到drop table,需对产生invalid page进行清理。
XLogDropRelation(RelFileNode rnode, ForkNumber forknum)
{
    forget_invalid_pages(rnode, forknum, 0);
}
在并行回放方案中,将invalid page的记录和清除交由统一的worker进行处理。
switch(page->cmd)
{
    case PR_LOG:
        PR_log_invalid_page_int(page->node, page->forkno, page->blkno, page->present);
        break;
    case PR_FORGET_PAGES:
        PR_forget_invalid_pages_int(page->node, page->forkno, page->blkno);
        break;
    case PR_FORGET_DB:
        PR_forget_invalid_pages_db_int(page->dboid);
        break;
    case PR_CHECK_INVALID_PAGES:
        PR_XLogCheckInvalidPages_int();
        break;
    default:
        PR_failing();
        elog(PANIC, "Inconsistent internal status.");
    break;
}

 

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