apache/pulsar · error · IllegalArgumentException
Cannot rebucket non-active segment: ${segmentId}
Error message
Cannot rebucket non-active segment: ${segmentId} What it means
rebucketSegment requires the target segment to be active; a sealed segment is draining historical data under its old bucketing and cannot be re-bucketed. If segment.isActive() is false it throws IllegalArgumentException("Cannot rebucket non-active segment: <id>").
Source
Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/service/scalable/SegmentLayout.java:273
* rollover"). A segment's bucketing is immutable for its life, so changing N is a layout
* operation: the sealed predecessor drains under its old buckets while the successor takes
* new writes under the new ones — the ordinary seal → successor flow, so per-key order
* 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);
}View on GitHub (pinned to 820761864e)
Solutions
- Verify segment.isActive() on the freshly loaded layout before rebucketing
- Treat this error as 'operation already applied' during CAS retries: reload the layout and check whether the successor already has the new splits
- Target only segments from getActiveSegments()
- Include the epoch with the CAS so a concurrent seal is detected before you retry
Example fix
// before
layout.rebucketSegment(id, newSplits, nowMs);
// after
SegmentInfo seg = fresh.getAllSegments().get(id);
if (seg != null && seg.isActive() && !newSplits.equals(seg.entryBucketSplits())) {
fresh.rebucketSegment(id, newSplits, nowMs);
} Defensive patterns
Strategy: validation
Validate before calling
boolean canRebucket(SegmentLayout layout, long segmentId, List<Integer> newSplits) {
var seg = layout.getAllSegments().get(segmentId);
return seg != null && seg.isActive() && !newSplits.equals(seg.entryBucketSplits());
} Type guard
boolean isActiveSegment(SegmentLayout layout, long id) {
return layout.getActiveSegments().containsKey(id);
} Try / catch
try {
newLayout = layout.rebucketSegment(id, newSplits, nowMs);
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Cannot rebucket non-active")) {
// segment sealed concurrently or plan already applied — reload and verify state
layout = SegmentLayout.fromMetadata(refreshMetadata());
} else {
throw e;
}
} Prevention
- Check isActive() immediately before the mutation on the same snapshot
- Handle CAS-retry loops by detecting already-applied operations (successor exists with same splits)
- Target only segments from getActiveSegments()
- Carry the epoch in the metadata write to serialize concurrent rebuckets
When it happens
Trigger: Calling rebucketSegment on a segment that was sealed by a prior split, merge, rebucket rollover, or rollover-on-expiry — including double-applying a rebucket plan after the first attempt already succeeded (and a CAS retry re-runs against the updated layout).
Common situations: Re-running a rebucket plan after a metadata CAS failure when the first application actually succeeded; auto-scale evaluator targeting segments from a stale layout; tests rebucketing sealed DAG ancestors.
Related errors
- Both segments must be active
- Segment ${segmentId} already has the requested entry-bucket
- Cannot prune an active segment: ${segmentId}
- configuredService should not be an instance of SystemTopicBa
- Segment not found
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/0bc4f81a69b102e8.
Report an issue: GitHub.