【Elasticsearch】副本分片(Replica Shard)的 globalCheckpoint 更新与推进机制

✅ 副本分片 globalCheckpoint 的更新推进方式(仅两种)

1. 副本恢复完成时(Recovery Finalization)

  • 触发时机:副本从主分片完成恢复(包括文件和 translog 恢复)后,进入 finalize 阶段。

  • 数据来源 :主分片在 FinalizeRecoveryRequest 中显式携带当前最新的 globalCheckpoint。

  • 副本操作 :

    复制代码
    indexShard.updateGlobalCheckpointOnReplica(globalCheckpoint, "finalizing recovery");
  • 作用:让刚恢复的副本立即获知当前全局已安全提交的操作边界,避免误删尚未持久化的 translog。

📌 这是副本首次获知 global checkpoint 的主要途径。


2. 正常写入流程中(Replica Operation)

  • 触发时机 :主分片处理写入请求(index/delete/update 等)并向副本发送 ReplicaRequest(如 BulkShardRequest)时。

  • 数据来源 :主分片将当前 globalCheckpoint 作为元数据字段嵌入到每个 replica 请求中。

  • 副本操作 :在获取操作许可时(acquireReplicaOperationPermit)调用:

    复制代码
    updateGlobalCheckpointOnReplica(globalCheckpoint, "operation");
  • 特点 :

    • 高频、低延迟同步:只要有写入,就自动更新。
    • 原子性保障:确保在执行操作前,副本已知晓最新的安全边界。

📌 这是副本在运行期间持续跟进 global checkpoint 的主要方式。


❌ 不属于"推进"方式的机制

GlobalCheckpointSyncAction

  • 目的 :确保副本(和主)将内存中已有的 globalCheckpoint 持久化到磁盘 (写入 translog.ckp)。
  • 不传递新值 :请求体为空,不包含 globalCheckpoint 数值。
  • 不改变内存值 :仅触发 sync(),用于崩溃恢复时的数据一致性。
  • 典型场景:分片即将变为 inactive(如索引关闭)且近期无写入。

🔸 它是 持久化兜底机制 ,不是 值传播或推进机制。


🧠 关键区分

操作 是否更新内存中的 globalCheckpoint? 是否涉及网络传值? 目的
Recovery Finalize ✅ 是 ✅ 是 初始化副本的 checkpoint
Replica Write Request ✅ 是 ✅ 是 持续同步最新 checkpoint
GlobalCheckpointSyncAction ❌ 否 ❌ 否 确保持久化(落盘)

✅ 总结一句话

副本分片的 globalCheckpoint 内存值仅通过两种方式推进:① 恢复完成时由主分片显式告知;② 正常写入时由主分片随请求附带传递。其他机制(如 GlobalCheckpointSyncAction)仅用于确保持久化,不参与值的更新。

这是 Elasticsearch 实现 高吞吐、强一致性、崩溃安全 的关键设计之一。

下面我们将 极其详细地 梳理 Elasticsearch 中 副本分片(Replica Shard)的 globalCheckpoint 是如何被更新和推进的,包括:

  • 背景概念澄清
  • 两种核心推进机制的完整流程(含代码路径与交互细节)
  • 为什么只有这两种方式
  • 其他相关机制(如 GlobalCheckpointSyncAction、RetentionLease)的作用边界
  • 实际场景举例
  • 源码关键点佐证

🧩 一、背景:什么是 globalCheckpoint?

在 Elasticsearch 的 序列号(Sequence Number)与 Checkpoint 机制中:

  • localCheckpoint:每个分片(主或副本)本地已安全持久化的最大 seqno。
  • globalCheckpoint :所有活跃副本都已确认持久化的最大 seqno。它代表"可以安全删除 translog"的边界。
  • 由主分片维护并传播 :主分片负责计算 globalCheckpoint = min(active_replicas.localCheckpoint),然后通知所有副本。

✅ 副本本身 不计算 global checkpoint,只 接收并记录 主分片告诉它的值。


🔁 二、副本 globalCheckpoint 更新的两种核心方式

方式 1️⃣:副本恢复完成时(Recovery Finalization)

📌 触发条件
  • 副本 shard 处于 UNASSIGNED → INITIALIZING → STARTED 过程
  • 完成 Phase 1(文件复制) + Phase 2(translog 重放)
  • 进入 finalize recovery 阶段
