Skip to content

[fix][offload] Fix final ledger not being offloaded for terminated topics#25955

Open
void-ptr974 wants to merge 1 commit into
apache:masterfrom
void-ptr974:fix/terminated-topic-final-ledger-offload
Open

[fix][offload] Fix final ledger not being offloaded for terminated topics#25955
void-ptr974 wants to merge 1 commit into
apache:masterfrom
void-ptr974:fix/terminated-topic-final-ledger-offload

Conversation

@void-ptr974

@void-ptr974 void-ptr974 commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Motivation

Terminated topics can leave their final ledger in BookKeeper even when tiered-storage offload is enabled and the ledger is eligible for offload.

Termination closes the current ledger outside the normal ledger rollover path. That means the final ledger is complete, but it is not handled like other closed ledgers for offload purposes. Its closed-ledger metadata is not refreshed during termination, automatic offload is not triggered from that path, and manual offloadPrefix() still excludes the last ledger as if it were active.

As a result, terminated topic data can be only partially offloaded, with the final ledger continuing to consume BookKeeper storage.

Fixes #25956

Modifications

  • Record closed-ledger metadata for the final ledger when a managed ledger is terminated.
  • Trigger automatic offload after the terminated state is persisted.
  • Allow manual offload to include the last ledger when the managed ledger is terminated and the requested position covers the terminated position.
  • Add managed-ledger tests for manual, automatic, bounded, and idempotent offload of the final ledger after termination.

Verifying this change

Ran:

./gradlew :managed-ledger:test   --tests org.apache.bookkeeper.mledger.impl.OffloadPrefixTest   --tests org.apache.bookkeeper.mledger.impl.ManagedLedgerTerminationTest   --no-daemon

Result: BUILD SUCCESSFUL.

}

long current = ledgers.lastKey();
boolean includeLastLedger = STATE_UPDATER.get(this) == State.Terminated

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.

I think this needs one more guard for the in-progress termination window. asyncTerminate() sets state = State.Terminated before the BookKeeper close callback refreshes the final LedgerInfo. If asyncOffloadPrefix() runs in that window, includeLastLedger becomes true, but the current ledger still has the active-ledger placeholder metadata (entries/size are still 0 and timestamp is not refreshed). The loop can then find no ledgers to offload and return lastConfirmedEntry.getNext(), which reports the prefix as fully offloaded even though the final ledger was never offloaded.

Could we gate includeLastLedger on the final LedgerInfo actually being closed/refreshed, or introduce a separate terminating state so manual offload does not treat an in-flight termination as completed? A regression test can block the BK close with PulsarMockBookKeeper.promiseAfter(0), call asyncTerminate(), call offloadPrefix() before releasing the close, and assert it does not report lastConfirmedEntry.getNext() without offloading the final ledger.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Recommended fix: Gate includeLastLedger on the final ledger actually being closed (metadata refreshed). The simplest reliable signal is whether the last ledger's LedgerInfo has been populated:

LedgerInfo lastLedgerInfo = ledgers.get(current);
boolean lastLedgerClosed = lastLedgerInfo != null && lastLedgerInfo.getEntries() > 0;
boolean includeLastLedger = STATE_UPDATER.get(this) == State.Terminated
&& requestOffloadTo.compareTo(lastConfirmedEntry) >= 0
&& lastLedgerClosed;

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.

[Bug] Final managed-ledger ledger cannot be offloaded after topic termination

3 participants