apache/pulsar · error · IllegalArgumentException
Segments are not adjacent: ${hashRange1} and ${hashRange2}
Error message
Segments are not adjacent: ${hashRange1} and ${hashRange2} What it means
mergeSegments only merges segments whose HashRanges are adjacent on the hash ring (a merge is defined as the inverse of a split). When the two active segments do not cover contiguous ranges it throws IllegalArgumentException("Segments are not adjacent: <range1> and <range2>") including both ranges for diagnosis.
Source
Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/service/scalable/SegmentLayout.java:226
}
/**
* As {@link #mergeSegments(long, long, long)}, clamping the merged segment's entry-bucket
* count to {@code maxBucketsPerSegment} (the configured per-segment ceiling): the merged
* segment recovers the parents' buckets, but never past the hard ceiling.
*/
public SegmentLayout mergeSegments(long segmentId1, long segmentId2, long nowMs,
int maxBucketsPerSegment) {
SegmentInfo seg1 = allSegments.get(segmentId1);
SegmentInfo seg2 = allSegments.get(segmentId2);
if (seg1 == null || seg2 == null) {
throw new IllegalArgumentException("Segment not found");
}
if (!seg1.isActive() || !seg2.isActive()) {
throw new IllegalArgumentException("Both segments must be active");
}
if (!seg1.hashRange().isAdjacentTo(seg2.hashRange())) {
throw new IllegalArgumentException("Segments are not adjacent: "
+ seg1.hashRange() + " and " + seg2.hashRange());
}
long newEpoch = epoch + 1;
long mergedId = nextSegmentId;
HashRange mergedRange = seg1.hashRange().merge(seg2.hashRange());
// PIP-486: a merge is the inverse of a split — the merged segment recovers both parents' buckets
// (N1 + N2), so the topic's total entry-bucket count stays ≈ the budget as segments coalesce.
List<Integer> mergedEntryBucketSplits = EntryBucketSplits.equalWidth(
Math.min(seg1.bucketCount() + seg2.bucketCount(), maxBucketsPerSegment));
SegmentInfo sealed1 = seg1.sealed(newEpoch, nowMs, List.of(mergedId));
SegmentInfo sealed2 = seg2.sealed(newEpoch, nowMs, List.of(mergedId));
SegmentInfo merged = SegmentInfo.active(mergedId, mergedRange,
List.of(segmentId1, segmentId2), newEpoch, nowMs)
.withEntryBucketSplits(mergedEntryBucketSplits);
Map<Long, SegmentInfo> newSegments = new LinkedHashMap<>(allSegments);View on GitHub (pinned to 820761864e)
Solutions
- Before merging, check seg1.hashRange().isAdjacentTo(seg2.hashRange()) and skip/choose another pair if false
- Select merge candidates as adjacent pairs by scanning active segments sorted by range start
- Merge the intermediate segment(s) first, or split-and-merge stepwise, to bring the desired ranges adjacent
- Group candidates by parent ID (children of one split are adjacent by construction)
Example fix
// before
SegmentInfo s1 = layout.getAllSegments().get(id1);
SegmentInfo s2 = layout.getAllSegments().get(id2);
layout.mergeSegments(id1, id2, nowMs);
// after
if (s1.hashRange().isAdjacentTo(s2.hashRange())) {
layout.mergeSegments(id1, id2, nowMs);
} else {
// pick an adjacent partner from layout.getActiveSegments()
} Defensive patterns
Strategy: validation
Validate before calling
boolean mergeable(SegmentLayout layout, long id1, long id2) {
var segs = layout.getAllSegments();
return segs.containsKey(id1) && segs.containsKey(id2)
&& segs.get(id1).hashRange().isAdjacentTo(segs.get(id2).hashRange());
} Try / catch
try {
newLayout = layout.mergeSegments(id1, id2, nowMs);
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Segments are not adjacent")) {
// choose a different candidate pair among active segments
} else {
throw e;
}
} Prevention
- Scan active segments sorted by range start and merge only immediate neighbors
- Prefer merging full sibling sets produced by the same split
- Check isAdjacentTo before proposing any merge in policy code
- Log both hash ranges on rejection to aid ring-layout debugging
When it happens
Trigger: Calling mergeSegments(segId1, segId2, ...) with two active segments whose hash ranges are contiguous-adjacent fails isAdjacentTo — e.g. segments separated by another segment between them on the ring, or ranges from opposite ends of the ring.
Common situations: Auto-scale merge heuristics picking the two lowest-load segments without checking adjacency; merging segments across different split trees (siblings of different parents); tests selecting arbitrary active segment pairs.
Related errors
- Segment not found
- Both segments must be active
- No more range can assigned to new consumer, assigned consume
- Range conflict with consumer ${conflictingConsumer}
- configuredService should not be an instance of SystemTopicBa
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/d145a6288f696ae2.
Report an issue: GitHub.