[ 
https://issues.apache.org/jira/browse/HDDS-16462?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Siyao Meng updated HDDS-16462:
------------------------------
    Description: 
h3. Finding
A full-snapshot swap onto an earlier OM lineage plus a crash before the async 
reprocess becomes durable leaves derived tables permanently built against the 
old OM lineage, because epoch-blind strict-less-than reconciliation judges the 
stale task caught up; Recon REST/API/UI then serves rows the current OM lineage 
does not contain, with no self-heal. Triggerable via the documented 
delta-failure to full-snapshot fallback after an OM DR restore or re-bootstrap.

h3. Classification
* Verdict: REPRODUCED
* Severity: Critical
* Source: Specula TLA+ model checking and confirmation debate, finding MC-2

h3. Reproduce
{noformat}
Ozone commit: 9fbf9ee0cb1bd2f5f5d437b6719ebbe5309351fb
Specula:      v1.1.0 (commit c6aa3dfa)
Target:       recon-om-sync
Guidance:     campaigns/ozone-9fbf9ee/targets/023-recon-om-sync/.prompt-extra.md
{noformat}
{code:none}
specula run --agent=claude-code --effort=medium --keep-original 
--max-parallel=2 \
  --enable-reviews --confirm-debate --tlc-memory-limit=28G --tlc-worker-limit=8 
\
  "recon-om-sync|apache/ozone|Java|Use the target-specific .prompt-extra.md"
{code}
Discovered under HDDS-16434 (Specula TLA+ verification effort). The TLA+ 
specification, counterexample, and confirmation debate live in the Specula run 
artifacts.

Generated with Specula (Claude Opus 4.8).

h3. Steps to reproduce

# An OM is restored or re-bootstrapped (disaster recovery), so it presents a 
new lineage whose RocksDB sequence number is lower than or equal to what Recon 
has already processed against the previous lineage.
# Recon's next delta fetch (getDBUpdates since the old high sequence) fails 
with SequenceNumberNotFoundException (the same path exercised by 
testSyncDataFromOMFullSnapshotForSNNFE), so Recon falls back to a full snapshot 
install via executeFullSnapshot.
# executeFullSnapshot sets both the OmSnapshotRequest and OmDeltaRequest 
cursors to the new (lower) sequence number, persists them to the embedded Derby 
TASK_STATUS table (fsync durable), and only then queues an asynchronous derived 
table reprocess (queueReInitializationEvent). The queued reprocess is held in 
an in-memory buffer and is not durable.
# Recon crashes after the cursors are persisted but before the async reprocess 
completes.
# On restart, OzoneManagerServiceProviderImpl.start() runs startup 
reconciliation. For each derived task it queues a reprocess only when 
derivedTask.getLastUpdatedSeqNumber() is strictly less than 
deltaTask.getLastUpdatedSeqNumber(). The stale derived cursor from the old 
lineage is numerically greater than the new (lower) delta cursor, so the strict 
less than is false and the derived task is judged caught up.

Observed wrong outcome: the derived tables (for example the ContainerKeyMapper 
CONTAINER_KEY tables) stay built against the old OM lineage. Recon REST/API/UI 
serve container key rows that the current OM lineage does not contain, and 
there is no self heal.

h3. Root cause

In OzoneManagerServiceProviderImpl.start(), the startup reconciliation filter 
selects a derived task for reprocess only when 
taskStatusUpdater.getLastUpdatedSeqNumber().compareTo(deltaTaskStatusUpdater.getLastUpdatedSeqNumber())
 is strictly less than zero. That comparison assumes one monotonic OM sequence 
space, but a full snapshot swap in executeFullSnapshot can install an OM DB 
copy whose sequence number is lower than or equal to what a derived task 
already recorded against a previous lineage, so the strict less than reads the 
higher stale cursor as caught up. Because the queued reprocess is asynchronous 
and not durable, a crash before it completes leaves this reconciliation as the 
only recovery net, and the net misfires on the lineage regression.

h3. Suggested fix

Change the reconciliation predicate from strict less than to inequality, so a 
derived task is reprocessed whenever its persisted cursor does not equal the 
delta cursor. This covers both the original lag case (derived cursor behind the 
delta cursor) and the lineage regression case (stale derived cursor ahead of a 
lower new delta cursor), while equal cursors (the healthy caught up state) 
still skip the rebuild. The filter is extracted into a helper 
(getTasksToReprocessOnStartup) so the decision is unit testable. The attached 
patch [^HDDS-16462.001.patch] makes this change and adds a unit test asserting 
that a task whose cursor is ahead of a lower new lineage delta cursor is 
reprocessed, that a task lagging the delta cursor is still reprocessed, and 
that equal cursors do not trigger a reprocess (no over triggering).

Residual note: a new lineage carrying exactly the same sequence number that 
Recon last recorded cannot be distinguished by sequence number alone; detecting 
that case would require a lineage or epoch identity on the persisted cursor, 
which is out of scope for this change.

Patch is an AI-drafted proposal (Specula + Claude); pending human build, full 
test, and review before merge.


  was:
h3. Finding
A full-snapshot swap onto an earlier OM lineage plus a crash before the async 
reprocess becomes durable leaves derived tables permanently built against the 
old OM lineage, because epoch-blind strict-less-than reconciliation judges the 
stale task caught up; Recon REST/API/UI then serves rows the current OM lineage 
does not contain, with no self-heal. Triggerable via the documented 
delta-failure to full-snapshot fallback after an OM DR restore or re-bootstrap.

h3. Classification
* Verdict: REPRODUCED
* Severity: Critical
* Source: Specula TLA+ model checking and confirmation debate, finding MC-2

h3. Reproduce
{noformat}
Ozone commit: 9fbf9ee0cb1bd2f5f5d437b6719ebbe5309351fb
Specula:      v1.1.0 (commit c6aa3dfa)
Target:       recon-om-sync
Guidance:     campaigns/ozone-9fbf9ee/targets/023-recon-om-sync/.prompt-extra.md
{noformat}
{code:none}
specula run --agent=claude-code --effort=medium --keep-original 
--max-parallel=2 \
  --enable-reviews --confirm-debate --tlc-memory-limit=28G --tlc-worker-limit=8 
\
  "recon-om-sync|apache/ozone|Java|Use the target-specific .prompt-extra.md"
{code}
Discovered under HDDS-16434 (Specula TLA+ verification effort). The TLA+ 
specification, counterexample, and confirmation debate live in the Specula run 
artifacts.

Generated with Specula (Claude Opus 4.8).


> Recon full snapshot onto an earlier OM lineage plus crash permanently serves 
> stale derived data
> -----------------------------------------------------------------------------------------------
>
>                 Key: HDDS-16462
>                 URL: https://issues.apache.org/jira/browse/HDDS-16462
>             Project: Apache Ozone
>          Issue Type: Bug
>            Reporter: Siyao Meng
>            Priority: Critical
>         Attachments: HDDS-16462.001.patch
>
>
> h3. Finding
> A full-snapshot swap onto an earlier OM lineage plus a crash before the async 
> reprocess becomes durable leaves derived tables permanently built against the 
> old OM lineage, because epoch-blind strict-less-than reconciliation judges 
> the stale task caught up; Recon REST/API/UI then serves rows the current OM 
> lineage does not contain, with no self-heal. Triggerable via the documented 
> delta-failure to full-snapshot fallback after an OM DR restore or 
> re-bootstrap.
> h3. Classification
> * Verdict: REPRODUCED
> * Severity: Critical
> * Source: Specula TLA+ model checking and confirmation debate, finding MC-2
> h3. Reproduce
> {noformat}
> Ozone commit: 9fbf9ee0cb1bd2f5f5d437b6719ebbe5309351fb
> Specula:      v1.1.0 (commit c6aa3dfa)
> Target:       recon-om-sync
> Guidance:     
> campaigns/ozone-9fbf9ee/targets/023-recon-om-sync/.prompt-extra.md
> {noformat}
> {code:none}
> specula run --agent=claude-code --effort=medium --keep-original 
> --max-parallel=2 \
>   --enable-reviews --confirm-debate --tlc-memory-limit=28G 
> --tlc-worker-limit=8 \
>   "recon-om-sync|apache/ozone|Java|Use the target-specific .prompt-extra.md"
> {code}
> Discovered under HDDS-16434 (Specula TLA+ verification effort). The TLA+ 
> specification, counterexample, and confirmation debate live in the Specula 
> run artifacts.
> Generated with Specula (Claude Opus 4.8).
> h3. Steps to reproduce
> # An OM is restored or re-bootstrapped (disaster recovery), so it presents a 
> new lineage whose RocksDB sequence number is lower than or equal to what 
> Recon has already processed against the previous lineage.
> # Recon's next delta fetch (getDBUpdates since the old high sequence) fails 
> with SequenceNumberNotFoundException (the same path exercised by 
> testSyncDataFromOMFullSnapshotForSNNFE), so Recon falls back to a full 
> snapshot install via executeFullSnapshot.
> # executeFullSnapshot sets both the OmSnapshotRequest and OmDeltaRequest 
> cursors to the new (lower) sequence number, persists them to the embedded 
> Derby TASK_STATUS table (fsync durable), and only then queues an asynchronous 
> derived table reprocess (queueReInitializationEvent). The queued reprocess is 
> held in an in-memory buffer and is not durable.
> # Recon crashes after the cursors are persisted but before the async 
> reprocess completes.
> # On restart, OzoneManagerServiceProviderImpl.start() runs startup 
> reconciliation. For each derived task it queues a reprocess only when 
> derivedTask.getLastUpdatedSeqNumber() is strictly less than 
> deltaTask.getLastUpdatedSeqNumber(). The stale derived cursor from the old 
> lineage is numerically greater than the new (lower) delta cursor, so the 
> strict less than is false and the derived task is judged caught up.
> Observed wrong outcome: the derived tables (for example the 
> ContainerKeyMapper CONTAINER_KEY tables) stay built against the old OM 
> lineage. Recon REST/API/UI serve container key rows that the current OM 
> lineage does not contain, and there is no self heal.
> h3. Root cause
> In OzoneManagerServiceProviderImpl.start(), the startup reconciliation filter 
> selects a derived task for reprocess only when 
> taskStatusUpdater.getLastUpdatedSeqNumber().compareTo(deltaTaskStatusUpdater.getLastUpdatedSeqNumber())
>  is strictly less than zero. That comparison assumes one monotonic OM 
> sequence space, but a full snapshot swap in executeFullSnapshot can install 
> an OM DB copy whose sequence number is lower than or equal to what a derived 
> task already recorded against a previous lineage, so the strict less than 
> reads the higher stale cursor as caught up. Because the queued reprocess is 
> asynchronous and not durable, a crash before it completes leaves this 
> reconciliation as the only recovery net, and the net misfires on the lineage 
> regression.
> h3. Suggested fix
> Change the reconciliation predicate from strict less than to inequality, so a 
> derived task is reprocessed whenever its persisted cursor does not equal the 
> delta cursor. This covers both the original lag case (derived cursor behind 
> the delta cursor) and the lineage regression case (stale derived cursor ahead 
> of a lower new delta cursor), while equal cursors (the healthy caught up 
> state) still skip the rebuild. The filter is extracted into a helper 
> (getTasksToReprocessOnStartup) so the decision is unit testable. The attached 
> patch [^HDDS-16462.001.patch] makes this change and adds a unit test 
> asserting that a task whose cursor is ahead of a lower new lineage delta 
> cursor is reprocessed, that a task lagging the delta cursor is still 
> reprocessed, and that equal cursors do not trigger a reprocess (no over 
> triggering).
> Residual note: a new lineage carrying exactly the same sequence number that 
> Recon last recorded cannot be distinguished by sequence number alone; 
> detecting that case would require a lineage or epoch identity on the 
> persisted cursor, which is out of scope for this change.
> Patch is an AI-drafted proposal (Specula + Claude); pending human build, full 
> test, and review before merge.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to