apache/pulsar · error · IllegalArgumentException
Cannot split non-active segment: ${segmentId}
Error message
Cannot split non-active segment: ${segmentId} What it means
SegmentLayout.splitSegment only allows splitting segments whose state is active. Passing the id of a non-active segment (e.g. a parent segment already split into children, or a sealed segment) throws an IllegalArgumentException to protect layout invariants — one hash range must map to exactly one active segment.
Source
Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/service/scalable/SegmentLayout.java:171
return depth;
}
/**
* Produce a new layout by splitting a segment at its midpoint.
*
* @param segmentId the active segment to split
* @param nowMs wall-clock millis used as the parent's seal time and the
* children's create time. Caller passes a single value so
* CAS retries and follow-up reads agree.
* @return a new SegmentLayout with the split applied
*/
public SegmentLayout splitSegment(long segmentId, long nowMs) {
SegmentInfo segment = allSegments.get(segmentId);
if (segment == null) {
throw new IllegalArgumentException("Segment not found: " + segmentId);
}
if (!segment.isActive()) {
throw new IllegalArgumentException("Cannot split non-active segment: " + segmentId);
}
HashRange[] splitRanges = segment.hashRange().split();
long newEpoch = epoch + 1;
long childId1 = nextSegmentId;
long childId2 = nextSegmentId + 1;
// PIP-486: a split divides the parent's entry-buckets between its children — N/2 each (at least
// 1) — so the topic's total stays ≈ the budget as it fans out into more, narrower segments.
List<Integer> childEntryBucketSplits =
EntryBucketSplits.equalWidth(Math.max(1, segment.bucketCount() / 2));
SegmentInfo sealedParent = segment.sealed(newEpoch, nowMs, List.of(childId1, childId2));
SegmentInfo child1 = SegmentInfo.active(childId1, splitRanges[0],
List.of(segmentId), newEpoch, nowMs).withEntryBucketSplits(childEntryBucketSplits);
SegmentInfo child2 = SegmentInfo.active(childId2, splitRanges[1],
List.of(segmentId), newEpoch, nowMs).withEntryBucketSplits(childEntryBucketSplits);
Map<Long, SegmentInfo> newSegments = new LinkedHashMap<>(allSegments);View on GitHub (pinned to 820761864e)
Solutions
- Select split targets only from segments where segment.isActive() is true in the current layout
- Refresh the layout snapshot and re-pick a candidate segment before splitting
- Track parent->children mapping so old parent ids are never reused as split targets
Example fix
// before
layout.splitSegment(parentId, now); // parent already split
// after
SegmentInfo target = layout.allSegments.values().stream()
.filter(SegmentInfo::isActive).findFirst().orElseThrow();
layout.splitSegment(target.segmentId(), now); Defensive patterns
Strategy: validation
Validate before calling
SegmentInfo s = layout.allSegments.get(segmentId);
if (s == null || !s.isActive()) {
throw new IllegalArgumentException("segmentId " + segmentId + " is not an active segment");
} Type guard
boolean isSplittable(SegmentLayout layout, long id) {
SegmentInfo s = layout.allSegments.get(id);
return s != null && s.isActive();
} Try / catch
try {
layout.splitSegment(id, now);
} catch (IllegalArgumentException e) {
log.warn("Cannot split segment {}: not active — pick an active child instead", id);
} Prevention
- Filter split candidates with isActive() before scheduling
- After a split, refer to child segment ids, never the parent's
- Refresh the layout before each split decision to avoid stale state
When it happens
Trigger: Calling splitSegment with the id of a parent segment that was previously split (children are the active ones), or any sealed/inactive segment id.
Common situations: Stale references to parent ids after a split; scheduling splits on segments selected from an outdated layout; tests exercising the non-active guard (testSplitNonActiveSegment).
Related errors
- Segment not found: ${segmentId}
- Must have at least 1 segment
- Failed to initialize controller for ${topic}
- The topic has a max partition index of %d, the number of par
- entryFilterNames can't be empty. To remove entry filters use
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/33f0618daa3a9252.
Report an issue: GitHub.