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

  1. Verify segment.isActive() on the freshly loaded layout before rebucketing
  2. Treat this error as 'operation already applied' during CAS retries: reload the layout and check whether the successor already has the new splits
  3. Target only segments from getActiveSegments()
  4. 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

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


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