{"record":{"id":"cbe958bd553ce517","repo":"apache/cassandra","slug":"when-adding-a-read-replica-that-replica-needs-to","errorCode":null,"errorMessage":"When adding a read replica, that replica needs to exist as a write replica before that: ","messagePattern":"When adding a read replica, that replica needs to exist as a write replica before that: ","errorType":"exception","errorClass":"Transformation.RejectedTransformationException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/tcm/ownership/PlacementTransitionPlan.java","lineNumber":226,"sourceCode":"                        for (Replica writeReplica : existingWriteReplicas)\n                        {\n                            if (writeReplica.isFull() == newReadReplica.isFull() || (writeReplica.isFull() && newReadReplica.isTransient()))\n                            {\n                                if (writeReplica.range().contains(newReadReplica.range()))\n                                {\n                                    contained = true;\n                                    break;\n                                }\n                                else if (writeReplica.range().intersects(newReadReplica.range()))\n                                {\n                                    intersectingRanges.add(writeReplica.range());\n                                }\n                            }\n                        }\n                        if (!contained && Range.normalize(intersectingRanges).stream().noneMatch(writeReplica -> writeReplica.contains(newReadReplica.range())))\n                        {\n                            String message = \"When adding a read replica, that replica needs to exist as a write replica before that: \" + newReadReplica + '\\n' + placements.get(params) + '\\n' + delta;\n                            logger.warn(message);\n                            throw new Transformation.RejectedTransformationException(message);\n                        }\n                    }\n                }\n            }\n            placements = deltas.apply(endpointLookup, Epoch.FIRST, placements);\n        }\n    }\n}\n","sourceCodeStart":208,"sourceCodeEnd":236,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/tcm/ownership/PlacementTransitionPlan.java#L208-L236","documentation":"Thrown by PlacementTransitionPlan.assertPreExistingWriteReplica when a placement transformation tries to add an endpoint as a read replica for a range in which it is not already a write replica. TCM requires read-replica additions to be preceded by full write replication for the same range, otherwise reads could miss data, so the transformation is rejected with RejectedTransformationException.","triggerScenarios":"Applying a placement delta (e.g. from a read-replica placement transform or repair-driven rebalance) where for some intersecting range, none of the write replicas contains the new read replica's range (contained==false and no writeReplica.contains(newReadReplica.range())).","commonSituations":"Operator attempts to add a read replica (nodetool readreplica / read-replica placement transform) before write replication to that node/range has completed; a previous write-placement transition failed or is still pending; running a rebalance on inconsistent placement state after a partial transformation.","solutions":["First ensure the target node is a write replica for the range (complete the write placement transition / run repair) and only then add the read replica","Retry the placement transform so pending write-replica deltas are applied before the read-replica delta","Inspect the logged placements and delta in the message to identify the missing write replication, and correct the transition order"],"exampleFix":"// before: read replica added before write replication\nPlacementTransitionPlan.addReadReplica(node, range)   // -> RejectedTransformationException\n// after: apply write placement first, then read replica\nPlacementTransitionPlan.addWriteReplica(node, range);\nawaitReplicationOf(node, range);\nPlacementTransitionPlan.addReadReplica(node, range);","handlingStrategy":"try-catch","validationCode":"// verify write replication exists before adding a read replica\nboolean isWriteReplica = currentPlacements.get(params)\n    .writes.forKey(token).endpoints().contains(node);\nif (!isWriteReplica) {\n    throw new IllegalStateException(node + \" is not a write replica for range \" + range + \"; apply write placement first\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    ClusterMetadataService.instance().transform(...addReadReplica...);\n} catch (Transformation.RejectedTransformationException e) {\n    if (e.getMessage().startsWith(\"When adding a read replica\")) {\n        // apply the write-placement transition first, then retry\n    } else throw e;\n}","preventionTips":["Always complete write-placement transitions (and repair) before adding read replicas","Order placement transforms: write replica first, then read replica","Check the target node's write coverage of the range with nodetool/CMC before requesting read-replica status"],"tags":["replication","read-replica","placement","invalid-state-transition"],"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-17T15:17:12.973Z"}