postgresql逻辑复制槽的推进

逻辑复制槽为什么要推进?

  • 我们知道逻辑复制槽有两个作用,一个是保护系统表避免被vacuum,一个是保护xlog,防止需要解码的xlog被回收
  • 如果系统表不往前推进,则系统表就会发生膨胀
  • 如果xlog不往前推进,xlog就会堆积在磁盘。
  • 所以,我们根据当前逻辑复制的进度,推进逻辑复制槽

逻辑复制槽的推进主要表现在哪些字段的推进?

  • 对于xlog,与物理复制槽相同,通过restart_lsn控制
  • 对于系统表,通过catalog_xmin控制

restart_lsn的推进

  • 物理复制restart lsn的推进比较简单,当备机flush某个lsn以后,告诉主机,主机收到后,立即就会推进restart lsn
  • 而逻辑复制,则相对比较复杂。
    • 我们用confirmed_flush表示当前订阅端逻辑复制的进度。但是,不是说confirmed_flush之前的xlog就可以回收。

    • 因为逻辑解码需要历史快照,而历史快照的构建本质就是根据running_xacts以及后续的commit等xlog来完成的

    • 所以为了能够解析后面的xlog,restart lsn还要对后续解码需要用的快照xlog进行保护。

    • 那么此时就得看confirmed_flush更新时,需要用到的最老的快照lsn是多少了。

    • 所以我们用两个lsn实现restart_lsn的推进,candidate_restart_valid就表示当前解码的进度lsn,而candidate_restart_lsn表示解码到candidate_restart_valid的时候,需要用的最老的快照的lsn。

    • 当confirmed_flush超过candidate_restart_valid时,就可以推进restart_lsn到candidate_restart_lsn了,可以进行更新了。

      // 主机或者发布端收到备机或订阅端的flush后的处理
      ProcessStandbyReplyMessage(void)
      {
      if (MyReplicationSlot && flushPtr != InvalidXLogRecPtr)
      {
      if (SlotIsLogical(MyReplicationSlot))
      LogicalConfirmReceivedLocation(flushPtr);
      else
      PhysicalConfirmReceivedLocation(flushPtr);
      }
      }

      // 物理复制槽,只要备机flush某个lsn,主机收到后,立即就会推进restart lsn
      PhysicalConfirmReceivedLocation(XLogRecPtr lsn)
      {
      if (slot->data.restart_lsn != lsn)
      {
      changed = true;
      slot->data.restart_lsn = lsn;
      }
      }

      // 逻辑复制槽,如果订阅端flush了某个lsn,并不会直接推进restart lsn。而是根据candidate的情况,判断是否推进restart lsn。
      // 如果可以就推进,如果不行,只把当前订阅端flush的lsn记录到confirmed_flush
      LogicalConfirmReceivedLocation(XLogRecPtr lsn)
      {
      if (MyReplicationSlot->candidate_xmin_lsn != InvalidXLogRecPtr ||
      MyReplicationSlot->candidate_restart_valid != InvalidXLogRecPtr)
      {
      if (MyReplicationSlot->candidate_restart_valid != InvalidXLogRecPtr &&
      MyReplicationSlot->candidate_restart_valid <= lsn)
      {
      MyReplicationSlot->data.restart_lsn = MyReplicationSlot->candidate_restart_lsn;
      MyReplicationSlot->candidate_restart_lsn = InvalidXLogRecPtr;
      MyReplicationSlot->candidate_restart_valid = InvalidXLogRecPtr;
      updated_restart = true;
      }
      }
      else
      {
      SpinLockAcquire(&MyReplicationSlot->mutex);
      MyReplicationSlot->data.confirmed_flush = lsn;
      SpinLockRelease(&MyReplicationSlot->mutex);
      }
      }

      // 为什么逻辑复制不能直接根据flush推进restart lsn呢?
      // 因为逻辑解码需要历史快照,而历史快照的构建本质就是根据running_xacts以及后续的commit等xlog来完成的
      // 所以为了能够解析后面的xlog,restart lsn还要保护对后续xlog解码需要用的快照xlog进行保护。
      // 因为,我们每次会在解析running_xacts后,序列化一次快照,所以我们可以在此时推进一下我们需要保护的lsn,也就是所谓的candidate
      // 等订阅端的flush满足条件后,就可以真正的推进restart lsn了
      SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *running)
      {
      // 处理running_xacts后,序列化一次快照
      SnapBuildSerialize(builder, lsn);

      复制代码
      // 我们从reorder buffer中找最老的txn,这样我们就可以获取最老的需要保护的xlog
      txn = ReorderBufferGetOldestTXN(builder->reorder);
      if (txn != NULL && txn->restart_decoding_lsn != InvalidXLogRecPtr)
          LogicalIncreaseRestartDecodingForSlot(lsn, txn->restart_decoding_lsn);
      // 如果当前没有在处理的事务,则使用最新序列化的快照位置即可
      else if (txn == NULL &&
               builder->reorder->current_restart_decoding_lsn != InvalidXLogRecPtr &&
               builder->last_serialized_snapshot != InvalidXLogRecPtr)
          LogicalIncreaseRestartDecodingForSlot(lsn,
                                                builder->last_serialized_snapshot);

      }

      // 这里有两个lsn,candidate_restart_valid是一个参照物,他是当前的lsn,当订阅端confirmed_flush超过他的时候,就可以推进此时对应的restart_lsn
      // 为什么这么设计?因为confirmed_flush只代表flush的进度,或者说只代表解码的xlog进度,而不代表快照的xlog进度。
      LogicalIncreaseRestartDecodingForSlot(XLogRecPtr current_lsn, XLogRecPtr restart_lsn)
      {
      if (current_lsn <= slot->data.confirmed_flush)
      {
      slot->candidate_restart_valid = current_lsn;
      slot->candidate_restart_lsn = restart_lsn;
      updated_lsn = true;
      }

      复制代码
      if (slot->candidate_restart_valid == InvalidXLogRecPtr)
      {
          slot->candidate_restart_valid = current_lsn;
          slot->candidate_restart_lsn = restart_lsn;
      }
      
      if (updated_lsn)
          LogicalConfirmReceivedLocation(slot->data.confirmed_flush);

      }