🔄 流程详解
  1. 主分片构造 FinalizeRecoveryRequest

    • 在 RecoverySourceHandler.finalize() 中:

      复制代码
      final long globalCheckpoint = indexShard().getGlobalCheckpoint();
      transportService.sendRequest(
          targetNode,
          RecoveryTarget.Actions.FINALIZE,
          new FinalizeRecoveryRequest(recoveryId, shardId, globalCheckpoint),
          ...
      );
  2. 副本接收请求并更新

    • 在 RecoveryTarget.doFinalize() 中:

      复制代码
      indexShard.updateGlobalCheckpointOnReplica(request.globalCheckpoint(), "finalizing recovery");
    • 此方法内部:

      复制代码
      public void updateGlobalCheckpointOnReplica(long globalCheckpoint, String reason) {
          assert Thread.holdsLock(this);
          if (globalCheckpoint > this.globalCheckpoint) {
              this.globalCheckpoint = globalCheckpoint;
              // 注意:此时不一定立即 sync 到磁盘!
          }
      }
  3. 结果

    • 副本内存中的 globalCheckpoint 被设为恢复完成时刻主分片的值
    • 后续可基于此值安全清理 translog

💡 此时副本可能还未持久化该值(取决于 Translog.Durability),但内存值已更新。


方式 2️⃣:正常写入流程中(Replica Operation)

📌 触发条件
  • 主分片处理写入请求(如 IndexRequest)
  • 执行到 TransportReplicationAction 的 shardOperationOnPrimary
  • 向副本发送 ReplicaRequest
🔄 流程详解
  1. 主分片在构建 replica 请求时嵌入 globalCheckpoint

    • 以 BulkShardRequest 为例:

      复制代码
      BulkShardRequest bulkShardRequest = new BulkShardRequest(
          shardId, 
          timeout, 
          items,
          primaryTerm,
          primary.getGlobalCheckpoint()  // ← 关键!
      );
  2. 副本在执行操作前更新 checkpoint

    • 在 TransportReplicationAction.shardOperationOnReplica 路径中:

      复制代码
      indexShard.acquireReplicaOperationPermit(..., listener);
    • 在 acquireReplicaOperationPermit 内部(IndexShard.java):

      复制代码
      updateGlobalCheckpointOnReplica(replicaRequest.getGlobalCheckpoint(), "operation");
    • 同样调用 updateGlobalCheckpointOnReplica(),逻辑同上

  3. 特点

    • 每次写入都携带最新值,即使 global checkpoint 未变
    • 高频同步:保证副本几乎实时跟进
    • 轻量级:只是内存赋值,无额外 RPC

⚠️ 注意:即使没有新操作推进 local checkpoint,只要主分片的 global checkpoint 更新了(比如其他副本追上了),下次写入就会把新值带给所有副本。


❌ 三、哪些操作 不会 推进副本的 globalCheckpoint?

1. GlobalCheckpointSyncAction

  • 作用 :触发 indexShard.sync() → 将当前 globalCheckpoint 写入 translog.ckp
  • 不包含任何 checkpoint 值
  • 请求体为空 (仅 ShardId)
  • 目的:durability,非 propagation

2. Background retention lease sync / stats fetch

  • 这些操作可能携带 localCheckpoint 用于 lease 维护,但 不更新 globalCheckpoint

3. Peer recovery 的 phase 1/2

  • 只传输数据文件和 translog 操作
  • global checkpoint 在 finalize 阶段才传递

🔍 四、为什么只有这两种方式?设计哲学

Elasticsearch 的设计原则是:

"避免额外的协调开销,复用已有数据通道"

  • 写入路径已经存在 → 顺带传 global checkpoint(零成本)
  • 恢复是特殊状态 → 单独一次显式同步(必要开销)
  • 不引入独立的 "checkpoint heartbeat" 或 "gossip" 机制 → 减少网络流量和复杂性

同时:

  • 副本不需要主动"拉取" global checkpoint(被动接收即可)
  • 主分片全权负责其计算与分发

🧪 五、实际场景举例

场景 A:索引刚创建,写入 10 条数据

  1. 主分片 localCP = 9,globalCP = 9(无副本)
  2. 添加副本 → 触发 recovery
  3. recovery finalize 时,主告知副本 globalCP = 9
  4. 副本内存 globalCP = 9
  5. 后续写入第 11 条 → 主在 replica request 中携带 globalCP(仍为 9,因副本未 ack)
  6. 副本执行后更新 localCP = 10 → 主检测到 → globalCP = 10
  7. 下次写入 → 主在请求中携带 globalCP = 10 → 副本更新内存值

场景 B:长时间无写入,但副本追上了

  • 主 localCP = 100,副本 localCP = 100
  • 主将 globalCP 推进到 100
  • 但没有新写入 → 副本内存 globalCP 仍为 99
  • 此时若关闭索引 → 触发 GlobalCheckpointSyncAction
    • 副本发现 lastKnownGlobalCheckpoint = 100(怎么来的?见下)

