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

浅谈 Postgres 同步复制原理

PolarDB 2025-03-21
22

浅谈 Postgres 同步复制原理

关于 PolarDB PostgreSQL 版

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

背景

postgres支持主备场景下,通过流复制进行同步,本文将以pg 14为例介绍同步复制的模式、相关内核代码的原理。

同步复制的模式

postgres共有两个参数用来控制同步复制,分别是synchronous_standby_names和synchronous_commit。

  • synchronous_commit 控制同步策略级别,共有五种模式,分别是:

off:异步复制,提交后立刻返回COMMIT响应。

local:异步复制,主库写入WAL刷盘后,返回COMMIT响应。

remote_write:同步复制,主库提交,并等到WAL传输到备库后,返回COMMIT响应。

on(remote_flush):默认的同步模式,在备库写入WAL之后,返回COMMIT响应。

remote_apply:最高级别的同步模式,要等待备库回放WAL完成后,主库才会提交commit。

  • synchronous_standby_names 控制哪些standby被应用同步策略。当没有指定standby的时候,同步复制会自动退化到local,即只保证主库写入WAL。

相关代码分析

同步复制的代码主要实现在syncrep.c中,基本都是在主节点上执行的。核心的流复制传输仍在walreceiver/walsender模块中进行。这种设计的核心思想是将所有关于等待/释放的逻辑隔离在主节点上。主节点定义了它希望等待的备节点。备节点完全不知道主节点上事务的同步要求,从而降低了代码的复杂性。

首先介绍插入数据后,生成的WAL写入磁盘的过程,主要由xact.c中的**RecordTransactionCommit()**函数完成,保证已提交的数据不会丢失。

/* 需要同步提交的情况 */
if ((wrote_xlog && markXidCommitted &&
  synchronous_commit > SYNCHRONOUS_COMMIT_OFF) ||
 forceSyncCommit || nrels > 0)
{
   /* 首先WAL落盘 */
 XLogFlush(XactLastRecEnd);

if (markXidCommitted)
  TransactionIdCommitTree(xid, nchildren, children);
}
else//异步提交情况,不会调用fsync刷盘,会由wal writer等进程完成wal刷盘。但在数据库崩溃时可能丢失数据。
{
/*
  * 设置异步提交LSN
  */

 XLogSetAsyncXactLSN(XactLastRecEnd);

if (markXidCommitted)
  TransactionIdAsyncCommitTree(xid, nchildren, children, XactLastRecEnd);
}

if (markXidCommitted)
{
 MyPgXact->delayChkpt = false;
 END_CRIT_SECTION();
}

latestXid = TransactionIdLatest(xid, nchildren, children);
/*
 * 调用SyncRepWaitForLSN,等待同步复制完成
 */

if (wrote_xlog && markXidCommitted)
 SyncRepWaitForLSN(XactLastRecEnd, true);


复制

接下来介绍下主库等待、唤醒这一套的实现机制,核心函数为**SyncRepWaitForLSN()**。

SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
{
    //...
/* 如果无需同步等待,直接返回,例如没有配置synchronous_standby_names。*/
if (!SyncRepRequested() ||
  !((volatile WalSndCtlData *) WalSndCtl)->sync_standbys_defined)
return;

    //...
/*
  * 如果没有定义同步流复制节点,或者判断到commit lsn小于已同步的LSN,说明WAL已经flush了,直接返回。
  */

if (!WalSndCtl->sync_standbys_defined ||
  lsn <= WalSndCtl->lsn[mode])
 {
  LWLockRelease(SyncRepLock);
return;
 }

/*
  * 该本进程插入 SyncRepQueue 队列中,然后开始等待
  */

 MyProc->waitLSN = lsn;
 MyProc->syncRepState = SYNC_REP_WAITING;
 SyncRepQueueInsert(mode);
 Assert(SyncRepQueueIsOrderedByLSN(mode));
 LWLockRelease(SyncRepLock);

    // ...
/*
  * 进入循环等待状态,说明本地的WAL已经flush了,只是等待同步流复制节点返回状态
  */

for (;;)
 {
        // ...
/*
   * SYNC_REP_WAIT_COMPLETE状态表示同步完成,退出
   */

if (MyProc->syncRepState == SYNC_REP_WAIT_COMPLETE)
   break;

        // ...
/*
   * 用户主动cancel query,退出
   */

if (QueryCancelPending)
  {
   QueryCancelPending = false;
   ereport(WARNING,
     (errmsg("canceling wait for synchronous replication due to user request"),
      errdetail("The transaction has already committed locally, but might not have been replicated to the standby.")));
   SyncRepCancelWait();
   break;
  }

/*
   * 等待备节点 LATCH 的释放信号
   */

  rc = WaitLatch(MyLatch, WL_LATCH_SET | WL_POSTMASTER_DEATH, -1,
        WAIT_EVENT_SYNC_REP);

/*
   * 如果postmaster挂了的话,直接退出 
   */

if (rc & WL_POSTMASTER_DEATH)
  {
   ProcDiePending = true;
   whereToSendOutput = DestNone;
   SyncRepCancelWait();
   break;
  }
 }
    // ...
}

复制

还包括以下相关函数:

- SyncRepQueueInsert:主库 SyncRepWaitForLSN 函数调用,作用是把该进程插入 SyncRepQueue 队列中,然后开始等待;

- SyncRepCancelWait:停止等待,并将该进程从队列中移除;

- SyncRepWakeQueue:唤醒队列中所有等待的进程,并将所有进程移除队列;

同步复制流程

完整的同步流程如下:

1. 主库插入数据,生成WAL;

2. 调用 SyncRepWaitForLSN 等待,并将本进程插入 SyncRepQueue 队列中;

3. 备库 walreceiver 进程将 WAL 刷入磁盘,并且通知主库的 walsender 进程;

4. 主库 walsender 进程收到备库的消息,根据同步策略的级别,使用 SyncRepWakeQueue 唤醒所有等待队列中的进程,并将其移出队列;

5. 主库执行 SQL 的进程继续执行,通知其他进程本事务已提交。

同步复制的问题

同步复制能够保证异常场景下数据不丢失,但是也有一些缺点。

  1. 性能明显下降。在主库与备库网络条件差的情况下,性能下降会更加明显。

  2. 影响主库可用性。当主库与备库在synchronous_commit为on的同步模式下,备库挂了会导致主库也hang住。这在大部分使用场景下,是用户很难接受的。

小结

异步复制的性能较同步复制有明显的提升,但是牺牲了数据安全性,在主库崩溃的时候存在丢失数据的可能。同步复制又可能导致备库异常情况下,主库状态受影响。因此建议选用合适的同步策略级别,或是日常配置为同步模式,利用外部HA组件,在备库异常情况下,配置参数转为异步复制。


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

评论