catalog_xmin的推进

  • 与restart lsn的推进是类似的。

  • 在序列化快照的时候,使用candidate_xmin_lsn表示当前解码的进度,使用candidate_catalog_xmin表示当前解码需要用的最老xmin

  • 当confirmed_flush超过candidate_xmin_lsn的时候,就可以更新复制槽的catalog_xmin为candidate_catalog_xmin了

    SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *running)
    {
    SnapBuildSerialize(builder, lsn);
    xmin = ReorderBufferGetOldestXmin(builder->reorder);
    LogicalIncreaseXminForSlot(lsn, xmin);
    }

    LogicalIncreaseXminForSlot(XLogRecPtr current_lsn, TransactionId xmin)
    {
    if (current_lsn <= slot->data.confirmed_flush)
    {
    slot->candidate_catalog_xmin = xmin;
    slot->candidate_xmin_lsn = current_lsn;
    updated_xmin = true;
    }
    else if (slot->candidate_xmin_lsn == InvalidXLogRecPtr)
    {
    slot->candidate_catalog_xmin = xmin;
    slot->candidate_xmin_lsn = current_lsn;
    }
    }

相关推荐
因缘而起14 分钟前
【Shell】模拟爬虫下载天龙八部小说
linux·服务器·数据库
JenKinJia20 分钟前
linux下编译Websocketpp,适用x86和armv8
linux·运维·服务器
czhc11400756631 小时前
LINUX 5 cat du head tail wc 计算机拓扑结构 计算机网络 服务器 计算机硬件
linux·服务器·计算机网络
呆呆的私房菜1 小时前
快戳进来!PostgreSQL数据库通用优化模板!
postgresql
叫我龙翔1 小时前
【项目日记】高并发服务器项目总结
android·运维·服务器
呦呦鹿鸣Rzh2 小时前
SpringMVC的数据响应
java·服务器·前端
去看日出2 小时前
SolidWorks2025三维计算机辅助设计(3D CAD)软件超详细图文安装教程(2025最新版保姆级教程)
运维·服务器·solidworks三维
yangshuo12812 小时前
实战代码:esp32-cam按钮控制手机拍照V1.0
网络·智能手机
christine-rr2 小时前
密码学基础——分组密码的运行模式
网络·密码学·密码
张一不吃豆芽2 小时前
TCPIP详解 卷1协议 三 链路层
网络·网络协议·tcp/ip