apache/pulsar · error · IllegalArgumentException
Segment ${segmentId} already has the requested entry-bucket
Error message
Segment ${segmentId} already has the requested entry-bucket splits What it means
A segment's entry-bucket split list is immutable for its life, so rebucketSegment rejects a no-op request: if newSplits equals the segment's current entryBucketSplits it throws IllegalArgumentException("Segment <id> already has the requested entry-bucket splits"). Rebucketing works by sealing the old segment and creating a successor, so applying identical splits would create a useless epoch bump and rollover.
Source
Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/service/scalable/SegmentLayout.java:276
* across the change is preserved by the existing machinery.
*
* @param segmentId the active segment to rebucket
* @param newSplits the successor's entry-bucket split points (ascending start hashes of
* buckets {@code 1..N-1}; empty = a single bucket spanning the ring)
* @param nowMs wall-clock millis used as the parent's seal time and the successor's
* create time
* @return a new SegmentLayout with the rollover applied
*/
public SegmentLayout rebucketSegment(long segmentId, List<Integer> newSplits, long nowMs) {
SegmentInfo segment = allSegments.get(segmentId);
if (segment == null) {
throw new IllegalArgumentException("Segment not found: " + segmentId);
}
if (!segment.isActive()) {
throw new IllegalArgumentException("Cannot rebucket non-active segment: " + segmentId);
}
if (newSplits.equals(segment.entryBucketSplits())) {
throw new IllegalArgumentException(
"Segment " + segmentId + " already has the requested entry-bucket splits");
}
long newEpoch = epoch + 1;
long successorId = nextSegmentId;
SegmentInfo sealedParent = segment.sealed(newEpoch, nowMs, List.of(successorId));
SegmentInfo successor = SegmentInfo.active(successorId, segment.hashRange(),
List.of(segmentId), newEpoch, nowMs).withEntryBucketSplits(newSplits);
Map<Long, SegmentInfo> newSegments = new LinkedHashMap<>(allSegments);
newSegments.put(segmentId, sealedParent);
newSegments.put(successorId, successor);
return new SegmentLayout(newEpoch, nextSegmentId + 1, newSegments);
}
/**
* Prune an expired segment from the DAG. The segment must be sealed and have noView on GitHub (pinned to 820761864e)
Solutions
- Compare the desired splits against segment.entryBucketSplits() and skip the call when equal (idempotent no-op)
- Make the policy stateful: record the epoch/splits already applied and only rebucket when the target differs
- On CAS retry, first check whether the successor segment already carries the new splits before re-invoking
Example fix
// before
layout.rebucketSegment(id, desiredSplits, nowMs);
// after
SegmentInfo seg = layout.getAllSegments().get(id);
if (!desiredSplits.equals(seg.entryBucketSplits())) {
layout.rebucketSegment(id, desiredSplits, nowMs);
} // else: already applied, no-op Defensive patterns
Strategy: validation
Validate before calling
boolean rebucketNeeded(SegmentLayout layout, long segmentId, List<Integer> newSplits) {
var seg = layout.getAllSegments().get(segmentId);
return seg != null && !newSplits.equals(seg.entryBucketSplits());
} Try / catch
try {
newLayout = layout.rebucketSegment(id, newSplits, nowMs);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().endsWith("already has the requested entry-bucket splits")) {
return layout; // idempotent no-op
}
throw e;
} Prevention
- Compare desired splits with entryBucketSplits() before invoking — make the call idempotent
- Short-circuit repeated policy evaluations when the target state equals current state
- Record applied epochs in policy state to avoid re-submission
- Tests: cover the no-op path explicitly rather than relying on the exception
When it happens
Trigger: Calling rebucketSegment(segmentId, newSplits, nowMs) where newSplits.equals(segment.entryBucketSplits()) — re-applying the same rebucket plan, or a policy recomputing splits that resolve to the existing configuration.
Common situations: Auto-scale policy firing repeatedly without a 'changed?' check; CAS-retry loops re-submitting the same already-applied plan; tests asserting idempotency via the exception path.
Related errors
- Cannot rebucket non-active segment: ${segmentId}
- configuredService should not be an instance of SystemTopicBa
- Segment not found
- Both segments must be active
- Segments are not adjacent: ${hashRange1} and ${hashRange2}
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/bee3070ce556c4a7.
Report an issue: GitHub.