Skip to content

[segment replication] Fix segment replication infinite retry due to stale metadata checkpoint - #20551

Merged
guojialiang92 merged 8 commits into
opensearch-project:mainfrom
guojialiang92:dev/fix_stale_ckp_exception
Feb 7, 2026
Merged

[segment replication] Fix segment replication infinite retry due to stale metadata checkpoint#20551
guojialiang92 merged 8 commits into
opensearch-project:mainfrom
guojialiang92:dev/fix_stale_ckp_exception

Conversation

@guojialiang92

Copy link
Copy Markdown
Contributor

Description

This PR is to address the issues in #[20550].

In the case of failure retry, no ckp verification is performed. As long as the process enters Phase GET_SEGMENT_FILES during the retry, the primary shard will clear the residual segment replication information due to IndexShardClosedException caused by the replica.

Related Issues

Resolves #[20550]

Check List

  • Functionality includes testing.
  • API changes companion pull request created, if applicable.
  • Public documentation issue/PR created, if applicable.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>
Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>
@guojialiang92
guojialiang92 requested a review from a team as a code owner February 5, 2026 10:02
@coderabbitai

coderabbitai Bot commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review
📝 Walkthrough

Walkthrough

The PR introduces an isRetry boolean flag to segment replication infrastructure, flowing through AbstractSegmentReplicationTarget, SegmentReplicationTarget, SegmentReplicationTargetService, and SegmentReplicator. It also adds a test verifying replica behavior during circuit-breaking exceptions.

Changes

Cohort / File(s) Summary
Test Infrastructure
server/src/internalClusterTest/java/org/opensearch/indices/replication/SegmentReplicationIT.java
Added testSegmentReplicationWithException() test method to verify replica behavior when CircuitBreakingException occurs during segment file fetch. Includes new imports for CircuitBreaker, CircuitBreakingException, and StoreFileMetadata.
Base Replication Target
server/src/main/java/org/opensearch/indices/replication/AbstractSegmentReplicationTarget.java
Introduced protected isRetry flag to constructor and extended checkpoint validation to bypass stale-check rejection when isRetry is true, allowing retry paths to proceed without immediate rejection.
Replication Target Implementations
server/src/main/java/org/opensearch/indices/replication/MergedSegmentReplicationTarget.java, server/src/main/java/org/opensearch/indices/replication/SegmentReplicationTarget.java
Updated constructors to wire isRetry flag to superclass. SegmentReplicationTarget adds 5-argument constructor overload and delegates existing 4-argument constructor with isRetry=false.
Service and Replicator Layer
server/src/main/java/org/opensearch/indices/replication/SegmentReplicationTargetService.java, server/src/main/java/org/opensearch/indices/replication/SegmentReplicator.java
Extended onNewCheckpoint() and startReplication() with isRetry parameter overloads, propagating flag through checkpoint processing and replication start flows. Added wrapper overloads defaulting isRetry to false for backward compatibility.

Sequence Diagram(s)

sequenceDiagram
    participant Service as SegmentReplicationTargetService
    participant Replicator as SegmentReplicator
    participant Target as SegmentReplicationTarget
    participant Checkpoint as ReplicationCheckpoint Validator

    Note over Service: onNewCheckpoint(checkpoint, shard, isRetry=true)
    Service->>Service: processLatestReceivedCheckpoint(shard, thread, isRetry=true)
    Service->>Service: onNewCheckpoint(checkpoint, shard, isRetry=true)
    Service->>Replicator: startReplication(shard, checkpoint, source, isRetry=true, listener)
    Replicator->>Target: new SegmentReplicationTarget(..., isRetry=true, listener)
    Replicator->>Target: startReplication()
    Target->>Checkpoint: validate checkpoint (isRetry=true)
    alt isRetry=true
        Checkpoint-->>Target: Bypass stale-check rejection
        Target-->>Service: Proceed with retry replication
    else isRetry=false
        Checkpoint-->>Target: Apply full validation (may reject stale)
        Target-->>Service: Reject or proceed based on validation
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • opensearch-project/OpenSearch#20422: Both PRs modify checkpoint validation logic in AbstractSegmentReplicationTarget's startReplication method—this PR adds isRetry flag bypass while the related PR introduces recovery path handling.

Suggested labels

bug, Indexing:Replication

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.84% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing segment replication infinite retry caused by stale metadata checkpoint validation during retries.
Description check ✅ Passed The description follows the template structure with a detailed explanation of the issue, the related issue reference, and completed checklist items confirming testing, API changes, and documentation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/src/main/java/org/opensearch/indices/replication/SegmentReplicationTarget.java (1)

