【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 是如何被更新和推进的,包括:

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

🧩 一、背景:什么是 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)
  • 执行到 TransportReplicationActionshardOperationOnPrimary
  • 向副本发送 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 格式等),欢迎继续提问。

相关推荐
Makoto_Kimur11 小时前
Elasticsearch面试八股整理
elasticsearch
青稞社区.20 小时前
Claude Code 源码深度解析:运行机制与 Memory 模块详解
大数据·人工智能·elasticsearch·搜索引擎·agi
Aktx20FNz21 小时前
iFlow CLI 完整工作流指南
大数据·elasticsearch·搜索引擎
学习3人组1 天前
TortoiseGit冲突解决实战上机练习
大数据·elasticsearch·搜索引擎
A__tao1 天前
Elasticsearch Mapping 一键生成 Go Struct,支持嵌套解析
elasticsearch·es
zs宝来了1 天前
Elasticsearch 索引原理:倒排索引与 Segment 管理
elasticsearch·索引·倒排索引·源码解析·segment
切糕师学AI1 天前
Elasticsearch 向量索引深度解析:从原理到生产实践
大数据·elasticsearch·搜索引擎·语义搜索·相似性搜索·语义理解
A__tao1 天前
告别手写!ES Mapping 自动生成 Go Struct(支持嵌套)
elasticsearch·golang·es
Elastic 中国社区官方博客2 天前
当 TSDS 遇到 ILM:设计不会拒绝延迟数据的时间序列数据流
大数据·运维·数据库·elasticsearch·搜索引擎·logstash