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 no

View on GitHub (pinned to 820761864e)

Solutions

  1. Compare the desired splits against segment.entryBucketSplits() and skip the call when equal (idempotent no-op)
  2. Make the policy stateful: record the epoch/splits already applied and only rebucket when the target differs
  3. 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

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


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