139-142: ⚠️ Potential issue | 🟠 Major

retryCopy() should create target with isRetry=true.

The retryCopy() method creates a new SegmentReplicationTarget using the 4-argument constructor which defaults isRetry to false. However, since this method is specifically called during retry scenarios, the new target should have isRetry=true to bypass stale checkpoint validation in startReplication.

🐛 Proposed fix
 `@Override`
 public SegmentReplicationTarget retryCopy() {
-    return new SegmentReplicationTarget(indexShard, checkpoint, source, listener);
+    return new SegmentReplicationTarget(indexShard, checkpoint, source, true, listener);
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea01bb5 and 0a46006.

📒 Files selected for processing (6)
  • server/src/internalClusterTest/java/org/opensearch/indices/replication/SegmentReplicationIT.java
  • server/src/main/java/org/opensearch/indices/replication/AbstractSegmentReplicationTarget.java
  • server/src/main/java/org/opensearch/indices/replication/MergedSegmentReplicationTarget.java
  • server/src/main/java/org/opensearch/indices/replication/SegmentReplicationTarget.java
  • server/src/main/java/org/opensearch/indices/replication/SegmentReplicationTargetService.java
  • server/src/main/java/org/opensearch/indices/replication/SegmentReplicator.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-13T17:40:27.167Z
Learnt from: reta
Repo: opensearch-project/OpenSearch PR: 20411
File: server/src/main/java/org/opensearch/index/codec/CodecService.java:112-133
Timestamp: 2026-01-13T17:40:27.167Z
Learning: Avoid capturing or evaluating a supplier (e.g., this::defaultCodec) upfront when passing it to a registry during object construction. If registries may replace defaults during iteration (as in EnginePlugin.getAdditionalCodecs), pass the supplier itself and only resolve it at use time. This ensures dynamic behavior is preserved during initialization and prevents premature binding of defaults in codecs/registry setup. This pattern should apply to similar initialization paths in Java server code where registries may mutate defaults during construction.

Applied to files:

  • server/src/main/java/org/opensearch/indices/replication/AbstractSegmentReplicationTarget.java
  • server/src/main/java/org/opensearch/indices/replication/MergedSegmentReplicationTarget.java
  • server/src/main/java/org/opensearch/indices/replication/SegmentReplicator.java
  • server/src/main/java/org/opensearch/indices/replication/SegmentReplicationTarget.java
  • server/src/main/java/org/opensearch/indices/replication/SegmentReplicationTargetService.java
🧬 Code graph analysis (1)
server/src/internalClusterTest/java/org/opensearch/indices/replication/SegmentReplicationIT.java (2)
server/src/main/java/org/opensearch/indices/replication/SegmentReplicationTargetService.java (1)
  • Actions (85-89)
server/src/main/java/org/opensearch/action/support/replication/TransportReplicationAction.java (1)
  • TransportReplicationAction (113-1689)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: gradle-check
  • GitHub Check: precommit (25, macos-15-intel)
  • GitHub Check: precommit (25, windows-latest)
  • GitHub Check: precommit (21, macos-15)
  • GitHub Check: precommit (25, macos-15)
  • GitHub Check: precommit (21, windows-2025, true)
  • GitHub Check: precommit (25, ubuntu-24.04-arm)
  • GitHub Check: precommit (21, ubuntu-latest)
  • GitHub Check: precommit (21, ubuntu-24.04-arm)
  • GitHub Check: precommit (25, ubuntu-latest)
  • GitHub Check: precommit (21, windows-latest)
  • GitHub Check: precommit (21, macos-15-intel)
  • GitHub Check: assemble (21, windows-latest)
  • GitHub Check: Analyze (java)
  • GitHub Check: assemble (25, windows-latest)
  • GitHub Check: assemble (21, ubuntu-latest)
  • GitHub Check: assemble (25, ubuntu-24.04-arm)
  • GitHub Check: assemble (21, ubuntu-24.04-arm)
  • GitHub Check: assemble (25, ubuntu-latest)
  • GitHub Check: detect-breaking-change
🔇 Additional comments (12)
server/src/main/java/org/opensearch/indices/replication/MergedSegmentReplicationTarget.java (1)

36-36: LGTM!

The constructor correctly passes isRetry=false to the superclass, which is appropriate since merged segment replication targets are not subject to the same retry semantics as regular segment replication.

server/src/main/java/org/opensearch/indices/replication/SegmentReplicationTarget.java (1)

45-56: LGTM!

The new constructor correctly accepts the isRetry parameter and passes it to the superclass. The existing constructor delegation pattern maintains backward compatibility.

server/src/internalClusterTest/java/org/opensearch/indices/replication/SegmentReplicationIT.java (1)

146-219: Good integration test for retry recovery scenario.

The test properly validates that segment replication recovers from a CircuitBreakingException by:

  1. Setting up controlled failure injection using MockTransportService
  2. Using latches to ensure proper sequencing of the exception and subsequent checkpoint
  3. Verifying that documents eventually become searchable on both nodes

However, the test doesn't explicitly verify that the isRetry flag is being set correctly during the retry path. Consider adding an assertion or logging to confirm the retry mechanism is working as intended.

server/src/main/java/org/opensearch/indices/replication/AbstractSegmentReplicationTarget.java (2)

50-50: LGTM!

The isRetry field is appropriately declared as protected final to allow subclass access while ensuring immutability.


174-177: Core fix correctly implemented.

The condition now properly bypasses stale checkpoint validation when isRetry=true. This allows retries to proceed even when the primary has cleared residual segment replication state (which was causing the infinite retry loop in issue #20550).

The boolean expression is clear and correctly combines all conditions:

  • isSegRepLocalEnabled() - feature check
  • checkpoint.isAheadOf(getMetadataCheckpoint) - stale checkpoint detection
  • false == isRecovering - recovery path bypass
  • false == isRetry - retry path bypass (new)
server/src/main/java/org/opensearch/indices/replication/SegmentReplicator.java (2)

114-124: LGTM!

The startReplication method signature is correctly updated to include isRetry, and the parameter is properly passed to the SegmentReplicationTarget constructor. The Javadoc is also updated to document the new parameter.


77-99: LGTM!

The default startReplication(IndexShard) method correctly passes isRetry=false for initial replication attempts that are not retries.

server/src/main/java/org/opensearch/indices/replication/SegmentReplicationTargetService.java (5)

291-307: LGTM!

The onNewCheckpoint method is correctly split into two overloads:

  1. A convenience method that defaults isRetry=false for backward compatibility
  2. The main implementation that accepts the isRetry flag

This maintains a clean API while supporting the retry scenario.


344-344: LGTM!

The startReplication call correctly passes the isRetry flag through to the replicator, ensuring retry state is propagated throughout the replication flow.


376-379: Key fix: Retry path correctly sets isRetry=true.

This is the critical fix for issue #20550. When replication fails without requiring a shard failure, the retry is initiated with isRetry=true, which will bypass stale checkpoint validation in AbstractSegmentReplicationTarget.startReplication.


491-522: LGTM!

The processLatestReceivedCheckpoint overloads follow the same pattern as onNewCheckpoint:

  1. A convenience method defaulting isRetry=false
  2. The main implementation that accepts and propagates isRetry

The isRetry flag is correctly passed through to onNewCheckpoint at line 510.


529-552: LGTM!

The startReplication overloads maintain backward compatibility while adding isRetry support. The parameter is correctly forwarded to replicator.startReplication.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@github-actions

github-actions Bot commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

❌ Gradle check result for 0a46006: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>
@github-actions

github-actions Bot commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

❗ AI-powered Code-Diff-Analyzer found issues on commit fd4ba88.

PathLineSeverityDescription
server/src/main/java/org/opensearch/indices/replication/AbstractSegmentReplicationTarget.java176mediumThe isRetry flag bypasses checkpoint validation that was added to fix issue #18490. On retry, replicas will accept checkpoints even when checkpoint.isAheadOf(getMetadataCheckpoint) is false. This could allow a compromised primary to force replicas to accept stale data by triggering retry scenarios, though this appears to be intentional behavior rather than malicious code.

The table above displays the top 10 most important findings.

Total: 1 | Critical: 0 | High: 0 | Medium: 1 | Low: 0


Pull Requests Author(s): Please update your Pull Request according to the report above.

Repository Maintainer(s): You can bypass diff analyzer by adding label skip-diff-analyzer after reviewing the changes carefully, then re-run failed actions. To re-enable the analyzer, remove the label, then re-run all actions.


⚠️ Note: The Code-Diff-Analyzer helps protect against potentially harmful code patterns. Please ensure you have thoroughly reviewed the changes beforehand.

Thanks.

@github-actions

github-actions Bot commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

❌ Gradle check result for fd4ba88: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>
@github-actions

github-actions Bot commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

❌ Gradle check result for 095718a: ABORTED

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>
@github-actions

github-actions Bot commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

❗ AI-powered Code-Diff-Analyzer found issues on commit 50ed2b5.

PathLineSeverityDescription
server/src/main/java/org/opensearch/indices/replication/AbstractSegmentReplicationTarget.java174lowCheckpoint validation bypass on retry: The isRetry flag disables checkpoint-ahead-of-metadata validation. While this appears to be a legitimate bug fix for issue #18490, bypassing validation on retry could theoretically allow invalid checkpoints to be processed if the original validation served a security purpose. Context suggests this is intentional design rather than malicious, but warrants verification that checkpoint validation is not security-critical.

The table above displays the top 10 most important findings.

Total: 1 | Critical: 0 | High: 0 | Medium: 0 | Low: 1


Pull Requests Author(s): Please update your Pull Request according to the report above.

Repository Maintainer(s): You can bypass diff analyzer by adding label skip-diff-analyzer after reviewing the changes carefully, then re-run failed actions. To re-enable the analyzer, remove the label, then re-run all actions.


⚠️ Note: The Code-Diff-Analyzer helps protect against potentially harmful code patterns. Please ensure you have thoroughly reviewed the changes beforehand.

Thanks.

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>
@github-actions

github-actions Bot commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

❌ Gradle check result for ab70719: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

@github-actions

github-actions Bot commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

❌ Gradle check result for ab70719: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

@guojialiang92 guojialiang92 added the skip-diff-analyzer Maintainer to skip code-diff-analyzer check, after reviewing issues in AI analysis. label Feb 6, 2026
@github-actions

github-actions Bot commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

❌ Gradle check result for ab70719: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

@github-actions

github-actions Bot commented Feb 7, 2026

Copy link
Copy Markdown
Contributor

❌ Gradle check result for ab70719: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

@github-actions

github-actions Bot commented Feb 7, 2026

Copy link
Copy Markdown
Contributor

❕ Gradle check result for c193495: UNSTABLE

Please review all flaky tests that succeeded after retry and create an issue if one does not already exist to track the flaky failure.

@codecov

codecov Bot commented Feb 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.11765% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 73.35%. Comparing base (3ba2f37) to head (c193495).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
.../indices/replication/SegmentReplicationTarget.java 75.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main   #20551      +/-   ##
============================================
+ Coverage     73.25%   73.35%   +0.09%     
- Complexity    72103    72205     +102     
============================================
  Files          5798     5798              
  Lines        329732   329756      +24     
  Branches      47519    47524       +5     
============================================
+ Hits         241554   241886     +332     
+ Misses        68805    68500     -305     
+ Partials      19373    19370       -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

tanyabti pushed a commit to tanyabti/OpenSearch that referenced this pull request Feb 24, 2026
…tale metadata checkpoint (opensearch-project#20551)

* reproduce stale ckp exception

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* fix stale ckp exception

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* fix test

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* fix test

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* fix test

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* add change log

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

---------

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>
tanyabti pushed a commit to tanyabti/OpenSearch that referenced this pull request Feb 24, 2026
…tale metadata checkpoint (opensearch-project#20551)

* reproduce stale ckp exception

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* fix stale ckp exception

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* fix test

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* fix test

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* fix test

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* add change log

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

---------

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>
pradeep-L pushed a commit to pradeep-L/OpenSearch that referenced this pull request Apr 21, 2026
…tale metadata checkpoint (opensearch-project#20551)

* reproduce stale ckp exception

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* fix stale ckp exception

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* fix test

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* fix test

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* fix test

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

* add change log

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>

---------

Signed-off-by: guojialiang <guojialiang.2012@bytedance.com>
mch2 pushed a commit that referenced this pull request Jul 13, 2026
A segment replication retry can finalize against a stale metadata
checkpoint returned by the primary (see #20550, #20551), leaving the
replica behind the checkpoint it was asked to sync to. This leads to the
replica being stale until the next publish. If another publish never
happens, it is stale forever. I believe this is the case of flakiness in
FullRollingRestartIT where the test fails on timeout waiting for the
replica to catch up. The fix is to compare against the replica's
achieved checkpoint instead so a round that finalized behind its target
retriggers catch-up. In the normal case the achieved checkpoint matches
the target, so no extra rounds are introduced.

Signed-off-by: Andrew Ross <andrross@amazon.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-diff-analyzer Maintainer to skip code-diff-analyzer check, after reviewing issues in AI analysis.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants