{"record":{"id":"0e1a1614e3c110a7","repo":"apache/cassandra","slug":"source-s-for-s-is-not-remaining-as-a-replica-aft","errorCode":null,"errorMessage":"Source %s for %s is not remaining as a replica after the move, can't do a consistent range movement, retry with that disabled","messagePattern":"Source (.+?) for (.+?) is not remaining as a replica after the move, can't do a consistent range movement, retry with that disabled","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/tcm/sequences/Move.java","lineNumber":545,"sourceCode":"        {\n            if (fd.isAlive(source.endpoint()))\n            {\n                if (source.isFull())\n                {\n                    assert fullSource == null;\n                    fullSource = source;\n                }\n                else\n                {\n                    assert transientSource == null;\n                    if (!destination.isSelf() && !source.isSelf())\n                    {\n                        // a transient replica is being removed, now, to be able to safely skip streaming from this\n                        // replica we need to make sure it remains a replica for the range after the move has finished:\n                        if (writeAdditions.get(source.endpoint()).byRange().get(destination.range()) == null)\n                        {\n                            if (strict)\n                                throw new IllegalStateException(String.format(\"Source %s for %s is not remaining as a replica after the move, can't do a consistent range movement, retry with that disabled\", source, destination));\n                            else\n                                return false;\n                        }\n                        return true;\n                    }\n                    else\n                    {\n                        transientSource = source;\n                    }\n                }\n                return true;\n            }\n            else if (strict)\n                throw new IllegalStateException(\"Strict consistency requires the node losing the range to be UP but \" + source + \" is DOWN\");\n            return false;\n        }\n\n        private void addToMovements(Replica destination, EndpointsByReplica.Builder movements)","sourceCodeStart":527,"sourceCodeEnd":563,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/tcm/sequences/Move.java#L527-L563","documentation":"Thrown by Move.addSource during consistent (strict) range move planning. A source replica of a range would no longer be a replica of that range after the move, which breaks the guarantee that a transient replica being removed can safely skip streaming. In strict mode this is fatal; in non-strict mode MoveMultiRange skips the source instead.","triggerScenarios":"Running a consistent range movement (strict consistency enabled, e.g. nodetool move/rebuild with strict consistency or MoveMultiRange) where a transient source replica's writeAdditions entry has no matching range for the destination, i.e. the node is not staying a replica of destination.range() after the move.","commonSituations":"Operator-driven topology changes (moving tokens, decommission/bootstrap combinations) in multi-DC or transient-replication (EVERYWHERE/2-of-3 RF with transient replica counts) setups where the move leaves a range with too few full replicas; retry loops that previously used non-strict consistency now running strict.","solutions":["Retry the operation with consistent range movement (strict consistency) disabled, as the message suggests","Adjust the move plan / token assignment so the source remains a replica (full or transient) of the range after the move","Verify transient replication configuration (replication factor and transient replica counts) allows every removed transient source to remain a replica elsewhere","Perform the movement in smaller steps (per-range) so strict invariants hold at each step"],"exampleFix":"// before\nMoveMultiRange.move(systemKeyspace, ranges, /* strict */ true);\n// after\nMoveMultiRange.move(systemKeyspace, ranges, /* strict */ false); // or fix placement so source remains a replica","handlingStrategy":"retry","validationCode":"// before strict move: ensure each transient source remains a replica of its destination range\nfor (Replica source : sources)\n    if (writeAdditions.get(source.endpoint()).byRange().get(destination.range()) == null)\n        throw new PreconditionFailed(source + \" will not remain a replica; use non-strict or fix placement\");","typeGuard":null,"tryCatchPattern":"try { move(ranges, true); }\ncatch (IllegalStateException e) { if (e.getMessage().contains(\"not remaining as a replica\")) move(ranges, false); else throw e; }","preventionTips":["Validate replica placement after the planned move before executing","Prefer non-strict consistency when transient replication is in play","Run topology changes in small per-range steps"],"tags":["consistency","range-movement","topology","streaming"],"backgroundTag":"invalid-state-transition","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}