CEP-58: Introduce ResponseTracker and CoordinationPlan

Extracts coordination completion logic out of the read/write response
handlers into a ResponseTracker abstraction (simple, write, per-DC), and
pairs it with the ReplicaPlan in a CoordinationPlan so replica selection
and quorum requirements are always produced together by the replication
strategy from a single view of state.

Replication strategies now own construction of both halves. Write/read
paths, paxos, batchlog, read repair, and range reads are converted to
consume CoordinationPlan; ReplicaPlanIterator/Merger are renamed
accordingly. Tests included for each tracker implementation.

Patch by Blake Eggleston; reviewed by Francisco Guerrero and Ariel Weisberg for CASSANDRA-21106
This commit is contained in:
Blake Eggleston 2026-02-06 11:16:28 -08:00
parent 550dd312fb
commit ab8fb8d9c2
54 changed files with 3191 additions and 603 deletions

View File

@ -48,7 +48,6 @@ import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.cql3.UntypedResultSet.Row;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.Mutation;
import org.apache.cassandra.db.SystemKeyspace;
@ -60,20 +59,17 @@ import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.RetryOnDifferentSystemException;
import org.apache.cassandra.exceptions.WriteFailureException;
import org.apache.cassandra.exceptions.WriteTimeoutException;
import org.apache.cassandra.gms.FailureDetector;
import org.apache.cassandra.hints.Hint;
import org.apache.cassandra.hints.HintsService;
import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaLayout;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.locator.Replicas;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessageFlag;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.PreserveTimestamp;
@ -609,22 +605,21 @@ public class BatchlogManager implements BatchlogManagerMBean
String ks = mutation.getKeyspaceName();
Token tk = mutation.key().getToken();
ClusterMetadata metadata = ClusterMetadata.current();
KeyspaceMetadata keyspaceMetadata = metadata.schema.getKeyspaceMetadata(ks);
Keyspace keyspace = Keyspace.open(ks);
// TODO: this logic could do with revisiting at some point, as it is unclear what its rationale is
// we perform a local write, ignoring errors and inline in this thread (potentially slowing replay down)
// effectively bumping CL for locally owned writes and also potentially stalling log replay if an error occurs
// once we decide how it should work, it can also probably be simplified, and avoid constructing a ReplicaPlan directly
ReplicaLayout.ForTokenWrite allReplias = ReplicaLayout.forTokenWriteLiveAndDown(metadata, keyspaceMetadata, tk);
ReplicaPlan.ForWrite replicaPlan = forReplayMutation(metadata, Keyspace.open(ks), tk);
ReplicaLayout.ForTokenWrite allReplicas = ReplicaLayout.forTokenWriteLiveAndDown(metadata, keyspace.getMetadata(), tk);
CoordinationPlan.ForWrite replayPlan = CoordinationPlan.forReplayMutation(metadata, keyspace, tk);
Replica selfReplica = allReplias.all().selfIfPresent();
Replica selfReplica = allReplicas.all().selfIfPresent();
if (selfReplica != null)
mutation.apply();
for (Replica replica : allReplias.all())
for (Replica replica : allReplicas.all())
{
if (replica == selfReplica || replicaPlan.liveAndDown().contains(replica))
if (replica == selfReplica || replayPlan.replicas().liveAndDown().contains(replica))
continue;
UUID hostId = metadata.directory.peerId(replica.endpoint()).toUUID();
@ -635,26 +630,13 @@ public class BatchlogManager implements BatchlogManagerMBean
}
}
ReplayWriteResponseHandler<Mutation> handler = new ReplayWriteResponseHandler<>(replicaPlan, mutation, Dispatcher.RequestTime.forImmediateExecution());
ReplayWriteResponseHandler<Mutation> handler = new ReplayWriteResponseHandler<>(replayPlan, mutation, Dispatcher.RequestTime.forImmediateExecution());
Message<Mutation> message = Message.outWithFlag(MUTATION_REQ, mutation, MessageFlag.CALL_BACK_ON_FAILURE);
for (Replica replica : replicaPlan.liveAndDown())
for (Replica replica : replayPlan.replicas().liveAndDown())
MessagingService.instance().sendWriteWithCallback(message, replica, handler);
return handler;
}
public static ReplicaPlan.ForWrite forReplayMutation(ClusterMetadata metadata, Keyspace keyspace, Token token)
{
ReplicaLayout.ForTokenWrite liveAndDown = ReplicaLayout.forTokenWriteLiveAndDown(metadata, keyspace.getMetadata(), token);
Replicas.temporaryAssertFull(liveAndDown.all()); // TODO in CASSANDRA-14549
Replica selfReplica = liveAndDown.all().selfIfPresent();
ReplicaLayout.ForTokenWrite liveRemoteOnly = liveAndDown.filter(r -> FailureDetector.isReplicaAlive.test(r) && r != selfReplica);
return new ReplicaPlan.ForWrite(keyspace, liveAndDown.replicationStrategy(),
ConsistencyLevel.ONE, liveRemoteOnly.pending(), liveRemoteOnly.all(), liveRemoteOnly.all(), liveRemoteOnly.all(),
(cm) -> forReplayMutation(cm, keyspace, token),
metadata.epoch);
}
private static int gcgs(Collection<Mutation> mutations)
{
int gcgs = Integer.MAX_VALUE;
@ -672,16 +654,10 @@ public class BatchlogManager implements BatchlogManagerMBean
private final Set<InetAddressAndPort> undelivered = Collections.newSetFromMap(new ConcurrentHashMap<>());
// TODO: should we be hinting here, since presumably batch log will retry? Maintaining historical behaviour for the moment.
ReplayWriteResponseHandler(ReplicaPlan.ForWrite replicaPlan, Supplier<Mutation> hintOnFailure, Dispatcher.RequestTime requestTime)
ReplayWriteResponseHandler(CoordinationPlan.ForWrite coordinationPlan, Supplier<Mutation> hintOnFailure, Dispatcher.RequestTime requestTime)
{
super(replicaPlan, null, WriteType.UNLOGGED_BATCH, hintOnFailure, requestTime);
Iterables.addAll(undelivered, replicaPlan.contacts().endpoints());
}
@Override
protected int blockFor()
{
return this.replicaPlan.contacts().size();
super(coordinationPlan, null, WriteType.UNLOGGED_BATCH, hintOnFailure, requestTime);
Iterables.addAll(undelivered, replicaPlan().contacts().endpoints());
}
@Override

View File

@ -20,30 +20,48 @@ package org.apache.cassandra.locator;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.PartitionPosition;
import org.apache.cassandra.db.Mutation;
import org.apache.cassandra.db.WriteType;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.locator.ReplicaCollection.Builder.Conflict;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.ReplicationParams;
import org.apache.cassandra.schema.ReplicationType;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.AbstractWriteResponseHandler;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.DatacenterSyncWriteResponseHandler;
import org.apache.cassandra.service.DatacenterWriteResponseHandler;
import org.apache.cassandra.service.WriteResponseHandler;
import org.apache.cassandra.service.paxos.Paxos;
import org.apache.cassandra.service.reads.ReadCoordinator;
import org.apache.cassandra.service.reads.SpeculativeRetryPolicy;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.compatibility.TokenRingUtils;
@ -52,6 +70,8 @@ import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import static org.apache.cassandra.locator.ReplicaLayout.forTokenWriteLiveAndDown;
/**
* A abstract parent for all replication strategies.
*/
@ -92,57 +112,49 @@ public abstract class AbstractReplicationStrategy
public abstract DataPlacement calculateDataPlacement(Epoch epoch, List<Range<Token>> ranges, ClusterMetadata metadata);
public <T> AbstractWriteResponseHandler<T> getWriteResponseHandler(ReplicaPlan.ForWrite replicaPlan,
public <T> AbstractWriteResponseHandler<T> getWriteResponseHandler(CoordinationPlan.ForWrite coordinationPlan,
CoordinationPlan.ForWrite idealPlan,
Runnable callback,
WriteType writeType,
Supplier<Mutation> hintOnFailure,
Dispatcher.RequestTime requestTime)
{
return getWriteResponseHandler(replicaPlan, callback, writeType, hintOnFailure,
requestTime, DatabaseDescriptor.getIdealConsistencyLevel());
}
public <T> AbstractWriteResponseHandler<T> getWriteResponseHandler(ReplicaPlan.ForWrite replicaPlan,
Runnable callback,
WriteType writeType,
Supplier<Mutation> hintOnFailure,
Dispatcher.RequestTime requestTime,
ConsistencyLevel idealConsistencyLevel)
{
AbstractWriteResponseHandler<T> resultResponseHandler;
if (replicaPlan.consistencyLevel().isDatacenterLocal())
if (coordinationPlan.consistencyLevel().isDatacenterLocal())
{
// block for in this context will be localnodes block.
resultResponseHandler = new DatacenterWriteResponseHandler<T>(replicaPlan, callback, writeType, hintOnFailure, requestTime);
resultResponseHandler = new DatacenterWriteResponseHandler<T>(coordinationPlan, callback, writeType, hintOnFailure, requestTime);
}
else if (replicaPlan.consistencyLevel() == ConsistencyLevel.EACH_QUORUM && (this instanceof NetworkTopologyStrategy))
else if (coordinationPlan.consistencyLevel() == ConsistencyLevel.EACH_QUORUM && (this instanceof NetworkTopologyStrategy))
{
resultResponseHandler = new DatacenterSyncWriteResponseHandler<T>(replicaPlan, callback, writeType, hintOnFailure, requestTime);
resultResponseHandler = new DatacenterSyncWriteResponseHandler<T>(coordinationPlan, callback, writeType, hintOnFailure, requestTime);
}
else
{
resultResponseHandler = new WriteResponseHandler<T>(replicaPlan, callback, writeType, hintOnFailure, requestTime);
resultResponseHandler = new WriteResponseHandler<T>(coordinationPlan, callback, writeType, hintOnFailure, requestTime);
}
//Check if tracking the ideal consistency level is configured
if (idealConsistencyLevel != null)
if (idealPlan != null)
{
//If ideal and requested are the same just use this handler to track the ideal consistency level
//This is also used so that the ideal consistency level handler when constructed knows it is the ideal
//one for tracking purposes
if (idealConsistencyLevel == replicaPlan.consistencyLevel())
if (coordinationPlan.consistencyLevel() == idealPlan.consistencyLevel())
{
resultResponseHandler.setIdealCLResponseHandler(resultResponseHandler);
}
else
{
//Construct a delegate response handler to use to track the ideal consistency level
AbstractWriteResponseHandler<T> idealHandler = getWriteResponseHandler(replicaPlan.withConsistencyLevel(idealConsistencyLevel),
// Construct a delegate response handler to track the ideal consistency level.
// We pass idealPlan twice so that the recursive call sees coordinationPlan == idealPlan,
// causing the ideal handler to set itself as its own idealCLDelegate. This is required
// for the idealCLWriteLatency metric to be recorded (only fires when idealCLDelegate == this).
AbstractWriteResponseHandler<T> idealHandler = getWriteResponseHandler(idealPlan, idealPlan,
callback,
writeType,
hintOnFailure,
requestTime,
idealConsistencyLevel);
requestTime);
resultResponseHandler.setIdealCLResponseHandler(idealHandler);
}
}
@ -150,6 +162,16 @@ public abstract class AbstractReplicationStrategy
return resultResponseHandler;
}
public <T> AbstractWriteResponseHandler<T> getWriteResponseHandler(CoordinationPlan.ForWriteWithIdeal forWritePlan,
Runnable callback,
WriteType writeType,
Supplier<Mutation> hintOnFailure,
Dispatcher.RequestTime requestTime)
{
return getWriteResponseHandler(forWritePlan, forWritePlan.ideal, callback, writeType, hintOnFailure, requestTime);
}
/**
* calculate the RF based on strategy_options. When overwriting, ensure that this get()
* is FAST, as this is called often.
@ -437,4 +459,354 @@ public abstract class AbstractReplicationStrategy
return newRanges;
}
}
protected CoordinationPlan.ForWrite planForWriteInternal(ClusterMetadata metadata,
Keyspace keyspace,
ConsistencyLevel consistencyLevel,
Function<ClusterMetadata, ReplicaLayout.ForTokenWrite> liveAndDown,
ReplicaPlans.Selector selector)
{
ReplicaPlan.ForWrite plan = ReplicaPlans.forWrite(metadata, keyspace, consistencyLevel, liveAndDown, selector);
ResponseTracker tracker = createTrackerForWrite(consistencyLevel, plan, plan.pending, metadata);
return new CoordinationPlan.ForWrite(plan, tracker);
}
protected ReplicaPlan.ForWrite createReplicaPlanForWrite(ClusterMetadata metadata,
Keyspace keyspace,
ConsistencyLevel consistencyLevel,
Function<ClusterMetadata, ReplicaLayout.ForTokenWrite> liveAndDown,
ReplicaPlans.Selector selector)
{
return ReplicaPlans.forWrite(metadata, keyspace, consistencyLevel, liveAndDown, selector);
}
public CoordinationPlan.ForWriteWithIdeal planForWrite(ClusterMetadata metadata,
Keyspace keyspace,
ConsistencyLevel consistencyLevel,
Function<ClusterMetadata, ReplicaLayout.ForTokenWrite> liveAndDown,
ReplicaPlans.Selector selector)
{
ReplicaPlan.ForWrite plan = createReplicaPlanForWrite(metadata, keyspace, consistencyLevel, liveAndDown, selector);
ResponseTracker tracker = createTrackerForWrite(consistencyLevel, plan, plan.pending, metadata);
CoordinationPlan.ForWrite ideal = null;
ConsistencyLevel idealCL = DatabaseDescriptor.getIdealConsistencyLevel();
if (idealCL != null)
{
if (idealCL == consistencyLevel)
{
ideal = new CoordinationPlan.ForWrite(plan, tracker);
}
else
{
ideal = new CoordinationPlan.ForWrite(createReplicaPlanForWrite(metadata, keyspace, idealCL, liveAndDown, selector),
createTrackerForWrite(idealCL, plan, plan.pending, metadata));
}
}
return new CoordinationPlan.ForWriteWithIdeal(plan, tracker, ideal);
}
public CoordinationPlan.ForWriteWithIdeal planForWrite(ClusterMetadata metadata,
Keyspace keyspace,
ConsistencyLevel consistencyLevel,
Token token,
ReplicaPlans.Selector selector)
{
return planForWrite(metadata, keyspace, consistencyLevel,
(newClusterMetadata) -> ReplicaLayout.forTokenWriteLiveAndDown(newClusterMetadata, keyspace, token), selector);
}
/**
* Create coordination plan for forwarding a counter write to the leader replica.
*
* In cases where the original coordinator is not a replica of the counter key, the counter
* mutation is forwarded to a leader replica that will coordinate the actual counter update.
*/
public CoordinationPlan.ForWrite planForForwardingCounterWrite(ClusterMetadata metadata,
Keyspace keyspace,
Token token,
Function<ClusterMetadata, Replica> replicaSupplier)
{
ReplicaPlan.ForWrite plan = ReplicaPlans.forSingleReplicaWrite(metadata, keyspace, token, replicaSupplier);
ResponseTracker tracker = createTrackerForWrite(plan.consistencyLevel(), plan, plan.pending, metadata);
return new CoordinationPlan.ForWriteWithIdeal(plan, tracker, null);
}
/**
* Create coordination plan for replaying a mutation from the batchlog.
*
* When recovering failed batches, mutations are replayed to remote replicas only
* (local replica is handled separately). This method creates a replica plan
* targeting live remote replicas with CL.ONE, and a response tracker that waits on
* all contacts
*/
public CoordinationPlan.ForWriteWithIdeal planForReplayMutation(ClusterMetadata metadata,
Keyspace keyspace,
Token token)
{
Preconditions.checkState(!replicationType.isTracked(), "Batch replay not supported with tracked keyspaces");
ReplicaPlan.ForWrite plan = ReplicaPlans.forReplayMutation(metadata, keyspace, token);
// wait until all contacts respond
int blockFor = plan.contacts().size();
ResponseTracker tracker = new SimpleResponseTracker(blockFor, blockFor);
return new CoordinationPlan.ForWriteWithIdeal(plan, tracker, null);
}
/**
* Create coordination plan for a single-partition token read.
*/
public CoordinationPlan.ForTokenRead planForTokenRead(ClusterMetadata metadata,
Keyspace keyspace,
TableId tableId,
Token token,
@Nullable Index.QueryPlan indexQueryPlan,
ConsistencyLevel consistencyLevel,
SpeculativeRetryPolicy retry,
ReadCoordinator coordinator)
{
ReplicaPlan.ForTokenRead plan = ReplicaPlans.forRead(metadata, keyspace, tableId, token, indexQueryPlan, consistencyLevel, retry, coordinator);
ReplicaPlan.SharedForTokenRead shared = ReplicaPlan.shared(plan);
ResponseTracker tracker = createTrackerForRead(plan);
return new CoordinationPlan.ForTokenRead(shared, tracker);
}
/**
* Create coordination plan for a range read.
*/
public CoordinationPlan.ForRangeRead planForRangeRead(ClusterMetadata metadata,
Keyspace keyspace,
TableId tableId,
@Nullable Index.QueryPlan indexQueryPlan,
ConsistencyLevel consistencyLevel,
AbstractBounds<PartitionPosition> range,
int vnodeCount)
{
ReplicaPlan.ForRangeRead plan = ReplicaPlans.forRangeRead(metadata, keyspace, tableId, indexQueryPlan, consistencyLevel, range, vnodeCount, true);
ReplicaPlan.SharedForRangeRead shared = ReplicaPlan.shared(plan);
ResponseTracker tracker = createTrackerForRead(plan);
return new CoordinationPlan.ForRangeRead(shared, tracker);
}
/**
* Attempt to merge two adjacent range read coordination plans into one.
*
* If the two plans share enough live endpoints to satisfy the consistency level
* and the merge is worthwhile returns a merged plan otherwise returns null.
*/
public CoordinationPlan.ForRangeRead maybeMergeRangeReads(ClusterMetadata metadata,
Keyspace keyspace,
TableId tableId,
ConsistencyLevel consistencyLevel,
ReplicaPlan.ForRangeRead left,
ReplicaPlan.ForRangeRead right)
{
ReplicaPlan.ForRangeRead merged = ReplicaPlans.maybeMerge(metadata, keyspace, tableId, consistencyLevel, left, right);
if (merged == null)
return null;
ReplicaPlan.SharedForRangeRead shared = ReplicaPlan.shared(merged);
ResponseTracker tracker = createTrackerForRead(merged);
return new CoordinationPlan.ForRangeRead(shared, tracker);
}
/**
* Create coordination plan for a full range read
*/
public CoordinationPlan.ForRangeRead planForFullRangeRead(Keyspace keyspace,
ConsistencyLevel consistencyLevel,
AbstractBounds<PartitionPosition> range,
Set<InetAddressAndPort> endpointsToContact,
int vnodeCount)
{
ReplicaPlan.ForRangeRead plan = ReplicaPlans.forFullRangeRead(keyspace, consistencyLevel, range, endpointsToContact, vnodeCount);
ReplicaPlan.SharedForRangeRead shared = ReplicaPlan.shared(plan);
ResponseTracker tracker = createTrackerForRead(plan);
return new CoordinationPlan.ForRangeRead(shared, tracker);
}
/**
* Create coordination plan for a single-replica token read.
*/
public CoordinationPlan.ForTokenRead planForSingleReplicaTokenRead(Keyspace keyspace, Token token, Replica replica)
{
ReplicaPlan.ForTokenRead plan = ReplicaPlans.forSingleReplicaRead(keyspace, token, replica);
ReplicaPlan.SharedForTokenRead shared = ReplicaPlan.shared(plan);
ResponseTracker tracker = createTrackerForRead(plan);
return new CoordinationPlan.ForTokenRead(shared, tracker);
}
/**
* Create coordination plan for a single-replica range read.
*
* Used by short read protection to fetch additional partitions from a
* specific replica. blockFor=1, totalReplicas=1.
*/
public CoordinationPlan.ForRangeRead planForSingleReplicaRangeRead(Keyspace keyspace,
AbstractBounds<PartitionPosition> range,
Replica replica,
int vnodeCount)
{
ReplicaPlan.ForRangeRead plan = ReplicaPlans.forSingleReplicaRead(keyspace, range, replica, vnodeCount);
ReplicaPlan.SharedForRangeRead shared = ReplicaPlan.shared(plan);
ResponseTracker tracker = createTrackerForRead(plan);
return new CoordinationPlan.ForRangeRead(shared, tracker);
}
/**
* Create ResponseTracker for read operation.
*/
private <E extends Endpoints<E>, P extends ReplicaPlan.ForRead<E, P>> ResponseTracker createTrackerForRead(P plan)
{
int blockFor = plan.readQuorum();
// Use candidates.size() for totalReplicas to allow for speculation
// (speculation can contact additional candidates beyond initial contacts)
int totalReplicas = plan.readCandidates().size();
return new SimpleResponseTracker(blockFor, totalReplicas);
}
public Paxos.Participants paxosParticipants(ClusterMetadata metadata,
TableMetadata table,
Token token,
ConsistencyLevel consistencyForConsensus,
Predicate<Replica> isReplicaAlive)
{
KeyspaceMetadata keyspaceMetadata = metadata.schema.getKeyspaceMetadata(table.keyspace);
// MetaStrategy distributes the entire keyspace to all replicas. In addition, its tables (currently only
// the dist log table) don't use the globally configured partitioner. For these reasons we don't lookup the
// replicas using the supplied token as this can actually be of the incorrect type (for example when
// performing Paxos repair).
final Token actualToken = table.partitioner == MetaStrategy.partitioner ? MetaStrategy.entireRange.right : token;
ReplicaLayout.ForTokenWrite all = forTokenWriteLiveAndDown(metadata, keyspaceMetadata, actualToken);
ReplicaLayout.ForTokenWrite electorate = consistencyForConsensus.isDatacenterLocal()
? all.filter(InOurDc.replicas()) : all;
EndpointsForToken live = all.all().filter(isReplicaAlive);
return new Paxos.Participants(metadata.epoch, Keyspace.open(table.keyspace), consistencyForConsensus, all, electorate, live,
(cm) -> Paxos.Participants.get(cm, table, actualToken, consistencyForConsensus));
}
/**
* Create ResponseTracker for write operation based on consistency level.
*/
@VisibleForTesting
public ResponseTracker createTrackerForWrite(ConsistencyLevel cl, ReplicaPlan.ForWrite plan, Endpoints<?> pending, ClusterMetadata metadata)
{
switch (cl)
{
case ANY:
case ONE:
case TWO:
case THREE:
case QUORUM:
case ALL:
{
int totalContacts = plan.contacts().size();
int baseBlockFor = cl.blockFor(this);
int totalBlockFor = cl.blockForWrite(this, pending);
// Check if double count model applies (some CLs like ANY don't add pending)
// If totalBlockFor == baseBlockFor, no double-count needed (e.g., ANY)
if (totalBlockFor == baseBlockFor)
return new SimpleResponseTracker(baseBlockFor, totalContacts);
// Double count model: natural must satisfy base CL, total must include pending
int pendingReplicas = pending.size();
// contacts() includes both natural and pending replicas
int naturalReplicas = totalContacts - pendingReplicas;
return new WriteResponseTracker(baseBlockFor, totalBlockFor,
naturalReplicas, pendingReplicas,
endpoint -> pending.endpoints().contains(endpoint));
}
case LOCAL_ONE:
case LOCAL_QUORUM:
{
int localContacts = plan.contacts().filter(InOurDc.replicas()).size();
// Check if double count model applies (depends on local pending)
int baseBlockFor = cl.blockFor(this);
int totalBlockFor = cl.blockForWrite(this, pending);
// If totalBlockFor == baseBlockFor, no local pending so no double-count needed
if (totalBlockFor == baseBlockFor)
return new SimpleResponseTracker(baseBlockFor, localContacts, InOurDc.endpoints());
// Double count model for local DC
int localPending = pending.count(InOurDc.replicas());
// localContacts includes both natural and pending in local DC
int localNatural = localContacts - localPending;
return new WriteResponseTracker(baseBlockFor, totalBlockFor,
localNatural, localPending,
endpoint -> pending.endpoints().contains(endpoint),
InOurDc.endpoints());
}
case EACH_QUORUM:
return createPerDcTracker(plan, pending, metadata);
default:
throw new UnsupportedOperationException("Unsupported consistency level for writes: " + cl);
}
}
/**
* Create per-datacenter tracker for EACH_QUORUM.
*/
private ResponseTracker createPerDcTracker(ReplicaPlan.ForWrite plan, Endpoints<?> pending, ClusterMetadata metadata)
{
Map<String, ResponseTracker> trackerPerDc = new HashMap<>();
Locator locator = metadata.locator;
// Group replicas by datacenter
Map<String, List<Replica>> replicasByDc = new HashMap<>();
for (Replica replica : plan.contacts())
{
String dc = locator.location(replica.endpoint()).datacenter;
replicasByDc.computeIfAbsent(dc, k -> new ArrayList<>()).add(replica);
}
// Group pending replicas by datacenter
Map<String, List<Replica>> pendingByDc = new HashMap<>();
for (Replica replica : pending)
{
String dc = locator.location(replica.endpoint()).datacenter;
pendingByDc.computeIfAbsent(dc, k -> new ArrayList<>()).add(replica);
}
// Create tracker for each DC
for (Map.Entry<String, List<Replica>> entry : replicasByDc.entrySet())
{
String dc = entry.getKey();
int dcContacts = entry.getValue().size();
List<Replica> dcPending = pendingByDc.getOrDefault(dc, Collections.emptyList());
int dcPendingCount = dcPending.size();
int dcNatural = dcContacts - dcPendingCount;
int dcBlockFor = dcNatural / 2 + 1;
// Each sub-tracker must filter by DC since CompositeTracker broadcasts to all children
Predicate<InetAddressAndPort> dcFilter = endpoint -> dc.equals(locator.location(endpoint).datacenter);
if (dcPending.isEmpty())
{
trackerPerDc.put(dc, new SimpleResponseTracker(dcBlockFor, dcContacts, dcFilter));
}
else
{
int totalBlockFor = dcBlockFor + dcPendingCount;
trackerPerDc.put(dc, new WriteResponseTracker(dcBlockFor, totalBlockFor,
dcNatural, dcPendingCount,
endpoint -> pending.endpoints().contains(endpoint),
dcFilter));
}
}
return new PerDcResponseTracker(trackerPerDc, locator);
}
}

View File

@ -0,0 +1,321 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.locator;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.PartitionPosition;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.UnavailableException;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.reads.ReadCoordinator;
import org.apache.cassandra.service.reads.SpeculativeRetryPolicy;
import org.apache.cassandra.tcm.ClusterMetadata;
/**
* Ties together replica selection and response tracking for a single operation.
*
* This immutable container ensures that the replica plan (who to contact) and
* the response tracker (how to determine success) are created atomically by the
* replication strategy, consulting the same state. This is particularly important
* for strategies that have per-range state (e.g., failover state in SRS) where
* the replica selection and quorum requirements must be consistent.
*
* The separation between ReplicaPlan and ResponseTracker allows:
* <ul>
* <li>ReplicaPlan to focus on replica topology and selection</li>
* <li>ResponseTracker to encapsulate completion logic</li>
* <li>Replication strategies to customize both consistently</li>
* </ul>
*
* No polymorphism is needed at this level - the variation is captured in the
* ResponseTracker implementations. This is just a typed tuple ensuring the
* two pieces travel together.
*
* @param <P> the type of ReplicaPlan (ForRead, ForWrite, ForPaxosWrite, etc.)
*/
public abstract class CoordinationPlan<E extends Endpoints<E>, P extends ReplicaPlan<E, P>>
{
// TODO (now): consolidate callback Condition instances into this. Replace all condition await calls with calls to this.
private final ResponseTracker responses;
/**
* Create a coordination plan.
*
* @param responses the response tracker for determining completion/success
*/
public CoordinationPlan(ResponseTracker responses)
{
Preconditions.checkNotNull(responses);
this.responses = responses;
}
public ConsistencyLevel consistencyLevel()
{
return replicas().consistencyLevel();
}
public AbstractReplicationStrategy replicationStrategy()
{
return replicas().replicationStrategy();
}
public abstract P replicas();
/**
* The response tracker for determining completion/success.
*
* The tracker encapsulates the logic for:
* - Recording responses and failures
* - Determining when the operation is complete
* - Checking if the operation succeeded
*
* @return the response tracker
*/
public ResponseTracker responses()
{
return responses;
}
@Override
public String toString()
{
return String.format("CoordinationPlan[replicaPlan=%s, tracker=%s]", replicas(), responses.getClass().getSimpleName());
}
/**
* Extended plan including source cluster metadata and ideal coordination plan.
*/
public static class ForWrite extends CoordinationPlan<EndpointsForToken, ReplicaPlan.ForWrite>
{
private final ReplicaPlan.ForWrite replicas;
public ForWrite(ReplicaPlan.ForWrite replicas, ResponseTracker responses)
{
super(responses);
this.replicas = replicas;
}
@Override
public ReplicaPlan.ForWrite replicas()
{
return replicas;
}
}
public static class ForWriteWithIdeal extends CoordinationPlan.ForWrite
{
public final CoordinationPlan.ForWrite ideal;
public ForWriteWithIdeal(ReplicaPlan.ForWrite replicas, ResponseTracker responses, CoordinationPlan.ForWrite ideal)
{
super(replicas, responses);
this.ideal = ideal;
}
/**
* Create coordination plan for batchlog write.
*
* The batchlog is a system-level durability mechanism independent of keyspace replication:
* - Stored in system.batches regardless of which keyspace(s) the mutations target
* - Replica selection is DC-local based on rack diversity and liveness
* - Uses simple ack counting (ONE or TWO based on available replicas)
*
* @param metadata the cluster metadata
* @param isAny whether to allow any node (for legacy batch compatibility)
* @return coordination plan for batchlog write
* @throws UnavailableException if insufficient replicas are available
*/
public static ForWriteWithIdeal forBatchlogWrite(ClusterMetadata metadata, boolean isAny)
throws UnavailableException
{
ReplicaPlan.ForWrite plan = ReplicaPlans.forBatchlogWrite(metadata, isAny);
int blockFor = plan.consistencyLevel().blockFor(plan.replicationStrategy());
ResponseTracker tracker = new SimpleResponseTracker(blockFor, plan.contacts().size());
return new ForWriteWithIdeal(plan, tracker, null);
}
}
public abstract static class ForRead<E extends Endpoints<E>, P extends ReplicaPlan<E, P>> extends CoordinationPlan<E, P> implements Supplier<P>
{
final ReplicaPlan.Shared<E, P> replicas;
public ForRead(ReplicaPlan.Shared<E, P> replicas, ResponseTracker responses)
{
super(responses);
this.replicas = replicas;
}
public abstract ForRead<E, P> copyWithResetTracker();
@Override
public P get()
{
return replicas.get();
}
@Override
public P replicas()
{
return replicas.get();
}
public void addToContacts(Replica replica)
{
replicas.addToContacts(replica);
}
}
public static class ForTokenRead extends ForRead<EndpointsForToken, ReplicaPlan.ForTokenRead>
{
public ForTokenRead(ReplicaPlan.Shared<EndpointsForToken, ReplicaPlan.ForTokenRead> replicas, ResponseTracker responses)
{
super(replicas, responses);
}
@Override
public ForTokenRead copyWithResetTracker()
{
return new ForTokenRead(replicas, responses().resetCopy());
}
}
public static class ForRangeRead extends ForRead<EndpointsForRange, ReplicaPlan.ForRangeRead>
{
public ForRangeRead(ReplicaPlan.Shared<EndpointsForRange, ReplicaPlan.ForRangeRead> replicas, ResponseTracker responses)
{
super(replicas, responses);
}
@Override
public ForRangeRead copyWithResetTracker()
{
return new ForRangeRead(replicas, responses().resetCopy());
}
}
// ---- Static convenience methods that look up the replication strategy internally ----
private static AbstractReplicationStrategy getStrategy(ClusterMetadata metadata, Keyspace keyspace)
{
if (SchemaConstants.isLocalSystemKeyspace(keyspace.getName()))
return keyspace.getReplicationStrategy();
return metadata.schema.getKeyspaceMetadata(keyspace.getName()).replicationStrategy;
}
public static ForWriteWithIdeal forWrite(ClusterMetadata metadata,
Keyspace keyspace,
ConsistencyLevel consistencyLevel,
Function<ClusterMetadata, ReplicaLayout.ForTokenWrite> liveAndDown,
ReplicaPlans.Selector selector)
{
return getStrategy(metadata, keyspace).planForWrite(metadata, keyspace, consistencyLevel, liveAndDown, selector);
}
public static ForWriteWithIdeal forWrite(ClusterMetadata metadata,
Keyspace keyspace,
ConsistencyLevel consistencyLevel,
Token token,
ReplicaPlans.Selector selector)
{
return getStrategy(metadata, keyspace).planForWrite(metadata, keyspace, consistencyLevel, token, selector);
}
public static ForWrite forForwardingCounterWrite(ClusterMetadata metadata,
Keyspace keyspace,
Token token,
Function<ClusterMetadata, Replica> replicaSupplier)
{
return getStrategy(metadata, keyspace).planForForwardingCounterWrite(metadata, keyspace, token, replicaSupplier);
}
public static ForWriteWithIdeal forReplayMutation(ClusterMetadata metadata,
Keyspace keyspace,
Token token)
{
return getStrategy(metadata, keyspace).planForReplayMutation(metadata, keyspace, token);
}
public static ForTokenRead forTokenRead(ClusterMetadata metadata,
Keyspace keyspace,
TableId tableId,
Token token,
@Nullable Index.QueryPlan indexQueryPlan,
ConsistencyLevel consistencyLevel,
SpeculativeRetryPolicy retry,
ReadCoordinator coordinator)
{
return getStrategy(metadata, keyspace).planForTokenRead(metadata, keyspace, tableId, token, indexQueryPlan, consistencyLevel, retry, coordinator);
}
public static ForRangeRead forRangeRead(ClusterMetadata metadata,
Keyspace keyspace,
TableId tableId,
@Nullable Index.QueryPlan indexQueryPlan,
ConsistencyLevel consistencyLevel,
AbstractBounds<PartitionPosition> range,
int vnodeCount)
{
return getStrategy(metadata, keyspace).planForRangeRead(metadata, keyspace, tableId, indexQueryPlan, consistencyLevel, range, vnodeCount);
}
public static ForRangeRead maybeMergeRangeReads(ClusterMetadata metadata,
Keyspace keyspace,
TableId tableId,
ConsistencyLevel consistencyLevel,
ForRangeRead left,
ForRangeRead right)
{
return getStrategy(metadata, keyspace).maybeMergeRangeReads(metadata, keyspace, tableId, consistencyLevel, left.replicas(), right.replicas());
}
public static ForRangeRead forFullRangeRead(Keyspace keyspace,
ConsistencyLevel consistencyLevel,
AbstractBounds<PartitionPosition> range,
Set<InetAddressAndPort> endpointsToContact,
int vnodeCount)
{
return keyspace.getReplicationStrategy().planForFullRangeRead(keyspace, consistencyLevel, range, endpointsToContact, vnodeCount);
}
public static ForTokenRead forSingleReplicaTokenRead(Keyspace keyspace, Token token, Replica replica)
{
return keyspace.getReplicationStrategy().planForSingleReplicaTokenRead(keyspace, token, replica);
}
public static ForRangeRead forSingleReplicaRangeRead(Keyspace keyspace,
AbstractBounds<PartitionPosition> range,
Replica replica,
int vnodeCount)
{
return keyspace.getReplicationStrategy().planForSingleReplicaRangeRead(keyspace, range, replica, vnodeCount);
}
}

View File

@ -0,0 +1,186 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.locator;
import java.util.Map;
import java.util.function.ToIntFunction;
import org.apache.cassandra.exceptions.RequestFailureReason;
/**
* Per-datacenter response tracker that requires ALL datacenters to independently reach quorum.
* <p>
* Composes multiple ResponseTrackers (one per datacenter) and delegates operations
* to the appropriate tracker based on endpoint datacenter. Used for EACH_QUORUM consistency.
* <p>
* The composed trackers can be any ResponseTracker implementation:
* <ul>
* <li>{@link SimpleResponseTracker} for basic quorum tracking</li>
* <li>{@link WriteResponseTracker} for writes with pending replicas (double-count model)</li>
* </ul>
* <p>
* Thread-safe through delegation to thread-safe ResponseTracker implementations.
*/
public class PerDcResponseTracker implements ResponseTracker
{
private final Map<String, ResponseTracker> trackerPerDc;
private final Locator locator;
/**
* Create per-DC tracker with pre-built trackers for each datacenter.
*
* @param trackerPerDc map of datacenter name to tracker (must be non-empty)
* @param locator for looking up datacenter from endpoint
*/
public PerDcResponseTracker(Map<String, ResponseTracker> trackerPerDc, Locator locator)
{
if (trackerPerDc == null || trackerPerDc.isEmpty())
throw new IllegalArgumentException("trackerPerDc cannot be null or empty");
if (locator == null)
throw new IllegalArgumentException("locator cannot be null");
this.locator = locator;
this.trackerPerDc = trackerPerDc;
}
private int count(ToIntFunction<ResponseTracker> getter)
{
int total = 0;
for (ResponseTracker tracker : trackerPerDc.values())
total += getter.applyAsInt(tracker);
return total;
}
@Override
public void onResponse(InetAddressAndPort from)
{
ResponseTracker tracker = getTrackerForEndpoint(from);
if (tracker != null)
tracker.onResponse(from);
}
@Override
public void onFailure(InetAddressAndPort from, RequestFailureReason reason)
{
ResponseTracker tracker = getTrackerForEndpoint(from);
if (tracker != null)
tracker.onFailure(from, reason);
}
@Override
public boolean isComplete()
{
// Complete when ALL DCs are complete (either success or definite failure)
for (ResponseTracker tracker : trackerPerDc.values())
{
if (!tracker.isComplete())
return false;
}
return true;
}
@Override
public boolean isSuccessful()
{
// Successful only if ALL DCs are successful
for (ResponseTracker tracker : trackerPerDc.values())
{
if (!tracker.isSuccessful())
return false;
}
return true;
}
@Override
public int required()
{
return count(ResponseTracker::required);
}
@Override
public int received()
{
return count(ResponseTracker::received);
}
@Override
public int failures()
{
return count(ResponseTracker::failures);
}
@Override
public boolean countsTowardQuorum(InetAddressAndPort from)
{
String dc = locator.location(from).datacenter;
if (dc == null)
return false;
ResponseTracker tracker = trackerPerDc.get(dc);
return tracker != null && tracker.countsTowardQuorum(from);
}
private ResponseTracker getTrackerForEndpoint(InetAddressAndPort from)
{
String dc = locator.location(from).datacenter;
return trackerPerDc.get(dc);
}
/**
* @return the tracker for the specified datacenter, or null if not tracked
*/
public ResponseTracker getTrackerForDc(String datacenter)
{
return trackerPerDc.get(datacenter);
}
@Override
public String toString()
{
return String.format("PerDcResponseTracker[datacenters=%s, trackerPerDc=%s]",
trackerPerDc.keySet(), trackerPerDc);
}
@Override
public boolean isPending(InetAddressAndPort from)
{
String dc = locator.location(from).datacenter;
if (dc == null)
return false;
ResponseTracker tracker = trackerPerDc.get(dc);
return tracker != null && tracker.isPending(from);
}
@Override
public int totalRequired()
{
return count(ResponseTracker::totalRequired);
}
@Override
public int totalContacts()
{
return count(ResponseTracker::totalContacts);
}
@Override
public int pendingContacts()
{
return count(ResponseTracker::pendingContacts);
}
}

View File

@ -264,15 +264,6 @@ public class ReplicaPlans
return localReplicas.get(ThreadLocalRandom.current().nextInt(localReplicas.size()));
}
/**
* A forwarding counter write is always sent to a single owning coordinator for the range, by the original coordinator
* (if it is not itself an owner)
*/
public static ReplicaPlan.ForWrite forForwardingCounterWrite(ClusterMetadata metadata, Keyspace keyspace, Token token, Function<ClusterMetadata, Replica> replica)
{
return forSingleReplicaWrite(metadata, keyspace, token, replica);
}
public static ReplicaPlan.ForWrite forLocalBatchlogWrite()
{
Token token = DatabaseDescriptor.getPartitioner().getMinimumToken();
@ -287,6 +278,36 @@ public class ReplicaPlans
return forWrite(systemKeyspace, ConsistencyLevel.ONE, (cm) -> liveAndDown, (cm) -> true, writeAll);
}
/**
* Create a replica plan for replaying a mutation from the batchlog.
*
* When recovering failed batches, mutations are replayed to live remote replicas only
* (local replica is handled separately by the caller).
*
* @param metadata the cluster metadata
* @param keyspace the keyspace
* @param token the token for the mutation
* @return replica plan targeting live remote replicas with CL.ONE
*/
public static ReplicaPlan.ForWrite forReplayMutation(ClusterMetadata metadata, Keyspace keyspace, Token token)
{
ReplicaLayout.ForTokenWrite liveAndDown = ReplicaLayout.forTokenWriteLiveAndDown(metadata, keyspace.getMetadata(), token);
Replicas.temporaryAssertFull(liveAndDown.all()); // TODO in CASSANDRA-14549
Replica selfReplica = liveAndDown.all().selfIfPresent();
ReplicaLayout.ForTokenWrite liveRemoteOnly = liveAndDown.filter(r -> FailureDetector.isReplicaAlive.test(r) && r != selfReplica);
EndpointsForToken allLiveRemoteOnly = liveRemoteOnly.all();
return new ReplicaPlan.ForWrite(keyspace, keyspace.getReplicationStrategy(),
ConsistencyLevel.ONE,
liveRemoteOnly.pending(),
allLiveRemoteOnly,
allLiveRemoteOnly,
allLiveRemoteOnly,
(cm) -> forReplayMutation(cm, keyspace, token),
metadata.epoch);
}
/**
* Requires that the provided endpoints are alive. Converts them to their relevant system replicas.
* Note that the liveAndDown collection and live are equal to the provided endpoints.

View File

@ -0,0 +1,127 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.locator;
import com.google.common.annotations.VisibleForTesting;
/**
* Black-box response tracker encapsulating coordination completion logic.
*
* Created by replication strategy for each operation, this interface allows
* strategies to customize how quorum requirements are calculated and enforced.
* Different implementations can provide different semantics.
*
* The tracker is responsible for:
* 1. Recording responses and failures from replicas
* 2. Determining when the operation has completed (success or definite failure)
* 3. Providing metrics for error messages and monitoring
*
* Thread safety: Implementations must be thread-safe as onResponse/onFailure
* can be called concurrently from multiple network threads.
*/
public interface ResponseTracker
{
// TODO: review replica plan members and move here as appropriate
/**
* Record a successful response from a replica.
*
* @param from endpoint that responded successfully
*/
void onResponse(InetAddressAndPort from);
/**
* Record a failed response from a replica.
*
* @param from endpoint that failed
*/
void onFailure(InetAddressAndPort from);
// TODO: consider having an outcome method that returns an enum (PENDING, SUCCESS, FAILURE)
/**
* Has the operation completed (either success or definite failure)?
*
* An operation is complete when:
* - Success: Required quorum has been achieved
* - Definite failure: Not enough replicas remain to achieve quorum
*
* @return true if no more responses are needed to make a decision
*/
boolean isComplete();
/**
* Did the operation succeed (quorum achieved)?
*
* Only meaningful if isComplete() returns true.
*
* @return true if required quorum was met
*/
boolean isSuccessful();
/**
* How many responses are required for success?
*
* Used for error messages, metrics, and UnavailableException construction.
* For complex trackers (e.g., EACH_QUORUM), this may be a sum or other
* aggregate value rather than the actual completion criteria.
*
* @return number of responses required
*/
int required();
/**
* How many successful responses have been received so far?
*
* @return number of successful responses
*/
int received();
/**
* How many failures have been recorded so far?
*
* @return number of failures
*/
int failures();
/**
* Should responses from this endpoint be counted toward quorum?
*
* Allows filtering of responses based on datacenter, state, or other criteria.
* For example, LOCAL_QUORUM trackers would return false for remote DC replicas.
*
* @param from endpoint to check
* @return true if responses from this endpoint count toward quorum
*/
@VisibleForTesting
boolean countsTowardQuorum(InetAddressAndPort from);
/**
* Indicates that the given address is a pending replica. Accepting writes but not reads
*/
boolean isPending(InetAddressAndPort from);
int totalContacts();
int pendingContacts();
/**
* creates a copy of the tracker will all response counts reset
*/
ResponseTracker resetCopy();
}

