apache/pulsar · critical · RuntimeException
there should not be multi ledger in a block %s %s
Error message
there should not be multi ledger in a block %s %s
What it means
BufferedOffloadStream buffers entries of exactly one BookKeeper ledger per offload block. When it pops the next head entry, if the entry's ledgerId differs from the stream's current ledgerId, it throws a RuntimeException because a single block must never span multiple ledgers.
Source
Thrown at tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/impl/BufferedOffloadStream.java:106
}
if (blockSize <= offset) {
return -1;
} else if (validDataOffset != NOT_INITIALIZED) {
return BLOCK_END_PADDING[(offset++ - validDataOffset) % BLOCK_END_PADDING.length];
}
if (entryBuffer.isEmpty()) {
validDataOffset = offset;
return read();
}
Entry headEntry = entryBuffer.remove(0);
//create new block when a ledger end
if (headEntry.getLedgerId() != this.ledgerId) {
throw new RuntimeException(
String.format("there should not be multi ledger in a block %s %s", headEntry.getLedgerId(),
this.ledgerId));
}
final int entryLength = headEntry.getLength();
final long entryId = headEntry.getEntryId();
CompositeByteBuf entryBuf = PulsarByteBufAllocator.DEFAULT.compositeBuffer(2);
ByteBuf entryHeaderBuf = PulsarByteBufAllocator.DEFAULT.buffer(ENTRY_HEADER_SIZE, ENTRY_HEADER_SIZE);
entryHeaderBuf.writeInt(entryLength).writeLong(entryId);
entryBuf.addComponents(true, entryHeaderBuf, headEntry.getDataBuffer().retain());
endEntryId = headEntry.getEntryId();
headEntry.release();
currentEntry = entryBuf;
return read();
}
@OverrideView on GitHub (pinned to 820761864e)
Solutions
- Feed BufferedOffloadStream entries from a single ledger per segment; stop and flush the current block before switching ledgers (ensure the offloader closes the block at ledger end)
- Verify offload threshold/segment logic so the block ends exactly at each ledger boundary
- Re-run offload for the affected segment after fixing the boundary logic
- If it occurs after a broker/offloader upgrade, check for changes in entry-buffering behavior and align versions
Example fix
// before: keep feeding across ledger boundary
stream.feed(nextLedgerEntries); // RuntimeException
// after
if (nextEntry.getLedgerId() != currentLedgerId) {
stream.finishBlock(); // flush + close current block
}
stream.feed(nextEntry); Defensive patterns
Strategy: validation
Validate before calling
// before feeding entries to BufferedOffloadStream
if (!entryBuffer.isEmpty()
&& entryBuffer.get(0).getLedgerId() != pendingEntry.getLedgerId()) {
stream.finishBlock(); // end block at ledger boundary
} Try / catch
try {
int n = stream.read(buf);
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().startsWith("there should not be multi ledger in a block")) {
log.error("offload block spans ledgers — fix boundary logic and re-offload segment", e);
// do not retry: offloaded block is malformed
} else throw e;
} Prevention
- Always close/flush the current block when the ledger changes
- Keep offload threshold logic aligned with ledger rollover points
- Add assertions when filling the entry buffer that all ledgerIds match
- Test offloads that cross ledger boundaries before upgrading the offloader
When it happens
Trigger: read() pops entryBuffer head and headEntry.getLedgerId() != this.ledgerId — i.e. the buffered entries fed into the stream mix entries from more than one ledger within one block.
Common situations: Offload boundary logic feeding the stream with entries from the next ledger without closing/rotating the current block; offloading across a ledger rollover that was previously cut at the wrong point; version/behavior change in how segment boundaries are computed.
Related errors
- Cursor %s mark-delete position %s is ahead of the last posit
- The '${offloaderName}' offloader does not provide an offload
- Class ${factoryClass} does not implement interface ${interfa
- Error offloading: ${lastError}
- Fail to read LedgerMetadata for ledgerId ${key}
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/d97b89bdea48f106.
Report an issue: GitHub.