apache/pulsar · error · IndexOutOfBoundsException

Entry index: ${messageEntryId} beyond lastEntryId: ${lastEnt

Error message

Entry index: ${messageEntryId} beyond lastEntryId: ${lastEntryId}

What it means

The v2 variant of getIndexEntryForEntry validates the requested entry id against the lastEntryId stored in the per-ledger segment metadata for the given ledgerId. Requesting an entry beyond that bound throws IndexOutOfBoundsException because the entry is not contained in the offloaded data for that ledger segment.

Source

Thrown at tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/impl/OffloadIndexBlockV2Impl.java:120

    public void recycle() {
        dataObjectLength = -1;
        dataHeaderLength = -1;
        segmentMetadata = null;
        indexEntries.clear();
        indexEntries = null;
        if (recyclerHandle != null) {
            recyclerHandle.recycle(this);
        }
    }

    @Override
    public OffloadIndexEntry getIndexEntryForEntry(long ledgerId, long messageEntryId) throws IOException {
        if (messageEntryId > getLedgerMetadata(ledgerId).getLastEntryId()) {
            log.warn().attr("entryId", messageEntryId)
                    .attr("lastEntryId", getLedgerMetadata(ledgerId).getLastEntryId())
                    .log("Requested entry beyond lastEntryId");
            throw new IndexOutOfBoundsException("Entry index: " + messageEntryId
                    + " beyond lastEntryId: " + getLedgerMetadata(ledgerId).getLastEntryId());
        }
        // find the greatest mapping Id whose entryId <= messageEntryId
        return this.indexEntries.get(ledgerId).floorEntry(messageEntryId).getValue();
    }

    public long getStartEntryId(long ledgerId) {
        return this.indexEntries.get(ledgerId).firstEntry().getValue().getEntryId();
    }

    @Override
    public int getEntryCount() {
        int ans = 0;
        for (TreeMap<Long, OffloadIndexEntryImpl> v : this.indexEntries.values()) {
            ans += v.size();
        }

        return ans;

View on GitHub (pinned to 820761864e)

Solutions

  1. Bounds-check entryId against the ledger's segment metadata before the call and route newer entries to BookKeeper
  2. Re-offload or wait for the next offload cycle if newer data must be served from storage
  3. Ensure per-ledger entry-range bookkeeping in the caller matches the v2 index's per-ledger metadata
  4. Log and inspect getLedgerMetadata(ledgerId).getLastEntryId() when the range expectation differs

Example fix

// before: direct lookup
OffloadIndexEntry e = index.getIndexEntryForEntry(ledgerId, entryId);
// after: validate range first
LedgerInfo meta = index.getLedgerMetadata(ledgerId);
if (entryId > meta.getLastEntryId()) {
    return readEntryFromBookKeeper(ledgerId, entryId);
}
OffloadIndexEntry e = index.getIndexEntryForEntry(ledgerId, entryId);
Defensive patterns

Strategy: validation

Validate before calling

LedgerInfo meta = index.getLedgerMetadata(ledgerId);
if (messageEntryId > meta.getLastEntryId()) {
    return readFromBookKeeper(ledgerId, messageEntryId); // not offloaded
}

Try / catch

try {
    OffloadIndexEntry e = index.getIndexEntryForEntry(ledgerId, messageEntryId);
} catch (IndexOutOfBoundsException e) {
    // fall back to BookKeeper for entries past the offloaded segment
}

Prevention

When it happens

Trigger: Calling getIndexEntryForEntry(ledgerId, messageEntryId) where messageEntryId exceeds getLedgerMetadata(ledgerId).getLastEntryId() — entries written after offload, an unknown ledgerId (which itself would fail earlier), or a wrong entry id from the caller.

Common situations: Multi-ledger (v2) indexes where callers mix up which segment covers which entry range; reads for entries still only in BookKeeper; retry logic replaying ids past the segment end.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/25fb2bc2af2e63bc. Report an issue: GitHub.