View File

@ -0,0 +1,164 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.locator;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.function.Predicate;
/**
* Simple response tracker that counts responses against a single threshold.
* <p>
* Supports optional predicate filtering to selectively count responses
* (e.g., LOCAL_* consistency levels that only count local DC responses).
*/
public class SimpleResponseTracker implements ResponseTracker
{
private static final AtomicIntegerFieldUpdater<SimpleResponseTracker> RESPONSES_UPDATER
= AtomicIntegerFieldUpdater.newUpdater(SimpleResponseTracker.class, "responses");
private static final AtomicIntegerFieldUpdater<SimpleResponseTracker> FAILURES_UPDATER
= AtomicIntegerFieldUpdater.newUpdater(SimpleResponseTracker.class, "failures");
private static final Predicate<InetAddressAndPort> NO_FILTER = address -> true;
private final int blockFor;
private final int totalReplicas;
private final Predicate<InetAddressAndPort> filter;
private volatile int responses = 0;
private volatile int failures = 0;
/**
* Create unfiltered tracker
*
* @param blockFor number of responses required for quorum
* @param totalReplicas total replicas available (for early failure detection)
*/
public SimpleResponseTracker(int blockFor, int totalReplicas)
{
this(blockFor, totalReplicas, NO_FILTER);
}
/**
* Create filtered tracker
*
* @param blockFor number of responses required for quorum
* @param totalReplicas total replicas available (for early failure detection)
* @param filter predicate to test if response counts (null = all count)
*/
public SimpleResponseTracker(int blockFor, int totalReplicas,
Predicate<InetAddressAndPort> filter)
{
if (blockFor < 0)
throw new IllegalArgumentException("blockFor must be non-negative: " + blockFor);
if (totalReplicas < 0)
throw new IllegalArgumentException("totalReplicas must be non-negative: " + totalReplicas);
this.blockFor = blockFor;
this.totalReplicas = totalReplicas;
this.filter = filter != null ? filter : NO_FILTER;
}
@Override
public void onResponse(InetAddressAndPort from)
{
if (countsTowardQuorum(from))
RESPONSES_UPDATER.incrementAndGet(this);
}
@Override
public void onFailure(InetAddressAndPort from)
{
if (countsTowardQuorum(from))
FAILURES_UPDATER.incrementAndGet(this);
}
@Override
public boolean isComplete()
{
int r = responses;
int f = failures;
if (r >= blockFor)
return true;
// failure: can't reach blockFor
int needed = blockFor - r;
int remaining = totalReplicas - (r + f);
return needed > remaining;
}
@Override
public boolean isSuccessful()
{
return responses >= blockFor;
}
@Override
public int required()
{
return blockFor;
}
@Override
public int received()
{
return responses;
}
@Override
public int failures()
{
return failures;
}
@Override
public boolean countsTowardQuorum(InetAddressAndPort from)
{
return filter.test(from);
}
@Override
public String toString()
{
return String.format("SimpleResponseTracker[blockFor=%d, totalReplicas=%d, responses=%d, failures=%d, filtered=%s]",
blockFor, totalReplicas, responses, failures, filter != NO_FILTER);
}
@Override
public boolean isPending(InetAddressAndPort from)
{
return false;
}
@Override
public int totalContacts()
{
return totalReplicas;
}
@Override
public int pendingContacts()
{
return 0;
}
@Override
public SimpleResponseTracker resetCopy()
{
return new SimpleResponseTracker(blockFor, totalReplicas, filter);
}
}

View File

@ -0,0 +1,229 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.locator;
import java.util.function.Predicate;
/**
* Response tracker for writes with pending replicas using the double count model.
* <p>
* Two requirements must be satisfied for success:
* <ol>
* <li>Committed replicas must satisfy base CL: {@code naturalSuccesses >= baseBlockFor}</li>
* <li>Total replicas (natural + pending) must satisfy: {@code totalSuccesses >= totalBlockFor}</li>
* </ol>
* <p>
* This ensures both consistency (natural replicas have the data) and bootstrap safety
* (pending replicas also receive the write).
* <p>
* Implemented by composing two {@link SimpleResponseTracker}s:
* <ul>
* <li>naturalTracker: tracks responses from natural replicas only</li>
* <li>totalTracker: tracks responses from all replicas (natural + pending)</li>
* </ul>
* <p>
* Thread-safe through delegation to thread-safe SimpleResponseTrackers.
*/
public class WriteResponseTracker implements ResponseTracker
{
private final SimpleResponseTracker natural;
private final SimpleResponseTracker total;
/**
* Create a write response tracker with the double count model.
*
* @param baseBlockFor number of natural replica responses required for CL
* @param totalBlockFor total responses required (baseBlockFor + pending count)
* @param naturalReplicas number of natural replicas available
* @param pendingReplicas number of pending replicas available
* @param isPending predicate to determine if an endpoint is pending
*/
public WriteResponseTracker(int baseBlockFor,
int totalBlockFor,
int naturalReplicas,
int pendingReplicas,
Predicate<InetAddressAndPort> isPending)
{
this(baseBlockFor, totalBlockFor, naturalReplicas, pendingReplicas, isPending, null);
}
/**
* Create a write response tracker with the double count model and a filter.
*
* @param naturalBlockFor number of natural replica responses required for CL
* @param totalBlockFor total responses required (naturalBlockFor + pending count)
* @param naturalReplicas number of natural replicas available
* @param pendingReplicas number of pending replicas available
* @param isPending predicate to determine if an endpoint is pending
* @param filter predicate to filter which responses count (e.g., InOurDc for LOCAL_QUORUM)
*/
public WriteResponseTracker(int naturalBlockFor,
int totalBlockFor,
int naturalReplicas,
int pendingReplicas,
Predicate<InetAddressAndPort> isPending,
Predicate<InetAddressAndPort> filter)
{
if (naturalBlockFor < 0)
throw new IllegalArgumentException("naturalBlockFor must be non-negative: " + naturalBlockFor);
if (totalBlockFor < naturalBlockFor)
throw new IllegalArgumentException("totalBlockFor (" + totalBlockFor + ") must be >= naturalBlockFor (" + naturalBlockFor + ")");
if (naturalReplicas < 0)
throw new IllegalArgumentException("naturalReplicas must be non-negative: " + naturalReplicas);
if (pendingReplicas < 0)
throw new IllegalArgumentException("pendingReplicas must be non-negative: " + pendingReplicas);
if (naturalBlockFor > naturalReplicas)
throw new IllegalArgumentException("naturalBlockFor (" + naturalBlockFor + ") cannot exceed naturalReplicas (" + naturalReplicas + ")");
if (totalBlockFor > naturalReplicas + pendingReplicas)
throw new IllegalArgumentException("totalBlockFor (" + totalBlockFor + ") cannot exceed total replicas (" + (naturalReplicas + pendingReplicas) + ")");
if (isPending == null)
throw new IllegalArgumentException("isPending predicate cannot be null");
Predicate<InetAddressAndPort> naturalFilter = isPending.negate();
if (filter != null)
naturalFilter = naturalFilter.and(filter);
this.natural = new SimpleResponseTracker(naturalBlockFor, naturalReplicas, naturalFilter);
this.total = new SimpleResponseTracker(totalBlockFor, naturalReplicas + pendingReplicas, filter);
}
private WriteResponseTracker(SimpleResponseTracker natural, SimpleResponseTracker total)
{
this.natural = natural;
this.total = total;
}
@Override
public void onResponse(InetAddressAndPort from)
{
natural.onResponse(from);
total.onResponse(from);
}
@Override
public void onFailure(InetAddressAndPort from)
{
natural.onFailure(from);
total.onFailure(from);
}
@Override
public boolean isComplete()
{
// Early failure if either tracker fails
if (natural.isComplete() && !natural.isSuccessful())
return true;
if (total.isComplete() && !total.isSuccessful())
return true;
// Success requires both to succeed
return natural.isSuccessful() && total.isSuccessful();
}
@Override
public boolean isSuccessful()
{
return natural.isSuccessful() && total.isSuccessful();
}
@Override
public int required()
{
// Return total requirement for error messages
return total.required();
}
@Override
public int received()
{
// Return total successes for error messages
return total.received();
}
@Override
public int failures()
{
// Return total failures for error messages
return total.failures();
}
@Override
public boolean countsTowardQuorum(InetAddressAndPort from)
{
return total.countsTowardQuorum(from);
}
public int naturalReceived()
{
return natural.received();
}
public int pendingReceived()
{
return total.received() - natural.received();
}
public int naturalFailures()
{
return natural.failures();
}
public int pendingFailures()
{
return total.failures() - natural.failures();
}
public int baseBlockFor()
{
return natural.required();
}
@Override
public String toString()
{
return String.format("WriteResponseTracker[baseBlockFor=%d, totalBlockFor=%d, " +
"naturalTracker=%s, totalTracker=%s]",
baseBlockFor(), required(),
natural, total);
}
@Override
public boolean isPending(InetAddressAndPort from)
{
return total.countsTowardQuorum(from) && !natural.countsTowardQuorum(from);
}
@Override
public int totalContacts()
{
return total.totalContacts();
}
@Override
public int pendingContacts()
{
return total.totalContacts() - natural.totalContacts();
}
@Override
public ResponseTracker resetCopy()
{
return new WriteResponseTracker(natural.resetCopy(), total.resetCopy());
}
}

View File

