{"record":{"id":"080d887f4eb26526","repo":"apache/cassandra","slug":"multiple-strict-sources-found-for-s-sources-s","errorCode":null,"errorMessage":"Multiple strict sources found for %s, sources: %s","messagePattern":"Multiple strict sources found for (.+?), sources: (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/dht/RangeStreamer.java","lineNumber":562,"sourceCode":"                 sources = sources.size() > 0 ? sources.subList(0, 1) : sources;\n             }\n\n             // storing range and preferred endpoint set\n             rangesToFetchWithPreferredEndpoints.putAll(toFetch, sources, Conflict.NONE);\n             logger.debug(\"Endpoints to fetch for {} are {}\", toFetch, sources);\n\n             EndpointsForRange addressList = rangesToFetchWithPreferredEndpoints.getIfPresent(toFetch);\n             if (addressList == null)\n                 throw new IllegalStateException(\"Failed to find endpoints to fetch \" + toFetch);\n\n             /*\n              * When we move forwards (shrink our bucket) we are the one losing a range and no one else loses\n              * from that action (we also don't gain). When we move backwards there are two people losing a range. One is a full replica\n              * and the other is a transient replica. So we must need fetch from two places in that case for the full range we gain.\n              * For a transient range we only need to fetch from one.\n              */\n             if (useStrictConsistency && addressList.size() > 1 && (addressList.filter(Replica::isFull).size() > 1 || addressList.filter(Replica::isTransient).size() > 1))\n                 throw new IllegalStateException(String.format(\"Multiple strict sources found for %s, sources: %s\", toFetch, addressList));\n\n             //We must have enough stuff to fetch from\n             if (!any(addressList, isSufficient))\n             {\n                 if (strat.getReplicationFactor().allReplicas == 1)\n                 {\n                     if (useStrictConsistency)\n                     {\n                         logger.warn(\"A node required to move the data consistently is down\");\n                         throw new IllegalStateException(\"Unable to find sufficient sources for streaming range \" + toFetch + \" in keyspace \" + keyspace + \" with RF=1. \" +\n                                                         \"Ensure this keyspace contains replicas in the source datacenter.\");\n                     }\n                     else\n                         logger.warn(\"Unable to find sufficient sources for streaming range {} in keyspace {} with RF=1. \" +\n                                     \"Keyspace might be missing data.\", toFetch, keyspace);\n                 }\n                 else\n                 {","sourceCodeStart":544,"sourceCodeEnd":580,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/dht/RangeStreamer.java#L544-L580","documentation":"With strict consistency enabled, a range must be fetched from at most one full and one transient source to preserve data correctness guarantees during range movements. If the computed address list contains more than one full or more than one transient strict source, RangeStreamer throws IllegalStateException because it cannot choose safely among multiple strict sources.","triggerScenarios":"calculateRangesToFetchWithPreferredEndpoints() with useStrictConsistency=true and an addressList where filter(Replica::isFull).size() > 1 or filter(Replica::isTransient).size() > 1 for a single range.","commonSituations":"Complex range movement scenarios (move/decommission in flight) producing overlapping ownership; token metadata inconsistencies after failed operations; incorrect strict-consistency setting combined with unusual topology changes.","solutions":["Verify token metadata consistency with `nodetool describering` / `nodetool status`; resolve overlapping ranges (usually via repair + restart)","Allow any in-flight move/decommission/bootstrap operations to complete before streaming, then retry","If caused by a bug in range-movement bookkeeping, upgrade to a version with fixes to RangeStreamer strict handling","Avoid mixing move and bootstrap operations concurrently on overlapping ranges"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// check for overlapping ownership before streaming\nif (tokenMetadata.getPendingRanges(keyspace).size() > 0)\n    throw new IllegalStateException(\"Pending ranges exist; complete in-flight operations before strict streaming\");","typeGuard":null,"tryCatchPattern":"try { streamer.fetch(); } catch (IllegalStateException e) { if (e.getMessage().startsWith(\"Multiple strict sources found\")) { waitForOperationsToSettle(); retry(); } else throw e; }","preventionTips":["Serialize topology operations (no concurrent move+bootstrap)","Validate ring consistency with describering before strict streaming","Upgrade if reproducible — this signals bookkeeping anomalies"],"tags":["streaming","strict-consistency","range-movement"],"backgroundTag":"internal-invariant-violation","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"}