{"record":{"id":"bee3070ce556c4a7","repo":"apache/pulsar","slug":"segment-segmentid-already-has-the-requested-ent","errorCode":null,"errorMessage":"Segment ${segmentId} already has the requested entry-bucket splits","messagePattern":"Segment (.+?) already has the requested entry-bucket splits","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/service/scalable/SegmentLayout.java","lineNumber":276,"sourceCode":"     * 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    }\n\n    /**\n     * Prune an expired segment from the DAG. The segment must be sealed and have no","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/scalable/SegmentLayout.java#L258-L294","documentation":"A segment's entry-bucket split list is immutable for its life, so rebucketSegment rejects a no-op request: if newSplits equals the segment's current entryBucketSplits it throws IllegalArgumentException(\"Segment <id> already has the requested entry-bucket splits\"). Rebucketing works by sealing the old segment and creating a successor, so applying identical splits would create a useless epoch bump and rollover.","triggerScenarios":"Calling rebucketSegment(segmentId, newSplits, nowMs) where newSplits.equals(segment.entryBucketSplits()) — re-applying the same rebucket plan, or a policy recomputing splits that resolve to the existing configuration.","commonSituations":"Auto-scale policy firing repeatedly without a 'changed?' check; CAS-retry loops re-submitting the same already-applied plan; tests asserting idempotency via the exception path.","solutions":["Compare the desired splits against segment.entryBucketSplits() and skip the call when equal (idempotent no-op)","Make the policy stateful: record the epoch/splits already applied and only rebucket when the target differs","On CAS retry, first check whether the successor segment already carries the new splits before re-invoking"],"exampleFix":"// before\nlayout.rebucketSegment(id, desiredSplits, nowMs);\n// after\nSegmentInfo seg = layout.getAllSegments().get(id);\nif (!desiredSplits.equals(seg.entryBucketSplits())) {\n    layout.rebucketSegment(id, desiredSplits, nowMs);\n} // else: already applied, no-op","handlingStrategy":"validation","validationCode":"boolean rebucketNeeded(SegmentLayout layout, long segmentId, List<Integer> newSplits) {\n    var seg = layout.getAllSegments().get(segmentId);\n    return seg != null && !newSplits.equals(seg.entryBucketSplits());\n}","typeGuard":null,"tryCatchPattern":"try {\n    newLayout = layout.rebucketSegment(id, newSplits, nowMs);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().endsWith(\"already has the requested entry-bucket splits\")) {\n        return layout; // idempotent no-op\n    }\n    throw e;\n}","preventionTips":["Compare desired splits with entryBucketSplits() before invoking — make the call idempotent","Short-circuit repeated policy evaluations when the target state equals current state","Record applied epochs in policy state to avoid re-submission","Tests: cover the no-op path explicitly rather than relying on the exception"],"tags":["java","illegal-argument","idempotency","rebucket"],"backgroundTag":"segment-already-has-splits","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}