apache/pulsar · error · IllegalArgumentException

Segment already has the requested entry-bucket splits

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.

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.

Appendix: 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)