{"record":{"id":"0bc4f81a69b102e8","repo":"apache/pulsar","slug":"cannot-rebucket-non-active-segment-segmentid","errorCode":null,"errorMessage":"Cannot rebucket non-active segment: ${segmentId}","messagePattern":"Cannot rebucket 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":273,"sourceCode":"     * rollover\"). A segment's bucketing is immutable for its life, so changing N is a layout\n     * operation: the sealed predecessor drains under its old buckets while the successor takes\n     * new writes under the new ones — the ordinary seal → successor flow, so per-key order\n     * across the change is preserved by the existing machinery.\n     *\n     * @param segmentId the active segment to rebucket\n     * @param newSplits the successor's entry-bucket split points (ascending start hashes of\n     *                  buckets {@code 1..N-1}; empty = a single bucket spanning the ring)\n     * @param nowMs     wall-clock millis used as the parent's seal time and the successor's\n     *                  create time\n     * @return a new SegmentLayout with the rollover applied\n     */\n    public SegmentLayout rebucketSegment(long segmentId, List<Integer> newSplits, 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 rebucket non-active segment: \" + segmentId);\n        }\n        if (newSplits.equals(segment.entryBucketSplits())) {\n            throw new IllegalArgumentException(\n                    \"Segment \" + segmentId + \" already has the requested entry-bucket splits\");\n        }\n\n        long newEpoch = epoch + 1;\n        long successorId = nextSegmentId;\n        SegmentInfo sealedParent = segment.sealed(newEpoch, nowMs, List.of(successorId));\n        SegmentInfo successor = SegmentInfo.active(successorId, segment.hashRange(),\n                List.of(segmentId), newEpoch, nowMs).withEntryBucketSplits(newSplits);\n\n        Map<Long, SegmentInfo> newSegments = new LinkedHashMap<>(allSegments);\n        newSegments.put(segmentId, sealedParent);\n        newSegments.put(successorId, successor);\n\n        return new SegmentLayout(newEpoch, nextSegmentId + 1, newSegments);\n    }","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/scalable/SegmentLayout.java#L255-L291","documentation":"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>\").","triggerScenarios":"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).","commonSituations":"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.","solutions":["Verify segment.isActive() on the freshly loaded layout before rebucketing","Treat this error as 'operation already applied' during CAS retries: reload the layout and check whether the successor already has the new splits","Target only segments from getActiveSegments()","Include the epoch with the CAS so a concurrent seal is detected before you retry"],"exampleFix":"// before\nlayout.rebucketSegment(id, newSplits, nowMs);\n// after\nSegmentInfo seg = fresh.getAllSegments().get(id);\nif (seg != null && seg.isActive() && !newSplits.equals(seg.entryBucketSplits())) {\n    fresh.rebucketSegment(id, newSplits, nowMs);\n}","handlingStrategy":"validation","validationCode":"boolean canRebucket(SegmentLayout layout, long segmentId, List<Integer> newSplits) {\n    var seg = layout.getAllSegments().get(segmentId);\n    return seg != null && seg.isActive() && !newSplits.equals(seg.entryBucketSplits());\n}","typeGuard":"boolean isActiveSegment(SegmentLayout layout, long id) {\n    return layout.getActiveSegments().containsKey(id);\n}","tryCatchPattern":"try {\n    newLayout = layout.rebucketSegment(id, newSplits, nowMs);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Cannot rebucket non-active\")) {\n        // segment sealed concurrently or plan already applied — reload and verify state\n        layout = SegmentLayout.fromMetadata(refreshMetadata());\n    } else {\n        throw e;\n    }\n}","preventionTips":["Check isActive() immediately before the mutation on the same snapshot","Handle CAS-retry loops by detecting already-applied operations (successor exists with same splits)","Target only segments from getActiveSegments()","Carry the epoch in the metadata write to serialize concurrent rebuckets"],"tags":["java","illegal-argument","segment-state","rebucket"],"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"}