{"record":{"id":"33f0618daa3a9252","repo":"apache/pulsar","slug":"cannot-split-non-active-segment-segmentid","errorCode":null,"errorMessage":"Cannot split non-active segment: ${segmentId}","messagePattern":"Cannot split non-active segment: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/service/scalable/SegmentLayout.java","lineNumber":171,"sourceCode":"        return depth;\n    }\n\n    /**\n     * Produce a new layout by splitting a segment at its midpoint.\n     *\n     * @param segmentId the active segment to split\n     * @param nowMs     wall-clock millis used as the parent's seal time and the\n     *                  children's create time. Caller passes a single value so\n     *                  CAS retries and follow-up reads agree.\n     * @return a new SegmentLayout with the split applied\n     */\n    public SegmentLayout splitSegment(long segmentId, long nowMs) {\n        SegmentInfo segment = allSegments.get(segmentId);\n        if (segment == null) {\n            throw new IllegalArgumentException(\"Segment not found: \" + segmentId);\n        }\n        if (!segment.isActive()) {\n            throw new IllegalArgumentException(\"Cannot split non-active segment: \" + segmentId);\n        }\n\n        HashRange[] splitRanges = segment.hashRange().split();\n        long newEpoch = epoch + 1;\n        long childId1 = nextSegmentId;\n        long childId2 = nextSegmentId + 1;\n\n        // PIP-486: a split divides the parent's entry-buckets between its children — N/2 each (at least\n        // 1) — so the topic's total stays ≈ the budget as it fans out into more, narrower segments.\n        List<Integer> childEntryBucketSplits =\n                EntryBucketSplits.equalWidth(Math.max(1, segment.bucketCount() / 2));\n        SegmentInfo sealedParent = segment.sealed(newEpoch, nowMs, List.of(childId1, childId2));\n        SegmentInfo child1 = SegmentInfo.active(childId1, splitRanges[0],\n                List.of(segmentId), newEpoch, nowMs).withEntryBucketSplits(childEntryBucketSplits);\n        SegmentInfo child2 = SegmentInfo.active(childId2, splitRanges[1],\n                List.of(segmentId), newEpoch, nowMs).withEntryBucketSplits(childEntryBucketSplits);\n\n        Map<Long, SegmentInfo> newSegments = new LinkedHashMap<>(allSegments);","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/scalable/SegmentLayout.java#L153-L189","documentation":"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.","triggerScenarios":"Calling splitSegment with the id of a parent segment that was previously split (children are the active ones), or any sealed/inactive segment id.","commonSituations":"Stale references to parent ids after a split; scheduling splits on segments selected from an outdated layout; tests exercising the non-active guard (testSplitNonActiveSegment).","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"],"exampleFix":"// before\nlayout.splitSegment(parentId, now); // parent already split\n// after\nSegmentInfo target = layout.allSegments.values().stream()\n    .filter(SegmentInfo::isActive).findFirst().orElseThrow();\nlayout.splitSegment(target.segmentId(), now);","handlingStrategy":"validation","validationCode":"SegmentInfo s = layout.allSegments.get(segmentId);\nif (s == null || !s.isActive()) {\n    throw new IllegalArgumentException(\"segmentId \" + segmentId + \" is not an active segment\");\n}","typeGuard":"boolean isSplittable(SegmentLayout layout, long id) {\n    SegmentInfo s = layout.allSegments.get(id);\n    return s != null && s.isActive();\n}","tryCatchPattern":"try {\n    layout.splitSegment(id, now);\n} catch (IllegalArgumentException e) {\n    log.warn(\"Cannot split segment {}: not active — pick an active child instead\", id);\n}","preventionTips":["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"],"tags":["pulsar","scalable-topic","segment-layout","state-validation"],"backgroundTag":"segment-not-active","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}