Skip to content

SDSTOR-23112: fix snapshot shard state when sealed_lsn exceeds snapshot LSN cutoff#451

Merged
JacksonYao287 merged 1 commit into
eBay:stable/v4.xfrom
JacksonYao287:SDSTOR-23112
Jul 10, 2026
Merged

SDSTOR-23112: fix snapshot shard state when sealed_lsn exceeds snapshot LSN cutoff#451
JacksonYao287 merged 1 commit into
eBay:stable/v4.xfrom
JacksonYao287:SDSTOR-23112

Conversation

@JacksonYao287

Copy link
Copy Markdown
Collaborator

Problem

During snapshot generation, PGBlobIterator builds a shard list from the leader's in-memory PG state filtered by upto_lsn (the snapshot LSN cutoff). Before this fix, a shard whose create_lsn <= upto_lsn was always included with its current state — even if its sealed_lsn was beyond upto_lsn (i.e., the seal happened after the snapshot point).

When a follower received such a snapshot it saw the shard as SEALED and released the backing chunk. Log replay then tries to replay the seal_shard entry for that LSN, but the chunk has already been reclaimed or relocated, causing the commit to fail with stale physical-chunk references.

Root Cause

The race window is:

  1. Leader creates shard S at LSN L1.
  2. Leader creates shard S2 at LSN L2 (L2 > L1).
  3. Leader seals shard S at LSN L3 (L3 > L2).
  4. Snapshot is taken with upto_lsn = L2.

In step 4, S has create_lsn = L1 <= L2 so it passes the LSN filter, but sealed_lsn = L3 > L2. Old code included S with state SEALED. The follower then applied the snapshot, treated S as SEALED, released its chunk, and later failed when replaying the seal_shard log at L3.

Fix

In PGBlobIterator::PGBlobIterator, after the create_lsn filter, add a second check:

if (shard_info.state == SEALED && shard_info.sealed_lsn > upto_lsn) {
    shard_info.state = OPEN;
    shard_info.sealed_lsn = INT64_MAX;
}

This converts the shard's snapshot state back to OPEN so the follower does not release the chunk prematurely. The follower will replay the seal_shard log entry when log replay catches up, at which point it correctly transitions the shard to SEALED with the right sealed_lsn.

Other changes in this commit:

  • Remove dead commented-out LOGDEBUG in heap_chunk_selector.cpp.
  • Bump version to 4.2.5 in conanfile.py.

Tests

  • PGBlobIteratorSealedLsnCutoff (new): directly exercises both branches of the fix:
    • Case A — snp_lsn between create_lsn and sealed_lsn: asserts the shard appears as OPEN with sealed_lsn == INT64_MAX.
    • Case B — snp_lsn >= sealed_lsn: asserts the shard remains SEALED with its original sealed_lsn.
  • PGBlobIterator (updated): the existing test seals a shard after the snapshot LSN, so its state/sealed_lsn assertions are updated to expect the downgraded OPEN state produced by the fix.

…ot LSN cutoff

## Problem

During snapshot generation, PGBlobIterator builds a shard list from the
leader's in-memory PG state filtered by `upto_lsn` (the snapshot LSN
cutoff). Before this fix, a shard whose `create_lsn <= upto_lsn` was
always included with its current state — even if its `sealed_lsn` was
beyond `upto_lsn` (i.e., the seal happened *after* the snapshot point).

When a follower received such a snapshot it saw the shard as SEALED and
released the backing chunk. Log replay then tries to replay the
`seal_shard` entry for that LSN, but the chunk has already been
reclaimed or relocated, causing the commit to fail with stale
physical-chunk references.

## Root Cause

The race window is:

1. Leader creates shard S at LSN L1.
2. Leader creates shard S2 at LSN L2 (L2 > L1).
3. Leader seals shard S at LSN L3 (L3 > L2).
4. Snapshot is taken with `upto_lsn = L2`.

In step 4, S has `create_lsn = L1 <= L2` so it passes the LSN filter,
but `sealed_lsn = L3 > L2`. Old code included S with state SEALED.
The follower then applied the snapshot, treated S as SEALED, released
its chunk, and later failed when replaying the seal_shard log at L3.

## Fix

In `PGBlobIterator::PGBlobIterator`, after the `create_lsn` filter,
add a second check:

    if (shard_info.state == SEALED && shard_info.sealed_lsn > upto_lsn) {
        shard_info.state = OPEN;
        shard_info.sealed_lsn = INT64_MAX;
    }

This converts the shard's snapshot state back to OPEN so the follower
does not release the chunk prematurely. The follower will replay the
`seal_shard` log entry when log replay catches up, at which point it
correctly transitions the shard to SEALED with the right `sealed_lsn`.

Other changes in this commit:
- Remove dead commented-out LOGDEBUG in heap_chunk_selector.cpp.
- Bump version to 4.2.5 in conanfile.py.

## Tests

- **PGBlobIteratorSealedLsnCutoff (new)**: directly exercises both
  branches of the fix:
  - Case A — `snp_lsn` between `create_lsn` and `sealed_lsn`: asserts
    the shard appears as OPEN with `sealed_lsn == INT64_MAX`.
  - Case B — `snp_lsn >= sealed_lsn`: asserts the shard remains SEALED
    with its original `sealed_lsn`.
- **PGBlobIterator (updated)**: the existing test seals a shard after
  the snapshot LSN, so its state/sealed_lsn assertions are updated to
  expect the downgraded OPEN state produced by the fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 87.50000% with 1 line in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (stable/v4.x@924e403). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/lib/homestore_backend/pg_blob_iterator.cpp 87.50% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@              Coverage Diff               @@
##             stable/v4.x     #451   +/-   ##
==============================================
  Coverage               ?   54.19%           
==============================================
  Files                  ?       36           
  Lines                  ?     5318           
  Branches               ?      670           
==============================================
  Hits                   ?     2882           
  Misses                 ?     2143           
  Partials               ?      293           

☔ View full report in Codecov by Harness.
📢 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.

Comment thread src/lib/homestore_backend/pg_blob_iterator.cpp

@koujl koujl 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.

lgtm

@Hooper9973 Hooper9973 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.

LGTM

@yuwmao yuwmao 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.

lgtm

@JacksonYao287 JacksonYao287 merged commit 400a379 into eBay:stable/v4.x Jul 10, 2026
68 of 73 checks passed
@JacksonYao287 JacksonYao287 deleted the SDSTOR-23112 branch July 10, 2026 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants