apache/pulsar · error · IllegalArgumentException
Cannot prune an active segment: ${segmentId}
Error message
Cannot prune an active segment: ${segmentId} What it means
pruneSegment may only remove segments that are sealed (not active); pruning an active segment would delete live data ownership. If segment.isActive() it throws IllegalArgumentException("Cannot prune an active segment: <id>").
Source
Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/service/scalable/SegmentLayout.java:307
return new SegmentLayout(newEpoch, nextSegmentId + 1, newSegments);
}
/**
* Prune an expired segment from the DAG. The segment must be sealed and have no
* children that are still in the DAG (i.e., children have already been pruned or
* the segment is a leaf that was sealed).
*
* @param segmentId the segment to prune
* @return a new SegmentLayout with the segment removed
*/
public SegmentLayout pruneSegment(long segmentId) {
SegmentInfo segment = allSegments.get(segmentId);
if (segment == null) {
throw new IllegalArgumentException("Segment not found: " + segmentId);
}
if (segment.isActive()) {
throw new IllegalArgumentException("Cannot prune an active segment: " + segmentId);
}
Map<Long, SegmentInfo> newSegments = new LinkedHashMap<>(allSegments);
newSegments.remove(segmentId);
// Remove this segment from its children's parent lists
for (long childId : segment.childIds()) {
SegmentInfo child = newSegments.get(childId);
if (child != null) {
List<Long> newParentIds = child.parentIds().stream()
.filter(id -> id != segmentId)
.collect(Collectors.toList());
newSegments.put(childId, child.withParentIds(newParentIds));
}
}
// Remove this segment from its parents' child lists
for (long parentId : segment.parentIds()) {View on GitHub (pinned to 820761864e)
Solutions
- Only prune segments that are sealed AND expired past retention: check !segment.isActive() plus the expiry timestamp before calling
- Derive prune candidates from the sealed segments in the layout, never from allSegments
- Re-check activity on the freshest layout immediately before each prune to avoid racing a recovery
- Let pruneAllAsync drive candidate selection instead of hand-picking IDs
Example fix
// before
layout.pruneSegment(id);
// after
SegmentInfo seg = layout.getAllSegments().get(id);
if (seg != null && !seg.isActive() && isExpired(seg, nowMs)) {
layout.pruneSegment(id);
} Defensive patterns
Strategy: validation
Validate before calling
boolean canPrune(SegmentLayout layout, long segmentId, long nowMs) {
var seg = layout.getAllSegments().get(segmentId);
return seg != null && !seg.isActive() && isExpired(seg, nowMs);
} Type guard
boolean isSealedSegment(SegmentLayout layout, long id) {
var seg = layout.getAllSegments().get(id);
return seg != null && !seg.isActive();
} Try / catch
try {
newLayout = layout.pruneSegment(id);
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Cannot prune an active segment")) {
// segment is live (or reactivated) — skip it this round
return;
}
throw e;
} Prevention
- Apply an explicit expiry (sealed-at + retention) check before pruning
- Prefer pruneAllAsync's candidate selection over hand-picked IDs
- Re-verify sealed status on the freshest snapshot immediately before the CAS
- Skip-and-continue policy for active segments in prune loops
When it happens
Trigger: Calling pruneSegment on an ID that is still an active serving segment — e.g. iterating all segments instead of only expired/sealed ones, or pruning against a stale layout where the segment has since become active again (state restored after failure).
Common situations: Custom retention/prune logic without an expiration check; tests calling testCannotPruneActiveSegment-style negative paths; prune logic racing a recovery that reactivated the segment.
Related errors
- Both segments must be active
- Cannot rebucket non-active segment: ${segmentId}
- configuredService should not be an instance of SystemTopicBa
- Segment not found
- Segments are not adjacent: ${hashRange1} and ${hashRange2}
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/9913379032912832.
Report an issue: GitHub.