@ -46,6 +46,7 @@ import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.NodeProximity;
@ -296,7 +297,7 @@ public class ForwardedWrite
}
}
public static AbstractWriteResponseHandler<Object> forwardMutation(Mutation mutation, ReplicaPlan.ForWrite plan, AbstractReplicationStrategy strategy, Dispatcher.RequestTime requestTime)
public static AbstractWriteResponseHandler<Object> forwardMutation(Mutation mutation, CoordinationPlan.ForWriteWithIdeal plan, AbstractReplicationStrategy strategy, Dispatcher.RequestTime requestTime)
{
// find leader
NodeProximity proximity = DatabaseDescriptor.getNodeProximity();
@ -310,7 +311,7 @@ public class ForwardedWrite
Replica leader = null;
for (Replica replica : proximity.sortedByProximity(FBUtilities.getBroadcastAddressAndPort(), endpoints))
{
if (plan.isAlive(replica))
if (plan.replicas().isAlive(replica))
leader = replica;
}
Preconditions.checkState(leader != null, "Could not find leader for %s", mutation);
@ -321,17 +322,17 @@ public class ForwardedWrite
AbstractWriteResponseHandler<Object> handler = strategy.getWriteResponseHandler(plan, null, WriteType.SIMPLE, null, requestTime);
// Add callbacks for replicas to respond directly to coordinator
Message<MutationRequest> toLeader = Message.outWithRequestTime(Verb.FORWARD_WRITE_REQ, new MutationRequest(mutation, plan), requestTime);
Message<MutationRequest> toLeader = Message.outWithRequestTime(Verb.FORWARD_WRITE_REQ, new MutationRequest(mutation, plan.replicas()), requestTime);
for (Replica endpoint : endpoints)
{
if (plan.isAlive(endpoint))
if (plan.replicas().isAlive(endpoint))
{
logger.trace("Adding forwarding callback for response from {} id {}", endpoint, toLeader.id());
MessagingService.instance().callbacks.addWithExpiration(handler, toLeader, endpoint);
}
else
{
handler.expired();
handler.expired(endpoint.endpoint());
}
}
@ -345,7 +346,7 @@ public class ForwardedWrite
* The leader will apply the counter mutation, assign a mutation ID, and replicate to other replicas.
*/
public static AbstractWriteResponseHandler<Object> forwardCounterMutation(CounterMutation counterMutation,
ReplicaPlan.ForWrite plan,
CoordinationPlan.ForWriteWithIdeal plan,
AbstractReplicationStrategy strategy,
Dispatcher.RequestTime requestTime)
{
@ -375,18 +376,19 @@ public class ForwardedWrite
// Create response handler for all replicas
AbstractWriteResponseHandler<Object> handler = strategy.getWriteResponseHandler(plan, null, WriteType.COUNTER, null, requestTime);
ReplicaPlan.ForWrite replicas = plan.replicas();
// Add callbacks for all live replicas to respond directly to coordinator
Message<CounterMutation> forwardMessage = Message.outWithRequestTime(Verb.COUNTER_MUTATION_REQ, counterMutation, requestTime);
for (Replica replica : plan.contacts())
for (Replica replica : replicas.contacts())
{
if (plan.isAlive(replica))
if (replicas.isAlive(replica))
{
logger.trace("Adding forwarding callback for tracked counter response from {} id {}", replica, forwardMessage.id());
MessagingService.instance().callbacks.addWithExpiration(handler, forwardMessage, replica);
}
else
{
handler.expired();
handler.expired(replica.endpoint());
}
}
@ -407,7 +409,7 @@ public class ForwardedWrite
* @return the write response handler
*/
public static AbstractWriteResponseHandler<Object> forward(IMutation mutation,
ReplicaPlan.ForWrite plan,
CoordinationPlan.ForWriteWithIdeal plan,
AbstractReplicationStrategy strategy,
Dispatcher.RequestTime requestTime)
{

View File

@ -43,6 +43,7 @@ import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.RequestFailure;
import org.apache.cassandra.exceptions.WriteTimeoutException;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.DynamicEndpointSnitch;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.Replica;
@ -171,13 +172,14 @@ public class TrackedWriteRequest
Preconditions.checkArgument(mutation.id().isNone());
String keyspaceName = mutation.getKeyspaceName();
ClusterMetadata cm = ClusterMetadata.current();
Keyspace keyspace = Keyspace.open(keyspaceName);
Token token = mutation.key().getToken();
ReplicaPlan.ForWrite plan = ReplicaPlans.forWrite(keyspace, consistencyLevel, token, ReplicaPlans.writeAll);
AbstractReplicationStrategy rs = plan.replicationStrategy();
AbstractReplicationStrategy rs = cm.schema.getKeyspaceMetadata(keyspaceName).replicationStrategy;
CoordinationPlan.ForWriteWithIdeal plan = CoordinationPlan.forWrite(cm, keyspace, consistencyLevel, token, ReplicaPlans.writeAll);
if (plan.lookup(FBUtilities.getBroadcastAddressAndPort()) == null)
if (plan.replicas().lookup(FBUtilities.getBroadcastAddressAndPort()) == null)
{
logger.trace("Remote tracked request {} {}", mutation, plan);
writeMetrics.remoteRequests.mark();
@ -193,19 +195,19 @@ public class TrackedWriteRequest
if (logger.isTraceEnabled())
{
logger.trace("Write replication plan for mutation {}: live={}, pending={}, all={}",
id, plan.live(), plan.pending(), plan.contacts());
id, plan.replicas().live(), plan.replicas().pending(), plan.replicas().contacts());
}
final TrackedWriteResponseHandler handler;
if (mutation instanceof CounterMutation)
{
handler = TrackedWriteResponseHandler.wrap(rs.getWriteResponseHandler(plan, null, WriteType.COUNTER, null, requestTime), id);
applyCounterMutationLocally((CounterMutation) mutation, plan, handler);
applyCounterMutationLocally((CounterMutation) mutation, plan.replicas(), handler);
}
else
{
handler = TrackedWriteResponseHandler.wrap(rs.getWriteResponseHandler(plan, null, WriteType.SIMPLE, null, requestTime), id);
applyLocallyAndSendToReplicas((Mutation) mutation, plan, handler);
applyLocallyAndSendToReplicas((Mutation) mutation, plan.replicas(), handler);
}
return handler;
}
@ -264,7 +266,7 @@ public class TrackedWriteRequest
logger.trace("Skipping dead replica {} for mutation {}", destination, mutation.id());
// Only call expired() for AbstractWriteResponseHandler (not for LeaderCallback)
if (handler instanceof AbstractWriteResponseHandler)
((AbstractWriteResponseHandler<?>) handler).expired(); // immediately mark the response as expired since the request will not be sent
((AbstractWriteResponseHandler<?>) handler).expired(destination.endpoint()); // immediately mark the response as expired since the request will not be sent
continue;
}

View File

@ -29,6 +29,9 @@ import java.util.function.Supplier;
import javax.annotation.Nullable;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.locator.CoordinationPlan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -45,7 +48,6 @@ import org.apache.cassandra.exceptions.WriteFailureException;
import org.apache.cassandra.exceptions.WriteTimeoutException;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.locator.ReplicaPlan.ForWrite;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.RequestCallback;
@ -81,17 +83,17 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
//Count down until all responses and expirations have occured before deciding whether the ideal CL was reached.
private AtomicInteger responsesAndExpirations;
private final Condition condition = newOneTimeCondition();
protected final ReplicaPlan.ForWrite replicaPlan;
protected final CoordinationPlan.ForWrite plan;
protected final Runnable callback;
protected final WriteType writeType;
private static final AtomicIntegerFieldUpdater<AbstractWriteResponseHandler> failuresUpdater =
AtomicIntegerFieldUpdater.newUpdater(AbstractWriteResponseHandler.class, "failures");
private volatile int failures = 0;
private static final AtomicIntegerFieldUpdater<AbstractWriteResponseHandler> alreadyHintedForRetryOnDifferentSystemUpdater =
AtomicIntegerFieldUpdater.newUpdater(AbstractWriteResponseHandler.class, "alreadyHintedForRetryOnDifferentSystem");
// Only write a hint to be applied as a transaction once
private volatile int alreadyHintedForRetryOnDifferentSystem = 0;
private static final AtomicIntegerFieldUpdater<AbstractWriteResponseHandler> signaledUpdater =
AtomicIntegerFieldUpdater.newUpdater(AbstractWriteResponseHandler.class, "signaled");
private volatile int signaled = 0;
private volatile Map<InetAddressAndPort, RequestFailureReason> failureReasonByEndpoint;
private final Dispatcher.RequestTime requestTime;
private @Nullable final Supplier<Mutation> hintOnFailure;
@ -117,16 +119,26 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
* @param hintOnFailure
* @param requestTime
*/
protected AbstractWriteResponseHandler(ForWrite replicaPlan, Runnable callback, WriteType writeType,
protected AbstractWriteResponseHandler(CoordinationPlan.ForWrite plan, Runnable callback, WriteType writeType,
Supplier<Mutation> hintOnFailure, Dispatcher.RequestTime requestTime)
{
this.replicaPlan = replicaPlan;
this.plan = plan;
this.callback = callback;
this.writeType = writeType;
this.hintOnFailure = hintOnFailure;
this.requestTime = requestTime;
}
public CoordinationPlan.ForWrite coordinationPlan()
{
return plan;
}
public ForWrite replicaPlan()
{
return plan.replicas();
}
public void get() throws WriteTimeoutException, WriteFailureException, RetryOnDifferentSystemException
{
long timeoutNanos = currentTimeoutNanos();
@ -144,9 +156,9 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
if (!signaled)
throwTimeout();
int candidateReplicaCount = candidateReplicaCount();
if (blockFor() + failures > candidateReplicaCount)
if (!plan.responses().isSuccessful())
{
int candidateReplicaCount = candidateReplicaCount();
// failures keeps incrementing, and this.failureReasonByEndpoint keeps getting new entries after signaling.
// Simpler to reason about what happened by copying this.failureReasonByEndpoint and then inferring
// failures from it
@ -179,10 +191,10 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
throw new CoordinatorBehindException("Write request failed due to coordinator behind");
}
throw new WriteFailureException(replicaPlan.consistencyLevel(), ackCount(), blockFor(), writeType, getFailureReasonByEndpointMap());
throw new WriteFailureException(replicaPlan().consistencyLevel(), ackCount(), blockFor(), writeType, getFailureReasonByEndpointMap());
}
if (replicaPlan.stillAppliesTo(ClusterMetadata.current()))
if (replicaPlan().stillAppliesTo(ClusterMetadata.current()))
{
if (warningContext != null)
{
@ -217,7 +229,7 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
// avoid sending confusing info to the user (see CASSANDRA-6491).
if (acks >= blockedFor)
acks = blockedFor - 1;
throw new WriteTimeoutException(writeType, replicaPlan.consistencyLevel(), acks, blockedFor);
throw new WriteTimeoutException(writeType, replicaPlan().consistencyLevel(), acks, blockedFor);
}
public final long currentTimeoutNanos()
@ -236,7 +248,7 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
public void setIdealCLResponseHandler(AbstractWriteResponseHandler handler)
{
this.idealCLDelegate = handler;
idealCLDelegate.responsesAndExpirations = new AtomicInteger(replicaPlan.contacts().size());
idealCLDelegate.responsesAndExpirations = new AtomicInteger(replicaPlan().contacts().size());
}
/**
@ -287,9 +299,12 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
}
}
public final void expired()
public final void expired(InetAddressAndPort from)
{
plan.responses().onFailure(from);
logFailureOrTimeoutToIdealCLDelegate();
if (plan.responses().isComplete() && !plan.responses().isSuccessful())
signal();
}
/**
@ -299,7 +314,7 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
{
// During bootstrap, we have to include the pending endpoints or we may fail the consistency level
// guarantees (see #833)
return replicaPlan.writeQuorum();
return replicaPlan().writeQuorum();
}
/**
@ -309,15 +324,15 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
*/
protected int candidateReplicaCount()
{
if (replicaPlan.consistencyLevel().isDatacenterLocal())
return countInOurDc(replicaPlan.liveAndDown()).allReplicas();
if (replicaPlan().consistencyLevel().isDatacenterLocal())
return countInOurDc(replicaPlan().liveAndDown()).allReplicas();
return replicaPlan.liveAndDown().size();
return replicaPlan().liveAndDown().size();
}
public ConsistencyLevel consistencyLevel()
{
return replicaPlan.consistencyLevel();
return replicaPlan().consistencyLevel();
}
/**
@ -345,14 +360,17 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
protected void signal()
{
if (!signaledUpdater.compareAndSet(this, 0, 1))
return;
//The ideal CL should only count as a strike if the requested CL was achieved.
//If the requested CL is not achieved it's fine for the ideal CL to also not be achieved.
if (idealCLDelegate != null && blockFor() + failures <= candidateReplicaCount())
if (idealCLDelegate != null && plan.responses().isSuccessful())
{
idealCLDelegate.requestedCLAchieved = true;
if (idealCLDelegate == this)
{
replicaPlan.keyspace().metric.idealCLWriteLatency.addNano(nanoTime() - requestTime.startedAtNanos());
replicaPlan().keyspace().metric.idealCLWriteLatency.addNano(nanoTime() - requestTime.startedAtNanos());
}
}
@ -361,9 +379,7 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
callback.run();
}
/**
* Check if the handler has completed (for testing purposes).
*/
@VisibleForTesting
public boolean isComplete()
{
return condition.isSignalled();
@ -374,10 +390,6 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
{
logger.trace("Got failure {} from {}", failure, from);
int n = waitingFor(from)
? failuresUpdater.incrementAndGet(this)
: failures;
if (failureReasonByEndpoint == null)
synchronized (this)
{
@ -386,14 +398,16 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
}
failureReasonByEndpoint.put(from, failure.reason);
plan.responses().onFailure(from);
logFailureOrTimeoutToIdealCLDelegate();
if (blockFor() + n > candidateReplicaCount())
if (plan.responses().isComplete() && !plan.responses().isSuccessful())
signal();
// If the failure was RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM then we only want to hint once
// and not for each instance since odds are it will be applied as a transaction at all replicas
if (hintOnFailure != null && StorageProxy.shouldHint(replicaPlan.lookup(from)) )
if (hintOnFailure != null && StorageProxy.shouldHint(replicaPlan().lookup(from)))
{
if (failure.reason == RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM)
{
@ -402,7 +416,7 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
}
else
{
StorageProxy.submitHint(hintOnFailure.get(), replicaPlan.lookup(from), null);
StorageProxy.submitHint(hintOnFailure.get(), replicaPlan().lookup(from), null);
}
}
}
@ -427,7 +441,7 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
// Only mark it as failed if the requested CL was achieved.
if (!condition.isSignalled() && requestedCLAchieved)
{
replicaPlan.keyspace().metric.writeFailedIdealCL.inc();
replicaPlan().keyspace().metric.writeFailedIdealCL.inc();
}
}
}
@ -437,7 +451,7 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
*/
public void maybeTryAdditionalReplicas(IMutation mutation, WritePerformer writePerformer, String localDC)
{
EndpointsForToken uncontacted = replicaPlan.liveUncontacted();
EndpointsForToken uncontacted = replicaPlan().liveUncontacted();
if (uncontacted.isEmpty())
return;
@ -459,7 +473,7 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
for (ColumnFamilyStore cf : cfs)
cf.metric.additionalWrites.inc();
writePerformer.apply(mutation, replicaPlan.withContacts(uncontacted),
writePerformer.apply(mutation, replicaPlan().withContacts(uncontacted),
(AbstractWriteResponseHandler<IMutation>) this,
localDC,
requestTime);

View File

@ -38,7 +38,7 @@ public class BatchlogResponseHandler<T> extends AbstractWriteResponseHandler<T>
public BatchlogResponseHandler(AbstractWriteResponseHandler<T> wrapped, int requiredBeforeFinish, BatchlogCleanup cleanup, Dispatcher.RequestTime requestTime)
{
super(wrapped.replicaPlan, wrapped.callback, wrapped.writeType, null, requestTime);
super(wrapped.plan, wrapped.callback, wrapped.writeType, null, requestTime);
this.wrapped = wrapped;
this.requiredBeforeFinish = requiredBeforeFinish;
this.cleanup = cleanup;

View File

@ -17,109 +17,65 @@
*/
package org.apache.cassandra.service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.MessageParams;
import org.apache.cassandra.db.Mutation;
import org.apache.cassandra.db.WriteType;
import org.apache.cassandra.locator.Locator;
import org.apache.cassandra.locator.NetworkTopologyStrategy;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.ParamType;
import org.apache.cassandra.service.writes.thresholds.WriteWarningContext;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.FBUtilities;
/**
* This class blocks for a quorum of responses _in all datacenters_ (CL.EACH_QUORUM).
*
* The PerDcResponseTracker handles per-datacenter counting.
*/
public class DatacenterSyncWriteResponseHandler<T> extends AbstractWriteResponseHandler<T>
{
private static final Locator locator = DatabaseDescriptor.getLocator();
private final Map<String, AtomicInteger> responses = new HashMap<String, AtomicInteger>();
private final AtomicInteger acks = new AtomicInteger(0);
public DatacenterSyncWriteResponseHandler(ReplicaPlan.ForWrite replicaPlan,
public DatacenterSyncWriteResponseHandler(CoordinationPlan.ForWrite coordinationPlan,
Runnable callback,
WriteType writeType,
Supplier<Mutation> hintOnFailure,
Dispatcher.RequestTime requestTime)
{
// Response is been managed by the map so make it 1 for the superclass.
super(replicaPlan, callback, writeType, hintOnFailure, requestTime);
assert replicaPlan.consistencyLevel() == ConsistencyLevel.EACH_QUORUM;
if (replicaPlan.replicationStrategy() instanceof NetworkTopologyStrategy)
{
NetworkTopologyStrategy strategy = (NetworkTopologyStrategy) replicaPlan.replicationStrategy();
for (String dc : strategy.getDatacenters())
{
int rf = strategy.getReplicationFactor(dc).allReplicas;
responses.put(dc, new AtomicInteger((rf / 2) + 1));
}
}
else
{
responses.put(locator.local().datacenter, new AtomicInteger(ConsistencyLevel.quorumFor(replicaPlan.replicationStrategy())));
}
// During bootstrap, we have to include the pending endpoints or we may fail the consistency level
// guarantees (see #833)
for (Replica pending : replicaPlan.pending())
{
responses.get(locator.location(pending.endpoint()).datacenter).incrementAndGet();
}
super(coordinationPlan, callback, writeType, hintOnFailure, requestTime);
assert replicaPlan().consistencyLevel() == ConsistencyLevel.EACH_QUORUM;
}
public void onResponse(Message<T> message)
{
try
{
Map<ParamType, Object> params;
String dataCenter;
if (message != null)
{
params = message.header.params();
dataCenter = locator.location(message.from()).datacenter;
}
else
{
params = MessageParams.capture();
dataCenter = locator.local().datacenter;
}
Map<ParamType, Object> params = message != null
? message.header.params()
: MessageParams.capture();
if (WriteWarningContext.isSupported(params.keySet()))
getWarningContext().updateCounters(params);
responses.get(dataCenter).getAndDecrement();
acks.incrementAndGet();
InetAddressAndPort from = message == null ? FBUtilities.getBroadcastAddressAndPort() : message.from();
for (AtomicInteger i : responses.values())
{
if (i.get() > 0)
return;
}
plan.responses().onResponse(from);
// all the quorum conditions are met
signal();
if (plan.responses().isComplete())
signal();
}
finally
{
//Must be last after all subclass processing
// Must be last - forward to ideal CL delegate
logResponseToIdealCLDelegate(message);
}
}
protected int ackCount()
{
return acks.get();
return plan.responses().received();
}
}

View File

@ -17,47 +17,63 @@
*/
package org.apache.cassandra.service;
import java.util.Map;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.apache.cassandra.db.MessageParams;
import org.apache.cassandra.db.Mutation;
import org.apache.cassandra.db.WriteType;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.InOurDc;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.ParamType;
import org.apache.cassandra.service.writes.thresholds.WriteWarningContext;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.FBUtilities;
/**
* This class blocks for a quorum of responses _in the local datacenter only_ (CL.LOCAL_QUORUM).
*
* The response tracker handles DC filtering via countsTowardQuorum().
* This handler still uses waitingFor to filter collectSuccess() calls.
*/
public class DatacenterWriteResponseHandler<T> extends WriteResponseHandler<T>
{
private final Predicate<InetAddressAndPort> waitingFor = InOurDc.endpoints();
public DatacenterWriteResponseHandler(ReplicaPlan.ForWrite replicaPlan,
public DatacenterWriteResponseHandler(CoordinationPlan.ForWrite coordinationPlan,
Runnable callback,
WriteType writeType,
Supplier<Mutation> hintOnFailure,
Dispatcher.RequestTime requestTime)
{
super(replicaPlan, callback, writeType, hintOnFailure, requestTime);
assert replicaPlan.consistencyLevel().isDatacenterLocal();
super(coordinationPlan, callback, writeType, hintOnFailure, requestTime);
assert coordinationPlan.consistencyLevel().isDatacenterLocal();
}
@Override
public void onResponse(Message<T> message)
{
if (message == null || waitingFor(message.from()))
InetAddressAndPort from = message == null ? FBUtilities.getBroadcastAddressAndPort() : message.from();
Map<ParamType, Object> params = message != null ? message.header.params() : MessageParams.capture();
if (WriteWarningContext.isSupported(params.keySet()))
getWarningContext().updateCounters(params);
plan.responses().onResponse(from);
if (message == null || waitingFor(from))
{
super.onResponse(message);
}
else
{
//WriteResponseHandler.response will call logResonseToIdealCLDelegate so only do it if not calling WriteResponseHandler.response.
//Must be last after all subclass processing
logResponseToIdealCLDelegate(message);
replicaPlan().collectSuccess(from);
}
if (plan.responses().isComplete())
signal();
// Must be last (see comment on AbstractWriteResponseHandler.logResponseToIdealCLDelegate for why) - forward to ideal CL delegate
logResponseToIdealCLDelegate(message);
}
@Override

View File

@ -120,6 +120,7 @@ import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.hints.Hint;
import org.apache.cassandra.hints.HintsService;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.DynamicEndpointSnitch;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.InetAddressAndPort;
@ -1088,11 +1089,11 @@ public class StorageProxy implements StorageProxyMBean
}
// NOTE: this ReplicaPlan is a lie, this usage of ReplicaPlan could do with being clarified - the selected() collection is essentially (I think) never used
ReplicaPlan.ForWrite replicaPlan = ReplicaPlans.forWrite(keyspace, consistencyLevel, tk, ReplicaPlans.writeAll);
AbstractReplicationStrategy rs = replicaPlan.replicationStrategy();
CoordinationPlan.ForWriteWithIdeal plan = CoordinationPlan.forWrite(ClusterMetadata.current(), keyspace, consistencyLevel, tk, ReplicaPlans.writeAll);
AbstractReplicationStrategy rs = plan.replicationStrategy();
// If we are coordinating a new mutation id for the first time then create a TrackedWriteResponseHandler
AbstractWriteResponseHandler<?> responseHandler = rs.getWriteResponseHandler(replicaPlan, null, WriteType.SIMPLE, null, requestTime);
AbstractWriteResponseHandler<?> responseHandler = rs.getWriteResponseHandler(plan, null, WriteType.SIMPLE, null, requestTime);
responseHandler = TrackedWriteResponseHandler.wrap(responseHandler, mutationId);
// For tracked keyspaces, the local commit MUST execute synchronously BEFORE sending to remote replicas.
@ -1100,7 +1101,7 @@ public class StorageProxy implements StorageProxyMBean
// reconciliation via ActiveLogReconciler.
Message<Commit> message = Message.outWithFlag(PAXOS_COMMIT_REQ, proposal, MessageFlag.CALL_BACK_ON_FAILURE);
Replica localReplica = null;
for (Replica replica : replicaPlan.liveAndDown())
for (Replica replica : plan.replicas().liveAndDown())
{
if (replica.isSelf())
{
@ -1129,7 +1130,7 @@ public class StorageProxy implements StorageProxyMBean
// Now send to remote replicas
IntHashSet remoteReplicas = new IntHashSet();
for (Replica replica : replicaPlan.liveAndDown())
for (Replica replica : plan.replicas().liveAndDown())
{
if (replica.isSelf())
continue; // Already executed locally above
@ -1158,22 +1159,22 @@ public class StorageProxy implements StorageProxyMBean
Token tk = update.partitionKey().getToken();
ClusterMetadata cm = ClusterMetadata.current();
CoordinationPlan.ForWriteWithIdeal plan = CoordinationPlan.forWrite(cm, keyspace, consistencyLevel, tk, ReplicaPlans.writeAll);
AbstractWriteResponseHandler<Commit> responseHandler = null;
// NOTE: this ReplicaPlan is a lie, this usage of ReplicaPlan could do with being clarified - the selected() collection is essentially (I think) never used
ReplicaPlan.ForWrite replicaPlan = ReplicaPlans.forWrite(keyspace, consistencyLevel, tk, ReplicaPlans.writeAll);
if (shouldBlock)
{
AbstractReplicationStrategy rs = replicaPlan.replicationStrategy();
responseHandler = rs.getWriteResponseHandler(replicaPlan, null, WriteType.SIMPLE, proposal::makeMutation, requestTime);
}
responseHandler = plan.replicationStrategy().getWriteResponseHandler(plan, null, WriteType.SIMPLE, proposal::makeMutation, requestTime);
Message<Commit> message = Message.outWithFlag(PAXOS_COMMIT_REQ, proposal, MessageFlag.CALL_BACK_ON_FAILURE);
for (Replica replica : replicaPlan.liveAndDown())
for (Replica replica : plan.replicas().liveAndDown())
{
InetAddressAndPort destination = replica.endpoint();
checkHintOverload(replica);
if (replicaPlan.isAlive(replica))
if (plan.replicas().isAlive(replica))
{
if (shouldBlock)
{
@ -1191,7 +1192,7 @@ public class StorageProxy implements StorageProxyMBean
{
if (responseHandler != null)
{
responseHandler.expired();
responseHandler.expired(destination);
}
if (allowHints && shouldHint(replica))
{
@ -1616,11 +1617,11 @@ public class StorageProxy implements StorageProxyMBean
pending.get());
};
ReplicaPlan.ForWrite replicaPlan = ReplicaPlans.forWrite(metadata, Keyspace.open(keyspaceName), consistencyLevel, computeReplicas, ReplicaPlans.writeAll);
CoordinationPlan.ForWriteWithIdeal plan = CoordinationPlan.forWrite(metadata, Keyspace.open(keyspaceName), consistencyLevel, computeReplicas, ReplicaPlans.writeAll);
wrappers.add(wrapViewBatchResponseHandler(mutation,
consistencyLevel,
replicaPlan,
plan,
baseComplete,
WriteType.BATCH,
cleanup,
@ -1911,7 +1912,7 @@ public class StorageProxy implements StorageProxyMBean
{
ConsistencyLevel batchConsistencyLevel = consistencyLevelForBatchLog(consistencyLevel, requireQuorumForRemove);
// This can't be updated for each iteration because cleanup has to go to the correct replicas which is where the batchlog is originally written
ReplicaPlan.ForWrite batchlogReplicaPlan = ReplicaPlans.forBatchlogWrite(ClusterMetadata.current(), batchConsistencyLevel == ConsistencyLevel.ANY);
CoordinationPlan.ForWrite coordinationPlan = CoordinationPlan.ForWriteWithIdeal.forBatchlogWrite(ClusterMetadata.current(), batchConsistencyLevel == ConsistencyLevel.ANY);
final TimeUUID batchUUID = nextTimeUUID();
boolean wroteToBatchLog = false;
while (true)
@ -1921,7 +1922,7 @@ public class StorageProxy implements StorageProxyMBean
attributeNonAccordLatency = true;
List<WriteResponseHandlerWrapper> wrappers = new ArrayList<>(mutations.size());
List<Mutation> accordMutations = new ArrayList<>(mutations.size());
BatchlogCleanup cleanup = new BatchlogCleanup(() -> asyncRemoveFromBatchlog(batchlogReplicaPlan, batchUUID, requestTime));
BatchlogCleanup cleanup = new BatchlogCleanup(() -> asyncRemoveFromBatchlog(coordinationPlan.replicas(), batchUUID, requestTime));
// add a handler for each mutation that will not be written on Accord - includes checking availability, but doesn't initiate any writes, yet
SplitConsumer<Mutation> splitConsumer = (accordMutation, untrackedMutation, trackedMutation, originalMutations, mutationIndex) -> {
@ -1940,15 +1941,15 @@ public class StorageProxy implements StorageProxyMBean
// Always construct the replica plan to check availability
ReplicaPlan.ForWrite dataReplicaPlan = ReplicaPlans.forWrite(cm, keyspace, consistencyLevel, tk, ReplicaPlans.writeAll);
CoordinationPlan.ForWriteWithIdeal plan = CoordinationPlan.forWrite(cm, keyspace, consistencyLevel, tk, ReplicaPlans.writeAll);
if (dataReplicaPlan.lookup(FBUtilities.getBroadcastAddressAndPort()) != null)
if (plan.replicas().lookup(FBUtilities.getBroadcastAddressAndPort()) != null)
writeMetrics.localRequests.mark();
else
writeMetrics.remoteRequests.mark();
WriteResponseHandlerWrapper wrapper = wrapBatchResponseHandler(untrackedMutation,
dataReplicaPlan,
plan,
batchConsistencyLevel,
WriteType.BATCH,
cleanup,
@ -1971,7 +1972,7 @@ public class StorageProxy implements StorageProxyMBean
// with the mutations delivered by the batch log since an unacknowledged Accord txn won't be retried
// unless those mutations are also written to the batch log
// Only write to the log once and reuse the batchUUID for every attempt to route the mutations correctly
doFallibleWriteWithMetricTracking(() -> syncWriteToBatchlog(mutations, batchlogReplicaPlan, batchUUID, requestTime), consistencyLevel);
doFallibleWriteWithMetricTracking(() -> syncWriteToBatchlog(mutations, coordinationPlan, batchUUID, requestTime), consistencyLevel);
Tracing.trace("Successfully wrote to batchlog");
wroteToBatchLog = true;
}
@ -2104,17 +2105,17 @@ public class StorageProxy implements StorageProxyMBean
}
}
private static void syncWriteToBatchlog(Collection<Mutation> mutations, ReplicaPlan.ForWrite replicaPlan, TimeUUID uuid, Dispatcher.RequestTime requestTime)
private static void syncWriteToBatchlog(Collection<Mutation> mutations, CoordinationPlan.ForWrite coordinationPlan, TimeUUID uuid, Dispatcher.RequestTime requestTime)
throws WriteTimeoutException, WriteFailureException
{
WriteResponseHandler<?> handler = new WriteResponseHandler<>(replicaPlan,
WriteResponseHandler<?> handler = new WriteResponseHandler<>(coordinationPlan,
WriteType.BATCH_LOG,
null,
requestTime);
Batch batch = Batch.createLocal(uuid, FBUtilities.timestampMicros(), mutations);
Message<Batch> message = Message.out(BATCH_STORE_REQ, batch);
for (Replica replica : replicaPlan.liveAndDown())
for (Replica replica : coordinationPlan.replicas().liveAndDown())
{
if (logger.isTraceEnabled())
logger.trace("Sending batchlog store request {} to {} for {} mutations", batch.id, replica, batch.size());
@ -2146,8 +2147,8 @@ public class StorageProxy implements StorageProxyMBean
{
for (WriteResponseHandlerWrapper wrapper : wrappers)
{
Replicas.temporaryAssertFull(wrapper.handler.replicaPlan.liveAndDown()); // TODO: CASSANDRA-14549
ReplicaPlan.ForWrite replicas = wrapper.handler.replicaPlan.withContacts(wrapper.handler.replicaPlan.liveAndDown());
Replicas.temporaryAssertFull(wrapper.handler.replicaPlan().liveAndDown()); // TODO: CASSANDRA-14549
ReplicaPlan.ForWrite replicas = wrapper.handler.replicaPlan().withContacts(wrapper.handler.replicaPlan().liveAndDown());
try
{
@ -2167,9 +2168,9 @@ public class StorageProxy implements StorageProxyMBean
for (WriteResponseHandlerWrapper wrapper : wrappers)
{
EndpointsForToken sendTo = wrapper.handler.replicaPlan.liveAndDown();
EndpointsForToken sendTo = wrapper.handler.replicaPlan().liveAndDown();
Replicas.temporaryAssertFull(sendTo); // TODO: CASSANDRA-14549
sendToHintedReplicas(wrapper.mutation, wrapper.handler.replicaPlan.withContacts(sendTo), wrapper.handler, localDataCenter, stage, requestTime);
sendToHintedReplicas(wrapper.mutation, wrapper.handler.replicaPlan().withContacts(sendTo), wrapper.handler, localDataCenter, stage, requestTime);
}
for (WriteResponseHandlerWrapper wrapper : wrappers)
@ -2202,30 +2203,32 @@ public class StorageProxy implements StorageProxyMBean
Keyspace keyspace = Keyspace.open(keyspaceName);
Token tk = mutation.key().getToken();
ReplicaPlan.ForWrite replicaPlan = ReplicaPlans.forWrite(keyspace, consistencyLevel, tk, ReplicaPlans.writeAll);
if (replicaPlan.lookup(FBUtilities.getBroadcastAddressAndPort()) != null)
ClusterMetadata cm = ClusterMetadata.current();
CoordinationPlan.ForWriteWithIdeal plan = CoordinationPlan.forWrite(cm, keyspace, consistencyLevel, tk, ReplicaPlans.writeAll);
if (plan.replicas().lookup(FBUtilities.getBroadcastAddressAndPort()) != null)
writeMetrics.localRequests.mark();
else
writeMetrics.remoteRequests.mark();
AbstractReplicationStrategy rs = replicaPlan.replicationStrategy();
AbstractWriteResponseHandler<IMutation> responseHandler = rs.getWriteResponseHandler(replicaPlan, callback, writeType, mutation.hintOnFailure(), requestTime);
AbstractWriteResponseHandler<IMutation> responseHandler = plan.replicationStrategy().getWriteResponseHandler(plan, callback, writeType, mutation.hintOnFailure(), requestTime);
performer.apply(mutation, replicaPlan, responseHandler, localDataCenter, requestTime);
performer.apply(mutation, plan.replicas(), responseHandler, localDataCenter, requestTime);
return responseHandler;
}
// same as performWrites except does not initiate writes (but does perform availability checks).
private static WriteResponseHandlerWrapper wrapBatchResponseHandler(Mutation mutation,
ReplicaPlan.ForWrite replicaPlan,
CoordinationPlan.ForWriteWithIdeal plan,
ConsistencyLevel batchConsistencyLevel,
WriteType writeType,
BatchlogResponseHandler.BatchlogCleanup cleanup,
Dispatcher.RequestTime requestTime)
{
AbstractReplicationStrategy rs = replicaPlan.replicationStrategy();
AbstractWriteResponseHandler<IMutation> writeHandler = rs.getWriteResponseHandler(replicaPlan, null, writeType, mutation, requestTime);
AbstractReplicationStrategy rs = plan.replicationStrategy();
AbstractWriteResponseHandler<IMutation> writeHandler = rs.getWriteResponseHandler(plan, null, writeType, mutation, requestTime);
BatchlogResponseHandler<IMutation> batchHandler = new BatchlogResponseHandler<>(writeHandler, batchConsistencyLevel.blockFor(rs), cleanup, requestTime);
return new WriteResponseHandlerWrapper(batchHandler, mutation);
}
@ -2236,14 +2239,14 @@ public class StorageProxy implements StorageProxyMBean
*/
private static WriteResponseHandlerWrapper wrapViewBatchResponseHandler(Mutation mutation,
ConsistencyLevel batchConsistencyLevel,
ReplicaPlan.ForWrite replicaPlan,
CoordinationPlan.ForWriteWithIdeal plan,
AtomicLong baseComplete,
WriteType writeType,
BatchlogResponseHandler.BatchlogCleanup cleanup,
Dispatcher.RequestTime requestTime)
{
AbstractReplicationStrategy replicationStrategy = replicaPlan.replicationStrategy();
AbstractWriteResponseHandler<IMutation> writeHandler = replicationStrategy.getWriteResponseHandler(replicaPlan, () -> {
AbstractReplicationStrategy replicationStrategy = plan.replicationStrategy();
AbstractWriteResponseHandler<IMutation> writeHandler = replicationStrategy.getWriteResponseHandler(plan, () -> {
long delay = Math.max(0, currentTimeMillis() - baseComplete.get());
viewWriteMetrics.viewWriteLatency.update(delay, MILLISECONDS);
}, writeType, mutation, requestTime);
@ -2362,7 +2365,7 @@ public class StorageProxy implements StorageProxyMBean
else
{
//Immediately mark the response as expired since the request will not be sent
responseHandler.expired();
responseHandler.expired(destination.endpoint());
if (shouldHint(destination))
{
if (endpointsToHint == null)
@ -2567,10 +2570,10 @@ public class StorageProxy implements StorageProxyMBean
// there we'll mark a local request against the metrics.
writeMetrics.remoteRequests.mark();
ReplicaPlan.ForWrite forWrite = ReplicaPlans.forForwardingCounterWrite(metadata, keyspace, tk,
clm -> ReplicaPlans.findCounterLeaderReplica(clm, cm.getKeyspaceName(), cm.key(), localDataCenter, cm.consistency()));
CoordinationPlan.ForWrite plan = CoordinationPlan.forForwardingCounterWrite(metadata, keyspace, tk,
clm -> ReplicaPlans.findCounterLeaderReplica(clm, cm.getKeyspaceName(), cm.key(), localDataCenter, cm.consistency()));
// Forward the actual update to the chosen leader replica
AbstractWriteResponseHandler<IMutation> responseHandler = new WriteResponseHandler<>(forWrite,
AbstractWriteResponseHandler<IMutation> responseHandler = new WriteResponseHandler<>(plan,
WriteType.COUNTER, null, requestTime);
Tracing.trace("Enqueuing counter update to {}", replica);
@ -3831,7 +3834,7 @@ public class StorageProxy implements StorageProxyMBean
HintsService.instance.write(hostIds, Hint.create(mutation, creationTime));
validTargets.forEach(HintsService.instance.metrics::incrCreatedHints);
// Notify the handler only for CL == ANY
if (responseHandler != null && responseHandler.replicaPlan.consistencyLevel() == ConsistencyLevel.ANY)
if (responseHandler != null && responseHandler.replicaPlan().consistencyLevel() == ConsistencyLevel.ANY)
responseHandler.onResponse(null);
}
};

View File

@ -38,7 +38,7 @@ public class TrackedWriteResponseHandler<T> extends AbstractWriteResponseHandler
private TrackedWriteResponseHandler(AbstractWriteResponseHandler<T> wrapped, MutationId mutationId)
{
super(wrapped.replicaPlan, wrapped.callback, wrapped.writeType, null, wrapped.getRequestTime());
super(wrapped.plan, wrapped.callback, wrapped.writeType, null, wrapped.getRequestTime());
this.wrapped = wrapped;
this.mutationId = mutationId;
}

View File

@ -18,7 +18,6 @@
package org.apache.cassandra.service;
import java.util.Map;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.function.Supplier;
import org.slf4j.Logger;
@ -27,8 +26,8 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.MessageParams;
import org.apache.cassandra.db.Mutation;
import org.apache.cassandra.db.WriteType;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.ParamType;
import org.apache.cassandra.service.writes.thresholds.WriteWarningContext;
@ -37,28 +36,25 @@ import org.apache.cassandra.utils.FBUtilities;
/**
* Handles blocking writes for ONE, ANY, TWO, THREE, QUORUM, and ALL consistency levels.
*
* Response tracking is delegated to coordinationPlan.tracker().
*/
public class WriteResponseHandler<T> extends AbstractWriteResponseHandler<T>
{
protected static final Logger logger = LoggerFactory.getLogger(WriteResponseHandler.class);
protected volatile int responses;
private static final AtomicIntegerFieldUpdater<WriteResponseHandler> responsesUpdater
= AtomicIntegerFieldUpdater.newUpdater(WriteResponseHandler.class, "responses");
public WriteResponseHandler(ReplicaPlan.ForWrite replicaPlan,
public WriteResponseHandler(CoordinationPlan.ForWrite coordinationPlan,
Runnable callback,
WriteType writeType,
Supplier<Mutation> hintOnFailure,
Dispatcher.RequestTime requestTime)
{
super(replicaPlan, callback, writeType, hintOnFailure, requestTime);
responses = blockFor();
super(coordinationPlan, callback, writeType, hintOnFailure, requestTime);
}
public WriteResponseHandler(ReplicaPlan.ForWrite replicaPlan, WriteType writeType, Supplier<Mutation> hintOnFailure, Dispatcher.RequestTime requestTime)
public WriteResponseHandler(CoordinationPlan.ForWrite coordinationPlan, WriteType writeType, Supplier<Mutation> hintOnFailure, Dispatcher.RequestTime requestTime)
{
this(replicaPlan, null, writeType, hintOnFailure, requestTime);
this(coordinationPlan, null, writeType, hintOnFailure, requestTime);
}
public void onResponse(Message<T> m)
@ -69,17 +65,19 @@ public class WriteResponseHandler<T> extends AbstractWriteResponseHandler<T>
if (WriteWarningContext.isSupported(params.keySet()))
getWarningContext().updateCounters(params);
replicaPlan.collectSuccess(from);
if (responsesUpdater.decrementAndGet(this) == 0)
replicaPlan().collectSuccess(from);
plan.responses().onResponse(from);
if (plan.responses().isComplete())
signal();
//Must be last after all subclass processing
//The two current subclasses both assume logResponseToIdealCLDelegate is called
//here.
// Must be last (see comment on AbstractWriteResponseHandler.logResponseToIdealCLDelegate for why) - forward to ideal CL delegate
logResponseToIdealCLDelegate(m);
}
protected int ackCount()
{
return blockFor() - responses;
return plan.responses().received();
}
}

View File

@ -28,7 +28,6 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.annotation.Nullable;
@ -88,7 +87,6 @@ import org.apache.cassandra.locator.ReplicaPlan.ForRead;
import org.apache.cassandra.metrics.ClientRequestMetrics;
import org.apache.cassandra.metrics.ClientRequestSizeMetrics;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
@ -404,7 +402,7 @@ public class Paxos
final Epoch epoch;
final Function<ClusterMetadata, Participants> recompute;
Participants(Epoch epoch, Keyspace keyspace, ConsistencyLevel consistencyForConsensus, ReplicaLayout.ForTokenWrite all, ReplicaLayout.ForTokenWrite electorate, EndpointsForToken live,
public Participants(Epoch epoch, Keyspace keyspace, ConsistencyLevel consistencyForConsensus, ReplicaLayout.ForTokenWrite all, ReplicaLayout.ForTokenWrite electorate, EndpointsForToken live,
Function<ClusterMetadata, Participants> recompute)
{
this.epoch = epoch;
@ -464,26 +462,15 @@ public class Paxos
}
static Participants get(ClusterMetadata metadata, TableMetadata table, Token token, ConsistencyLevel consistencyForConsensus)
public static Participants get(ClusterMetadata metadata, TableMetadata table, Token token, ConsistencyLevel consistencyForConsensus)
{
return get(metadata, table, token, consistencyForConsensus, FailureDetector.isReplicaAlive);
}
static Participants get(ClusterMetadata metadata, TableMetadata table, Token token, ConsistencyLevel consistencyForConsensus, Predicate<Replica> isReplicaAlive)
{
KeyspaceMetadata keyspaceMetadata = metadata.schema.getKeyspaceMetadata(table.keyspace);
// MetaStrategy distributes the entire keyspace to all replicas. In addition, its tables (currently only
// the dist log table) don't use the globally configured partitioner. For these reasons we don't lookup the
// replicas using the supplied token as this can actually be of the incorrect type (for example when
// performing Paxos repair).
final Token actualToken = table.partitioner == MetaStrategy.partitioner ? MetaStrategy.entireRange.right : token;
ReplicaLayout.ForTokenWrite all = forTokenWriteLiveAndDown(keyspaceMetadata, actualToken);
ReplicaLayout.ForTokenWrite electorate = consistencyForConsensus.isDatacenterLocal()
? all.filter(InOurDc.replicas()) : all;
EndpointsForToken live = all.all().filter(isReplicaAlive);
return new Participants(metadata.epoch, Keyspace.open(table.keyspace), consistencyForConsensus, all, electorate, live,
(cm) -> get(cm, table, actualToken, consistencyForConsensus));
AbstractReplicationStrategy strategy = metadata.schema.getKeyspaceMetadata(table.keyspace).replicationStrategy;
return strategy.paxosParticipants(metadata, table, token, consistencyForConsensus, isReplicaAlive);
}
static Participants get(TableMetadata table, Token token, ConsistencyLevel consistencyForConsensus)
@ -1139,7 +1126,6 @@ public class Paxos
// round's proposal (if any).
PaxosPrepare.Success success = prepare.success();
Supplier<Participants> plan = () -> success.participants;
List<Message<IReadResponse>> responses = success.responses;
// There should be only a single response from the coordinator that was selected to do the tracked read
@ -1161,7 +1147,7 @@ public class Paxos
}
else
{
DataResolver<EndpointsForToken, Participants> resolver = new DataResolver<>(ReadCoordinator.DEFAULT, query, plan, NoopReadRepair.instance, requestTime);
DataResolver<EndpointsForToken, Participants> resolver = new DataResolver<>(ReadCoordinator.DEFAULT, query, () -> success.participants, NoopReadRepair.instance, requestTime);
for (int i = 0 ; i < responses.size() ; ++i)
{

View File

@ -323,11 +323,22 @@ public class PaxosCommit<OnDone extends Consumer<? super PaxosCommit.Status>> ex
}
}
}
if (localIsReplica)
{
executeOnSelf();
localExecutedSynchronously = true;
}
// Set up additional commit work from the replication strategy (e.g., satellite writes for SRS).
// This needs to happen before executeOnSelf() can trigger onPaxosDecision(), so that
// additionalCommitFuture is set before it's read. The base strategy returns an
// already-completed future, so this is a no-op for non-SRS keyspaces.
// For SRS, satellite messages are sent here (in parallel with local execution below).
// MutationTrackingService.retryFailedWrite for down satellite endpoints schedules async retries,
// which will find the mutation in the journal after executeOnSelf() completes below.
AbstractReplicationStrategy strategy = Keyspace.open(commit.metadata().keyspace).getReplicationStrategy();
setAugmentedCommitFuture(strategy.sendPaxosCommitMutations(commit, isUrgent));
}
// Now send to remote replicas (and record local execution for non-tracked keyspaces)

View File

@ -34,12 +34,12 @@ import org.apache.cassandra.db.transform.DuplicateRowChecker;
import org.apache.cassandra.exceptions.ReadFailureException;
import org.apache.cassandra.exceptions.ReadTimeoutException;
import org.apache.cassandra.exceptions.UnavailableException;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaCollection;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.locator.ReplicaPlans;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.service.StorageProxy.LocalReadRunnable;
@ -68,7 +68,7 @@ public abstract class AbstractReadExecutor implements ReadExecutor
protected final ReadCoordinator coordinator;
protected final ReadCommand command;
private final ReplicaPlan.SharedForTokenRead replicaPlan;
private final CoordinationPlan.ForTokenRead plan;
protected final ReadRepair<EndpointsForToken, ReplicaPlan.ForTokenRead> readRepair;
protected final DigestResolver<EndpointsForToken, ReplicaPlan.ForTokenRead> digestResolver;
protected final ReadCallback<EndpointsForToken, ReplicaPlan.ForTokenRead> handler;
@ -79,16 +79,16 @@ public abstract class AbstractReadExecutor implements ReadExecutor
private final int initialDataRequestCount;
protected volatile PartitionIterator result = null;
AbstractReadExecutor(ReadCoordinator coordinator, ColumnFamilyStore cfs, ReadCommand command, ReplicaPlan.ForTokenRead replicaPlan, int initialDataRequestCount, Dispatcher.RequestTime requestTime)
AbstractReadExecutor(ReadCoordinator coordinator, ColumnFamilyStore cfs, ReadCommand command, CoordinationPlan.ForTokenRead plan, int initialDataRequestCount, Dispatcher.RequestTime requestTime)
{
this.coordinator = coordinator;
this.command = command;
this.replicaPlan = ReplicaPlan.shared(replicaPlan);
this.plan = plan;
this.initialDataRequestCount = initialDataRequestCount;
// the ReadRepair and DigestResolver both need to see our updated
this.readRepair = ReadRepair.create(coordinator, command, this.replicaPlan, requestTime);
this.digestResolver = new DigestResolver<>(coordinator, command, this.replicaPlan, requestTime);
this.handler = new ReadCallback<>(digestResolver, command, this.replicaPlan, requestTime);
this.readRepair = ReadRepair.create(coordinator, command, plan, requestTime);
this.digestResolver = new DigestResolver<>(coordinator, command, plan, requestTime);
this.handler = new ReadCallback<>(digestResolver, command, plan, requestTime);
this.cfs = cfs;
this.traceState = Tracing.instance.get();
this.requestTime = requestTime;
@ -99,7 +99,7 @@ public abstract class AbstractReadExecutor implements ReadExecutor
// TODO: we need this when talking with pre-3.0 nodes. So if we preserve the digest format moving forward, we can get rid of this once
// we stop being compatible with pre-3.0 nodes.
int digestVersion = MessagingService.current_version;
for (Replica replica : replicaPlan.contacts())
for (Replica replica : plan.replicas().contacts())
digestVersion = Math.min(digestVersion, MessagingService.instance().versions.get(replica.endpoint()));
command.setDigestVersion(digestVersion);
}
@ -195,32 +195,32 @@ public abstract class AbstractReadExecutor implements ReadExecutor
ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(command.metadata().id);
SpeculativeRetryPolicy retry = cfs.metadata().params.speculativeRetry;
ReplicaPlan.ForTokenRead replicaPlan = ReplicaPlans.forRead(metadata,
keyspace,
command.metadata().id,
command.partitionKey().getToken(),
command.indexQueryPlan(),
consistencyLevel,
retry,
coordinator);
CoordinationPlan.ForTokenRead plan = CoordinationPlan.forTokenRead(metadata,
keyspace,
command.metadata().id,
command.partitionKey().getToken(),
command.indexQueryPlan(),
consistencyLevel,
retry,
coordinator);
// Speculative retry is disabled *OR*
// 11980: Disable speculative retry if using EACH_QUORUM in order to prevent miscounting DC responses
if (retry.equals(NeverSpeculativeRetryPolicy.INSTANCE) || consistencyLevel == ConsistencyLevel.EACH_QUORUM)
return new NeverSpeculatingReadExecutor(coordinator, cfs, command, replicaPlan, requestTime, false);
return new NeverSpeculatingReadExecutor(coordinator, cfs, command, plan, requestTime, false);
if (retry.equals(AlwaysSpeculativeRetryPolicy.INSTANCE))
return new AlwaysSpeculatingReadExecutor(coordinator, cfs, command, replicaPlan, requestTime);
return new AlwaysSpeculatingReadExecutor(coordinator, cfs, command, plan, requestTime);
// There are simply no extra replicas to speculate.
// Handle this separately so it can record failed attempts to speculate due to lack of replicas
if (replicaPlan.contacts().size() == replicaPlan.readCandidates().size())
if (plan.replicas().contacts().size() == plan.replicas().readCandidates().size())
{
boolean recordFailedSpeculation = consistencyLevel != ConsistencyLevel.ALL;
return new NeverSpeculatingReadExecutor(coordinator, cfs, command, replicaPlan, requestTime, recordFailedSpeculation);
return new NeverSpeculatingReadExecutor(coordinator, cfs, command, plan, requestTime, recordFailedSpeculation);
}
else // PERCENTILE or CUSTOM.
return new SpeculatingReadExecutor(coordinator, cfs, command, replicaPlan, requestTime);
return new SpeculatingReadExecutor(coordinator, cfs, command, plan, requestTime);
}
/**
@ -256,7 +256,7 @@ public abstract class AbstractReadExecutor implements ReadExecutor
@Override
public ReplicaPlan.ForTokenRead replicaPlan()
{
return replicaPlan.get();
return plan.replicas();
}
void onReadTimeout() {}
@ -273,11 +273,11 @@ public abstract class AbstractReadExecutor implements ReadExecutor
public NeverSpeculatingReadExecutor(ReadCoordinator coordinator,
ColumnFamilyStore cfs,
ReadCommand command,
ReplicaPlan.ForTokenRead replicaPlan,
CoordinationPlan.ForTokenRead plan,
Dispatcher.RequestTime requestTime,
boolean logFailedSpeculation)
{
super(coordinator, cfs, command, replicaPlan, 1, requestTime);
super(coordinator, cfs, command, plan, 1, requestTime);
this.logFailedSpeculation = logFailedSpeculation;
}
@ -297,13 +297,13 @@ public abstract class AbstractReadExecutor implements ReadExecutor
public SpeculatingReadExecutor(ReadCoordinator coordinator,
ColumnFamilyStore cfs,
ReadCommand command,
ReplicaPlan.ForTokenRead replicaPlan,
CoordinationPlan.ForTokenRead plan,
Dispatcher.RequestTime requestTime)
{
// We're hitting additional targets for read repair (??). Since our "extra" replica is the least-
// preferred by the snitch, we do an extra data read to start with against a replica more
// likely to respond; better to let RR fail than the entire query.
super(coordinator, cfs, command, replicaPlan, replicaPlan.readQuorum() < replicaPlan.contacts().size() ? 2 : 1, requestTime);
super(coordinator, cfs, command, plan, plan.replicas().readQuorum() < plan.replicas().contacts().size() ? 2 : 1, requestTime);
}
public void maybeTryAdditionalReplicas()
@ -342,7 +342,7 @@ public abstract class AbstractReadExecutor implements ReadExecutor
// we must update the plan to include this new node, else when we come to read-repair, we may not include this
// speculated response in the data requests we make again, and we will not be able to 'speculate' an extra repair read,
// nor would we be able to speculate a new 'write' if the repair writes are insufficient
super.replicaPlan.addToContacts(extraReplica);
super.plan.addToContacts(extraReplica);
if (traceState != null)
traceState.trace("speculating read retry on {}", extraReplica);
@ -367,12 +367,12 @@ public abstract class AbstractReadExecutor implements ReadExecutor
public AlwaysSpeculatingReadExecutor(ReadCoordinator coordinator,
ColumnFamilyStore cfs,
ReadCommand command,
ReplicaPlan.ForTokenRead replicaPlan,
CoordinationPlan.ForTokenRead plan,
Dispatcher.RequestTime requestTime)
{
// presumably, we speculate an extra data request here in case it is our data request that fails to respond,
// and there are no more nodes to consult
super(coordinator, cfs, command, replicaPlan, replicaPlan.contacts().size() > 1 ? 2 : 1, requestTime);
super(coordinator, cfs, command, plan, plan.replicas().contacts().size() > 1 ? 2 : 1, requestTime);
}
public void maybeTryAdditionalReplicas()
@ -397,7 +397,7 @@ public abstract class AbstractReadExecutor implements ReadExecutor
public void setResult(PartitionIterator result)
{
Preconditions.checkState(this.result == null, "Result can only be set once");
this.result = DuplicateRowChecker.duringRead(result, this.replicaPlan.get().readCandidates().endpointList());
this.result = DuplicateRowChecker.duringRead(result, this.plan.replicas().readCandidates().endpointList());
}
public void awaitResponses() throws ReadTimeoutException

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.service.reads;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
@ -45,7 +46,7 @@ public class DigestResolver<E extends Endpoints<E>, P extends ReplicaPlan.ForRea
{
private volatile Message<ReadResponse> dataResponse;
public DigestResolver(ReadCoordinator coordinator, ReadCommand command, ReplicaPlan.Shared<E, P> replicaPlan, Dispatcher.RequestTime requestTime)
public DigestResolver(ReadCoordinator coordinator, ReadCommand command, Supplier<P> replicaPlan, Dispatcher.RequestTime requestTime)
{
super(coordinator, command, replicaPlan, requestTime);
Preconditions.checkArgument(command instanceof SinglePartitionReadCommand,

View File

@ -21,7 +21,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import com.google.common.collect.ImmutableMap;
@ -40,6 +39,7 @@ import org.apache.cassandra.exceptions.ReadTimeoutException;
import org.apache.cassandra.exceptions.RequestFailure;
import org.apache.cassandra.exceptions.RequestFailureReason;
import org.apache.cassandra.exceptions.RetryOnDifferentSystemException;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.Endpoints;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.ReplicaPlan;
@ -57,7 +57,6 @@ import org.apache.cassandra.utils.concurrent.Condition;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater;
import static org.apache.cassandra.exceptions.RequestFailureReason.COORDINATOR_BEHIND;
import static org.apache.cassandra.exceptions.RequestFailureReason.RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM;
import static org.apache.cassandra.tracing.Tracing.isTracing;
@ -72,33 +71,30 @@ public class ReadCallback<E extends Endpoints<E>, P extends ReplicaPlan.ForRead<
private final Dispatcher.RequestTime requestTime;
// this uses a plain reference, but is initialised before handoff to any other threads; the later updates
// may not be visible to the threads immediately, but ReplicaPlan only contains final fields, so they will never see an uninitialised object
final ReplicaPlan.Shared<E, P> replicaPlan;
final CoordinationPlan.ForRead<E, P> plan;
private final ReadCommand command;
private static final AtomicIntegerFieldUpdater<ReadCallback> failuresUpdater
= newUpdater(ReadCallback.class, "failures");
private volatile int failures = 0;
private final Map<InetAddressAndPort, RequestFailureReason> failureReasonByEndpoint;
private volatile WarningContext warningContext;
private static final AtomicReferenceFieldUpdater<ReadCallback, WarningContext> warningsUpdater
= AtomicReferenceFieldUpdater.newUpdater(ReadCallback.class, WarningContext.class, "warningContext");
public ReadCallback(ResponseResolver<E, P> resolver, ReadCommand command, ReplicaPlan.Shared<E, P> replicaPlan, Dispatcher.RequestTime requestTime)
public ReadCallback(ResponseResolver<E, P> resolver, ReadCommand command, CoordinationPlan.ForRead<E, P> plan, Dispatcher.RequestTime requestTime)
{
this.command = command;
this.resolver = resolver;
this.requestTime = requestTime;
this.replicaPlan = replicaPlan;
this.plan = plan;
this.failureReasonByEndpoint = new ConcurrentHashMap<>();
// we don't support read repair (or rapid read protection) for range scans yet (CASSANDRA-6897)
assert !(command instanceof PartitionRangeReadCommand) || replicaPlan().readQuorum() >= replicaPlan().contacts().size();
if (logger.isTraceEnabled())
logger.trace("Blockfor is {}; setting up requests to {}", replicaPlan().readQuorum(), this.replicaPlan);
logger.trace("Blockfor is {}; setting up requests to {}", replicaPlan().readQuorum(), this.plan.replicas());
}
protected P replicaPlan()
{
return replicaPlan.get();
return plan.replicas();
}
public boolean await(long commandTimeout, TimeUnit unit)
@ -141,8 +137,8 @@ public class ReadCallback<E extends Endpoints<E>, P extends ReplicaPlan.ForRead<
* See {@link DigestResolver#preprocess(Message)}
* CASSANDRA-16097
*/
int received = resolver.responses.size();
boolean failed = failures > 0 && (replicaPlan().readQuorum() > received || !resolver.isDataPresent());
int received = plan.responses().received();
boolean failed = plan.responses().failures() > 0 && (!plan.responses().isSuccessful() || !resolver.isDataPresent());
// If all messages came back as a TIMEOUT then signaled=true and failed=true.
// Need to distinguish between a timeout and a failure (network, bad data, etc.), so store an extra field.
// see CASSANDRA-17828
@ -168,16 +164,16 @@ public class ReadCallback<E extends Endpoints<E>, P extends ReplicaPlan.ForRead<
if (isTracing())
{
String gotData = received > 0 ? (resolver.isDataPresent() ? " (including data)" : " (only digests)") : "";
Tracing.trace("{}; received {} of {} responses{}", !timedout ? "Failed" : "Timed out", received, replicaPlan().readQuorum(), gotData);
Tracing.trace("{}; received {} of {} responses{}", !timedout ? "Failed" : "Timed out", received, plan.responses().required(), gotData);
}
else if (logger.isDebugEnabled())
{
String gotData = received > 0 ? (resolver.isDataPresent() ? " (including data)" : " (only digests)") : "";
logger.debug("{}; received {} of {} responses{}", !timedout ? "Failed" : "Timed out", received, replicaPlan().readQuorum(), gotData);
logger.debug("{}; received {} of {} responses{}", !timedout ? "Failed" : "Timed out", received, plan.responses().required(), gotData);
}
if (snapshot != null)
snapshot.maybeAbort(command, replicaPlan().consistencyLevel(), received, replicaPlan().readQuorum(), resolver.isDataPresent(), failureReasonByEndpoint);
snapshot.maybeAbort(command, replicaPlan().consistencyLevel(), received, plan.responses().required(), resolver.isDataPresent(), failureReasonByEndpoint);
// failures keeps incrementing, and this.failureReasonByEndpoint keeps getting new entries after signaling.
// Simpler to reason about what happened by copying this.failureReasonByEndpoint and then inferring
@ -209,8 +205,8 @@ public class ReadCallback<E extends Endpoints<E>, P extends ReplicaPlan.ForRead<
// Same as for writes, see AbstractWriteResponseHandler
throw !timedout
? new ReadFailureException(replicaPlan().consistencyLevel(), received, replicaPlan().readQuorum(), resolver.isDataPresent(), failureReasonByEndpoint)
: new ReadTimeoutException(replicaPlan().consistencyLevel(), received, replicaPlan().readQuorum(), resolver.isDataPresent());
? new ReadFailureException(replicaPlan().consistencyLevel(), received, plan.responses().required(), resolver.isDataPresent(), failureReasonByEndpoint)
: new ReadTimeoutException(replicaPlan().consistencyLevel(), received, plan.responses().required(), resolver.isDataPresent());
}
@Override
@ -231,6 +227,7 @@ public class ReadCallback<E extends Endpoints<E>, P extends ReplicaPlan.ForRead<
}
resolver.preprocess(message);
replicaPlan().collectSuccess(message.from());
plan.responses().onResponse(from);
/*
* Ensure that data is present and the response accumulator has properly published the
@ -238,7 +235,7 @@ public class ReadCallback<E extends Endpoints<E>, P extends ReplicaPlan.ForRead<
* the minimum number of required results, but it guarantees at least the minimum will
* be accessible when we do signal. (see CASSANDRA-16807)
*/
if (resolver.isDataPresent() && resolver.responses.size() >= replicaPlan().readQuorum())
if (resolver.isDataPresent() && plan.responses().isSuccessful())
condition.signalAll();
}
@ -274,10 +271,11 @@ public class ReadCallback<E extends Endpoints<E>, P extends ReplicaPlan.ForRead<
public void onFailure(InetAddressAndPort from, RequestFailure failure)
{
assertWaitingFor(from);
failureReasonByEndpoint.put(from, failure.reason);
if (replicaPlan().readQuorum() + failuresUpdater.incrementAndGet(this) > replicaPlan().contacts().size())
failureReasonByEndpoint.put(from, failure.reason);
plan.responses().onFailure(from);
if (plan.responses().isComplete() && !plan.responses().isSuccessful())
condition.signalAll();
}

View File

@ -60,11 +60,11 @@ import org.apache.cassandra.db.rows.UnfilteredRowIterators;
import org.apache.cassandra.exceptions.OverloadedException;
import org.apache.cassandra.exceptions.ReadTimeoutException;
import org.apache.cassandra.exceptions.UnavailableException;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.Endpoints;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.locator.ReplicaPlans;
import org.apache.cassandra.metrics.TableMetrics;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.schema.ColumnMetadata;
@ -164,13 +164,13 @@ public class ReplicaFilteringProtection<E extends Endpoints<E>>
mergeListener = new QueryMergeListener();
}
private UnfilteredPartitionIterator executeReadCommand(ReadCommand cmd, Replica source, ReplicaPlan.Shared<EndpointsForToken, ReplicaPlan.ForTokenRead> replicaPlan)
private UnfilteredPartitionIterator executeReadCommand(ReadCommand cmd, Replica source, CoordinationPlan.ForTokenRead plan)
{
@SuppressWarnings("unchecked")
DataResolver<EndpointsForToken, ReplicaPlan.ForTokenRead> resolver =
new DataResolver<>(coordinator, cmd, replicaPlan, (NoopReadRepair<EndpointsForToken, ReplicaPlan.ForTokenRead>) NoopReadRepair.instance, requestTime);
new DataResolver<>(coordinator, cmd, plan, (NoopReadRepair<EndpointsForToken, ReplicaPlan.ForTokenRead>) NoopReadRepair.instance, requestTime);
ReadCallback<EndpointsForToken, ReplicaPlan.ForTokenRead> handler = new ReadCallback<>(resolver, cmd, replicaPlan, requestTime);
ReadCallback<EndpointsForToken, ReplicaPlan.ForTokenRead> handler = new ReadCallback<>(resolver, cmd, plan, requestTime);
// TODO No tracked path here yet so assert it doesn't handle transient replication correctly
checkState(!source.isTransient());
if (source.isSelf() && coordinator.localReadSupported())
@ -636,20 +636,20 @@ public class ReplicaFilteringProtection<E extends Endpoints<E>>
key,
filter);
ReplicaPlan.ForTokenRead replicaPlan = ReplicaPlans.forSingleReplicaRead(keyspace, key.getToken(), source);
CoordinationPlan.ForTokenRead plan = CoordinationPlan.forSingleReplicaTokenRead(keyspace, key.getToken(), source);
try
{
return executeReadCommand(cmd, source, ReplicaPlan.shared(replicaPlan));
return executeReadCommand(cmd, source, plan);
}
catch (ReadTimeoutException e)
{
int blockFor = consistency.blockFor(replicaPlan.replicationStrategy());
int blockFor = consistency.blockFor(plan.replicationStrategy());
throw new ReadTimeoutException(consistency, blockFor - 1, blockFor, true);
}
catch (UnavailableException e)
{
int blockFor = consistency.blockFor(replicaPlan.replicationStrategy());
int blockFor = consistency.blockFor(plan.replicationStrategy());
throw UnavailableException.create(consistency, blockFor, blockFor - 1);
}
}

View File

@ -38,10 +38,10 @@ import org.apache.cassandra.db.transform.Transformation;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.ExcludingBounds;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.Endpoints;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.locator.ReplicaPlans;
import org.apache.cassandra.service.StorageProxy;
import org.apache.cassandra.service.reads.repair.NoopReadRepair;
import org.apache.cassandra.tracing.Tracing;
@ -93,17 +93,17 @@ public class ShortReadPartitionsProtection extends Transformation<UnfilteredRowI
lastPartitionKey = partition.partitionKey();
Keyspace keyspace = Keyspace.open(command.metadata().keyspace);
/*
* Extend for moreContents() then apply protection to track lastClustering by applyToRow().
*
* If we don't apply the transformation *after* extending the partition with MoreRows,
* applyToRow() method of protection will not be called on the first row of the new extension iterator.
*/
ReplicaPlan.ForTokenRead replicaPlan = ReplicaPlans.forSingleReplicaRead(Keyspace.open(command.metadata().keyspace), partition.partitionKey().getToken(), source);
ReplicaPlan.SharedForTokenRead sharedReplicaPlan = ReplicaPlan.shared(replicaPlan);
CoordinationPlan.ForTokenRead plan = CoordinationPlan.forSingleReplicaTokenRead(keyspace, partition.partitionKey().getToken(), source);
ShortReadRowsProtection protection = new ShortReadRowsProtection(partition.partitionKey(),
command, source,
(cmd) -> executeReadCommand(cmd, sharedReplicaPlan),
(cmd) -> executeReadCommand(cmd, plan),
singleResultCounter,
mergedResultCounter);
return Transformation.apply(MoreRows.extend(partition, protection), protection);
@ -177,16 +177,17 @@ public class ShortReadPartitionsProtection extends Transformation<UnfilteredRowI
: new ExcludingBounds<>(lastPartitionKey, bounds.right);
DataRange newDataRange = cmd.dataRange().forSubRange(newBounds);
ReplicaPlan.ForRangeRead replicaPlan = ReplicaPlans.forSingleReplicaRead(Keyspace.open(command.metadata().keyspace), cmd.dataRange().keyRange(), source, 1);
return executeReadCommand(cmd.withUpdatedLimitsAndDataRange(newLimits, newDataRange), ReplicaPlan.shared(replicaPlan));
Keyspace keyspace = Keyspace.open(command.metadata().keyspace);
CoordinationPlan.ForRangeRead plan = CoordinationPlan.forSingleReplicaRangeRead(keyspace, cmd.dataRange().keyRange(), source, 1);
return executeReadCommand(cmd.withUpdatedLimitsAndDataRange(newLimits, newDataRange), plan);
}
private <E extends Endpoints<E>, P extends ReplicaPlan.ForRead<E, P>>
UnfilteredPartitionIterator executeReadCommand(ReadCommand cmd, ReplicaPlan.Shared<E, P> replicaPlan)
UnfilteredPartitionIterator executeReadCommand(ReadCommand cmd, CoordinationPlan.ForRead<E, P> plan)
{
cmd = coordinator.maybeAllowOutOfRangeReads(cmd, replicaPlan.get().consistencyLevel());
DataResolver<E, P> resolver = new DataResolver<>(coordinator, cmd, replicaPlan, (NoopReadRepair<E, P>)NoopReadRepair.instance, requestTime);
ReadCallback<E, P> handler = new ReadCallback<>(resolver, cmd, replicaPlan, requestTime);
cmd = coordinator.maybeAllowOutOfRangeReads(cmd, plan.consistencyLevel());
DataResolver<E, P> resolver = new DataResolver<>(coordinator, cmd, plan, (NoopReadRepair<E, P>)NoopReadRepair.instance, requestTime);
ReadCallback<E, P> handler = new ReadCallback<>(resolver, cmd, plan, requestTime);
if (source.isSelf() && coordinator.localReadSupported())
{

View File

@ -34,8 +34,8 @@ import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.Bounds;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.locator.ReplicaPlans;
import org.apache.cassandra.schema.ReplicationParams;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.tcm.ClusterMetadata;
@ -43,7 +43,7 @@ import org.apache.cassandra.tcm.compatibility.TokenRingUtils;
import org.apache.cassandra.utils.AbstractIterator;
import org.apache.cassandra.utils.Pair;
class ReplicaPlanIterator extends AbstractIterator<ReplicaPlan.ForRangeRead>
class CoordinationPlanIterator extends AbstractIterator<CoordinationPlan.ForRangeRead>
{
private final Keyspace keyspace;
private final ConsistencyLevel consistency;
@ -53,11 +53,11 @@ class ReplicaPlanIterator extends AbstractIterator<ReplicaPlan.ForRangeRead>
final Iterator<? extends AbstractBounds<PartitionPosition>> ranges;
private final int rangeCount;
ReplicaPlanIterator(AbstractBounds<PartitionPosition> keyRange,
@Nullable Index.QueryPlan indexQueryPlan,
Keyspace keyspace,
TableId tableId,
ConsistencyLevel consistency)
CoordinationPlanIterator(AbstractBounds<PartitionPosition> keyRange,
@Nullable Index.QueryPlan indexQueryPlan,
Keyspace keyspace,
TableId tableId,
ConsistencyLevel consistency)
{
this.indexQueryPlan = indexQueryPlan;
this.keyspace = keyspace;
@ -81,12 +81,12 @@ class ReplicaPlanIterator extends AbstractIterator<ReplicaPlan.ForRangeRead>
}
@Override
protected ReplicaPlan.ForRangeRead computeNext()
protected CoordinationPlan.ForRangeRead computeNext()
{
if (!ranges.hasNext())
return endOfData();
return ReplicaPlans.forRangeRead(keyspace, tableId, indexQueryPlan, consistency, ranges.next(), 1);
return CoordinationPlan.forRangeRead(ClusterMetadata.current(), keyspace, tableId, indexQueryPlan, consistency, ranges.next(), 1);
}
/**

View File

@ -25,20 +25,19 @@ import com.google.common.collect.PeekingIterator;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.locator.ReplicaPlans;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.utils.AbstractIterator;
class ReplicaPlanMerger extends AbstractIterator<ReplicaPlan.ForRangeRead>
class CoordinationPlanMerger extends AbstractIterator<CoordinationPlan.ForRangeRead>
{
private final Keyspace keyspace;
private final ConsistencyLevel consistency;
private final TableId tableId;
private final PeekingIterator<ReplicaPlan.ForRangeRead> ranges;
private final PeekingIterator<CoordinationPlan.ForRangeRead> ranges;
ReplicaPlanMerger(Iterator<ReplicaPlan.ForRangeRead> iterator, Keyspace keyspace, TableId tableId, ConsistencyLevel consistency)
CoordinationPlanMerger(Iterator<CoordinationPlan.ForRangeRead> iterator, Keyspace keyspace, TableId tableId, ConsistencyLevel consistency)
{
this.keyspace = keyspace;
this.tableId = tableId;
@ -47,12 +46,12 @@ class ReplicaPlanMerger extends AbstractIterator<ReplicaPlan.ForRangeRead>
}
@Override
protected ReplicaPlan.ForRangeRead computeNext()
protected CoordinationPlan.ForRangeRead computeNext()
{
if (!ranges.hasNext())
return endOfData();
ReplicaPlan.ForRangeRead current = ranges.next();
CoordinationPlan.ForRangeRead current = ranges.next();
ClusterMetadata metadata = ClusterMetadata.current();
// getRestrictedRange has broken the queried range into per-[vnode] token ranges, but this doesn't take
@ -65,11 +64,11 @@ class ReplicaPlanMerger extends AbstractIterator<ReplicaPlan.ForRangeRead>
// Note: it would be slightly more efficient to have CFS.getRangeSlice on the destination nodes unwraps
// the range if necessary and deal with it. However, we can't start sending wrapped range without breaking
// wire compatibility, so it's likely easier not to bother;
if (current.range().right.isMinimum())
if (current.replicas().range().right.isMinimum())
break;
ReplicaPlan.ForRangeRead next = ranges.peek();
ReplicaPlan.ForRangeRead merged = ReplicaPlans.maybeMerge(metadata, keyspace, tableId, consistency, current, next);
CoordinationPlan.ForRangeRead next = ranges.peek();
CoordinationPlan.ForRangeRead merged = CoordinationPlan.maybeMergeRangeReads(metadata, keyspace, tableId, consistency, current, next);
if (merged == null)
break;

View File

@ -26,6 +26,9 @@ import java.util.function.Function;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.db.*;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.service.reads.DataResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -56,7 +59,6 @@ import org.apache.cassandra.service.accord.txn.TxnResult;
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter;
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.RangeReadTarget;
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.RangeReadWithTarget;
import org.apache.cassandra.service.reads.DataResolver;
import org.apache.cassandra.service.reads.ReadCallback;
import org.apache.cassandra.service.reads.ReadCoordinator;
import org.apache.cassandra.service.reads.repair.ReadRepair;
@ -82,7 +84,7 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
public static final ClientRangeRequestMetrics rangeMetrics = new ClientRangeRequestMetrics("RangeSlice");
final CloseableIterator<ReplicaPlan.ForRangeRead> replicaPlans;
final CloseableIterator<CoordinationPlan.ForRangeRead> coordinatorPlans;
final int totalRangeCount;
final PartitionRangeReadCommand command;
final boolean enforceStrictLiveness;
@ -101,7 +103,7 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
// when it was not good enough initially.
private int liveReturned;
RangeCommandIterator(CloseableIterator<ReplicaPlan.ForRangeRead> replicaPlans,
RangeCommandIterator(CloseableIterator<CoordinationPlan.ForRangeRead> coordinatorPlans,
PartitionRangeReadCommand command,
ReadCoordinator readCoordinator,
int concurrencyFactor,
@ -109,7 +111,7 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
int totalRangeCount,
Dispatcher.RequestTime requestTime)
{
this.replicaPlans = replicaPlans;
this.coordinatorPlans = coordinatorPlans;
this.command = command;
this.readCoordinator = readCoordinator;
this.concurrencyFactor = concurrencyFactor;
@ -127,7 +129,7 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
while (sentQueryIterator == null || !sentQueryIterator.hasNext())
{
// If we don't have more range to handle, we're done
if (!replicaPlans.hasNext())
if (!coordinatorPlans.hasNext())
return endOfData();
// else, sends the next batch of concurrent queries (after having close the previous iterator)
@ -205,31 +207,30 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
return new AccordRangeResponse(result, rangeCommand.isReversed());
}
private SingleRangeResponse executeNormal(ReplicaPlan.ForRangeRead replicaPlan, PartitionRangeReadCommand rangeCommand, ReadCoordinator readCoordinator)
private SingleRangeResponse executeNormal(CoordinationPlan.ForRangeRead plan, PartitionRangeReadCommand rangeCommand, ReadCoordinator readCoordinator)
{
rangeCommand = (PartitionRangeReadCommand) readCoordinator.maybeAllowOutOfRangeReads(rangeCommand, replicaPlan.consistencyLevel());
rangeCommand = (PartitionRangeReadCommand) readCoordinator.maybeAllowOutOfRangeReads(rangeCommand, plan.consistencyLevel());
// If enabled, request repaired data tracking info from full replicas, but
// only if there are multiple full replicas to compare results from.
boolean trackRepairedStatus = DatabaseDescriptor.getRepairedDataTrackingForRangeReadsEnabled()
&& replicaPlan.contacts().filter(Replica::isFull).size() > 1
&& plan.replicas().contacts().filter(Replica::isFull).size() > 1
&& !command.metadata().replicationType().isTracked();
ReplicaPlan.SharedForRangeRead sharedReplicaPlan = ReplicaPlan.shared(replicaPlan);
ReadRepair<EndpointsForRange, ReplicaPlan.ForRangeRead> readRepair =
ReadRepair.create(readCoordinator, command, sharedReplicaPlan, requestTime);
ReadRepair.create(readCoordinator, command, plan, requestTime);
DataResolver<EndpointsForRange, ReplicaPlan.ForRangeRead> resolver =
new DataResolver<>(readCoordinator, rangeCommand, sharedReplicaPlan, readRepair, requestTime, trackRepairedStatus);
new DataResolver<>(readCoordinator, rangeCommand, plan, readRepair, requestTime, trackRepairedStatus);
ReadCallback<EndpointsForRange, ReplicaPlan.ForRangeRead> handler =
new ReadCallback<>(resolver, rangeCommand, sharedReplicaPlan, requestTime);
checkState(!replicaPlan.contacts().anyMatch(Replica::isTransient), "Transient replication requires mutation tracking");
new ReadCallback<>(resolver, rangeCommand, plan, requestTime);
checkState(!plan.replicas().contacts().anyMatch(Replica::isTransient), "Transient replication requires mutation tracking");
if (replicaPlan.contacts().size() == 1 && replicaPlan.contacts().get(0).isSelf() && readCoordinator.localReadSupported())
if (plan.replicas().contacts().size() == 1 && plan.replicas().contacts().get(0).isSelf() && readCoordinator.localReadSupported())
{
Stage.READ.execute(new StorageProxy.LocalReadRunnable(rangeCommand, handler, requestTime, trackRepairedStatus));
}
else
{
for (Replica replica : replicaPlan.contacts())
for (Replica replica : plan.replicas().contacts())
{
Tracing.trace("Enqueuing request to {}", replica);
Message<ReadCommand> message = rangeCommand.createMessage(trackRepairedStatus && replica.isFull(), requestTime);
@ -243,20 +244,20 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
/**
* Queries the provided sub-range.
*
* @param replicaPlan the subRange to query.
* @param plan the subRange to query.
* @param isFirst in the case where multiple queries are sent in parallel, whether that's the first query on
* that batch or not. The reason it matters is that whe paging queries, the command (more specifically the
* {@code DataLimits}) may have "state" information and that state may only be valid for the first query (in
* that it's the query that "continues" whatever we're previously queried).
*/
private PartitionIterator query(ClusterMetadata cm, ReplicaPlan.ForRangeRead replicaPlan, ReadCoordinator readCoordinator, List<ReadRepair<?, ?>> readRepairs, boolean isFirst)
private PartitionIterator query(ClusterMetadata cm, CoordinationPlan.ForRangeRead plan, ReadCoordinator readCoordinator, List<ReadRepair<?, ?>> readRepairs, boolean isFirst)
{
PartitionRangeReadCommand rangeCommand = command.forSubRange(replicaPlan.range(), isFirst);
PartitionRangeReadCommand rangeCommand = command.forSubRange(plan.replicas().range(), isFirst);
// Accord interop execution should always be coordinated through the C* plumbing
if (!readCoordinator.isEventuallyConsistent())
{
SingleRangeResponse response = executeNormal(replicaPlan, rangeCommand, readCoordinator);
SingleRangeResponse response = executeNormal(plan, rangeCommand, readCoordinator);
readRepairs.add(response.getReadRepair());
return response;
}
@ -271,11 +272,11 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
if (accordSplit.target == RangeReadTarget.accord && readCoordinator.isEventuallyConsistent())
{
return executeAccord(cm, accordSplit.read, replicaPlan.consistencyLevel());
return executeAccord(cm, accordSplit.read, plan.consistencyLevel());
}
else
{
return executeNormalWithMigrationSplit(cm, replicaPlan, readCoordinator, readRepairs, accordSplit.read);
return executeNormalWithMigrationSplit(cm, plan, readCoordinator, readRepairs, accordSplit.read);
}
}
@ -310,11 +311,11 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
{
if (accordSplit.target == RangeReadTarget.accord && readCoordinator.isEventuallyConsistent())
{
responses.add(executeAccord(cm, accordSplit.read, replicaPlan.consistencyLevel()));
responses.add(executeAccord(cm, accordSplit.read, plan.consistencyLevel()));
}
else
{
responses.add(executeNormalWithMigrationSplit(cm, replicaPlan, readCoordinator, readRepairs, accordSplit.read));
responses.add(executeNormalWithMigrationSplit(cm, plan, readCoordinator, readRepairs, accordSplit.read));
}
}
@ -325,7 +326,7 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
command.metadata().enforceStrictLiveness());
}
private PartitionIterator executeSplit(RangeReadWithReplication split, ReplicaPlan.ForRangeRead replicaPlan, List<ReadRepair<?, ?>> readRepairs)
private PartitionIterator executeSplit(RangeReadWithReplication split, CoordinationPlan.ForRangeRead replicaPlan, List<ReadRepair<?, ?>> readRepairs)
{
if (split.useTracked)
{
@ -345,20 +346,20 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
* Execute a normal C* range, splitting for migration if needed.
*/
private PartitionIterator executeNormalWithMigrationSplit(ClusterMetadata cm,
ReplicaPlan.ForRangeRead replicaPlan,
ReadCoordinator readCoordinator,
List<ReadRepair<?, ?>> readRepairs,
PartitionRangeReadCommand rangeCommand)
CoordinationPlan.ForRangeRead plan,
ReadCoordinator readCoordinator,
List<ReadRepair<?, ?>> readRepairs,
PartitionRangeReadCommand rangeCommand)
{
List<RangeReadWithReplication> migrationSplits = MigrationRouter.splitRangeRead(cm, rangeCommand);
if (migrationSplits.size() == 1)
return executeSplit(migrationSplits.get(0), replicaPlan, readRepairs);
return executeSplit(migrationSplits.get(0), plan, readRepairs);
List<PartitionIterator> responses = new ArrayList<>(migrationSplits.size());
for (RangeReadWithReplication split : migrationSplits)
responses.add(executeSplit(split, replicaPlan, readRepairs));
responses.add(executeSplit(split, plan, readRepairs));
// Apply limits since migration splits may have gaps in results
return rangeCommand.limits().filter(PartitionIterators.concat(responses),
@ -375,26 +376,26 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
ClusterMetadata cm = ClusterMetadata.current();
try
{
for (int i = 0; i < concurrencyFactor && replicaPlans.hasNext(); )
for (int i = 0; i < concurrencyFactor && coordinatorPlans.hasNext(); )
{
ReplicaPlan.ForRangeRead replicaPlan = replicaPlans.next();
CoordinationPlan.ForRangeRead plan = coordinatorPlans.next();
boolean isFirst = i == 0;
PartitionIterator response;
// Only add the retry wrapper to reroute for the top level coordinator execution
// not Accord's interop execution
if (readCoordinator.isEventuallyConsistent())
{
Function<ClusterMetadata, PartitionIterator> querySupplier = clusterMetadata -> query(clusterMetadata, replicaPlan, readCoordinator, readRepairs, isFirst);
response = retryingPartitionIterator(querySupplier, replicaPlan.consistencyLevel());
Function<ClusterMetadata, PartitionIterator> querySupplier = clusterMetadata -> query(clusterMetadata, plan, readCoordinator, readRepairs, isFirst);
response = retryingPartitionIterator(querySupplier, plan.consistencyLevel());
}
else
{
response = query(cm, replicaPlan, readCoordinator, readRepairs, isFirst);
response = query(cm, plan, readCoordinator, readRepairs, isFirst);
}
concurrentQueries.add(response);
// due to RangeMerger, coordinator may fetch more ranges than required by concurrency factor.
rangesQueried += replicaPlan.vnodeCount();
i += replicaPlan.vnodeCount();
rangesQueried += plan.replicas().vnodeCount();
i += plan.replicas().vnodeCount();
}
batchesRequested++;
}
@ -480,7 +481,7 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
if (sentQueryIterator != null)
sentQueryIterator.close();
replicaPlans.close();
coordinatorPlans.close();
}
finally
{

View File

@ -77,11 +77,11 @@ public class RangeCommands
Tracing.trace("Computing ranges to query");
Keyspace keyspace = Keyspace.open(command.metadata().keyspace);
ReplicaPlanIterator replicaPlans = new ReplicaPlanIterator(command.dataRange().keyRange(),
command.indexQueryPlan(),
keyspace,
command.metadata().id(),
consistencyLevel);
CoordinationPlanIterator replicaPlans = new CoordinationPlanIterator(command.dataRange().keyRange(),
command.indexQueryPlan(),
keyspace,
command.metadata().id(),
consistencyLevel);
if (command.isTopK())
return new ScanAllRangesCommandIterator(keyspace, replicaPlans, command, readCoordinator, replicaPlans.size(), requestTime);
@ -112,7 +112,7 @@ public class RangeCommands
Tracing.trace("Submitting range requests on {} ranges with a concurrency of {}", replicaPlans.size(), concurrencyFactor);
}
ReplicaPlanMerger mergedReplicaPlans = new ReplicaPlanMerger(replicaPlans, keyspace, command.metadata().id(), consistencyLevel);
CoordinationPlanMerger mergedReplicaPlans = new CoordinationPlanMerger(replicaPlans, keyspace, command.metadata().id(), consistencyLevel);
return new RangeCommandIterator(mergedReplicaPlans,
command,
readCoordinator,
@ -150,15 +150,15 @@ public class RangeCommands
try
{
Keyspace keyspace = Keyspace.open(metadata.keyspace);
ReplicaPlanIterator rangeIterator = new ReplicaPlanIterator(DataRange.allData(metadata.partitioner).keyRange(),
null,
keyspace,
metadata.id,
consistency);
CoordinationPlanIterator rangeIterator = new CoordinationPlanIterator(DataRange.allData(metadata.partitioner).keyRange(),
null,
keyspace,
metadata.id,
consistency);
// Called for the side effect of running assureSufficientLiveReplicasForRead.
// Deliberately called with an invalid vnode count in case it is used elsewhere in the future..
rangeIterator.forEachRemaining(r -> ReplicaPlans.forRangeRead(keyspace, metadata.id, null, consistency, r.range(), -1));
rangeIterator.forEachRemaining(r -> ReplicaPlans.forRangeRead(keyspace, metadata.id, null, consistency, r.replicas().range(), -1));
return true;
}
catch (UnavailableException e)

View File

@ -30,10 +30,10 @@ import org.apache.cassandra.db.PartitionRangeReadCommand;
import org.apache.cassandra.db.ReadCommand;
import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.EndpointsForRange;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.locator.ReplicaPlans;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.service.reads.DataResolver;
@ -61,13 +61,13 @@ public class ScanAllRangesCommandIterator extends RangeCommandIterator
{
private final Keyspace keyspace;
ScanAllRangesCommandIterator(Keyspace keyspace, CloseableIterator<ReplicaPlan.ForRangeRead> replicaPlans,
ScanAllRangesCommandIterator(Keyspace keyspace, CloseableIterator<CoordinationPlan.ForRangeRead> coordinationPlans,
PartitionRangeReadCommand command,
ReadCoordinator readCoordinator,
int totalRangeCount,
Dispatcher.RequestTime requestTime)
{
super(replicaPlans, command, readCoordinator, totalRangeCount, totalRangeCount, totalRangeCount, requestTime);
super(coordinationPlans, command, readCoordinator, totalRangeCount, totalRangeCount, totalRangeCount, requestTime);
Preconditions.checkState(command.isTopK());
this.keyspace = keyspace;
@ -79,23 +79,22 @@ public class ScanAllRangesCommandIterator extends RangeCommandIterator
// get all replicas to contact
Set<InetAddressAndPort> replicasToQuery = null;
ConsistencyLevel consistencyLevel = null;
while (replicaPlans.hasNext())
while (coordinatorPlans.hasNext())
{
if (replicasToQuery == null)
replicasToQuery = new HashSet<>();
ReplicaPlan.ForRangeRead replicaPlan = replicaPlans.next();
replicasToQuery.addAll(replicaPlan.contacts().endpoints());
CoordinationPlan.ForRangeRead replicaPlan = coordinatorPlans.next();
replicasToQuery.addAll(replicaPlan.replicas().contacts().endpoints());
consistencyLevel = replicaPlan.consistencyLevel();
}
if (replicasToQuery == null || replicasToQuery.isEmpty())
return EmptyIterators.partition();
ReplicaPlan.ForRangeRead plan = ReplicaPlans.forFullRangeRead(keyspace, consistencyLevel, command.dataRange().keyRange(), replicasToQuery, totalRangeCount);
ReplicaPlan.SharedForRangeRead sharedReplicaPlan = ReplicaPlan.shared(plan);
DataResolver<EndpointsForRange, ReplicaPlan.ForRangeRead> resolver = new DataResolver<>(ReadCoordinator.DEFAULT, command, sharedReplicaPlan, NoopReadRepair.instance, requestTime, false);
ReadCallback<EndpointsForRange, ReplicaPlan.ForRangeRead> handler = new ReadCallback<>(resolver, command, sharedReplicaPlan, requestTime);
CoordinationPlan.ForRangeRead plan = CoordinationPlan.forFullRangeRead(keyspace, consistencyLevel, command.dataRange().keyRange(), replicasToQuery, totalRangeCount);
DataResolver<EndpointsForRange, ReplicaPlan.ForRangeRead> resolver = new DataResolver<>(ReadCoordinator.DEFAULT, command, plan, NoopReadRepair.instance, requestTime, false);
ReadCallback<EndpointsForRange, ReplicaPlan.ForRangeRead> handler = new ReadCallback<>(resolver, command, plan, requestTime);
int nodes = 0;
for (InetAddressAndPort endpoint : replicasToQuery)
@ -106,7 +105,7 @@ public class ScanAllRangesCommandIterator extends RangeCommandIterator
nodes++;
}
rangesQueried += plan.vnodeCount();
rangesQueried += plan.replicas().vnodeCount();
batchesRequested++;
Tracing.trace("Submitted scanning all ranges requests to {} nodes", nodes);

View File

@ -34,6 +34,7 @@ import org.apache.cassandra.db.ReadCommand;
import org.apache.cassandra.db.SinglePartitionReadCommand;
import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.exceptions.ReadTimeoutException;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.Endpoints;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaPlan;
@ -58,7 +59,7 @@ public abstract class AbstractReadRepair<E extends Endpoints<E>, P extends Repli
protected final ReadCoordinator coordinator;
protected final ReadCommand command;
protected final Dispatcher.RequestTime requestTime;
protected final ReplicaPlan.Shared<E, P> replicaPlan;
protected final CoordinationPlan.ForRead<E, P> plan;
protected final ColumnFamilyStore cfs;
private volatile DigestRepair<E, P> digestRepair = null;
@ -78,19 +79,19 @@ public abstract class AbstractReadRepair<E extends Endpoints<E>, P extends Repli
}
public AbstractReadRepair(ReadCoordinator coordinator, ReadCommand command,
ReplicaPlan.Shared<E, P> replicaPlan,
CoordinationPlan.ForRead<E, P> plan,
Dispatcher.RequestTime requestTime)
{
this.coordinator = coordinator;
this.command = command;
this.requestTime = requestTime;
this.replicaPlan = replicaPlan;
this.plan = plan;
this.cfs = Keyspace.openAndGetStore(command.metadata());
}
protected P replicaPlan()
{
return replicaPlan.get();
return plan.replicas();
}
void sendReadCommand(Replica to, ReadCallback<E, P> readCallback, boolean speculative, boolean trackRepairedStatus)
@ -135,9 +136,10 @@ public abstract class AbstractReadRepair<E extends Endpoints<E>, P extends Repli
*/
boolean trackRepairedStatus = DatabaseDescriptor.getRepairedDataTrackingForPartitionReadsEnabled();
CoordinationPlan.ForRead<E, P> repairPlan = plan.copyWithResetTracker();
// Do a full data read to resolve the correct response (and repair node that need be)
DataResolver<E, P> resolver = new DataResolver<>(coordinator, command, replicaPlan, this, requestTime, trackRepairedStatus);
ReadCallback<E, P> readCallback = new ReadCallback<>(resolver, command, replicaPlan, requestTime);
DataResolver<E, P> resolver = new DataResolver<>(coordinator, command, repairPlan, this, requestTime, trackRepairedStatus);
ReadCallback<E, P> readCallback = new ReadCallback<>(resolver, command, repairPlan, requestTime);
digestRepair = new DigestRepair<>(resolver, readCallback, resultConsumer);
@ -175,7 +177,7 @@ public abstract class AbstractReadRepair<E extends Endpoints<E>, P extends Repli
ConsistencyLevel consistency = replicaPlan().consistencyLevel();
ConsistencyLevel speculativeCL = consistency.isDatacenterLocal() ? ConsistencyLevel.LOCAL_QUORUM : ConsistencyLevel.QUORUM;
return consistency != ConsistencyLevel.EACH_QUORUM
&& consistency.satisfies(speculativeCL, replicaPlan.get().replicationStrategy())
&& consistency.satisfies(speculativeCL, plan.replicationStrategy())
&& cfs.sampleReadLatencyMicros <= command.getTimeout(MICROSECONDS);
}
@ -193,7 +195,7 @@ public abstract class AbstractReadRepair<E extends Endpoints<E>, P extends Repli
if (uncontacted == null)
return;
replicaPlan.addToContacts(uncontacted);
plan.addToContacts(uncontacted);
sendReadCommand(uncontacted, repair.readCallback, true, false);
ReadRepairMetrics.speculatedRead.mark();
ReadRepairDiagnostics.speculatedRead(this, uncontacted.endpoint(), replicaPlan());

View File

@ -43,6 +43,7 @@ import org.apache.cassandra.db.ReadCommand;
import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
import org.apache.cassandra.exceptions.ReadTimeoutException;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.Endpoints;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaPlan;
@ -135,9 +136,9 @@ public class BlockingReadRepair<E extends Endpoints<E>, P extends ReplicaPlan.Fo
ForWrite repairPlan();
}
BlockingReadRepair(ReadCoordinator coordinator, ReadCommand command, ReplicaPlan.Shared<E, P> replicaPlan, Dispatcher.RequestTime requestTime)
BlockingReadRepair(ReadCoordinator coordinator, ReadCommand command, CoordinationPlan.ForRead<E, P> plan, Dispatcher.RequestTime requestTime)
{
super(coordinator, command, replicaPlan, requestTime);
super(coordinator, command, plan, requestTime);
}
@Override
@ -293,7 +294,7 @@ public class BlockingReadRepair<E extends Endpoints<E>, P extends ReplicaPlan.Fo
public void repairPartitionDirectly(ReadCoordinator readCoordinator, DecoratedKey dk, Map<Replica, Mutation> mutations, ForWrite writePlan)
{
ReadRepair delegateRR = ReadRepairStrategy.BLOCKING.create(readCoordinator, command, replicaPlan, requestTime);
ReadRepair delegateRR = ReadRepairStrategy.BLOCKING.create(readCoordinator, command, plan, requestTime);
delegateRR.repairPartition(dk, mutations, writePlan, ReadRepairSource.REPAIR_VIA_ACCORD);
delegateRR.maybeSendAdditionalWrites();
delegateRR.awaitWrites();

View File

@ -26,6 +26,7 @@ import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.Mutation;
import org.apache.cassandra.db.ReadCommand;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.Endpoints;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaPlan;
@ -41,9 +42,9 @@ import org.apache.cassandra.transport.Dispatcher;
public class ReadOnlyReadRepair<E extends Endpoints<E>, P extends ReplicaPlan.ForRead<E, P>>
extends AbstractReadRepair<E, P>
{
ReadOnlyReadRepair(ReadCoordinator coordinator, ReadCommand command, ReplicaPlan.Shared<E, P> replicaPlan, Dispatcher.RequestTime requestTime)
ReadOnlyReadRepair(ReadCoordinator coordinator, ReadCommand command, CoordinationPlan.ForRead<E, P> plan, Dispatcher.RequestTime requestTime)
{
super(coordinator, command, replicaPlan, requestTime);
super(coordinator, command, plan, requestTime);
}
@Override

View File

@ -29,6 +29,7 @@ import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
import org.apache.cassandra.exceptions.ReadTimeoutException;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.Endpoints;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaPlan;
@ -41,13 +42,13 @@ public interface ReadRepair<E extends Endpoints<E>, P extends ReplicaPlan.ForRea
public interface Factory
{
<E extends Endpoints<E>, P extends ReplicaPlan.ForRead<E, P>>
ReadRepair<E, P> create(ReadCoordinator coordinator, ReadCommand command, ReplicaPlan.Shared<E, P> replicaPlan, Dispatcher.RequestTime requestTime);
ReadRepair<E, P> create(ReadCoordinator coordinator, ReadCommand command, CoordinationPlan.ForRead<E, P> plan, Dispatcher.RequestTime requestTime);
}
static <E extends Endpoints<E>, P extends ReplicaPlan.ForRead<E, P>>
ReadRepair<E, P> create(ReadCoordinator coordinator, ReadCommand command, ReplicaPlan.Shared<E, P> replicaPlan, Dispatcher.RequestTime requestTime)
ReadRepair<E, P> create(ReadCoordinator coordinator, ReadCommand command, CoordinationPlan.ForRead<E, P> plan, Dispatcher.RequestTime requestTime)
{
return command.metadata().params.readRepair.create(coordinator, command, replicaPlan, requestTime);
return command.metadata().params.readRepair.create(coordinator, command, plan, requestTime);
}
/**

View File

@ -19,6 +19,7 @@
package org.apache.cassandra.service.reads.repair;
import org.apache.cassandra.db.ReadCommand;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.Endpoints;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.service.reads.ReadCoordinator;
@ -31,18 +32,18 @@ public enum ReadRepairStrategy implements ReadRepair.Factory
NONE
{
public <E extends Endpoints<E>, P extends ReplicaPlan.ForRead<E, P>>
ReadRepair<E, P> create(ReadCoordinator coordinator, ReadCommand command, ReplicaPlan.Shared<E, P> replicaPlan, Dispatcher.RequestTime requestTime)
ReadRepair<E, P> create(ReadCoordinator coordinator, ReadCommand command, CoordinationPlan.ForRead<E, P> plan, Dispatcher.RequestTime requestTime)
{
return new ReadOnlyReadRepair<>(coordinator, command, replicaPlan, requestTime);
return new ReadOnlyReadRepair<>(coordinator, command, plan, requestTime);
}
},
BLOCKING
{
public <E extends Endpoints<E>, P extends ReplicaPlan.ForRead<E, P>>
ReadRepair<E, P> create(ReadCoordinator coordinator, ReadCommand command, ReplicaPlan.Shared<E, P> replicaPlan, Dispatcher.RequestTime requestTime)
ReadRepair<E, P> create(ReadCoordinator coordinator, ReadCommand command, CoordinationPlan.ForRead<E, P> plan, Dispatcher.RequestTime requestTime)
{
return new BlockingReadRepair<>(coordinator, command, replicaPlan, requestTime);
return new BlockingReadRepair<>(coordinator, command, plan, requestTime);
}
};

View File

@ -53,9 +53,9 @@ import org.apache.cassandra.dht.ExcludingBounds;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.index.transactions.UpdateTransaction;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.locator.ReplicaPlans;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.concurrent.Future;
@ -333,14 +333,15 @@ public abstract class PartialTrackedRangeRead extends PartialTrackedRead
Keyspace keyspace = Keyspace.open(command.metadata().keyspace);
PartitionRangeReadCommand followUpCmd = command.withUpdatedLimitsAndDataRange(newLimits, newDataRange);
ReplicaPlan.ForRangeRead replicaPlan = ReplicaPlans.forRangeRead(keyspace,
command.metadata().id,
followUpCmd.indexQueryPlan(),
consistencyLevel,
followUpCmd.dataRange().keyRange(),
1);
CoordinationPlan.ForRangeRead plan = CoordinationPlan.forRangeRead(ClusterMetadata.current(),
keyspace,
command.metadata().id,
followUpCmd.indexQueryPlan(),
consistencyLevel,
followUpCmd.dataRange().keyRange(),
1);
TrackedRead.Range read = TrackedRead.Range.create(followUpCmd, replicaPlan, requestTime);
TrackedRead.Range read = TrackedRead.Range.create(followUpCmd, plan, requestTime);
logger.trace("Short read detected, starting followup read {}", read);
return read;
}

View File

@ -57,13 +57,13 @@ import org.apache.cassandra.gms.FailureDetector;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.Endpoints;
import org.apache.cassandra.locator.EndpointsForRange;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.locator.ReplicaPlans;
import org.apache.cassandra.net.IVerbHandler;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessageFlag;
@ -167,7 +167,7 @@ public abstract class TrackedRead<E extends Endpoints<E>, P extends ReplicaPlan.
private final Id readId = Id.nextId();
private final ReadCommand command;
private final ReplicaPlan.AbstractForRead<E, P> replicaPlan;
private final CoordinationPlan.ForRead<E, P> plan;
private final ConsistencyLevel consistencyLevel;
private final Dispatcher.RequestTime requestTime;
@ -188,17 +188,17 @@ public abstract class TrackedRead<E extends Endpoints<E>, P extends ReplicaPlan.
}
}
public TrackedRead(ReadCommand command, ReplicaPlan.AbstractForRead<E, P> replicaPlan, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
public TrackedRead(ReadCommand command, CoordinationPlan.ForRead<E, P> plan, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
{
this.command = command;
this.replicaPlan = replicaPlan;
this.plan = plan;
this.consistencyLevel = consistencyLevel;
this.requestTime = requestTime;
}
public ReplicaPlan.AbstractForRead<E, P> replicaPlan()
public ReplicaPlan.ForRead<E, P> replicaPlan()
{
return replicaPlan;
return plan.replicas();
}
@Override
@ -216,9 +216,9 @@ public abstract class TrackedRead<E extends Endpoints<E>, P extends ReplicaPlan.
public static class Partition extends TrackedRead<EndpointsForToken, ReplicaPlan.ForTokenRead>
{
private Partition(SinglePartitionReadCommand command, ReplicaPlan.AbstractForRead<EndpointsForToken, ReplicaPlan.ForTokenRead> replicaPlan, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
private Partition(SinglePartitionReadCommand command, CoordinationPlan.ForTokenRead plan, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
{
super(command, replicaPlan, consistencyLevel, requestTime);
super(command, plan, consistencyLevel, requestTime);
}
public static Partition create(ClusterMetadata metadata, SinglePartitionReadCommand command, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
@ -227,15 +227,15 @@ public abstract class TrackedRead<E extends Endpoints<E>, P extends ReplicaPlan.
Keyspace keyspace = Keyspace.open(command.metadata().keyspace);
ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(command.metadata().id);
SpeculativeRetryPolicy retry = cfs.metadata().params.speculativeRetry;
ReplicaPlan.ForTokenRead replicaPlan = ReplicaPlans.forRead(metadata,
keyspace,
cfs.getTableId(),
command.partitionKey().getToken(),
command.indexQueryPlan(),
consistencyLevel,
retry,
ReadCoordinator.DEFAULT);
return new Partition(command, replicaPlan, consistencyLevel, requestTime);
CoordinationPlan.ForTokenRead plan = CoordinationPlan.forTokenRead(metadata,
keyspace,
cfs.getTableId(),
command.partitionKey().getToken(),
command.indexQueryPlan(),
consistencyLevel,
retry,
ReadCoordinator.DEFAULT);
return new Partition(command, plan, consistencyLevel, requestTime);
}
@Override
@ -247,15 +247,15 @@ public abstract class TrackedRead<E extends Endpoints<E>, P extends ReplicaPlan.
public static class Range extends TrackedRead<EndpointsForRange, ReplicaPlan.ForRangeRead>
{
private Range(PartitionRangeReadCommand command, ReplicaPlan.AbstractForRead<EndpointsForRange, ReplicaPlan.ForRangeRead> replicaPlan, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
private Range(PartitionRangeReadCommand command, CoordinationPlan.ForRangeRead plan, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
{
super(command, replicaPlan, consistencyLevel, requestTime);
super(command, plan, consistencyLevel, requestTime);
}
public static TrackedRead.Range create(PartitionRangeReadCommand command, ReplicaPlan.ForRangeRead replicaPlan, Dispatcher.RequestTime requestTime)
public static TrackedRead.Range create(PartitionRangeReadCommand command, CoordinationPlan.ForRangeRead plan, Dispatcher.RequestTime requestTime)
{
Preconditions.checkArgument(command.metadata().replicationType().isTracked());
return new Range(command, replicaPlan, replicaPlan.consistencyLevel(), requestTime);
return new Range(command, plan, plan.consistencyLevel(), requestTime);
}
@Override
@ -285,7 +285,7 @@ public abstract class TrackedRead<E extends Endpoints<E>, P extends ReplicaPlan.
{
// TODO: skip local coordination if this node knows its recovering from an outage
// TODO: read speculation
Replica localReplica = replicaPlan.lookup(FBUtilities.getBroadcastAddressAndPort());
Replica localReplica = plan.replicas().lookup(FBUtilities.getBroadcastAddressAndPort());
if (localReplica != null)
readMetrics.localRequests.mark();
else
@ -294,10 +294,10 @@ public abstract class TrackedRead<E extends Endpoints<E>, P extends ReplicaPlan.
// create an id
// select data node
// select summary nodes
E selected = replicaPlan.contacts().filter(r -> FailureDetector.instance.isAlive(r.endpoint()));
if (selected.size() < replicaPlan.readQuorum())
throw new UnavailableException(String.format("Insufficient replicas available for read (%d < %d)", selected.size(), replicaPlan.readQuorum()),
replicaPlan.consistencyLevel(), selected.size(), replicaPlan.readQuorum());
E selected = plan.replicas().contacts().filter(r -> FailureDetector.instance.isAlive(r.endpoint()));
if (selected.size() < plan.replicas().readQuorum())
throw new UnavailableException(String.format("Insufficient replicas available for read (%d < %d)", selected.size(), plan.replicas().readQuorum()),
plan.consistencyLevel(), selected.size(), plan.replicas().readQuorum());
Replica dataReplica = localReplica != null && localReplica.isFull()
? localReplica
: Iterables.getOnlyElement(selected.filter(Replica::isFull, 1));
@ -413,17 +413,17 @@ public abstract class TrackedRead<E extends Endpoints<E>, P extends ReplicaPlan.
RequestFailure failure = (RequestFailure) ex;
if (failure.reason == RequestFailureReason.TIMEOUT)
{
throw new ReadTimeoutException(replicaPlan.consistencyLevel(), 0, replicaPlan.readQuorum(), false);
throw new ReadTimeoutException(plan.consistencyLevel(), 0, plan.replicas().readQuorum(), false);
}
reasons = failure.reasonByEndpoint();
}
throw new ReadFailureException(replicaPlan.consistencyLevel(), 0, replicaPlan.readQuorum(), false, reasons);
throw new ReadFailureException(plan.consistencyLevel(), 0, plan.replicas().readQuorum(), false, reasons);
}
catch (TimeoutException e)
{
throw new ReadTimeoutException(replicaPlan.consistencyLevel(), 0, replicaPlan.readQuorum(), false);
throw new ReadTimeoutException(plan.consistencyLevel(), 0, plan.replicas().readQuorum(), false);
}
}

View File

@ -0,0 +1,73 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.locator;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.tcm.ClusterMetadata;
/**
* Creates coordination plans with arbitrary replica plans
*/
public class CoordinationPlans
{
private static ResponseTracker createTrackerForWrite(ReplicaPlan.ForWrite plan)
{
return plan.replicationStrategy().createTrackerForWrite(plan.consistencyLevel(), plan, plan.pending, ClusterMetadata.current());
}
public static CoordinationPlan.ForWriteWithIdeal create(ReplicaPlan.ForWrite plan, ConsistencyLevel idealCL)
{
ResponseTracker tracker = createTrackerForWrite(plan);
CoordinationPlan.ForWrite idealPlan = null;
if (idealCL != null && idealCL != plan.consistencyLevel())
{
ReplicaPlan.ForWrite idealReplicaPlan = plan.withConsistencyLevel(idealCL);
ResponseTracker idealTracker = createTrackerForWrite(idealReplicaPlan);
idealPlan = new CoordinationPlan.ForWrite(idealReplicaPlan, idealTracker);
}
return new CoordinationPlan.ForWriteWithIdeal(plan, tracker, idealPlan);
}
public static CoordinationPlan.ForTokenRead create(ReplicaPlan.ForTokenRead plan)
{
return new CoordinationPlan.ForTokenRead(ReplicaPlan.shared(plan), trackerForRead(plan));
}
public static CoordinationPlan.ForTokenRead create(ReplicaPlan.SharedForTokenRead shared)
{
return new CoordinationPlan.ForTokenRead(shared, trackerForRead(shared.get()));
}
public static CoordinationPlan.ForRangeRead create(ReplicaPlan.ForRangeRead plan)
{
return new CoordinationPlan.ForRangeRead(ReplicaPlan.shared(plan), trackerForRead(plan));
}
public static CoordinationPlan.ForRangeRead create(ReplicaPlan.SharedForRangeRead shared)
{
return new CoordinationPlan.ForRangeRead(shared, trackerForRead(shared.get()));
}
private static <E extends Endpoints<E>, P extends ReplicaPlan.ForRead<E, P>> ResponseTracker trackerForRead(P plan)
{
return new SimpleResponseTracker(plan.readQuorum(), plan.readCandidates().size());
}
}

View File

@ -0,0 +1,401 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.locator;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.apache.cassandra.exceptions.RequestFailureReason;
import org.apache.cassandra.tcm.membership.Location;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
public class PerDcResponseTrackerTest
{
private static final RequestFailureReason TIMEOUT = RequestFailureReason.TIMEOUT;
private InetAddressAndPort endpoint(String ip) throws UnknownHostException
{
return InetAddressAndPort.getByName(ip);
}
private Locator mockLocator(Map<InetAddressAndPort, String> endpointToDc) throws Exception
{
Locator locator = mock(Locator.class);
for (Map.Entry<InetAddressAndPort, String> entry : endpointToDc.entrySet())
{
when(locator.location(entry.getKey())).thenReturn(new Location(entry.getValue(), "rack1"));
}
return locator;
}
/**
* Helper to create a map of SimpleResponseTrackers from blockFor/totalReplicas config.
*/
private Map<String, ResponseTracker> simpleTrackers(Object... dcBlockForTotal)
{
Map<String, ResponseTracker> trackers = new HashMap<>();
for (int i = 0; i < dcBlockForTotal.length; i += 3)
{
String dc = (String) dcBlockForTotal[i];
int blockFor = (Integer) dcBlockForTotal[i + 1];
int totalReplicas = (Integer) dcBlockForTotal[i + 2];
trackers.put(dc, new SimpleResponseTracker(blockFor, totalReplicas));
}
return trackers;
}
@Test
public void testAllDcsReachQuorum() throws Exception
{
Map<String, ResponseTracker> trackers = simpleTrackers(
"DC1", 2, 3,
"DC2", 2, 3
);
Map<InetAddressAndPort, String> endpointToDc = new HashMap<>();
endpointToDc.put(endpoint("127.0.0.1"), "DC1");
endpointToDc.put(endpoint("127.0.0.2"), "DC1");
endpointToDc.put(endpoint("127.0.0.3"), "DC1");
endpointToDc.put(endpoint("192.168.1.1"), "DC2");
endpointToDc.put(endpoint("192.168.1.2"), "DC2");
endpointToDc.put(endpoint("192.168.1.3"), "DC2");
Locator locator = mockLocator(endpointToDc);
PerDcResponseTracker tracker = new PerDcResponseTracker(trackers, locator);
// DC1 reaches quorum
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
assertFalse(tracker.isComplete()); // DC2 not done yet
// DC2 reaches quorum
tracker.onResponse(endpoint("192.168.1.1"));
tracker.onResponse(endpoint("192.168.1.2"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(4, tracker.received());
assertEquals(4, tracker.required()); // 2 + 2
}
@Test
public void testOneDcFails() throws Exception
{
Map<String, ResponseTracker> trackers = simpleTrackers(
"DC1", 2, 3,
"DC2", 2, 3
);
Map<InetAddressAndPort, String> endpointToDc = new HashMap<>();
endpointToDc.put(endpoint("127.0.0.1"), "DC1");
endpointToDc.put(endpoint("127.0.0.2"), "DC1");
endpointToDc.put(endpoint("127.0.0.3"), "DC1");
endpointToDc.put(endpoint("192.168.1.1"), "DC2");
endpointToDc.put(endpoint("192.168.1.2"), "DC2");
endpointToDc.put(endpoint("192.168.1.3"), "DC2");
Locator locator = mockLocator(endpointToDc);
PerDcResponseTracker tracker = new PerDcResponseTracker(trackers, locator);
// DC1 reaches quorum
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
// DC2 fails (all fail)
tracker.onFailure(endpoint("192.168.1.1"), TIMEOUT);
tracker.onFailure(endpoint("192.168.1.2"), TIMEOUT);
tracker.onFailure(endpoint("192.168.1.3"), TIMEOUT);
assertTrue(tracker.isComplete()); // DC2 reached definite failure
assertFalse(tracker.isSuccessful()); // DC2 failed
assertEquals(2, tracker.received());
assertEquals(3, tracker.failures());
}
@Test
public void testPartialDcProgress() throws Exception
{
Map<String, ResponseTracker> trackers = simpleTrackers(
"DC1", 2, 3,
"DC2", 2, 3
);
Map<InetAddressAndPort, String> endpointToDc = new HashMap<>();
endpointToDc.put(endpoint("127.0.0.1"), "DC1");
endpointToDc.put(endpoint("192.168.1.1"), "DC2");
Locator locator = mockLocator(endpointToDc);
PerDcResponseTracker tracker = new PerDcResponseTracker(trackers, locator);
// DC1 gets one response
tracker.onResponse(endpoint("127.0.0.1"));
assertFalse(tracker.isComplete());
assertFalse(tracker.isSuccessful());
assertEquals(1, tracker.received());
assertEquals(4, tracker.required()); // 2 + 2
}
@Test
public void testIgnoresUnknownDc() throws Exception
{
Map<String, ResponseTracker> trackers = simpleTrackers(
"DC1", 2, 3
);
Map<InetAddressAndPort, String> endpointToDc = new HashMap<>();
endpointToDc.put(endpoint("127.0.0.1"), "DC1");
endpointToDc.put(endpoint("127.0.0.2"), "DC1");
endpointToDc.put(endpoint("192.168.1.1"), "DC2"); // DC2 not in config
Locator locator = mockLocator(endpointToDc);
PerDcResponseTracker tracker = new PerDcResponseTracker(trackers, locator);
// DC2 response is ignored
tracker.onResponse(endpoint("192.168.1.1"));
assertFalse(tracker.countsTowardQuorum(endpoint("192.168.1.1")));
assertEquals(0, tracker.received());
// DC1 responses count
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(2, tracker.received());
}
@Test
public void testAsymmetricRequirements() throws Exception
{
Map<String, ResponseTracker> trackers = simpleTrackers(
"DC1", 3, 5, // Need 3 of 5
"DC2", 2, 3 // Need 2 of 3
);
Map<InetAddressAndPort, String> endpointToDc = new HashMap<>();
endpointToDc.put(endpoint("127.0.0.1"), "DC1");
endpointToDc.put(endpoint("127.0.0.2"), "DC1");
endpointToDc.put(endpoint("127.0.0.3"), "DC1");
endpointToDc.put(endpoint("192.168.1.1"), "DC2");
endpointToDc.put(endpoint("192.168.1.2"), "DC2");
Locator locator = mockLocator(endpointToDc);
PerDcResponseTracker tracker = new PerDcResponseTracker(trackers, locator);
// DC2 reaches quorum (2 of 3)
tracker.onResponse(endpoint("192.168.1.1"));
tracker.onResponse(endpoint("192.168.1.2"));
assertFalse(tracker.isComplete()); // DC1 not done
// DC1 reaches quorum (3 of 5)
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
tracker.onResponse(endpoint("127.0.0.3"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(5, tracker.received());
assertEquals(5, tracker.required()); // 3 + 2
}
// Aggregation tests
@Test
public void testRequiredSum() throws Exception
{
Map<String, ResponseTracker> trackers = simpleTrackers(
"DC1", 3, 5,
"DC2", 2, 3,
"DC3", 1, 2
);
Map<InetAddressAndPort, String> endpointToDc = new HashMap<>();
Locator locator = mockLocator(endpointToDc);
PerDcResponseTracker tracker = new PerDcResponseTracker(trackers, locator);
assertEquals(6, tracker.required()); // 3 + 2 + 1
}
@Test
public void testReceivedSum() throws Exception
{
Map<String, ResponseTracker> trackers = simpleTrackers(
"DC1", 2, 3,
"DC2", 2, 3
);
Map<InetAddressAndPort, String> endpointToDc = new HashMap<>();
endpointToDc.put(endpoint("127.0.0.1"), "DC1");
endpointToDc.put(endpoint("127.0.0.2"), "DC1");
endpointToDc.put(endpoint("192.168.1.1"), "DC2");
Locator locator = mockLocator(endpointToDc);
PerDcResponseTracker tracker = new PerDcResponseTracker(trackers, locator);
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
tracker.onResponse(endpoint("192.168.1.1"));
assertEquals(3, tracker.received()); // 2 from DC1 + 1 from DC2
}
@Test
public void testFailuresSum() throws Exception
{
Map<String, ResponseTracker> trackers = simpleTrackers(
"DC1", 2, 3,
"DC2", 2, 3
);
Map<InetAddressAndPort, String> endpointToDc = new HashMap<>();
endpointToDc.put(endpoint("127.0.0.1"), "DC1");
endpointToDc.put(endpoint("192.168.1.1"), "DC2");
endpointToDc.put(endpoint("192.168.1.2"), "DC2");
Locator locator = mockLocator(endpointToDc);
PerDcResponseTracker tracker = new PerDcResponseTracker(trackers, locator);
tracker.onFailure(endpoint("127.0.0.1"), TIMEOUT);
tracker.onFailure(endpoint("192.168.1.1"), TIMEOUT);
tracker.onFailure(endpoint("192.168.1.2"), TIMEOUT);
assertEquals(3, tracker.failures()); // 1 from DC1 + 2 from DC2
}
// Composition tests
@Test
public void testCountsTowardQuorum() throws Exception
{
Map<String, ResponseTracker> trackers = simpleTrackers(
"DC1", 2, 3
);
Map<InetAddressAndPort, String> endpointToDc = new HashMap<>();
endpointToDc.put(endpoint("127.0.0.1"), "DC1");
endpointToDc.put(endpoint("192.168.1.1"), "DC2");
Locator locator = mockLocator(endpointToDc);
PerDcResponseTracker tracker = new PerDcResponseTracker(trackers, locator);
assertTrue(tracker.countsTowardQuorum(endpoint("127.0.0.1"))); // DC1 tracked
assertFalse(tracker.countsTowardQuorum(endpoint("192.168.1.1"))); // DC2 not tracked
}
@Test
public void testGetTrackerForDc() throws Exception
{
Map<String, ResponseTracker> trackers = simpleTrackers(
"DC1", 2, 3,
"DC2", 1, 2
);
Locator locator = mock(Locator.class);
PerDcResponseTracker tracker = new PerDcResponseTracker(trackers, locator);
assertNotNull(tracker.getTrackerForDc("DC1"));
assertNotNull(tracker.getTrackerForDc("DC2"));
assertNull(tracker.getTrackerForDc("DC3"));
assertEquals(2, tracker.getTrackerForDc("DC1").required());
assertEquals(1, tracker.getTrackerForDc("DC2").required());
}
@Test
public void testWithWriteResponseTrackers() throws Exception
{
// Test that PerDcResponseTracker works with WriteResponseTrackers (double-count model)
Map<String, ResponseTracker> trackers = new HashMap<>();
// DC1: baseBlockFor=2, totalBlockFor=3, committed=3, pending=1
trackers.put("DC1", new WriteResponseTracker(2, 3, 3, 1,
addr -> addr.getHostAddress(false).equals("127.0.0.4"))); // .4 is pending
// DC2: no pending, degenerates to simple case
trackers.put("DC2", new SimpleResponseTracker(2, 3));
Map<InetAddressAndPort, String> endpointToDc = new HashMap<>();
endpointToDc.put(endpoint("127.0.0.1"), "DC1");
endpointToDc.put(endpoint("127.0.0.2"), "DC1");
endpointToDc.put(endpoint("127.0.0.3"), "DC1");
endpointToDc.put(endpoint("127.0.0.4"), "DC1"); // pending
endpointToDc.put(endpoint("192.168.1.1"), "DC2");
endpointToDc.put(endpoint("192.168.1.2"), "DC2");
endpointToDc.put(endpoint("192.168.1.3"), "DC2");
Locator locator = mockLocator(endpointToDc);
PerDcResponseTracker tracker = new PerDcResponseTracker(trackers, locator);
// DC1: 2 committed successes (meets base requirement but not total)
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
assertFalse(tracker.isComplete());
// DC2: 2 successes (meets requirement)
tracker.onResponse(endpoint("192.168.1.1"));
tracker.onResponse(endpoint("192.168.1.2"));
assertFalse(tracker.isComplete()); // DC1 still needs pending
// DC1: 1 pending success (now meets total requirement)
tracker.onResponse(endpoint("127.0.0.4"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
}
// Validation tests
@Test(expected = IllegalArgumentException.class)
public void testNullTrackers()
{
Locator locator = mock(Locator.class);
new PerDcResponseTracker(null, locator);
}
@Test(expected = IllegalArgumentException.class)
public void testEmptyTrackers()
{
Locator locator = mock(Locator.class);
new PerDcResponseTracker(new HashMap<>(), locator);
}
@Test(expected = IllegalArgumentException.class)
public void testNullLocator()
{
Map<String, ResponseTracker> trackers = simpleTrackers("DC1", 2, 3);
new PerDcResponseTracker(trackers, null);
}
@Test
public void testToString() throws Exception
{
Map<String, ResponseTracker> trackers = simpleTrackers("DC1", 2, 3);
Locator locator = mock(Locator.class);
PerDcResponseTracker tracker = new PerDcResponseTracker(trackers, locator);
String str = tracker.toString();
assertTrue(str.contains("PerDcResponseTracker"));
assertTrue(str.contains("DC1"));
}
}

View File

@ -0,0 +1,361 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.locator;
import java.net.UnknownHostException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import org.junit.Test;
import static org.junit.Assert.*;
public class SimpleResponseTrackerTest
{
private InetAddressAndPort endpoint(String ip) throws UnknownHostException
{
return InetAddressAndPort.getByName(ip);
}
@Test
public void testQuorumReached() throws Exception
{
SimpleResponseTracker tracker = new SimpleResponseTracker(2, 3);
assertFalse(tracker.isComplete());
assertFalse(tracker.isSuccessful());
assertEquals(0, tracker.received());
tracker.onResponse(endpoint("127.0.0.1"));
assertFalse(tracker.isComplete());
assertEquals(1, tracker.received());
tracker.onResponse(endpoint("127.0.0.2"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(2, tracker.received());
}
@Test
public void testPartialProgress() throws Exception
{
SimpleResponseTracker tracker = new SimpleResponseTracker(3, 5);
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
assertFalse(tracker.isComplete());
assertFalse(tracker.isSuccessful());
assertEquals(2, tracker.received());
assertEquals(3, tracker.required());
}
@Test
public void testEarlyFailure() throws Exception
{
SimpleResponseTracker tracker = new SimpleResponseTracker(3, 5);
// Need 3, have 5 total
tracker.onResponse(endpoint("127.0.0.1")); // 1 success
tracker.onFailure(endpoint("127.0.0.2")); // 1 failure
tracker.onFailure(endpoint("127.0.0.3")); // 2 failures
tracker.onFailure(endpoint("127.0.0.4")); // 3 failures
// Have 1 success, 3 failures, 1 remaining
// Need 2 more but only 1 remaining -> impossible
assertTrue(tracker.isComplete());
assertFalse(tracker.isSuccessful());
assertEquals(1, tracker.received());
assertEquals(3, tracker.failures());
}
@Test
public void testAllSucceed() throws Exception
{
SimpleResponseTracker tracker = new SimpleResponseTracker(3, 3);
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
tracker.onResponse(endpoint("127.0.0.3"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(3, tracker.received());
assertEquals(0, tracker.failures());
}
@Test
public void testAllFail() throws Exception
{
SimpleResponseTracker tracker = new SimpleResponseTracker(2, 3);
tracker.onFailure(endpoint("127.0.0.1"));
tracker.onFailure(endpoint("127.0.0.2"));
tracker.onFailure(endpoint("127.0.0.3"));
assertTrue(tracker.isComplete());
assertFalse(tracker.isSuccessful());
assertEquals(0, tracker.received());
assertEquals(3, tracker.failures());
}
@Test
public void testBlockForOne() throws Exception
{
SimpleResponseTracker tracker = new SimpleResponseTracker(1, 3);
tracker.onResponse(endpoint("127.0.0.1"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(1, tracker.received());
}
@Test
public void testBlockForAll() throws Exception
{
SimpleResponseTracker tracker = new SimpleResponseTracker(3, 3);
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
assertFalse(tracker.isComplete());
tracker.onResponse(endpoint("127.0.0.3"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
}
@Test
public void testZeroResponses() throws Exception
{
SimpleResponseTracker tracker = new SimpleResponseTracker(2, 3);
assertFalse(tracker.isComplete());
assertFalse(tracker.isSuccessful());
assertEquals(0, tracker.received());
assertEquals(0, tracker.failures());
assertEquals(2, tracker.required());
}
@Test
public void testConcurrentResponses() throws Exception
{
SimpleResponseTracker tracker = new SimpleResponseTracker(50, 100);
ExecutorService executor = Executors.newFixedThreadPool(10);
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch doneLatch = new CountDownLatch(50);
try
{
// Launch 50 threads to call onResponse concurrently
for (int i = 0; i < 50; i++)
{
final int index = i;
executor.submit(() -> {
try
{
startLatch.await();
tracker.onResponse(endpoint("127.0.0." + index));
doneLatch.countDown();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
});
}
// Start all threads at once
startLatch.countDown();
// Wait for completion
assertTrue(doneLatch.await(10, TimeUnit.SECONDS));
// Verify no lost updates
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(50, tracker.received());
assertEquals(0, tracker.failures());
}
finally
{
executor.shutdownNow();
}
}
// Filtering tests
@Test
public void testUnfilteredTracker() throws Exception
{
SimpleResponseTracker tracker = new SimpleResponseTracker(2, 4);
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("192.168.1.1"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(2, tracker.received());
}
@Test
public void testFilteredTracker() throws Exception
{
// Filter that only accepts local endpoints (127.0.0.*)
Predicate<InetAddressAndPort> localFilter = endpoint ->
endpoint.getHostAddress(false).startsWith("127.0.0.");
SimpleResponseTracker tracker = new SimpleResponseTracker(2, 3, localFilter);
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(2, tracker.received());
assertTrue(tracker.countsTowardQuorum(endpoint("127.0.0.1")));
}
@Test
public void testFilteredIgnoresNonMatching() throws Exception
{
// Filter that only accepts local endpoints
Predicate<InetAddressAndPort> localFilter = endpoint ->
endpoint.getHostAddress(false).startsWith("127.0.0.");
SimpleResponseTracker tracker = new SimpleResponseTracker(2, 2, localFilter);
// Remote endpoint response is ignored
tracker.onResponse(endpoint("192.168.1.1"));
assertFalse(tracker.isComplete());
assertEquals(0, tracker.received());
assertFalse(tracker.countsTowardQuorum(endpoint("192.168.1.1")));
// Local endpoints count
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(2, tracker.received());
}
@Test
public void testCountsTowardQuorum() throws Exception
{
Predicate<InetAddressAndPort> filter = endpoint ->
endpoint.getHostAddress(false).startsWith("127.0.0.");
SimpleResponseTracker unfilteredTracker = new SimpleResponseTracker(2, 3);
assertTrue(unfilteredTracker.countsTowardQuorum(endpoint("127.0.0.1")));
assertTrue(unfilteredTracker.countsTowardQuorum(endpoint("192.168.1.1")));
SimpleResponseTracker filteredTracker = new SimpleResponseTracker(2, 3, filter);
assertTrue(filteredTracker.countsTowardQuorum(endpoint("127.0.0.1")));
assertFalse(filteredTracker.countsTowardQuorum(endpoint("192.168.1.1")));
}
// Usage pattern tests
@Test
public void testQuorumUsage() throws Exception
{
// Simulates QUORUM with RF=5
int rf = 5;
int blockFor = rf / 2 + 1; // 3
SimpleResponseTracker tracker = new SimpleResponseTracker(blockFor, rf);
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
tracker.onResponse(endpoint("127.0.0.3"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(3, tracker.required());
}
@Test
public void testLocalQuorumUsage() throws Exception
{
// Simulates LOCAL_QUORUM with localRF=3
int localRf = 3;
int blockFor = localRf / 2 + 1; // 2
Predicate<InetAddressAndPort> localFilter = endpoint ->
endpoint.getHostAddress(false).startsWith("127.0.0.");
SimpleResponseTracker tracker = new SimpleResponseTracker(blockFor, localRf, localFilter);
// Remote response ignored
tracker.onResponse(endpoint("192.168.1.1"));
assertFalse(tracker.isComplete());
// Local responses count
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(2, tracker.required());
}
@Test
public void testSerialUsage() throws Exception
{
// Simulates SERIAL paxos with participants=5 (RF=4 + 1 pending)
int participants = 5;
int blockFor = participants / 2 + 1; // 3
SimpleResponseTracker tracker = new SimpleResponseTracker(blockFor, participants);
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
tracker.onResponse(endpoint("127.0.0.3"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(3, tracker.required());
}
// Validation tests
@Test(expected = IllegalArgumentException.class)
public void testNegativeBlockFor()
{
new SimpleResponseTracker(-1, 3);
}
@Test(expected = IllegalArgumentException.class)
public void testNegativeTotalReplicas()
{
new SimpleResponseTracker(2, -1);
}
@Test
public void testToString() throws Exception
{
SimpleResponseTracker tracker = new SimpleResponseTracker(2, 3);
String str = tracker.toString();
assertTrue(str.contains("SimpleResponseTracker"));
assertTrue(str.contains("blockFor=2"));
assertTrue(str.contains("totalReplicas=3"));
}
}

View File

@ -0,0 +1,331 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.locator;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Tests for WriteResponseTracker implementing the double count model.
*/
public class WriteResponseTrackerTest
{
private InetAddressAndPort endpoint(String ip) throws UnknownHostException
{
return InetAddressAndPort.getByName(ip);
}
@Test
public void testBothRequirementsMet() throws Exception
{
// RF=3, pending=1: baseBlockFor=2, totalBlockFor=3
Set<InetAddressAndPort> pending = new HashSet<>();
pending.add(endpoint("127.0.0.4"));
Predicate<InetAddressAndPort> isPending = pending::contains;
WriteResponseTracker tracker = new WriteResponseTracker(2, 3, 3, 1, isPending);
// 2 committed successes
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
assertFalse("Should not be complete - need 3 total", tracker.isComplete());
// 1 pending success -> 3 total
tracker.onResponse(endpoint("127.0.0.4"));
assertTrue("Should be complete", tracker.isComplete());
assertTrue("Should be successful", tracker.isSuccessful());
assertEquals(2, tracker.naturalReceived());
assertEquals(1, tracker.pendingReceived());
assertEquals(3, tracker.received());
}
@Test
public void testCommittedRequirementNotMet() throws Exception
{
// RF=3, pending=1: baseBlockFor=2, totalBlockFor=3
Set<InetAddressAndPort> pending = new HashSet<>();
pending.add(endpoint("127.0.0.4"));
Predicate<InetAddressAndPort> isPending = pending::contains;
WriteResponseTracker tracker = new WriteResponseTracker(2, 3, 3, 1, isPending);
// 1 committed success, 1 pending success
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.4"));
assertFalse("Should not be complete - need 2 committed", tracker.isComplete());
assertEquals(1, tracker.naturalReceived());
assertEquals(1, tracker.pendingReceived());
// 2 committed failures -> can't reach 2 committed
tracker.onFailure(endpoint("127.0.0.2"));
tracker.onFailure(endpoint("127.0.0.3"));
assertTrue("Should be complete - impossible to reach committed requirement", tracker.isComplete());
assertFalse("Should not be successful", tracker.isSuccessful());
}
@Test
public void testTotalRequirementNotMet() throws Exception
{
// RF=3, pending=2: baseBlockFor=2, totalBlockFor=4
Set<InetAddressAndPort> pending = new HashSet<>();
pending.add(endpoint("127.0.0.4"));
pending.add(endpoint("127.0.0.5"));
Predicate<InetAddressAndPort> isPending = pending::contains;
WriteResponseTracker tracker = new WriteResponseTracker(2, 4, 3, 2, isPending);
// 2 committed successes (meets base requirement)
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
assertFalse("Should not be complete - need 4 total", tracker.isComplete());
assertEquals(2, tracker.naturalReceived());
// 1 committed failure, 2 pending failures -> only 3 total possible, need 4
tracker.onFailure(endpoint("127.0.0.3"));
tracker.onFailure(endpoint("127.0.0.4"));
tracker.onFailure(endpoint("127.0.0.5"));
assertTrue("Should be complete - impossible to reach total requirement", tracker.isComplete());
assertFalse("Should not be successful", tracker.isSuccessful());
}
@Test
public void testNoPendingReplicas() throws Exception
{
// RF=3, pending=0: baseBlockFor=2, totalBlockFor=2 (degenerates to simple case)
Predicate<InetAddressAndPort> isPending = addr -> false;
WriteResponseTracker tracker = new WriteResponseTracker(2, 2, 3, 0, isPending);
tracker.onResponse(endpoint("127.0.0.1"));
assertFalse(tracker.isComplete());
tracker.onResponse(endpoint("127.0.0.2"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(2, tracker.naturalReceived());
assertEquals(0, tracker.pendingReceived());
}
@Test
public void testAllFail() throws Exception
{
Set<InetAddressAndPort> pending = new HashSet<>();
pending.add(endpoint("127.0.0.4"));
Predicate<InetAddressAndPort> isPending = pending::contains;
WriteResponseTracker tracker = new WriteResponseTracker(2, 3, 3, 1, isPending);
tracker.onFailure(endpoint("127.0.0.1"));
tracker.onFailure(endpoint("127.0.0.2"));
// After 2 committed failures, can't reach baseBlockFor=2 with only 1 remaining
assertTrue(tracker.isComplete());
assertFalse(tracker.isSuccessful());
assertEquals(0, tracker.received());
assertEquals(2, tracker.naturalFailures());
}
@Test
public void testAllSucceed() throws Exception
{
Set<InetAddressAndPort> pending = new HashSet<>();
pending.add(endpoint("127.0.0.4"));
Predicate<InetAddressAndPort> isPending = pending::contains;
WriteResponseTracker tracker = new WriteResponseTracker(2, 3, 3, 1, isPending);
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
tracker.onResponse(endpoint("127.0.0.3"));
tracker.onResponse(endpoint("127.0.0.4"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(3, tracker.naturalReceived());
assertEquals(1, tracker.pendingReceived());
assertEquals(4, tracker.received());
assertEquals(0, tracker.failures());
}
@Test
public void testPendingSuccessBeforeCommitted() throws Exception
{
// Pending responses arrive first
Set<InetAddressAndPort> pending = new HashSet<>();
pending.add(endpoint("127.0.0.4"));
Predicate<InetAddressAndPort> isPending = pending::contains;
WriteResponseTracker tracker = new WriteResponseTracker(2, 3, 3, 1, isPending);
// Pending arrives first
tracker.onResponse(endpoint("127.0.0.4"));
assertFalse(tracker.isComplete());
assertEquals(0, tracker.naturalReceived());
assertEquals(1, tracker.pendingReceived());
// Then committed
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
}
@Test
public void testExactlyMeetsRequirements() throws Exception
{
// RF=2, pending=1: baseBlockFor=2, totalBlockFor=3
Set<InetAddressAndPort> pending = new HashSet<>();
pending.add(endpoint("127.0.0.3"));
Predicate<InetAddressAndPort> isPending = pending::contains;
WriteResponseTracker tracker = new WriteResponseTracker(2, 3, 2, 1, isPending);
// Exactly 2 committed (all of them)
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
assertFalse("Need 3 total", tracker.isComplete());
// Exactly 1 pending (all of them)
tracker.onResponse(endpoint("127.0.0.3"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
}
@Test
public void testMixedSuccessesAndFailures() throws Exception
{
// RF=5, pending=2: baseBlockFor=3, totalBlockFor=5
Set<InetAddressAndPort> pending = new HashSet<>();
pending.add(endpoint("127.0.0.6"));
pending.add(endpoint("127.0.0.7"));
Predicate<InetAddressAndPort> isPending = pending::contains;
WriteResponseTracker tracker = new WriteResponseTracker(3, 5, 5, 2, isPending);
// 3 committed successes, 2 committed failures
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onResponse(endpoint("127.0.0.2"));
tracker.onResponse(endpoint("127.0.0.3"));
tracker.onFailure(endpoint("127.0.0.4"));
tracker.onFailure(endpoint("127.0.0.5"));
assertFalse("Need 5 total, only have 3", tracker.isComplete());
assertEquals(3, tracker.naturalReceived());
assertEquals(2, tracker.naturalFailures());
// 2 pending successes
tracker.onResponse(endpoint("127.0.0.6"));
tracker.onResponse(endpoint("127.0.0.7"));
assertTrue(tracker.isComplete());
assertTrue(tracker.isSuccessful());
assertEquals(3, tracker.naturalReceived());
assertEquals(2, tracker.pendingReceived());
assertEquals(5, tracker.received());
}
@Test(expected = IllegalArgumentException.class)
public void testNegativeBaseBlockFor()
{
new WriteResponseTracker(-1, 2, 3, 1, addr -> false);
}
@Test(expected = IllegalArgumentException.class)
public void testTotalRequiredLessThanBase()
{
new WriteResponseTracker(3, 2, 3, 1, addr -> false);
}
@Test(expected = IllegalArgumentException.class)
public void testBaseBlockForExceedsCommitted()
{
new WriteResponseTracker(4, 5, 3, 2, addr -> false);
}
@Test(expected = IllegalArgumentException.class)
public void testTotalBlockForExceedsTotalReplicas()
{
new WriteResponseTracker(2, 6, 3, 2, addr -> false);
}
@Test(expected = IllegalArgumentException.class)
public void testNullPredicate()
{
new WriteResponseTracker(2, 3, 3, 1, null);
}
@Test
public void testAccessors() throws Exception
{
Set<InetAddressAndPort> pending = new HashSet<>();
pending.add(endpoint("127.0.0.4"));
Predicate<InetAddressAndPort> isPending = pending::contains;
WriteResponseTracker tracker = new WriteResponseTracker(2, 3, 3, 1, isPending);
assertEquals(2, tracker.baseBlockFor());
assertEquals(3, tracker.required()); // Returns totalBlockFor for error messages
tracker.onResponse(endpoint("127.0.0.1"));
tracker.onFailure(endpoint("127.0.0.2"));
tracker.onResponse(endpoint("127.0.0.4"));
assertEquals(1, tracker.naturalReceived());
assertEquals(1, tracker.pendingReceived());
assertEquals(1, tracker.naturalFailures());
assertEquals(0, tracker.pendingFailures());
assertEquals(2, tracker.received());
assertEquals(1, tracker.failures());
}
@Test
public void testCountsTowardQuorum() throws Exception
{
Set<InetAddressAndPort> pending = new HashSet<>();
pending.add(endpoint("127.0.0.4"));
Predicate<InetAddressAndPort> isPending = pending::contains;
WriteResponseTracker tracker = new WriteResponseTracker(2, 3, 3, 1, isPending);
// All endpoints count toward quorum in writes
assertTrue(tracker.countsTowardQuorum(endpoint("127.0.0.1")));
assertTrue(tracker.countsTowardQuorum(endpoint("127.0.0.4")));
assertTrue(tracker.countsTowardQuorum(endpoint("192.168.1.1")));
}
@Test
public void testToString() throws Exception
{
Set<InetAddressAndPort> pending = new HashSet<>();
pending.add(endpoint("127.0.0.4"));
Predicate<InetAddressAndPort> isPending = pending::contains;
WriteResponseTracker tracker = new WriteResponseTracker(2, 3, 3, 1, isPending);
String str = tracker.toString();
assertTrue(str.contains("WriteResponseTracker"));
assertTrue(str.contains("baseBlockFor=2"));
assertTrue(str.contains("totalBlockFor=3"));
}
}

View File

@ -24,7 +24,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import com.google.common.collect.ImmutableList;
import org.junit.Assert;
@ -34,6 +33,8 @@ import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.ReadResponse;
import org.apache.cassandra.exceptions.RequestFailure;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.CoordinationPlans;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.Replica;
@ -179,9 +180,9 @@ public class ReadCallbackPropertyTest extends ResponseHandlerPropertyTestBase
{
private volatile boolean dataPresent = false;
public TestResponseResolver(Supplier<ReplicaPlan.ForTokenRead> replicaPlan, Dispatcher.RequestTime requestTime)
public TestResponseResolver(CoordinationPlan.ForTokenRead plan, Dispatcher.RequestTime requestTime)
{
super(null, null, replicaPlan, requestTime);
super(null, null, plan, requestTime);
}
@Override
@ -216,9 +217,9 @@ public class ReadCallbackPropertyTest extends ResponseHandlerPropertyTestBase
/**
* Creates a ReplicaPlan for testing with the given parameters.
*/
private static ReplicaPlan.SharedForTokenRead createReplicaPlan(Keyspace ks,
ConsistencyLevel cl,
EndpointsForToken contacts)
private static CoordinationPlan.ForTokenRead createReplicaPlan(Keyspace ks,
ConsistencyLevel cl,
EndpointsForToken contacts)
{
ReplicaPlan.ForTokenRead plan = new ReplicaPlan.ForTokenRead(
ks,
@ -231,7 +232,7 @@ public class ReadCallbackPropertyTest extends ResponseHandlerPropertyTestBase
(self) -> null, // repair plan function
Epoch.EMPTY
);
return ReplicaPlan.shared(plan);
return CoordinationPlans.create(plan);
}
/**
@ -313,11 +314,11 @@ public class ReadCallbackPropertyTest extends ResponseHandlerPropertyTestBase
contactedIndices.add(i);
}
ReplicaPlan.SharedForTokenRead sharedPlan = createReplicaPlan(ks, cl, contacts);
CoordinationPlan.ForTokenRead plan = createReplicaPlan(ks, cl, contacts);
Dispatcher.RequestTime requestTime = new Dispatcher.RequestTime(System.nanoTime(), System.nanoTime());
TestResponseResolver resolver = new TestResponseResolver(sharedPlan, requestTime);
ReadCallback<EndpointsForToken, ReplicaPlan.ForTokenRead> handler = new ReadCallback<>(resolver, null, sharedPlan, requestTime);
TestResponseResolver resolver = new TestResponseResolver(plan, requestTime);
ReadCallback<EndpointsForToken, ReplicaPlan.ForTokenRead> handler = new ReadCallback<>(resolver, null, plan, requestTime);
return new HandlerWithContacts(handler, contactedIndices);
}

View File

@ -18,33 +18,24 @@
package org.apache.cassandra.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.WriteType;
import org.apache.cassandra.exceptions.CoordinatorBehindException;
import org.apache.cassandra.exceptions.RequestFailure;
import org.apache.cassandra.exceptions.RetryOnDifferentSystemException;
import org.apache.cassandra.exceptions.WriteFailureException;
import org.apache.cassandra.exceptions.WriteTimeoutException;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.InOurDc;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaPlans;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.locator.*;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.Verb;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Dispatcher;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.apache.cassandra.net.NoPayload.noPayload;
import static org.quicktheories.QuickTheory.qt;
@ -286,13 +277,15 @@ public class WriteResponseHandlerPropertyTest extends ResponseHandlerPropertyTes
EndpointsForToken targets,
EndpointsForToken pending) throws Exception
{
return ks.getReplicationStrategy().getWriteResponseHandler(
ReplicaPlans.forWrite(ks, cl, (cm) -> targets, (cm) -> pending, ClusterMetadata.current().epoch,
Predicates.alwaysTrue(), ReplicaPlans.writeAll),
null,
WriteType.SIMPLE,
null,
Dispatcher.RequestTime.forImmediateExecution());
ReplicaPlan.ForWrite replicaPlan = ReplicaPlans.forWrite(ks, cl,
(cm) -> targets,
(cm) -> pending,
ClusterMetadata.current().epoch,
Predicates.alwaysTrue(),
ReplicaPlans.writeAll);
CoordinationPlan.ForWriteWithIdeal coordinationPlan = CoordinationPlans.create(replicaPlan, null);
return ks.getReplicationStrategy().getWriteResponseHandler(coordinationPlan, null, WriteType.SIMPLE, null,
Dispatcher.RequestTime.forImmediateExecution());
}
/**
@ -496,8 +489,8 @@ public class WriteResponseHandlerPropertyTest extends ResponseHandlerPropertyTes
// Build endpoint lookup for replica indices
List<InetAddressAndPort> allEndpoints = new ArrayList<>();
handler.replicaPlan.contacts().forEach(r -> allEndpoints.add(r.endpoint()));
handler.replicaPlan.pending().forEach(r -> allEndpoints.add(r.endpoint()));
handler.replicaPlan().contacts().forEach(r -> allEndpoints.add(r.endpoint()));
handler.replicaPlan().pending().forEach(r -> allEndpoints.add(r.endpoint()));
for (int i = 0; i < Math.min(responses.size(), 10); i++)
{
@ -529,8 +522,8 @@ public class WriteResponseHandlerPropertyTest extends ResponseHandlerPropertyTes
}
// Debug: show what the handler thinks about pending replicas
diagnostic.append(String.format("Handler's replicaPlan.pending() size: %d\n", handler.replicaPlan.pending().size()));
for (Replica replica : handler.replicaPlan.pending())
diagnostic.append(String.format("Handler's replicaPlan.pending() size: %d\n", handler.replicaPlan().pending().size()));
for (Replica replica : handler.replicaPlan().pending())
{
diagnostic.append(String.format(" Pending replica: %s\n", replica.endpoint()));
}

View File

@ -25,6 +25,8 @@ import java.util.concurrent.TimeUnit;
import com.google.common.base.Predicates;
import org.apache.cassandra.locator.*;
import org.apache.cassandra.locator.CoordinationPlans;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@ -57,6 +59,7 @@ import static java.util.concurrent.TimeUnit.DAYS;
import static org.apache.cassandra.net.NoPayload.noPayload;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class WriteResponseHandlerTest
@ -148,8 +151,8 @@ public class WriteResponseHandlerTest
assertEquals(startingCount + 1, ks.metric.idealCLWriteLatency.latency.getCount());
//Don't need the others
awr.expired();
awr.expired();
awr.expired(targets.get(2).endpoint());
awr.expired(targets.get(3).endpoint());
assertEquals(0, ks.metric.writeFailedIdealCL.getCount());
}
@ -216,10 +219,9 @@ public class WriteResponseHandlerTest
awr.onResponse(createDummyMessage(2));
//Fail in remote DC
awr.expired();
awr.expired();
awr.expired();
assertEquals(1, ks.metric.writeFailedIdealCL.getCount());
awr.expired(targets.get(3).endpoint());
awr.expired(targets.get(4).endpoint());
awr.expired(targets.get(5).endpoint());
assertEquals(0, ks.metric.idealCLWriteLatency.totalLatency.getCount());
}
@ -260,14 +262,14 @@ public class WriteResponseHandlerTest
// Failure in local DC
awr.onResponse(createDummyMessage(0));
awr.expired();
awr.expired();
awr.expired(targets.get(1).endpoint());
awr.expired(targets.get(2).endpoint());
//Fail in remote DC
awr.expired();
awr.expired();
awr.expired();
awr.expired(targets.get(3).endpoint());
awr.expired(targets.get(4).endpoint());
awr.expired(targets.get(5).endpoint());
assertEquals(startingCount, ks.metric.writeFailedIdealCL.getCount());
}
@ -297,6 +299,28 @@ public class WriteResponseHandlerTest
}
/**
* expired(from) must notify the ResponseTracker so that when enough down-node expirations
* accumulate to make quorum mathematically impossible, the handler signals failure immediately
* rather than blocking until the full RPC timeout.
*/
@Test
public void expiredUpdatesResponseTrackerAndSignalsFailureWhenQuorumImpossible()
{
// QUORUM on 6 replicas (3 DC1 + 3 DC2) requires 4 acks.
// If 3 replicas are expired (down at dispatch time), only 3 remain quorum is impossible.
AbstractWriteResponseHandler awr = createWriteResponseHandler(ConsistencyLevel.QUORUM, null);
awr.expired(targets.get(0).endpoint());
awr.expired(targets.get(1).endpoint());
awr.expired(targets.get(2).endpoint()); // 3 remaining, blockFor=4: impossible to succeed
assertTrue("handler must be complete (failed) after quorum becomes impossible via expired()",
awr.isComplete());
assertFalse("handler must not report success",
awr.coordinationPlan().responses().isSuccessful());
}
private static AbstractWriteResponseHandler createWriteResponseHandler(ConsistencyLevel cl, ConsistencyLevel ideal)
{
return createWriteResponseHandler(cl, ideal, Dispatcher.RequestTime.forImmediateExecution());
@ -304,8 +328,9 @@ public class WriteResponseHandlerTest
private static AbstractWriteResponseHandler createWriteResponseHandler(ConsistencyLevel cl, ConsistencyLevel ideal, Dispatcher.RequestTime requestTime)
{
return ks.getReplicationStrategy().getWriteResponseHandler(ReplicaPlans.forWrite(ks, cl, (cm) -> targets, (cm) -> pending, Epoch.FIRST, Predicates.alwaysTrue(), ReplicaPlans.writeAll),
null, WriteType.SIMPLE, null, requestTime, ideal);
ReplicaPlan.ForWrite replicaPlan = ReplicaPlans.forWrite(ks, cl, (cm) -> targets, (cm) -> pending, Epoch.FIRST, Predicates.alwaysTrue(), ReplicaPlans.writeAll);
CoordinationPlan.ForWriteWithIdeal coordinationPlan = CoordinationPlans.create(replicaPlan, ideal);
return ks.getReplicationStrategy().getWriteResponseHandler(coordinationPlan, null, WriteType.SIMPLE, null, requestTime);
}
private static Message createDummyMessage(int target)

View File

@ -58,6 +58,8 @@ import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.RowIterator;
import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.CoordinationPlans;
import org.apache.cassandra.locator.EndpointsForRange;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.Replica;
@ -1250,7 +1252,7 @@ public class DataResolverTest extends AbstractReadResponseTest
}
private DataResolver resolverWithVerifier(final ReadCommand command,
final ReplicaPlan.SharedForRangeRead plan,
final CoordinationPlan.ForRangeRead plan,
final ReadRepair readRepair,
final Dispatcher.RequestTime requestTime,
final RepairedDataVerifier verifier)
@ -1258,7 +1260,7 @@ public class DataResolverTest extends AbstractReadResponseTest
class TestableDataResolver extends DataResolver
{
public TestableDataResolver(ReadCommand command, ReplicaPlan.SharedForRangeRead plan, ReadRepair readRepair, Dispatcher.RequestTime requestTime)
public TestableDataResolver(ReadCommand command, CoordinationPlan.ForRangeRead plan, ReadRepair readRepair, Dispatcher.RequestTime requestTime)
{
super(ReadCoordinator.DEFAULT, command, plan, readRepair, requestTime, true);
}
@ -1324,17 +1326,17 @@ public class DataResolverTest extends AbstractReadResponseTest
assertEquals(update.metadata().name, cfm.name);
}
private ReplicaPlan.SharedForRangeRead plan(EndpointsForRange replicas, ConsistencyLevel consistencyLevel)
private CoordinationPlan.ForRangeRead plan(EndpointsForRange replicas, ConsistencyLevel consistencyLevel)
{
BiFunction<ReplicaPlan<?, ?>, Token, ReplicaPlan.ForWrite> repairPlan = (self, t) -> ReplicaPlans.forReadRepair(self, ClusterMetadata.current(), ks, null, consistencyLevel, t, (i) -> true, ReadCoordinator.DEFAULT);
return ReplicaPlan.shared(new ReplicaPlan.ForRangeRead(ks,
ks.getReplicationStrategy(),
consistencyLevel,
ReplicaUtils.FULL_BOUNDS,
replicas, replicas, replicas,
1, null,
repairPlan,
Epoch.EMPTY));
return CoordinationPlans.create(ReplicaPlan.shared(new ReplicaPlan.ForRangeRead(ks,
ks.getReplicationStrategy(),
consistencyLevel,
ReplicaUtils.FULL_BOUNDS,
replicas, replicas, replicas,
1, null,
repairPlan,
Epoch.EMPTY)));
}
private static void resolveAndConsume(DataResolver resolver)

View File

@ -23,6 +23,9 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.CoordinationPlans;
import org.apache.cassandra.locator.ReplicaPlan;
import org.junit.Assert;
import org.junit.Test;
@ -32,7 +35,6 @@ import org.apache.cassandra.db.SinglePartitionReadCommand;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.transport.Dispatcher;
@ -91,7 +93,7 @@ public class DigestResolverTest extends AbstractReadResponseTest
SinglePartitionReadCommand command = SinglePartitionReadCommand.fullPartitionRead(cfm, nowInSec, dk);
EndpointsForToken targetReplicas = EndpointsForToken.of(dk.getToken(), full(EP1), full(EP2));
PartitionUpdate response = update(row(1000, 4, 4), row(1000, 5, 5)).build();
ReplicaPlan.SharedForTokenRead plan = plan(ConsistencyLevel.ONE, targetReplicas);
CoordinationPlan.ForTokenRead plan = plan(ConsistencyLevel.ONE, targetReplicas);
ExecutorService pool = Executors.newFixedThreadPool(2);
long endTime = System.nanoTime() + TimeUnit.MINUTES.toNanos(2);
@ -213,9 +215,9 @@ public class DigestResolverTest extends AbstractReadResponseTest
resolver.getData());
}
private ReplicaPlan.SharedForTokenRead plan(ConsistencyLevel consistencyLevel, EndpointsForToken replicas)
private CoordinationPlan.ForTokenRead plan(ConsistencyLevel consistencyLevel, EndpointsForToken replicas)
{
return ReplicaPlan.shared(new ReplicaPlan.ForTokenRead(ks, ks.getReplicationStrategy(), consistencyLevel, replicas, replicas, replicas, null, (self) -> null, Epoch.EMPTY));
return CoordinationPlans.create(new ReplicaPlan.ForTokenRead(ks, ks.getReplicationStrategy(), consistencyLevel, replicas, replicas, replicas, null, (self) -> null, Epoch.EMPTY));
}
private void waitForLatch(CountDownLatch startlatch)

View File

@ -39,6 +39,8 @@ import org.apache.cassandra.exceptions.ReadFailureException;
import org.apache.cassandra.exceptions.ReadTimeoutException;
import org.apache.cassandra.exceptions.RequestFailure;
import org.apache.cassandra.exceptions.RequestFailureReason;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.CoordinationPlans;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.ReplicaPlan;
@ -207,7 +209,7 @@ public class ReadExecutorTest
public void testRaceWithNonSpeculativeFailure()
{
MockSinglePartitionReadCommand command = new MockSinglePartitionReadCommand(TimeUnit.DAYS.toMillis(365));
ReplicaPlan.ForTokenRead plan = plan(ConsistencyLevel.LOCAL_ONE, targets, targets.subList(0, 1));
CoordinationPlan.ForTokenRead plan = plan(ConsistencyLevel.LOCAL_ONE, targets, targets.subList(0, 1));
AbstractReadExecutor executor = new AbstractReadExecutor.SpeculatingReadExecutor(ReadCoordinator.DEFAULT, cfs, command, plan, Dispatcher.RequestTime.forImmediateExecution());
// Issue an initial request against the first endpoint...
@ -271,13 +273,13 @@ public class ReadExecutorTest
}
}
private ReplicaPlan.ForTokenRead plan(EndpointsForToken targets, ConsistencyLevel consistencyLevel)
private CoordinationPlan.ForTokenRead plan(EndpointsForToken targets, ConsistencyLevel consistencyLevel)
{
return plan(consistencyLevel, targets, targets);
}
private ReplicaPlan.ForTokenRead plan(ConsistencyLevel consistencyLevel, EndpointsForToken natural, EndpointsForToken selected)
private CoordinationPlan.ForTokenRead plan(ConsistencyLevel consistencyLevel, EndpointsForToken natural, EndpointsForToken selected)
{
return new ReplicaPlan.ForTokenRead(ks, ks.getReplicationStrategy(), consistencyLevel, natural, selected, natural, (cm) -> null, (self) -> null, Epoch.EMPTY);
return CoordinationPlans.create(new ReplicaPlan.ForTokenRead(ks, ks.getReplicationStrategy(), consistencyLevel, natural, selected, natural, (cm) -> null, (self) -> null, Epoch.EMPTY));
}
}

View File

@ -42,7 +42,7 @@ import static org.apache.cassandra.Util.rp;
import static org.apache.cassandra.Util.token;
import static org.junit.Assert.assertEquals;
public class ReplicaPlanIteratorTest
public class CoordinationPlanIteratorTest
{
private static final String KEYSPACE = "ReplicaPlanIteratorTest";
private static final TableId TABLE_ID = TableId.generate();
@ -165,11 +165,11 @@ public class ReplicaPlanIteratorTest
@SafeVarargs
private final void testRanges(Keyspace keyspace, AbstractBounds<PartitionPosition> queryRange, AbstractBounds<PartitionPosition>... expected)
{
try (ReplicaPlanIterator iterator = new ReplicaPlanIterator(queryRange, null, keyspace, TABLE_ID, ConsistencyLevel.ANY))
try (CoordinationPlanIterator iterator = new CoordinationPlanIterator(queryRange, null, keyspace, TABLE_ID, ConsistencyLevel.ANY))
{
List<AbstractBounds<PartitionPosition>> restrictedRanges = new ArrayList<>(expected.length);
while (iterator.hasNext())
restrictedRanges.add(iterator.next().range());
restrictedRanges.add(iterator.next().replicas().range());
// verify range counts
assertEquals(expected.length, restrictedRanges.size());

View File

@ -63,9 +63,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
/**
* Tests for {@link ReplicaPlanMerger}.
* Tests for {@link CoordinationPlanMerger}.
*/
public class ReplicaPlanMergerTest
public class CoordinationPlanMergerTest
{
private static final String KEYSPACE = "ReplicaPlanMergerTest";
private static Keyspace keyspace;
@ -416,13 +416,13 @@ public class ReplicaPlanMergerTest
AbstractBounds<PartitionPosition> queryRange,
AbstractBounds<PartitionPosition>... expected)
{
try (ReplicaPlanIterator originals = new ReplicaPlanIterator(queryRange, null, keyspace, null, ANY); // ANY avoids endpoint erros
ReplicaPlanMerger merger = new ReplicaPlanMerger(originals, keyspace, null, consistencyLevel))
try (CoordinationPlanIterator originals = new CoordinationPlanIterator(queryRange, null, keyspace, null, ANY); // ANY avoids endpoint erros
CoordinationPlanMerger merger = new CoordinationPlanMerger(originals, keyspace, null, consistencyLevel))
{
// collect the merged ranges
List<AbstractBounds<PartitionPosition>> mergedRanges = new ArrayList<>(expected.length);
while (merger.hasNext())
mergedRanges.add(merger.next().range());
mergedRanges.add(merger.next().replicas().range());
assertFalse("The number of merged ranges should never be greater than the number of original ranges",
mergedRanges.size() > originals.size());

View File

@ -38,7 +38,8 @@ import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.CoordinationPlans;
import org.apache.cassandra.locator.ReplicaPlans;
import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.schema.TableId;
@ -70,18 +71,18 @@ public class RangeCommandIteratorTest
int vnodeCount = 0;
Keyspace keyspace = Keyspace.open(KEYSPACE1);
List<ReplicaPlan.ForRangeRead> ranges = new ArrayList<>();
List<CoordinationPlan.ForRangeRead> ranges = new ArrayList<>();
for (int i = 0; i + 1 < tokens.size(); i++)
{
Range<PartitionPosition> range = Range.makeRowRange(tokens.get(i), tokens.get(i + 1));
ranges.add(ReplicaPlans.forRangeRead(keyspace, TABLE_ID, null, ConsistencyLevel.ONE, range, 1));
ranges.add(CoordinationPlans.create(ReplicaPlans.forRangeRead(keyspace, TABLE_ID, null, ConsistencyLevel.ONE, range, 1)));
vnodeCount++;
}
ReplicaPlanMerger merge = new ReplicaPlanMerger(ranges.iterator(), keyspace, TABLE_ID, ConsistencyLevel.ONE);
ReplicaPlan.ForRangeRead mergedRange = Iterators.getOnlyElement(merge);
CoordinationPlanMerger merge = new CoordinationPlanMerger(ranges.iterator(), keyspace, TABLE_ID, ConsistencyLevel.ONE);
CoordinationPlan.ForRangeRead mergedRange = Iterators.getOnlyElement(merge);
// all ranges are merged as test has only one node.
assertEquals(vnodeCount, mergedRange.vnodeCount());
assertEquals(vnodeCount, mergedRange.replicas().vnodeCount());
}
@Test
@ -108,27 +109,27 @@ public class RangeCommandIteratorTest
AbstractBounds<PartitionPosition> keyRange = command.dataRange().keyRange();
// without range merger, there will be 2 batches requested: 1st batch with 1 range and 2nd batch with remaining ranges
CloseableIterator<ReplicaPlan.ForRangeRead> replicaPlans = replicaPlanIterator(keyRange, keyspace, false);
CloseableIterator<CoordinationPlan.ForRangeRead> replicaPlans = coordinatorPlanIterator(keyRange, keyspace, false);
RangeCommandIterator data = new RangeCommandIterator(replicaPlans, command, ReadCoordinator.DEFAULT, 1, 1000, vnodeCount, Dispatcher.RequestTime.forImmediateExecution());
verifyRangeCommandIterator(data, rows, 2, vnodeCount);
// without range merger and initial cf=5, there will be 1 batches requested: 5 vnode ranges for 1st batch
replicaPlans = replicaPlanIterator(keyRange, keyspace, false);
replicaPlans = coordinatorPlanIterator(keyRange, keyspace, false);
data = new RangeCommandIterator(replicaPlans, command, ReadCoordinator.DEFAULT, vnodeCount, 1000, vnodeCount, Dispatcher.RequestTime.forImmediateExecution());
verifyRangeCommandIterator(data, rows, 1, vnodeCount);
// without range merger and max cf=1, there will be 5 batches requested: 1 vnode range per batch
replicaPlans = replicaPlanIterator(keyRange, keyspace, false);
replicaPlans = coordinatorPlanIterator(keyRange, keyspace, false);
data = new RangeCommandIterator(replicaPlans, command, ReadCoordinator.DEFAULT, 1, 1, vnodeCount, Dispatcher.RequestTime.forImmediateExecution());
verifyRangeCommandIterator(data, rows, vnodeCount, vnodeCount);
// with range merger, there will be only 1 batch requested, as all ranges share the same replica - localhost
replicaPlans = replicaPlanIterator(keyRange, keyspace, true);
replicaPlans = coordinatorPlanIterator(keyRange, keyspace, true);
data = new RangeCommandIterator(replicaPlans, command, ReadCoordinator.DEFAULT, 1, 1000, vnodeCount, Dispatcher.RequestTime.forImmediateExecution());
verifyRangeCommandIterator(data, rows, 1, vnodeCount);
// with range merger and max cf=1, there will be only 1 batch requested, as all ranges share the same replica - localhost
replicaPlans = replicaPlanIterator(keyRange, keyspace, true);
replicaPlans = coordinatorPlanIterator(keyRange, keyspace, true);
data = new RangeCommandIterator(replicaPlans, command, ReadCoordinator.DEFAULT, 1, 1, vnodeCount, Dispatcher.RequestTime.forImmediateExecution());
verifyRangeCommandIterator(data, rows, 1, vnodeCount);
}
@ -164,13 +165,13 @@ public class RangeCommandIteratorTest
return new TokenUpdater().withKeys(values).update().getTokens();
}
private static CloseableIterator<ReplicaPlan.ForRangeRead> replicaPlanIterator(AbstractBounds<PartitionPosition> keyRange,
Keyspace keyspace,
boolean withRangeMerger)
private static CloseableIterator<CoordinationPlan.ForRangeRead> coordinatorPlanIterator(AbstractBounds<PartitionPosition> keyRange,
Keyspace keyspace,
boolean withRangeMerger)
{
CloseableIterator<ReplicaPlan.ForRangeRead> replicaPlans = new ReplicaPlanIterator(keyRange, null, keyspace, null, ConsistencyLevel.ONE);
CloseableIterator<CoordinationPlan.ForRangeRead> replicaPlans = new CoordinationPlanIterator(keyRange, null, keyspace, null, ConsistencyLevel.ONE);
if (withRangeMerger)
replicaPlans = new ReplicaPlanMerger(replicaPlans, keyspace, null, ConsistencyLevel.ONE);
replicaPlans = new CoordinationPlanMerger(replicaPlans, keyspace, null, ConsistencyLevel.ONE);
return replicaPlans;
}

View File

@ -80,7 +80,7 @@ public class RangeCommandsTest extends CQLTester
// verify that a low concurrency factor is not capped by the max concurrency factor
PartitionRangeReadCommand command = command(cfs, 50, 50);
try (RangeCommandIterator partitions = RangeCommands.rangeCommandIterator(command, ONE, ReadCoordinator.DEFAULT, Dispatcher.RequestTime.forImmediateExecution());
ReplicaPlanIterator ranges = new ReplicaPlanIterator(command.dataRange().keyRange(), command.indexQueryPlan(), keyspace, command.metadata().id, ONE))
CoordinationPlanIterator ranges = new CoordinationPlanIterator(command.dataRange().keyRange(), command.indexQueryPlan(), keyspace, command.metadata().id, ONE))
{
assertEquals(2, partitions.concurrencyFactor());
assertEquals(MAX_CONCURRENCY_FACTOR, partitions.maxConcurrencyFactor());
@ -90,7 +90,7 @@ public class RangeCommandsTest extends CQLTester
// verify that a high concurrency factor is capped by the max concurrency factor
command = command(cfs, 1000, 50);
try (RangeCommandIterator partitions = RangeCommands.rangeCommandIterator(command, ONE, ReadCoordinator.DEFAULT, Dispatcher.RequestTime.forImmediateExecution());
ReplicaPlanIterator ranges = new ReplicaPlanIterator(command.dataRange().keyRange(), command.indexQueryPlan(), keyspace, command.metadata().id, ONE))
CoordinationPlanIterator ranges = new CoordinationPlanIterator(command.dataRange().keyRange(), command.indexQueryPlan(), keyspace, command.metadata().id, ONE))
{
assertEquals(MAX_CONCURRENCY_FACTOR, partitions.concurrencyFactor());
assertEquals(MAX_CONCURRENCY_FACTOR, partitions.maxConcurrencyFactor());
@ -100,7 +100,7 @@ public class RangeCommandsTest extends CQLTester
// with 0 estimated results per range the concurrency factor should be 1
command = command(cfs, 1000, 0);
try (RangeCommandIterator partitions = RangeCommands.rangeCommandIterator(command, ONE, ReadCoordinator.DEFAULT, Dispatcher.RequestTime.forImmediateExecution());
ReplicaPlanIterator ranges = new ReplicaPlanIterator(command.dataRange().keyRange(), command.indexQueryPlan(), keyspace, command.metadata().id, ONE))
CoordinationPlanIterator ranges = new CoordinationPlanIterator(command.dataRange().keyRange(), command.indexQueryPlan(), keyspace, command.metadata().id, ONE))
{
assertEquals(1, partitions.concurrencyFactor());
assertEquals(MAX_CONCURRENCY_FACTOR, partitions.maxConcurrencyFactor());

View File

@ -61,6 +61,8 @@ import org.apache.cassandra.dht.ByteOrderedPartitioner.BytesToken;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.distributed.test.log.ClusterMetadataTestHelper;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.CoordinationPlans;
import org.apache.cassandra.locator.EndpointsForRange;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.InetAddressAndPort;
@ -366,11 +368,11 @@ public abstract class AbstractReadRepairTest
Epoch.EMPTY);
}
public abstract InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, ReplicaPlan.Shared<?, ?> replicaPlan, Dispatcher.RequestTime requestTime);
public abstract InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, CoordinationPlan.ForRead<?, ?> plan, Dispatcher.RequestTime requestTime);
public InstrumentedReadRepair createInstrumentedReadRepair(ReplicaPlan.Shared<?, ?> replicaPlan)
public InstrumentedReadRepair createInstrumentedReadRepair(CoordinationPlan.ForRead<?, ?> plan)
{
return createInstrumentedReadRepair(command, replicaPlan, Dispatcher.RequestTime.forImmediateExecution());
return createInstrumentedReadRepair(command, plan, Dispatcher.RequestTime.forImmediateExecution());
}
@ -381,7 +383,7 @@ public abstract class AbstractReadRepairTest
@Test
public void readSpeculationCycle()
{
InstrumentedReadRepair repair = createInstrumentedReadRepair(ReplicaPlan.shared(replicaPlan(replicas, EndpointsForRange.of(replica1, replica2))));
InstrumentedReadRepair repair = createInstrumentedReadRepair(CoordinationPlans.create(replicaPlan(replicas, EndpointsForRange.of(replica1, replica2))));
ResultConsumer consumer = new ResultConsumer();
Assert.assertEquals(epSet(), repair.getReadRecipients());
@ -400,7 +402,7 @@ public abstract class AbstractReadRepairTest
@Test
public void noSpeculationRequired()
{
InstrumentedReadRepair repair = createInstrumentedReadRepair(ReplicaPlan.shared(replicaPlan(replicas, EndpointsForRange.of(replica1, replica2))));
InstrumentedReadRepair repair = createInstrumentedReadRepair(CoordinationPlans.create(replicaPlan(replicas, EndpointsForRange.of(replica1, replica2))));
ResultConsumer consumer = new ResultConsumer();
Assert.assertEquals(epSet(), repair.getReadRecipients());

View File

@ -35,6 +35,7 @@ import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.Mutation;
import org.apache.cassandra.db.ReadCommand;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.Endpoints;
import org.apache.cassandra.locator.EndpointsForRange;
import org.apache.cassandra.locator.InetAddressAndPort;
@ -86,9 +87,9 @@ public class BlockingReadRepairTest extends AbstractReadRepairTest
private static class InstrumentedBlockingReadRepair<E extends Endpoints<E>, P extends ReplicaPlan.ForRead<E, P>>
extends BlockingReadRepair<E, P> implements InstrumentedReadRepair<E, P>
{
public InstrumentedBlockingReadRepair(ReadCommand command, ReplicaPlan.Shared<E, P> replicaPlan, Dispatcher.RequestTime requestTime)
public InstrumentedBlockingReadRepair(ReadCommand command, CoordinationPlan.ForRead<E, P> plan, Dispatcher.RequestTime requestTime)
{
super(ReadCoordinator.DEFAULT, command, replicaPlan, requestTime);
super(ReadCoordinator.DEFAULT, command, plan, requestTime);
}
Set<InetAddressAndPort> readCommandRecipients = new HashSet<>();
@ -116,9 +117,9 @@ public class BlockingReadRepairTest extends AbstractReadRepairTest
}
@Override
public InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, ReplicaPlan.Shared<?, ?> replicaPlan, Dispatcher.RequestTime requestTime)
public InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, CoordinationPlan.ForRead<?, ?> plan, Dispatcher.RequestTime requestTime)
{
return new InstrumentedBlockingReadRepair(command, replicaPlan, requestTime);
return new InstrumentedBlockingReadRepair(command, plan, requestTime);
}
@Test

View File

@ -29,6 +29,8 @@ import java.util.function.Predicate;
import com.google.common.collect.Lists;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.ReplicaPlan;
import org.junit.After;
import org.junit.Assert;
import org.junit.BeforeClass;
@ -44,7 +46,6 @@ import org.apache.cassandra.locator.Endpoints;
import org.apache.cassandra.locator.EndpointsForRange;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaPlan;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.service.reads.ReadCallback;
import org.apache.cassandra.service.reads.ReadCoordinator;
@ -119,9 +120,9 @@ public class DiagEventsBlockingReadRepairTest extends AbstractReadRepairTest
}
@Override
public InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, ReplicaPlan.Shared<?,?> replicaPlan, Dispatcher.RequestTime requestTime)
public InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, CoordinationPlan.ForRead<?,?> plan, Dispatcher.RequestTime requestTime)
{
return new DiagnosticBlockingRepairHandler(command, replicaPlan, requestTime);
return new DiagnosticBlockingRepairHandler(command, plan, requestTime);
}
private static DiagnosticPartitionReadRepairHandler createRepairHandler(Map<Replica, Mutation> repairs, ReplicaPlan.ForWrite writePlan)
@ -134,9 +135,9 @@ public class DiagEventsBlockingReadRepairTest extends AbstractReadRepairTest
private Set<InetAddressAndPort> recipients = Collections.emptySet();
private ReadCallback readCallback = null;
DiagnosticBlockingRepairHandler(ReadCommand command, ReplicaPlan.Shared<?,?> replicaPlan, Dispatcher.RequestTime requestTime)
DiagnosticBlockingRepairHandler(ReadCommand command, CoordinationPlan.ForRead<?,?> plan, Dispatcher.RequestTime requestTime)
{
super(ReadCoordinator.DEFAULT, command, replicaPlan, requestTime);
super(ReadCoordinator.DEFAULT, command, plan, requestTime);
DiagnosticEventService.instance().subscribe(ReadRepairEvent.class, this::onRepairEvent);
}

View File

@ -28,6 +28,8 @@ import org.junit.Test;
import org.apache.cassandra.db.ReadCommand;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
import org.apache.cassandra.locator.CoordinationPlan;
import org.apache.cassandra.locator.CoordinationPlans;
import org.apache.cassandra.locator.Endpoints;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.Replica;
@ -42,9 +44,9 @@ public class ReadOnlyReadRepairTest extends AbstractReadRepairTest
private static class InstrumentedReadOnlyReadRepair<E extends Endpoints<E>, P extends ReplicaPlan.ForRead<E, P>>
extends ReadOnlyReadRepair implements InstrumentedReadRepair
{
public InstrumentedReadOnlyReadRepair(ReadCommand command, ReplicaPlan.Shared<E, P> replicaPlan, Dispatcher.RequestTime requestTime)
public InstrumentedReadOnlyReadRepair(ReadCommand command, CoordinationPlan.ForRead<E, P> plan, Dispatcher.RequestTime requestTime)
{
super(ReadCoordinator.DEFAULT, command, replicaPlan, requestTime);
super(ReadCoordinator.DEFAULT, command, plan, requestTime);
}
Set<InetAddressAndPort> readCommandRecipients = new HashSet<>();
@ -78,25 +80,25 @@ public class ReadOnlyReadRepairTest extends AbstractReadRepairTest
}
@Override
public InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, ReplicaPlan.Shared<?, ?> replicaPlan, Dispatcher.RequestTime requestTime)
public InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, CoordinationPlan.ForRead<?, ?> plan, Dispatcher.RequestTime requestTime)
{
return new InstrumentedReadOnlyReadRepair(command, replicaPlan, requestTime);
return new InstrumentedReadOnlyReadRepair(command, plan, requestTime);
}
@Test
public void getMergeListener()
{
ReplicaPlan.SharedForRangeRead replicaPlan = ReplicaPlan.shared(replicaPlan(replicas, replicas));
InstrumentedReadRepair repair = createInstrumentedReadRepair(replicaPlan);
Assert.assertSame(UnfilteredPartitionIterators.MergeListener.NOOP, repair.getMergeListener(replicaPlan.get()));
CoordinationPlan.ForRangeRead plan = CoordinationPlans.create(replicaPlan(replicas, replicas));
InstrumentedReadRepair repair = createInstrumentedReadRepair(plan);
Assert.assertSame(UnfilteredPartitionIterators.MergeListener.NOOP, repair.getMergeListener(plan.replicas()));
}
@Test(expected = UnsupportedOperationException.class)
public void repairPartitionFailure()
{
ReplicaPlan.SharedForRangeRead readPlan = ReplicaPlan.shared(replicaPlan(replicas, replicas));
CoordinationPlan.ForRangeRead plan = CoordinationPlans.create(replicaPlan(replicas, replicas));
ReplicaPlan.ForWrite writePlan = repairPlan(replicas, replicas);
InstrumentedReadRepair repair = createInstrumentedReadRepair(readPlan);
InstrumentedReadRepair repair = createInstrumentedReadRepair(plan);
repair.repairPartition(null, Collections.emptyMap(), writePlan, ReadRepairSource.OTHER);
}
}