❓ 等等!副本怎么知道 globalCP=100 的?

→ 实际上,主分片会在副本的 retention lease 心跳响应中捎带 global checkpoint!


🔄 补充:第三种隐式方式?Retention Lease 心跳

⚠️ 这是一个容易被忽略的细节!

从 Elasticsearch 7.x+ 开始,为了支持 follower index(CCR) 和更精确的 retention lease,主分片会在 处理副本的 retention lease 请求时返回 global checkpoint。

  • 副本定期发送 RetentionLeaseSyncAction.Request

  • 主分片响应中包含:

    复制代码
    response = new RetentionLeaseSyncAction.Response(globalCheckpoint);
  • 副本收到后:

    复制代码
    indexShard.updateGlobalCheckpointOnReplica(response.getGlobalCheckpoint(), "retention lease sync");

✅ 所以严格来说,有第三种方式:通过 retention lease 心跳同步!

但注意:

  • 该机制主要用于 CCR(跨集群复制) 或 长期无写入的副本保活
  • 在普通索引中,如果副本活跃,写入路径已足够;如果不活跃,lease sync 会兜底
  • 它仍然是 主 → 副本的 push(通过响应),不是副本主动查询

📌 因此,更完整的说法是:
副本 globalCheckpoint 的更新来源有三种:

  1. Recovery finalize
  2. Replica write request
  3. Retention lease sync response(隐式,常被忽略)

但在 纯写入场景(无 CCR) 中,通常只体现前两种。


📚 六、关键源码位置(Elasticsearch 8.x)

功能 类/方法
更新副本 global CP IndexShard.updateGlobalCheckpointOnReplica()
Recovery finalize 发送 RecoverySourceHandler.finalize()
Replica request 携带 BulkShardRequest.getGlobalCheckpoint()
Lease sync 返回 TransportRetentionLeaseSyncAction.masterOperation()
GlobalCheckpointSyncAction GlobalCheckpointSyncAction.maybeSyncTranslog()

✅ 最终总结(超详细版)

副本分片的 globalCheckpoint 内存值更新,通过以下 三种机制(按优先级和频率):

机制 触发条件 是否主流 是否传值 备注
1. Replica Write Request 有写入操作 ✅ 是 ✅ 是 最常见,低延迟
2. Recovery Finalize 副本恢复完成 ✅ 是 ✅ 是 初始化必经之路
3. Retention Lease Sync 副本定期心跳(尤其 CCR) ⚠️ 次要 ✅ 是 防止长期无写入时滞后

而:

  • GlobalCheckpointSyncAction 仅做持久化(sync to disk),不更新内存值
  • 副本 永远不会自己计算 global checkpoint
  • 所有值都来自 主分片的主动推送

希望这份超详细解析能彻底解答你的疑问!如需进一步深入某一部分(如 retention lease 机制、translog ckp 格式等),欢迎继续提问。

相关推荐
Elasticsearch1 小时前
使用 Elastic Stack 丰富邮政地址信息 —— 第一部分
elasticsearch
ofoxcoding1 天前
借助 CLAUDE.md 约束 Sonnet 5.5 多文件重构行为的提示词实践
大数据·elasticsearch·ai·重构
Elasticsearch1 天前
隆重推出 AlertZero:让你的告警队列实现 “收件箱清零” 式管理
elasticsearch
ly76891 天前
从 TF-IDF 到 BM25:Elasticsearch 相关性评分的字段长度归一化与 boost 调参边界
java·elasticsearch·query dsl·bm25·相关性评分
SL-staff1 天前
技术实践:将Excel自动升级为可搜索的知识节点(JVS平台实现)
mongodb·elasticsearch·知识图谱·jvs平台·语义解析·企业文档管理·excel结构化
Elasticsearch1 天前
2026 年唯一获得端点预防与响应(EPR)满分的厂商是 Elastic
elasticsearch
vx_Biye_Design1 天前
springboot旅游管理系统18006-计算机课程设计、毕业设计
java·spring boot·后端·python·elasticsearch·django·课程设计
ly76892 天前
倒排索引与 FST 在 Lucene 中的内存布局:segment、docValues 与词典压缩的工程取舍
elasticsearch·lucene·倒排索引·doc values·fst
Elasticsearch2 天前
利用预计算上下文,更快速、更低成本地开展支持问题调查
elasticsearch
小小小米粒2 天前
重置本地git
大数据·git·elasticsearch