{"record":{"id":"960e32a01bb6c1c9","repo":"apache/cassandra","slug":"can-t-add-full-replicas-if-there-are-any-transient","errorCode":null,"errorMessage":"Can't add full replicas if there are any transient replicas. You must first remove all transient replicas, then change the # of full replicas, then add back the transient replicas","messagePattern":"Can't add full replicas if there are any transient replicas\\. You must first remove all transient replicas, then change the # of full replicas, then add back the transient replicas","errorType":"validation","errorClass":"ConfigurationException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java","lineNumber":217,"sourceCode":"        if (newTrans > 0)\n        {\n            if (DatabaseDescriptor.getNumTokens() > 1)\n                throw new ConfigurationException(String.format(\"Transient replication is not supported with vnodes yet\"));\n\n\n            if (!current.views.isEmpty())\n                throw new ConfigurationException(\"Cannot use transient replication on keyspaces using materialized views\");\n\n            for (TableMetadata table : current.tables)\n                if (!table.indexes.isEmpty())\n                    throw new ConfigurationException(\"Cannot use transient replication on keyspaces using secondary indexes\");\n        }\n\n        //This is true right now because the transition from transient -> full lacks the pending state\n        //necessary for correctness. What would happen if we allowed this is that we would attempt\n        //to read from a transient replica as if it were a full replica.\n        if (oldFull > newFull && oldTrans > 0)\n            throw new ConfigurationException(\"Can't add full replicas if there are any transient replicas. You must first remove all transient replicas, then change the # of full replicas, then add back the transient replicas\");\n\n        //Don't increase transient replication factor by more than one at a time if changing number of replicas\n        //Just like with changing full replicas it's not safe to do this as you could read from too many replicas\n        //that don't have the necessary data. W/O transient replication this alteration was allowed and it's not clear\n        //if it should be.\n        //This is structured so you can convert as many full replicas to transient replicas as you want.\n        boolean numReplicasChanged = oldTrans + oldFull != newTrans + newFull;\n        if (numReplicasChanged && (newTrans > oldTrans && newTrans != oldTrans + 1))\n            throw new ConfigurationException(\"Can only safely increase number of transients one at a time with incremental repair run in between each time\");\n    }\n\n    @Override\n    public AuditLogContext getAuditLogContext()\n    {\n        return new AuditLogContext(AuditLogEntryType.ALTER_KEYSPACE, keyspaceName);\n    }\n\n    public String toString()","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java#L199-L235","documentation":"Transient replication does not support a direct transition from more full replicas with existing transients to fewer full replicas: the transient->full transition lacks the pending state needed for correctness, so a read could hit a transient replica as if it were a full one. The change must be done stepwise.","triggerScenarios":"`ALTER KEYSPACE ks WITH replication = {'dc1':'2/1' -> '3/1'}` style change where oldFull > newFull and oldTrans > 0 — i.e. adding full replicas while transient replicas still exist.","commonSituations":"Operators trying to rebalance RF in one step on a transient-replication keyspace, e.g. increasing full RF from 2 to 3 while keeping 1 transient replica.","solutions":["First remove all transient replicas (e.g. '3/1' -> '3/0'), run incremental repair, then change full RF ('3/0' -> '2/0' or target), then re-add transients one at a time with repair between steps.","Follow the documented multi-step sequence; verify with an incremental repair after each step.","Avoid mixing full-RF changes and transient-RF changes in a single ALTER KEYSPACE."],"exampleFix":"// before (one step)\n\"ALTER KEYSPACE ks WITH replication = {'dc1':'3/1'}\"; // from '2/1'\n// after (staged)\n\"ALTER KEYSPACE ks WITH replication = {'dc1':'2/0'}\"; runIncrementalRepair();\n\"ALTER KEYSPACE ks WITH replication = {'dc1':'3/0'}\"; runIncrementalRepair();\n\"ALTER KEYSPACE ks WITH replication = {'dc1':'3/1'}\";","handlingStrategy":"validation","validationCode":"if (oldFull > newFull && oldTrans > 0) throw new IllegalArgumentException(\"stage the RF change: remove transients first, repair, then change full RF\");","typeGuard":null,"tryCatchPattern":"try { session.execute(alterKeyspace); } catch (ConfigurationException e) { if (e.getMessage().contains(\"Can't add full replicas if there are any transient replicas\")) applyStagedReplicationChange(current, target); else throw e; }","preventionTips":["Never combine full-RF and transient-RF changes in one ALTER KEYSPACE","Script the remove-transients -> repair -> change-full -> re-add-transients sequence","Run incremental repair between every stage"],"tags":["configuration","transient-replication","replication-factor","alter-keyspace"],"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"}