mansikhara commented on code in PR #257:
URL: 
https://github.com/apache/cassandra-analytics/pull/257#discussion_r4380893667


##########
cassandra-analytics-core/src/main/java/org/apache/cassandra/spark/data/CassandraDataLayer.java:
##########
@@ -782,6 +804,204 @@ public CassandraRing 
createCassandraRingFromRing(Partitioner partitioner,
         return new CassandraRing(partitioner, keyspace, replicationFactor, 
instances);
     }
 
+    /**
+     * Builds the ring using the token ranges Cassandra reports, rather than 
deriving them from tokens and the
+     * replication factor. The local derivation assumes racks are not in use, 
whereas Cassandra's replica assignment
+     * is rack aware, so for keyspaces with transient replicas - where a 
replica may be a witness holding no data -
+     * the derived ranges cannot be relied on to identify which instance 
replicates which range.
+     * <p>
+     * Node discovery still comes from the ring response, so snapshot 
creation, sizing and the Sidecar client pool
+     * are unaffected. Only the range to replica mapping is taken from 
Cassandra.
+     *
+     * @param partitioner       the partitioner
+     * @param replicationFactor the keyspace replication factor
+     * @param ring              the ring response, used for node discovery
+     * @param topology          the token range replicas response, used for 
the range to replica mapping
+     * @return a ring whose token ranges were reported by Cassandra
+     */
+    public CassandraRing createCassandraRingFromTokenRangeReplicas(Partitioner 
partitioner,
+                                                                   
ReplicationFactor replicationFactor,
+                                                                   
RingResponse ring,
+                                                                   
TokenRangeReplicasResponse topology)
+    {
+        List<CassandraInstance> instances = ring
+                                            .stream()
+                                            .filter(status -> datacenter == 
null || datacenter.equalsIgnoreCase(status.datacenter()))
+                                            .map(status -> new 
CassandraInstance(status.token(), status.fqdn(), status.datacenter()))
+                                            .collect(Collectors.toList());
+
+        // Token range replicas identify replicas by "address:port", while 
instances built from the ring are keyed
+        // by fqdn, so the replica metadata is used to translate between them
+        Map<String, CassandraInstance> instanceByNodeName = new 
HashMap<>(instances.size());
+        for (CassandraInstance instance : instances)
+        {
+            CassandraInstance previous = 
instanceByNodeName.put(instance.nodeName(), instance);
+            if (previous != null && !previous.equals(instance))
+            {
+                // A node owning several tokens cannot be represented as a 
single CassandraInstance. Cassandra
+                // rejects transient replicas when num_tokens > 1, so this is 
only reachable when the path is forced
+                throw new IllegalStateException(String.format(
+                "Node %s owns multiple tokens (%s and %s). Sourcing token 
ranges from Cassandra requires "
+                + "single-token nodes.", instance.nodeName(), 
previous.token(), instance.token()));
+            }
+        }
+
+        Map<Range<BigInteger>, List<CassandraInstance>> rangeReplicas = new 
HashMap<>();
+        Map<Range<BigInteger>, List<CassandraInstance>> rangeWitnesses = new 
HashMap<>();
+        for (TokenRangeReplicasResponse.ReplicaInfo replicaInfo : 
topology.readReplicas())
+        {
+            Range<BigInteger> range = Range.openClosed(new 
BigInteger(replicaInfo.start()),
+                                                       new 
BigInteger(replicaInfo.end()));
+            Map<String, List<String>> witnessesByDatacenter = 
witnessesByDatacenter(replicationFactor, replicaInfo);
+            List<CassandraInstance> replicas = new ArrayList<>();
+            List<CassandraInstance> witnesses = new ArrayList<>();
+            for (Map.Entry<String, List<String>> byDatacenter : 
replicaInfo.replicasByDatacenter().entrySet())
+            {
+                if (datacenter != null && 
!datacenter.equalsIgnoreCase(byDatacenter.getKey()))
+                {
+                    continue;
+                }
+                List<String> datacenterWitnesses = 
witnessesByDatacenter.getOrDefault(byDatacenter.getKey(),
+                                                                               
       Collections.emptyList());
+                for (String replicaKey : byDatacenter.getValue())
+                {
+                    TokenRangeReplicasResponse.ReplicaMetadata metadata = 
topology.replicaMetadata().get(replicaKey);
+                    String nodeName = metadata != null ? metadata.fqdn() : 
replicaKey;
+                    CassandraInstance instance = 
instanceByNodeName.get(nodeName);
+                    if (instance == null)
+                    {
+                        // The ring response is the source of truth for which 
nodes the reader can talk to, so a
+                        // replica absent from it is skipped rather than 
failing the job
+                        LOGGER.warn("Skipping replica reported by Cassandra 
that is absent from the ring "
+                                    + "replica={} fqdn={} range=({}, {}]",
+                                    replicaKey, nodeName, replicaInfo.start(), 
replicaInfo.end());
+                        continue;
+                    }
+                    replicas.add(instance);
+                    if (datacenterWitnesses.contains(replicaKey))
+                    {
+                        witnesses.add(instance);
+                    }
+                }
+            }
+            if (!replicas.isEmpty())
+            {
+                // Distinct, because a duplicated replica would inflate the 
per-range replica count that
+                // PartitionedDataLayer uses to decide whether the consistency 
level is satisfied
+                rangeReplicas.put(range, 
replicas.stream().distinct().collect(Collectors.toList()));
+                if (!witnesses.isEmpty())
+                {
+                    rangeWitnesses.put(range, 
witnesses.stream().distinct().collect(Collectors.toList()));
+                }
+            }
+        }
+
+        if (rangeReplicas.isEmpty())
+        {
+            throw new IllegalStateException(
+            "Cassandra reported no read replicas for keyspace " + keyspace + " 
in datacenter " + datacenter);
+        }
+
+        validateCompleteRingCoverage(partitioner, rangeReplicas.keySet());
+
+        LOGGER.info("Using token ranges reported by Cassandra keyspace={} 
numRanges={} numRangesWithWitnesses={} "
+                    + "numInstances={}", keyspace, rangeReplicas.size(), 
rangeWitnesses.size(), instances.size());
+        return new CassandraRing(partitioner, keyspace, replicationFactor, 
instances, rangeReplicas, rangeWitnesses);
+    }
+
+    /**
+     * Returns the witness replicas the Sidecar reported for a range, after 
checking them against the replication
+     * factor.
+     * <p>
+     * A keyspace with transient replicas must have its witnesses reported: a 
witness does not hold complete data, so
+     * without knowing which replica is the witness the reader could select it 
and silently return incomplete
+     * results. Failing is the only safe option.
+     *
+     * @param replicationFactor the keyspace replication factor
+     * @param replicaInfo       the replicas of one token range
+     * @return datacenter to witness replicas, empty when the range has none
+     * @throws IllegalStateException when witnesses are expected but not 
reported, or do not match the replication
+     *                               factor
+     */
+    @VisibleForTesting
+    static Map<String, List<String>> witnessesByDatacenter(ReplicationFactor 
replicationFactor,
+                                                           
TokenRangeReplicasResponse.ReplicaInfo replicaInfo)
+    {
+        Map<String, List<String>> witnessesByDatacenter = 
replicaInfo.witnessesByDatacenter();
+        if (witnessesByDatacenter == null)
+        {
+            if (replicationFactor.hasTransientReplicas())

Review Comment:
   Thanks Arjun! Added the test `WitnessReplicaBulkReaderTest` and fixed the 
above case.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to