diff --git a/build.xml b/build.xml
index 3fe92490d4..d209b9af14 100644
--- a/build.xml
+++ b/build.xml
@@ -1239,6 +1239,7 @@
+
diff --git a/ide/idea/workspace.xml b/ide/idea/workspace.xml
index 08a8a73ae4..13018f4052 100644
--- a/ide/idea/workspace.xml
+++ b/ide/idea/workspace.xml
@@ -190,6 +190,7 @@
-Dcassandra.ring_delay_ms=10000
-Dcassandra.skip_sync=true
-Dcassandra.storagedir=$PROJECT_DIR$/data
+ -Dcassandra.test.accord.allow_test_modes=true
-Dcassandra.strict.runtime.checks=true
-Dcassandra.test.flush_local_schema_changes=false
-Dcassandra.test.messagingService.nonGracefulShutdown=true
diff --git a/src/java/org/apache/cassandra/batchlog/BatchlogManager.java b/src/java/org/apache/cassandra/batchlog/BatchlogManager.java
index 1844f84249..ff7f2d2cb3 100644
--- a/src/java/org/apache/cassandra/batchlog/BatchlogManager.java
+++ b/src/java/org/apache/cassandra/batchlog/BatchlogManager.java
@@ -31,7 +31,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
-
import javax.annotation.Nullable;
import com.google.common.annotations.VisibleForTesting;
@@ -454,12 +453,12 @@ public class BatchlogManager implements BatchlogManagerMBean
if (accordResult != null)
{
IAccordService accord = AccordService.instance();
- TxnResult.Kind kind = accord.getTxnResult(accordResult, true, ConsistencyLevel.QUORUM, accordTxnStart).kind();
+ TxnResult.Kind kind = accord.getTxnResult(accordResult).kind();
if (kind == retry_new_protocol)
throw new RetryOnDifferentSystemException();
}
}
- catch (WriteTimeoutException|WriteFailureException|RetryOnDifferentSystemException e)
+ catch (WriteTimeoutException | WriteFailureException | RetryOnDifferentSystemException e )
{
logger.trace("Failed replaying a batched mutation on Accord, will write a hint");
logger.trace("Failure was : {}", e.getMessage());
diff --git a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
index bb7dc7c551..7459fecb8c 100644
--- a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
+++ b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java
@@ -39,6 +39,7 @@ import static org.apache.cassandra.utils.LocalizeString.toUpperCaseLocalized;
public enum CassandraRelevantProperties
{
ACCORD_AGENT_CLASS("cassandra.test.accord.agent"),
+ ACCORD_ALLOW_TEST_MODES("cassandra.test.accord.allow_test_modes", "false"),
ACCORD_KEY_PARANOIA_COSTFACTOR(Invariants.KEY_PARANOIA_COSTFACTOR),
ACCORD_KEY_PARANOIA_CPU(Invariants.KEY_PARANOIA_CPU),
ACCORD_KEY_PARANOIA_MEMORY(Invariants.KEY_PARANOIA_MEMORY),
diff --git a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
index 45c433a530..d12f9e8922 100644
--- a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
@@ -53,6 +53,7 @@ import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.IMutation;
+import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.Slice;
import org.apache.cassandra.db.Slices;
@@ -356,7 +357,7 @@ public class BatchStatement implements CQLStatement.CompositeCQLStatement
}
// local is either executeWithoutConditions modifying a virtual table (doesn't support txns) or executeLocal
// which is called by test or internal things that are bypassing distributed system modification/checks
- return collector.toMutations(state, local);
+ return collector.toMutations(state, local ? PotentialTxnConflicts.ALLOW : PotentialTxnConflicts.DISALLOW);
}
/**
diff --git a/src/java/org/apache/cassandra/cql3/statements/BatchUpdatesCollector.java b/src/java/org/apache/cassandra/cql3/statements/BatchUpdatesCollector.java
index 49b5a404dd..4bc0d909d2 100644
--- a/src/java/org/apache/cassandra/cql3/statements/BatchUpdatesCollector.java
+++ b/src/java/org/apache/cassandra/cql3/statements/BatchUpdatesCollector.java
@@ -32,6 +32,7 @@ import org.apache.cassandra.db.CounterMutation;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.IMutation;
import org.apache.cassandra.db.Mutation;
+import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.commitlog.CommitLogSegment;
import org.apache.cassandra.db.partitions.PartitionUpdate;
@@ -137,14 +138,14 @@ final class BatchUpdatesCollector implements UpdatesCollector
* @return a collection containing all the mutations.
*/
@Override
- public List toMutations(ClientState state, boolean allowPotentialTxnConflicts)
+ public List toMutations(ClientState state, PotentialTxnConflicts potentialTxnConflicts)
{
List ms = new ArrayList<>();
for (Map ksMap : mutationBuilders.values())
{
for (IMutationBuilder builder : ksMap.values())
{
- IMutation mutation = builder.build(allowPotentialTxnConflicts);
+ IMutation mutation = builder.build(potentialTxnConflicts);
mutation.validateIndexedColumns(state);
mutation.validateSize(MessagingService.current_version, CommitLogSegment.ENTRY_OVERHEAD_SIZE);
ms.add(mutation);
@@ -182,7 +183,7 @@ final class BatchUpdatesCollector implements UpdatesCollector
/**
* Build the immutable mutation
*/
- IMutation build(boolean allowPotentialTxnConflicts);
+ IMutation build(PotentialTxnConflicts potentialTxnConflicts);
/**
* Get the builder for the given tableId
@@ -215,7 +216,7 @@ final class BatchUpdatesCollector implements UpdatesCollector
return this;
}
- public Mutation build(boolean allowPotentialTxnConflicts)
+ public Mutation build(PotentialTxnConflicts potentialTxnConflicts)
{
ImmutableMap.Builder updates = new ImmutableMap.Builder<>();
for (Map.Entry updateEntry : modifications.entrySet())
@@ -223,7 +224,7 @@ final class BatchUpdatesCollector implements UpdatesCollector
PartitionUpdate update = updateEntry.getValue().build();
updates.put(updateEntry.getKey(), update);
}
- return new Mutation(keyspaceName, key, updates.build(), createdAt, allowPotentialTxnConflicts);
+ return new Mutation(keyspaceName, key, updates.build(), createdAt, potentialTxnConflicts);
}
public PartitionUpdate.Builder get(TableId tableId)
@@ -263,9 +264,9 @@ final class BatchUpdatesCollector implements UpdatesCollector
return mutationBuilder.add(builder);
}
- public IMutation build(boolean allowPotentialTxnConflicts)
+ public IMutation build(PotentialTxnConflicts potentialTxnConflicts)
{
- return new CounterMutation(mutationBuilder.build(allowPotentialTxnConflicts), cl);
+ return new CounterMutation(mutationBuilder.build(potentialTxnConflicts), cl);
}
public PartitionUpdate.Builder get(TableId id)
@@ -297,7 +298,7 @@ final class BatchUpdatesCollector implements UpdatesCollector
}
@Override
- public VirtualMutation build(boolean allowPotentialTxnConflicts)
+ public VirtualMutation build(PotentialTxnConflicts potentialTxnConflicts)
{
ImmutableMap.Builder updates = new ImmutableMap.Builder<>();
modifications.forEach((tableId, updateBuilder) -> updates.put(tableId, updateBuilder.build()));
diff --git a/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java b/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
index c315bd04a8..03a9876efa 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java
@@ -63,8 +63,8 @@ import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.accord.txn.TxnCondition;
import org.apache.cassandra.service.accord.txn.TxnData;
import org.apache.cassandra.service.accord.txn.TxnDataKeyValue;
-import org.apache.cassandra.service.accord.txn.TxnKeyRead;
import org.apache.cassandra.service.accord.txn.TxnQuery;
+import org.apache.cassandra.service.accord.txn.TxnRead;
import org.apache.cassandra.service.accord.txn.TxnReference;
import org.apache.cassandra.service.accord.txn.TxnResult;
import org.apache.cassandra.service.accord.txn.TxnUpdate;
@@ -83,7 +83,6 @@ import static org.apache.cassandra.service.accord.txn.TxnData.txnDataName;
import static org.apache.cassandra.service.accord.txn.TxnResult.Kind.retry_new_protocol;
import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.getTableMetadata;
-
/**
* Processed CAS conditions and update on potentially multiple rows of the same partition.
*/
@@ -504,8 +503,8 @@ public class CQL3CasRequest implements CASRequest
// If the write strategy is sending all writes through Accord there is no need to use the supplied consistency
// level since Accord will manage reading safely
TableParams tableParams = getTableMetadata(cm, metadata.id).params;
- consistencyLevel = tableParams.transactionalMode.readCLForStrategy(tableParams.transactionalMigrationFrom, consistencyLevel, cm, metadata.id, readCommand.partitionKey().getToken());
- TxnKeyRead read = TxnKeyRead.createCasRead(readCommand, consistencyLevel);
+ consistencyLevel = tableParams.transactionalMode.readCLForMode(tableParams.transactionalMigrationFrom, consistencyLevel, cm, metadata.id, readCommand.partitionKey().getToken());
+ TxnRead read = TxnRead.createCasRead(readCommand, consistencyLevel);
// In a CAS requesting only one key is supported and writes
// can't be dependent on any data that is read (only conditions)
// so the only relevant keys are the read key
@@ -518,7 +517,7 @@ public class CQL3CasRequest implements CASRequest
// since it is safe to match what non-SERIAL writes do
TableMetadata tableMetadata = getTableMetadata(cm, metadata.id);
TableParams tableParams = tableMetadata.params;
- commitConsistencyLevel = tableParams.transactionalMode.commitCLForStrategy(tableParams.transactionalMigrationFrom, commitConsistencyLevel, cm, metadata.id, key.getToken());
+ commitConsistencyLevel = tableParams.transactionalMode.commitCLForMode(tableParams.transactionalMigrationFrom, commitConsistencyLevel, cm, metadata.id, key.getToken());
// CAS requires using the new txn timestamp to correctly linearize some kinds of updates
return new TxnUpdate(createWriteFragments(clientState), createCondition(), commitConsistencyLevel, false);
}
diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
index a02cd27b4e..cecf839e8b 100644
--- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
@@ -73,6 +73,7 @@ import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.IMutation;
import org.apache.cassandra.db.Keyspace;
+import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.ReadExecutionController;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.SinglePartitionReadCommand;
@@ -870,7 +871,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
SingleTableSinglePartitionUpdatesCollector collector = new SingleTableSinglePartitionUpdatesCollector(metadata, updatedColumns);
addUpdates(collector, keys, state, options, local, timestamp, nowInSeconds, requestTime, constructingAccordBaseUpdate);
// local means this is test or internal things that are bypassing distributed system modification/checks
- return collector.toMutations(state, local);
+ return collector.toMutations(state, local ? PotentialTxnConflicts.ALLOW : PotentialTxnConflicts.DISALLOW);
}
else
{
@@ -878,7 +879,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
SingleTableUpdatesCollector collector = new SingleTableUpdatesCollector(metadata, updatedColumns, perPartitionKeyCounts);
addUpdates(collector, keys, state, options, local, timestamp, nowInSeconds, requestTime, constructingAccordBaseUpdate);
// local means this is test or internal things that are bypassing distributed system modification/checks
- return collector.toMutations(state, local);
+ return collector.toMutations(state, local ? PotentialTxnConflicts.ALLOW : PotentialTxnConflicts.DISALLOW);
}
}
diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index 45f2499af7..7f025cf02f 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -79,6 +79,7 @@ import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.PartitionPosition;
import org.apache.cassandra.db.PartitionRangeReadQuery;
+import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.ReadExecutionController;
import org.apache.cassandra.db.ReadQuery;
import org.apache.cassandra.db.SinglePartitionReadCommand;
@@ -386,7 +387,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
}
}
- ReadQuery query = getQuery(options, state.getClientState(), selectors.getColumnFilter(), nowInSec, limit);
+ ReadQuery query = getQuery(options, state.getClientState(), selectors.getColumnFilter(), nowInSec, limit, PotentialTxnConflicts.DISALLOW);
if (options.isReadThresholdsEnabled())
query.trackWarnings();
@@ -437,7 +438,8 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
getLimit(options),
getPerPartitionLimit(options),
options.getPageSize(),
- getAggregationSpec(options));
+ getAggregationSpec(options),
+ PotentialTxnConflicts.DISALLOW);
}
public ReadQuery getQuery(QueryOptions options,
@@ -447,18 +449,20 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
int userLimit,
int perPartitionLimit,
int pageSize,
- AggregationSpecification aggregationSpec)
+ AggregationSpecification aggregationSpec,
+ PotentialTxnConflicts potentialTxnConflicts)
{
DataLimits limit = getDataLimits(userLimit, perPartitionLimit, pageSize, aggregationSpec);
- return getQuery(options, state, columnFilter, nowInSec, limit);
+ return getQuery(options, state, columnFilter, nowInSec, limit, potentialTxnConflicts);
}
public ReadQuery getQuery(QueryOptions options,
ClientState state,
ColumnFilter columnFilter,
long nowInSec,
- DataLimits limit)
+ DataLimits limit,
+ PotentialTxnConflicts potentialTxnConflicts)
{
RowFilter rowFilter = getRowFilter(options, state);
@@ -467,13 +471,13 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
if (restrictions.usesSecondaryIndexing() && !SchemaConstants.isLocalSystemKeyspace(table.keyspace))
Guardrails.nonPartitionRestrictedIndexQueryEnabled.ensureEnabled(state);
- return getRangeCommand(options, state, columnFilter, rowFilter, limit, nowInSec);
+ return getRangeCommand(options, state, columnFilter, rowFilter, limit, nowInSec, potentialTxnConflicts);
}
if (restrictions.usesSecondaryIndexing() && !rowFilter.isStrict())
- return getRangeCommand(options, state, columnFilter, rowFilter, limit, nowInSec);
+ return getRangeCommand(options, state, columnFilter, rowFilter, limit, nowInSec, potentialTxnConflicts);
- return getSliceCommands(options, state, columnFilter, rowFilter, limit, nowInSec);
+ return getSliceCommands(options, state, columnFilter, rowFilter, limit, nowInSec, potentialTxnConflicts);
}
private ResultMessage.Rows execute(ReadQuery query,
@@ -657,7 +661,8 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
userLimit,
userPerPartitionLimit,
pageSize,
- aggregationSpec);
+ aggregationSpec,
+ PotentialTxnConflicts.ALLOW);
try (ReadExecutionController executionController = query.executionController())
{
@@ -704,7 +709,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
throw new IllegalStateException();
Selectors selectors = selection.newSelectors(options);
- ReadQuery query = getQuery(options, state, selectors.getColumnFilter(), nowInSec, userLimit, userPerPartitionLimit, Integer.MAX_VALUE, null);
+ ReadQuery query = getQuery(options, state, selectors.getColumnFilter(), nowInSec, userLimit, userPerPartitionLimit, Integer.MAX_VALUE, null, PotentialTxnConflicts.ALLOW);
Map> result = Collections.emptyMap();
try (ReadExecutionController executionController = query.executionController())
@@ -778,7 +783,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
}
private ReadQuery getSliceCommands(QueryOptions options, ClientState state, ColumnFilter columnFilter,
- RowFilter rowFilter, DataLimits limit, long nowInSec)
+ RowFilter rowFilter, DataLimits limit, long nowInSec, PotentialTxnConflicts potentialTxnConflicts)
{
Collection keys = restrictions.getPartitionKeys(options, state);
if (keys.isEmpty())
@@ -801,7 +806,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
}
SinglePartitionReadQuery.Group extends SinglePartitionReadQuery> group =
- SinglePartitionReadQuery.createGroup(table, nowInSec, columnFilter, rowFilter, limit, decoratedKeys, filter);
+ SinglePartitionReadQuery.createGroup(table, nowInSec, columnFilter, rowFilter, limit, decoratedKeys, filter, potentialTxnConflicts);
// If there's a secondary index that the commands can use, have it validate the request parameters.
group.maybeValidateIndex();
@@ -855,7 +860,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
}
private ReadQuery getRangeCommand(QueryOptions options, ClientState state, ColumnFilter columnFilter,
- RowFilter rowFilter, DataLimits limit, long nowInSec)
+ RowFilter rowFilter, DataLimits limit, long nowInSec, PotentialTxnConflicts potentialTxnConflicts)
{
ClusteringIndexFilter clusteringIndexFilter = makeClusteringIndexFilter(options, state, columnFilter);
if (clusteringIndexFilter == null)
@@ -868,7 +873,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
return ReadQuery.empty(table);
ReadQuery command =
- PartitionRangeReadQuery.create(table, nowInSec, columnFilter, rowFilter, limit, new DataRange(keyBounds, clusteringIndexFilter));
+ PartitionRangeReadQuery.create(table, nowInSec, columnFilter, rowFilter, limit, new DataRange(keyBounds, clusteringIndexFilter), potentialTxnConflicts);
// If there's a secondary index that the command can use, have it validate the request parameters.
command.maybeValidateIndex();
diff --git a/src/java/org/apache/cassandra/cql3/statements/SingleTableSinglePartitionUpdatesCollector.java b/src/java/org/apache/cassandra/cql3/statements/SingleTableSinglePartitionUpdatesCollector.java
index 6664c09e2c..eba1c27bfa 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SingleTableSinglePartitionUpdatesCollector.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SingleTableSinglePartitionUpdatesCollector.java
@@ -25,6 +25,7 @@ import org.apache.cassandra.db.CounterMutation;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.IMutation;
import org.apache.cassandra.db.Mutation;
+import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.commitlog.CommitLogSegment;
import org.apache.cassandra.db.partitions.PartitionUpdate;
@@ -78,16 +79,16 @@ final class SingleTableSinglePartitionUpdatesCollector implements UpdatesCollect
* Returns a collection containing all the mutations.
*/
@Override
- public List toMutations(ClientState state, boolean allowPotentialTxnConflicts)
+ public List toMutations(ClientState state, PotentialTxnConflicts potentialTxnConflicts)
{
// it is possible that a modification statement does not create any mutations
// for example: DELETE FROM some_table WHERE part_key = 1 AND clust_key < 3 AND clust_key > 5
if (builder == null)
return Collections.emptyList();
- return Collections.singletonList(createMutation(state, builder, allowPotentialTxnConflicts));
+ return Collections.singletonList(createMutation(state, builder, potentialTxnConflicts));
}
- private IMutation createMutation(ClientState state, PartitionUpdate.Builder builder, boolean allowPotentialTxnConflicts)
+ private IMutation createMutation(ClientState state, PartitionUpdate.Builder builder, PotentialTxnConflicts potentialTxnConflicts)
{
IMutation mutation;
@@ -96,7 +97,7 @@ final class SingleTableSinglePartitionUpdatesCollector implements UpdatesCollect
else if (metadata.isCounter())
mutation = new CounterMutation(new Mutation(builder.build()), counterConsistencyLevel);
else
- mutation = new Mutation(builder.build(), allowPotentialTxnConflicts);
+ mutation = new Mutation(builder.build(), potentialTxnConflicts);
mutation.validateIndexedColumns(state);
mutation.validateSize(MessagingService.current_version, CommitLogSegment.ENTRY_OVERHEAD_SIZE);
diff --git a/src/java/org/apache/cassandra/cql3/statements/SingleTableUpdatesCollector.java b/src/java/org/apache/cassandra/cql3/statements/SingleTableUpdatesCollector.java
index c2497360a7..b2570e9d70 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SingleTableUpdatesCollector.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SingleTableUpdatesCollector.java
@@ -31,6 +31,7 @@ import org.apache.cassandra.db.CounterMutation;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.IMutation;
import org.apache.cassandra.db.Mutation;
+import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.commitlog.CommitLogSegment;
import org.apache.cassandra.db.partitions.PartitionUpdate;
@@ -95,24 +96,24 @@ final class SingleTableUpdatesCollector implements UpdatesCollector
* @return a collection containing all the mutations.
*/
@Override
- public List toMutations(ClientState state, boolean allowPotentialTxnConflicts)
+ public List toMutations(ClientState state, PotentialTxnConflicts potentialTxnConflicts)
{
if (puBuilders.size() == 1)
{
PartitionUpdate.Builder builder = puBuilders.values().iterator().next();
- return Collections.singletonList(createMutation(state, builder, allowPotentialTxnConflicts));
+ return Collections.singletonList(createMutation(state, builder, potentialTxnConflicts));
}
List ms = new ArrayList<>(puBuilders.size());
for (PartitionUpdate.Builder builder : puBuilders.values())
{
- IMutation mutation = createMutation(state, builder, allowPotentialTxnConflicts);
+ IMutation mutation = createMutation(state, builder, potentialTxnConflicts);
ms.add(mutation);
}
return ms;
}
- private IMutation createMutation(ClientState state, PartitionUpdate.Builder builder, boolean allowPotentialTxnConflicts)
+ private IMutation createMutation(ClientState state, PartitionUpdate.Builder builder, PotentialTxnConflicts potentialTxnConflicts)
{
IMutation mutation;
@@ -121,7 +122,7 @@ final class SingleTableUpdatesCollector implements UpdatesCollector
else if (metadata.isCounter())
mutation = new CounterMutation(new Mutation(builder.build()), counterConsistencyLevel);
else
- mutation = new Mutation(builder.build(), allowPotentialTxnConflicts);
+ mutation = new Mutation(builder.build(), potentialTxnConflicts);
mutation.validateIndexedColumns(state);
mutation.validateSize(MessagingService.current_version, CommitLogSegment.ENTRY_OVERHEAD_SIZE);
diff --git a/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java b/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java
index b95c1faa4a..f61c394954 100644
--- a/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java
@@ -40,6 +40,7 @@ import com.google.common.collect.Iterables;
import accord.api.Key;
import accord.primitives.Keys;
+import accord.primitives.Routable.Domain;
import accord.primitives.Txn;
import org.agrona.collections.Int2ObjectHashMap;
import org.apache.cassandra.audit.AuditLogContext;
@@ -56,34 +57,43 @@ import org.apache.cassandra.cql3.transactions.ConditionStatement;
import org.apache.cassandra.cql3.transactions.ReferenceOperation;
import org.apache.cassandra.cql3.transactions.RowDataReference;
import org.apache.cassandra.cql3.transactions.SelectReferenceSource;
+import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.SinglePartitionReadCommand;
import org.apache.cassandra.db.SinglePartitionReadQuery;
import org.apache.cassandra.db.filter.DataLimits;
import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.Schema;
+import org.apache.cassandra.schema.TableId;
+import org.apache.cassandra.schema.TableParams;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.api.AccordRoutableKey;
+import org.apache.cassandra.service.accord.api.PartitionKey;
import org.apache.cassandra.service.accord.txn.AccordUpdate;
import org.apache.cassandra.service.accord.txn.TxnCondition;
import org.apache.cassandra.service.accord.txn.TxnData;
import org.apache.cassandra.service.accord.txn.TxnDataKeyValue;
-import org.apache.cassandra.service.accord.txn.TxnKeyRead;
import org.apache.cassandra.service.accord.txn.TxnNamedRead;
import org.apache.cassandra.service.accord.txn.TxnQuery;
+import org.apache.cassandra.service.accord.txn.TxnRead;
import org.apache.cassandra.service.accord.txn.TxnReference;
import org.apache.cassandra.service.accord.txn.TxnResult;
import org.apache.cassandra.service.accord.txn.TxnUpdate;
import org.apache.cassandra.service.accord.txn.TxnWrite;
+import org.apache.cassandra.service.consensus.TransactionalMode;
+import org.apache.cassandra.service.consensus.migration.TransactionalMigrationFromMode;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.utils.FBUtilities;
import static accord.primitives.Txn.Kind.Read;
+import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
@@ -92,8 +102,9 @@ import static org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind.AU
import static org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind.RETURNING;
import static org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind.USER;
import static org.apache.cassandra.service.accord.txn.TxnData.txnDataName;
-import static org.apache.cassandra.service.accord.txn.TxnKeyRead.createTxnRead;
+import static org.apache.cassandra.service.accord.txn.TxnRead.createTxnRead;
import static org.apache.cassandra.service.accord.txn.TxnResult.Kind.retry_new_protocol;
+import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.getTableMetadata;
import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.shouldReadEphemerally;
public class TransactionStatement implements CQLStatement.CompositeCQLStatement, CQLStatement.ReturningCQLStatement
@@ -261,8 +272,8 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
for (NamedSelect select : assignments)
{
TxnNamedRead read = createNamedRead(select, options, state);
+ keyConsumer.accept((Key)read.key());
minEpoch = Math.max(minEpoch, read.command().metadata().epoch.getEpoch());
- keyConsumer.accept(read.key());
reads.add(read);
}
@@ -270,7 +281,7 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
{
for (TxnNamedRead read : createNamedReads(returningSelect, options, state))
{
- keyConsumer.accept(read.key());
+ keyConsumer.accept((Key)read.key());
minEpoch = Math.max(minEpoch, read.command().metadata().epoch.getEpoch());
reads.add(read);
}
@@ -279,8 +290,11 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
if (autoReads != null)
{
for (NamedSelect select : autoReads.values())
- // don't need keyConsumer as the keys are known to exist due to Modification
- reads.add(createNamedRead(select, options, state));
+ {
+ TxnNamedRead read = createNamedRead(select, options, state);
+ keyConsumer.accept((Key)read.key());
+ reads.add(read);
+ }
}
return reads;
@@ -301,15 +315,15 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
return new TxnCondition.BooleanGroup(TxnCondition.Kind.AND, result);
}
- List createWriteFragments(ClientState state, QueryOptions options, Map autoReads, Consumer keyConsumer)
+ List createWriteFragments(ClientState state, QueryOptions options, Map autoReads, Set keys)
{
List fragments = new ArrayList<>(updates.size());
int idx = 0;
for (ModificationStatement modification : updates)
{
TxnWrite.Fragment fragment = modification.getTxnWriteFragment(idx, state, options);
+ keys.add(fragment.key);
minEpoch = Math.max(minEpoch, fragment.baseUpdate.metadata().epoch.getEpoch());
- keyConsumer.accept(fragment.key);
fragments.add(fragment);
if (modification.allReferenceOperations().stream().anyMatch(ReferenceOperation::requiresRead))
@@ -325,9 +339,12 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
return fragments;
}
- AccordUpdate createUpdate(ClientState state, QueryOptions options, Map autoReads, Consumer keyConsumer)
+ AccordUpdate createUpdate(ClusterMetadata cm, ClientState state, QueryOptions options, Map autoReads, Set keys)
{
- return new TxnUpdate(createWriteFragments(state, options, autoReads, keyConsumer), createCondition(options), null, false);
+ checkArgument(keys.isEmpty(), "Construct update before reads so the key set can be used to determine commit consistency level");
+ List writeFragments = createWriteFragments(state, options, autoReads, keys);
+ ConsistencyLevel commitCL = consistencyLevelForAccordCommit(cm, keys, options.getConsistency());
+ return new TxnUpdate(writeFragments, createCondition(options), commitCL, false);
}
Keys toKeys(SortedSet keySet)
@@ -335,10 +352,90 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
return new Keys(keySet);
}
+ private ConsistencyLevel consistencyLevelForAccordRead(ClusterMetadata cm, Set keys, @Nullable ConsistencyLevel consistencyLevel)
+ {
+ // Write transactions are read/write so it creates a read and ends up needing a consistency level
+ // which is fine to leave null
+ if (keys.isEmpty())
+ return null;
+
+ // Null means no specific consistency behavior is required from Accord, it's functionally similar to
+ // reading at ONE if you are reading data that wasn't written via Accord
+ if (consistencyLevel == null)
+ return null;
+
+ for (Key key : keys)
+ {
+ // readCLForMode should return either null or the supplied consistency level
+ // in which case we will read everything at that CL since Accord doesn't support per table
+ // read consistency
+ ConsistencyLevel readCL = consistencyLevelForAccordRead(cm, key, consistencyLevel);
+ if (readCL != null)
+ return readCL;
+ }
+ return null;
+ }
+
+ private ConsistencyLevel consistencyLevelForAccordRead(ClusterMetadata cm, Key key, ConsistencyLevel consistencyLevel)
+ {
+ // Null means no specific consistency behavior is required from Accord, it's functionally similar to
+ // reading at ONE if you are reading data that wasn't written via Accord
+ if (consistencyLevel == null)
+ return null;
+
+ PartitionKey pk = (PartitionKey)key;
+ TableId tableId = pk.table();
+ Token token = pk.token();
+ TableParams tableParams = getTableMetadata(cm, tableId).params;
+ TransactionalMode mode = tableParams.transactionalMode;
+ TransactionalMigrationFromMode migrationFromMode = tableParams.transactionalMigrationFrom;
+ return mode.readCLForMode(migrationFromMode, consistencyLevel, cm, tableId, token);
+ }
+
+ private static ConsistencyLevel consistencyLevelForAccordCommit(ClusterMetadata cm, Set keys, @Nullable ConsistencyLevel consistencyLevel)
+ {
+ checkArgument(!keys.isEmpty(), "keys should not be empty");
+ // Null means no specific consistency behavior is required from Accord, it's functionally similar to ANY
+ // if you aren't reading the result back via Accord
+ if (consistencyLevel == null)
+ return null;
+
+ for (Key key : keys)
+ {
+ // commitCLForMode should return either null or the supplied consistency level
+ // in which case we will commit everything at that CL since Accord doesn't support per table
+ // commit consistency
+ ConsistencyLevel commitCL = consistencyLevelForAccordCommit(cm, key, consistencyLevel);
+ if (commitCL != null)
+ return commitCL;
+ }
+ return null;
+ }
+
+ private static ConsistencyLevel consistencyLevelForAccordCommit(ClusterMetadata cm, Key key, @Nullable ConsistencyLevel consistencyLevel)
+ {
+ // Null means no specific consistency behavior is required from Accord, it's functionally similar to ANY
+ // if you aren't reading the result back via Accord
+ if (consistencyLevel == null)
+ return null;
+
+ PartitionKey pk = (PartitionKey)key;
+ TableId tableId = pk.table();
+ Token token = pk.token();
+ TableParams tableParams = getTableMetadata(cm, tableId).params;
+ TransactionalMode mode = tableParams.transactionalMode;
+ TransactionalMigrationFromMode migrationFromMode = tableParams.transactionalMigrationFrom;
+ // commitCLForMode should return either null or the supplied consistency level
+ // in which case we will commit everything at that CL since Accord doesn't support per table
+ // commit consistency
+ return mode.commitCLForMode(migrationFromMode, consistencyLevel, cm, tableId, token);
+ }
+
@VisibleForTesting
public Txn createTxn(ClientState state, QueryOptions options)
{
SortedSet keySet = new TreeSet<>();
+ ClusterMetadata cm = ClusterMetadata.current();
if (updates.isEmpty())
{
@@ -346,18 +443,17 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
Preconditions.checkState(conditions.isEmpty(), "No condition should exist without updates present");
List reads = createNamedReads(options, state, null, keySet::add);
Keys txnKeys = toKeys(keySet);
- TxnKeyRead read = createTxnRead(reads, null);
+ TxnRead read = createTxnRead(reads, consistencyLevelForAccordRead(cm, keySet, options.getSerialConsistency()), Domain.Key);
Txn.Kind kind = shouldReadEphemerally(txnKeys, Schema.instance.getTableMetadata(((AccordRoutableKey) txnKeys.get(0)).table()).params, Read);
return new Txn.InMemory(kind, txnKeys, read, TxnQuery.ALL, null);
}
else
{
Int2ObjectHashMap autoReads = new Int2ObjectHashMap<>();
- AccordUpdate update = createUpdate(state, options, autoReads, keySet::add);
+ AccordUpdate update = createUpdate(cm, state, options, autoReads, keySet);
List reads = createNamedReads(options, state, autoReads, keySet::add);
- Keys txnKeys = toKeys(keySet);
- TxnKeyRead read = createTxnRead(reads, null);
- return new Txn.InMemory(txnKeys, read, TxnQuery.ALL, update);
+ TxnRead read = createTxnRead(reads, null, Domain.Key);
+ return new Txn.InMemory(toKeys(keySet), read, TxnQuery.ALL, update);
}
}
diff --git a/src/java/org/apache/cassandra/cql3/statements/UpdatesCollector.java b/src/java/org/apache/cassandra/cql3/statements/UpdatesCollector.java
index de19a8e567..a3867b608f 100644
--- a/src/java/org/apache/cassandra/cql3/statements/UpdatesCollector.java
+++ b/src/java/org/apache/cassandra/cql3/statements/UpdatesCollector.java
@@ -23,6 +23,7 @@ import java.util.List;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.IMutation;
+import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
@@ -30,5 +31,5 @@ import org.apache.cassandra.service.ClientState;
public interface UpdatesCollector
{
PartitionUpdate.Builder getPartitionUpdateBuilder(TableMetadata metadata, DecoratedKey dk, ConsistencyLevel consistency);
- List toMutations(ClientState state, boolean allowPotentialTxnConflicts);
+ List toMutations(ClientState state, PotentialTxnConflicts allowPotentialTxnConflicts);
}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
index 2475e1dcaa..8c8ae8b45c 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
@@ -595,7 +595,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
MemtableParams.get(attrs.getString(TableParams.Option.MEMTABLE.toString()));
Guardrails.tableProperties.guard(attrs.updatedProperties(), attrs::removeProperty, state);
- validateDefaultTimeToLive(attrs.asNewTableParams());
+ validateDefaultTimeToLive(attrs.asNewTableParams(keyspaceName));
}
private TableParams validateAndUpdateTransactionalMigration(boolean isCounter, TableParams prev, TableParams next)
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CopyTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CopyTableStatement.java
index 3a79311dc7..c5b38e5e08 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CopyTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CopyTableStatement.java
@@ -236,7 +236,7 @@ public final class CopyTableStatement extends AlterSchemaStatement
.sum();
Guardrails.tables.guard(totalUserTables + 1, targetTableName, false, state);
}
- validateDefaultTimeToLive(attrs.asNewTableParams());
+ validateDefaultTimeToLive(attrs.asNewTableParams(keyspaceName));
}
private void maybeCopyIndexes(TableMetadata.Builder builder, TableMetadata sourceTableMeta, KeyspaceMetadata targetKeyspaceMeta)
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
index 711af0c7ee..2e25291ed8 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
@@ -17,13 +17,20 @@
*/
package org.apache.cassandra.cql3.statements.schema;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
import java.util.stream.Collectors;
-
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableSet;
-
import org.apache.commons.lang3.StringUtils;
import org.apache.cassandra.audit.AuditLogContext;
@@ -32,26 +39,45 @@ import org.apache.cassandra.auth.DataResource;
import org.apache.cassandra.auth.IResource;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.config.DatabaseDescriptor;
-import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.constraints.ColumnConstraints;
+import org.apache.cassandra.cql3.CQL3Type;
+import org.apache.cassandra.cql3.CQLFragmentParser;
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.CqlParser;
+import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.cql3.functions.masking.ColumnMask;
import org.apache.cassandra.db.guardrails.Guardrails;
-import org.apache.cassandra.db.marshal.*;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.BytesType;
+import org.apache.cassandra.db.marshal.CounterColumnType;
+import org.apache.cassandra.db.marshal.EmptyType;
+import org.apache.cassandra.db.marshal.ReversedType;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.exceptions.AlreadyExistsException;
-import org.apache.cassandra.schema.*;
+import org.apache.cassandra.schema.KeyspaceMetadata;
+import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
+import org.apache.cassandra.schema.MemtableParams;
+import org.apache.cassandra.schema.Schema;
+import org.apache.cassandra.schema.SchemaConstants;
+import org.apache.cassandra.schema.TableId;
+import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.schema.TableParams;
+import org.apache.cassandra.schema.Types;
+import org.apache.cassandra.schema.UserFunctions;
import org.apache.cassandra.service.ClientState;
-import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.service.reads.repair.ReadRepairStrategy;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
+import static com.google.common.collect.Iterables.concat;
import static java.lang.String.format;
import static java.util.Comparator.comparing;
-import static com.google.common.collect.Iterables.concat;
-
public final class CreateTableStatement extends AlterSchemaStatement
{
private final String tableName;
@@ -189,7 +215,7 @@ public final class CreateTableStatement extends AlterSchemaStatement
if (useCompactStorage)
Guardrails.compactTablesEnabled.ensureEnabled(state);
- validateDefaultTimeToLive(attrs.asNewTableParams());
+ validateDefaultTimeToLive(attrs.asNewTableParams(keyspaceName));
rawColumns.forEach((name, raw) -> raw.validate(state, name));
}
@@ -224,7 +250,7 @@ public final class CreateTableStatement extends AlterSchemaStatement
public TableMetadata.Builder builder(Types types, UserFunctions functions)
{
attrs.validate();
- TableParams params = attrs.asNewTableParams();
+ TableParams params = attrs.asNewTableParams(keyspaceName);
// use a TreeMap to preserve ordering across JDK versions (see CASSANDRA-9492) - important for stable unit tests
Map columns = new TreeMap<>(comparing(o -> o.bytes));
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
index e68e0c003d..284edf112b 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
@@ -17,7 +17,12 @@
*/
package org.apache.cassandra.cql3.statements.schema;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Set;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -27,7 +32,11 @@ import org.apache.cassandra.audit.AuditLogContext;
import org.apache.cassandra.audit.AuditLogEntryType;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.config.DatabaseDescriptor;
-import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.QualifiedName;
+import org.apache.cassandra.cql3.VariableSpecifications;
+import org.apache.cassandra.cql3.WhereClause;
import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
import org.apache.cassandra.cql3.selection.RawSelector;
import org.apache.cassandra.cql3.selection.Selectable;
@@ -38,19 +47,24 @@ import org.apache.cassandra.db.marshal.ReversedType;
import org.apache.cassandra.db.view.View;
import org.apache.cassandra.exceptions.AlreadyExistsException;
import org.apache.cassandra.exceptions.InvalidRequestException;
-import org.apache.cassandra.schema.*;
+import org.apache.cassandra.schema.ColumnMetadata;
+import org.apache.cassandra.schema.KeyspaceMetadata;
+import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
+import org.apache.cassandra.schema.TableId;
+import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.schema.TableParams;
+import org.apache.cassandra.schema.ViewMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
-import static java.lang.String.join;
-
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;
+import static java.lang.String.join;
import static org.apache.cassandra.config.CassandraRelevantProperties.MV_ALLOW_FILTERING_NONKEY_COLUMNS_UNSAFE;
public final class CreateViewStatement extends AlterSchemaStatement
@@ -334,7 +348,7 @@ public final class CreateViewStatement extends AlterSchemaStatement
else if (!builder.hasId())
builder.id(TableId.get(metadata));
- builder.params(attrs.asNewTableParams())
+ builder.params(attrs.asNewTableParams(keyspaceName))
.kind(TableMetadata.Kind.VIEW);
partitionKeyColumns.stream()
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/TableAttributes.java b/src/java/org/apache/cassandra/cql3/statements/schema/TableAttributes.java
index eb9c492798..2e643e5472 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/TableAttributes.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/TableAttributes.java
@@ -31,6 +31,8 @@ import org.apache.cassandra.schema.CachingParams;
import org.apache.cassandra.schema.CompactionParams;
import org.apache.cassandra.schema.CompressionParams;
import org.apache.cassandra.schema.MemtableParams;
+import org.apache.cassandra.schema.Schema;
+import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableParams;
import org.apache.cassandra.schema.TableParams.Option;
@@ -41,7 +43,24 @@ import org.apache.cassandra.service.reads.SpeculativeRetryPolicy;
import org.apache.cassandra.service.reads.repair.ReadRepairStrategy;
import static java.lang.String.format;
-import static org.apache.cassandra.schema.TableParams.Option.*;
+import static org.apache.cassandra.schema.TableParams.Option.ADDITIONAL_WRITE_POLICY;
+import static org.apache.cassandra.schema.TableParams.Option.ALLOW_AUTO_SNAPSHOT;
+import static org.apache.cassandra.schema.TableParams.Option.BLOOM_FILTER_FP_CHANCE;
+import static org.apache.cassandra.schema.TableParams.Option.CACHING;
+import static org.apache.cassandra.schema.TableParams.Option.CDC;
+import static org.apache.cassandra.schema.TableParams.Option.COMMENT;
+import static org.apache.cassandra.schema.TableParams.Option.COMPACTION;
+import static org.apache.cassandra.schema.TableParams.Option.COMPRESSION;
+import static org.apache.cassandra.schema.TableParams.Option.CRC_CHECK_CHANCE;
+import static org.apache.cassandra.schema.TableParams.Option.DEFAULT_TIME_TO_LIVE;
+import static org.apache.cassandra.schema.TableParams.Option.GC_GRACE_SECONDS;
+import static org.apache.cassandra.schema.TableParams.Option.INCREMENTAL_BACKUPS;
+import static org.apache.cassandra.schema.TableParams.Option.MAX_INDEX_INTERVAL;
+import static org.apache.cassandra.schema.TableParams.Option.MEMTABLE_FLUSH_PERIOD_IN_MS;
+import static org.apache.cassandra.schema.TableParams.Option.MIN_INDEX_INTERVAL;
+import static org.apache.cassandra.schema.TableParams.Option.READ_REPAIR;
+import static org.apache.cassandra.schema.TableParams.Option.SPECULATIVE_RETRY;
+import static org.apache.cassandra.schema.TableParams.Option.TRANSACTIONAL_MODE;
public final class TableAttributes extends PropertyDefinitions
{
@@ -65,10 +84,10 @@ public final class TableAttributes extends PropertyDefinitions
build(TableParams.builder()).validate();
}
- TableParams asNewTableParams()
+ TableParams asNewTableParams(String keyspaceName)
{
TableParams.Builder builder = TableParams.builder();
- if (!hasOption(TRANSACTIONAL_MODE))
+ if (!hasOption(TRANSACTIONAL_MODE) && !SchemaConstants.isSystemKeyspace(keyspaceName) && Schema.instance.distributedKeyspaces().names().contains(keyspaceName))
builder.transactionalMode(DatabaseDescriptor.defaultTransactionalMode());
return build(builder);
}
diff --git a/src/java/org/apache/cassandra/db/CounterMutation.java b/src/java/org/apache/cassandra/db/CounterMutation.java
index 6935b09452..c5ed2364be 100644
--- a/src/java/org/apache/cassandra/db/CounterMutation.java
+++ b/src/java/org/apache/cassandra/db/CounterMutation.java
@@ -36,6 +36,7 @@ import com.google.common.collect.PeekingIterator;
import com.google.common.util.concurrent.Striped;
import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.context.CounterContext;
import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter;
import org.apache.cassandra.db.filter.ColumnFilter;
@@ -189,9 +190,9 @@ public class CounterMutation implements IMutation
* anyways and it's safe to continue non-transactionally updating them
*/
@Override
- public boolean allowsPotentialTransactionConflicts()
+ public PotentialTxnConflicts potentialTxnConflicts()
{
- return true;
+ return PotentialTxnConflicts.ALLOW;
}
private void grabCounterLocks(Keyspace keyspace, List locks) throws WriteTimeoutException
diff --git a/src/java/org/apache/cassandra/db/IMutation.java b/src/java/org/apache/cassandra/db/IMutation.java
index 1e77ee7fc8..282db28916 100644
--- a/src/java/org/apache/cassandra/db/IMutation.java
+++ b/src/java/org/apache/cassandra/db/IMutation.java
@@ -24,6 +24,7 @@ import java.util.function.Supplier;
import javax.annotation.Nullable;
import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.ClientState;
@@ -84,9 +85,9 @@ public interface IMutation
* like Accord that can't safely read data that is written non-transactionally.
*
*/
- default boolean allowsPotentialTransactionConflicts()
+ default PotentialTxnConflicts potentialTxnConflicts()
{
- return false;
+ return PotentialTxnConflicts.DISALLOW;
}
// Construct replacement mutation that is identical except it only includes updates for the specified tables
diff --git a/src/java/org/apache/cassandra/db/MutableDeletionInfo.java b/src/java/org/apache/cassandra/db/MutableDeletionInfo.java
index f9636909c4..3bbfe163b3 100644
--- a/src/java/org/apache/cassandra/db/MutableDeletionInfo.java
+++ b/src/java/org/apache/cassandra/db/MutableDeletionInfo.java
@@ -23,7 +23,8 @@ import java.util.Iterator;
import com.google.common.base.Objects;
import org.apache.cassandra.config.DatabaseDescriptor;
-import org.apache.cassandra.db.rows.*;
+import org.apache.cassandra.db.rows.EncodingStats;
+import org.apache.cassandra.db.rows.RangeTombstoneMarker;
import org.apache.cassandra.utils.ObjectSizes;
import org.apache.cassandra.utils.memory.ByteBufferCloner;
@@ -230,7 +231,7 @@ public class MutableDeletionInfo implements DeletionInfo
return this;
}
- public DeletionInfo updateAllTimestampAndLocalDeletionTime(long timestamp, int localDeletionTime)
+ public DeletionInfo updateAllTimestampAndLocalDeletionTime(long timestamp, long localDeletionTime)
{
if (partitionDeletion.markedForDeleteAt() != Long.MIN_VALUE)
partitionDeletion = DeletionTime.build(timestamp, localDeletionTime);
diff --git a/src/java/org/apache/cassandra/db/Mutation.java b/src/java/org/apache/cassandra/db/Mutation.java
index f7afb8d568..441163c8bd 100644
--- a/src/java/org/apache/cassandra/db/Mutation.java
+++ b/src/java/org/apache/cassandra/db/Mutation.java
@@ -39,6 +39,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.cassandra.concurrent.Stage;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.commitlog.CommitLog;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.rows.DeserializationHelper;
@@ -98,31 +99,31 @@ public class Mutation implements IMutation, Supplier
// that are only safe to write to from a transaction system.
// Don't refuse to apply this mutation because it should go through a transaction system
// because it is being applied by one or in a context where transaction conflicts don't occur
- private boolean allowPotentialTransactionConflicts;
+ private PotentialTxnConflicts potentialTxnConflicts;
public Mutation(PartitionUpdate update)
{
- this(update, false);
+ this(update, PotentialTxnConflicts.DISALLOW);
}
- public Mutation(PartitionUpdate update, boolean allowPotentialTransactionConflicts)
+ public Mutation(PartitionUpdate update, PotentialTxnConflicts potentialTxnConflicts)
{
- this(update.metadata().keyspace, update.partitionKey(), ImmutableMap.of(update.metadata().id, update), approxTime.now(), update.metadata().params.cdc, allowPotentialTransactionConflicts);
+ this(update.metadata().keyspace, update.partitionKey(), ImmutableMap.of(update.metadata().id, update), approxTime.now(), update.metadata().params.cdc, potentialTxnConflicts);
}
- public Mutation(String keyspaceName, DecoratedKey key, ImmutableMap modifications, long approxCreatedAtNanos, boolean allowPotentialTransactionConflicts)
+ public Mutation(String keyspaceName, DecoratedKey key, ImmutableMap modifications, long approxCreatedAtNanos, PotentialTxnConflicts potentialTxnConflicts)
{
- this(keyspaceName, key, modifications, approxCreatedAtNanos, cdcEnabled(modifications.values()), allowPotentialTransactionConflicts);
+ this(keyspaceName, key, modifications, approxCreatedAtNanos, cdcEnabled(modifications.values()), potentialTxnConflicts);
}
- public Mutation(String keyspaceName, DecoratedKey key, ImmutableMap modifications, long approxCreatedAtNanos, boolean cdcEnabled, boolean allowPotentialTransactionConflicts)
+ public Mutation(String keyspaceName, DecoratedKey key, ImmutableMap modifications, long approxCreatedAtNanos, boolean cdcEnabled, PotentialTxnConflicts potentialTxnConflicts)
{
this.keyspaceName = keyspaceName;
this.key = key;
this.modifications = modifications;
this.cdcEnabled = cdcEnabled;
this.approxCreatedAtNanos = approxCreatedAtNanos;
- this.allowPotentialTransactionConflicts = allowPotentialTransactionConflicts;
+ this.potentialTxnConflicts = potentialTxnConflicts;
}
private static boolean cdcEnabled(Iterable modifications)
@@ -156,7 +157,7 @@ public class Mutation implements IMutation, Supplier
Map updates = builder.build();
checkState(!updates.isEmpty(), "Updates should not be empty");
- return new Mutation(keyspaceName, key, builder.build(), approxCreatedAtNanos, allowPotentialTransactionConflicts);
+ return new Mutation(keyspaceName, key, builder.build(), approxCreatedAtNanos, potentialTxnConflicts);
}
public @Nullable Mutation without(TableId tableId)
@@ -248,12 +249,12 @@ public class Mutation implements IMutation, Supplier
Set updatedTables = new HashSet<>();
String ks = null;
DecoratedKey key = null;
- Boolean allowPotentialTransactionConflicts = null;
+ PotentialTxnConflicts potentialTxnConflicts = null;
for (Mutation mutation : mutations)
{
- if (allowPotentialTransactionConflicts != null && allowPotentialTransactionConflicts != mutation.allowPotentialTransactionConflicts)
+ if (potentialTxnConflicts != null && potentialTxnConflicts != mutation.potentialTxnConflicts)
throw new IllegalArgumentException("Can't merge mutations with differing policies on allowing potential transaction conflicts");
- allowPotentialTransactionConflicts = mutation.allowPotentialTransactionConflicts;
+ potentialTxnConflicts = mutation.potentialTxnConflicts;
updatedTables.addAll(mutation.modifications.keySet());
if (ks != null && !ks.equals(mutation.keyspaceName))
throw new IllegalArgumentException();
@@ -280,7 +281,7 @@ public class Mutation implements IMutation, Supplier
modifications.put(table, updates.size() == 1 ? updates.get(0) : PartitionUpdate.merge(updates));
updates.clear();
}
- return new Mutation(ks, key, modifications.build(), approxTime.now(), allowPotentialTransactionConflicts);
+ return new Mutation(ks, key, modifications.build(), approxTime.now(), potentialTxnConflicts);
}
public Future> applyFuture()
@@ -339,24 +340,24 @@ public class Mutation implements IMutation, Supplier
public void allowPotentialTransactionConflicts()
{
- allowPotentialTransactionConflicts = true;
+ potentialTxnConflicts = PotentialTxnConflicts.ALLOW;
Arrays.fill(cachedSerializations, null);
}
@Override
- public boolean allowsPotentialTransactionConflicts()
+ public PotentialTxnConflicts potentialTxnConflicts()
{
- return allowPotentialTransactionConflicts;
+ return potentialTxnConflicts;
}
- private static int allowPotentialTransactionConflictsFlag(boolean allowPotentialTransactionConflicts)
+ private static int potentialTxnConflictsFlag(PotentialTxnConflicts potentialTxnConflicts)
{
- return allowPotentialTransactionConflicts ? ALLOW_POTENTIAL_TRANSACTION_CONFLICTS : 0;
+ return potentialTxnConflicts.allowed ? ALLOW_POTENTIAL_TRANSACTION_CONFLICTS : 0;
}
- public static boolean allowPotentialTransactionConflicts(int flags)
+ public static PotentialTxnConflicts potentialTxnConflicts(int flags)
{
- return (flags & ALLOW_POTENTIAL_TRANSACTION_CONFLICTS) != 0;
+ return (flags & ALLOW_POTENTIAL_TRANSACTION_CONFLICTS) != 0 ? PotentialTxnConflicts.ALLOW : PotentialTxnConflicts.DISALLOW;
}
public String toString()
@@ -443,7 +444,7 @@ public class Mutation implements IMutation, Supplier
* being handled by the caller
* @return this builder
*/
- public SimpleBuilder allowPotentialTransactionConflicts();
+ public SimpleBuilder allowPotentialTxnConflicts();
/**
* Sets the timestamp to use for the following additions to this builder or any derived (update or row) builder.
@@ -560,7 +561,7 @@ public class Mutation implements IMutation, Supplier
if (version >= VERSION_51)
{
int flags = 0;
- flags |= allowPotentialTransactionConflictsFlag(mutation.allowPotentialTransactionConflicts);
+ flags |= potentialTxnConflictsFlag(mutation.potentialTxnConflicts);
out.write(flags);
}
@@ -583,11 +584,11 @@ public class Mutation implements IMutation, Supplier
{
teeIn = new TeeDataInputPlus(in, dob, CACHEABLE_MUTATION_SIZE_LIMIT);
- boolean allowPotentialTransactionConflicts = false;
+ PotentialTxnConflicts potentialTxnConflicts = PotentialTxnConflicts.DISALLOW;
if (version >= VERSION_51)
{
int flags = teeIn.readByte();
- allowPotentialTransactionConflicts = allowPotentialTransactionConflicts(flags);
+ potentialTxnConflicts = potentialTxnConflicts(flags);
}
int size = teeIn.readUnsignedVInt32();
assert size > 0;
@@ -595,7 +596,7 @@ public class Mutation implements IMutation, Supplier
PartitionUpdate update = PartitionUpdate.serializer.deserialize(teeIn, version, flag);
if (size == 1)
{
- m = new Mutation(update, allowPotentialTransactionConflicts);
+ m = new Mutation(update, potentialTxnConflicts);
}
else
{
@@ -608,7 +609,7 @@ public class Mutation implements IMutation, Supplier
update = PartitionUpdate.serializer.deserialize(teeIn, version, flag);
modifications.put(update.metadata().id, update);
}
- m = new Mutation(update.metadata().keyspace, dk, modifications.build(), approxTime.now(), allowPotentialTransactionConflicts);
+ m = new Mutation(update.metadata().keyspace, dk, modifications.build(), approxTime.now(), potentialTxnConflicts);
}
//Only cache serializations that don't hit the limit
@@ -708,18 +709,18 @@ public class Mutation implements IMutation, Supplier
private final long approxCreatedAtNanos = approxTime.now();
private boolean empty = true;
- private boolean allowPotentialTransactionConflicts;
+ private PotentialTxnConflicts potentialTxnConflicts;
public PartitionUpdateCollector(String keyspaceName, DecoratedKey key)
{
- this(keyspaceName, key, false);
+ this(keyspaceName, key, PotentialTxnConflicts.DISALLOW);
}
- public PartitionUpdateCollector(String keyspaceName, DecoratedKey key, boolean allowPotentialTransactionConflicts)
+ public PartitionUpdateCollector(String keyspaceName, DecoratedKey key, PotentialTxnConflicts potentialTxnConflicts)
{
this.keyspaceName = keyspaceName;
this.key = key;
- this.allowPotentialTransactionConflicts = allowPotentialTransactionConflicts;
+ this.potentialTxnConflicts = potentialTxnConflicts;
}
public PartitionUpdateCollector add(PartitionUpdate partitionUpdate)
@@ -753,7 +754,7 @@ public class Mutation implements IMutation, Supplier
public Mutation build()
{
- return new Mutation(keyspaceName, key, modifications.build(), approxCreatedAtNanos, allowPotentialTransactionConflicts);
+ return new Mutation(keyspaceName, key, modifications.build(), approxCreatedAtNanos, potentialTxnConflicts);
}
}
}
diff --git a/src/java/org/apache/cassandra/db/MutationVerbHandler.java b/src/java/org/apache/cassandra/db/MutationVerbHandler.java
index c30fae63b4..3312a12b65 100644
--- a/src/java/org/apache/cassandra/db/MutationVerbHandler.java
+++ b/src/java/org/apache/cassandra/db/MutationVerbHandler.java
@@ -19,7 +19,10 @@ package org.apache.cassandra.db;
import org.apache.cassandra.exceptions.WriteTimeoutException;
import org.apache.cassandra.locator.InetAddressAndPort;
-import org.apache.cassandra.net.*;
+import org.apache.cassandra.net.ForwardingInfo;
+import org.apache.cassandra.net.Message;
+import org.apache.cassandra.net.MessagingService;
+import org.apache.cassandra.net.ParamType;
import org.apache.cassandra.tracing.Tracing;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
diff --git a/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java b/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java
index 130c47a31f..52fbcaa98c 100644
--- a/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java
+++ b/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java
@@ -72,7 +72,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
- boolean allowOutOfRangeReads,
+ PotentialTxnConflicts potentialTxnConflicts,
TableMetadata metadata,
long nowInSec,
ColumnFilter columnFilter,
@@ -82,7 +82,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
Index.QueryPlan indexQueryPlan,
boolean trackWarnings)
{
- super(serializedAtEpoch, Kind.PARTITION_RANGE, isDigest, digestVersion, acceptsTransient, allowOutOfRangeReads, metadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan, trackWarnings, dataRange);
+ super(serializedAtEpoch, Kind.PARTITION_RANGE, isDigest, digestVersion, acceptsTransient, potentialTxnConflicts, metadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan, trackWarnings, dataRange);
this.requestedSlices = dataRange.clusteringIndexFilter.getSlices(metadata());
}
@@ -90,7 +90,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
- boolean allowsOutOfRangeReads,
+ PotentialTxnConflicts potentialTxnConflicts,
TableMetadata metadata,
long nowInSec,
ColumnFilter columnFilter,
@@ -118,7 +118,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
isDigest,
digestVersion,
acceptsTransient,
- allowsOutOfRangeReads,
+ potentialTxnConflicts,
metadata,
nowInSec,
columnFilter,
@@ -140,7 +140,30 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
false,
0,
false,
+ PotentialTxnConflicts.DISALLOW,
+ metadata,
+ nowInSec,
+ columnFilter,
+ rowFilter,
+ limits,
+ dataRange,
+ findIndexQueryPlan(metadata, rowFilter),
+ false);
+ }
+
+ public static PartitionRangeReadCommand create(TableMetadata metadata,
+ long nowInSec,
+ ColumnFilter columnFilter,
+ RowFilter rowFilter,
+ DataLimits limits,
+ DataRange dataRange,
+ PotentialTxnConflicts potentialTxnConflicts)
+ {
+ return create(metadata.epoch,
false,
+ 0,
+ false,
+ potentialTxnConflicts,
metadata,
nowInSec,
columnFilter,
@@ -165,7 +188,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
false,
0,
false,
- false,
+ PotentialTxnConflicts.DISALLOW,
metadata,
nowInSec,
ColumnFilter.all(metadata),
@@ -212,7 +235,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
isDigestQuery(),
digestVersion(),
acceptsTransient(),
- allowsOutOfRangeReads(),
+ potentialTxnConflicts(),
metadata(),
nowInSec(),
columnFilter(),
@@ -223,32 +246,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
isTrackingWarnings());
}
- /*
- * The execution method does not need to perform reconciliation so the read command
- * should execute in a mannager suited to not needing reconciliation. Such as when
- * executing transactionally at a single replica and doing an index scan where the index
- * scan should not return extra rows and expect post filtering at the coordinator.
- */
- public PartitionRangeReadCommand withoutReconciliation()
- {
- if (rowFilter().isEmpty())
- return this;
- return create(serializedAtEpoch(),
- isDigestQuery(),
- digestVersion(),
- acceptsTransient(),
- allowsOutOfRangeReads(),
- metadata(),
- nowInSec(),
- columnFilter(),
- rowFilter().withoutReconciliation(),
- limits(),
- dataRange(),
- indexQueryPlan(),
- isTrackingWarnings());
- }
-
- public PartitionRangeReadCommand forSubRangeWithNowInSeconds(long nowInSec, AbstractBounds range, boolean isRangeContinuation)
+ public PartitionRangeReadCommand withTransactionalSettings(long nowInSec, AbstractBounds range, boolean isRangeContinuation, boolean withoutReconciliation)
{
// If we're not a continuation of whatever range we've previously queried, we should ignore the states of the
// DataLimits as it's either useless, or misleading. This is particularly important for GROUP BY queries, where
@@ -259,26 +257,26 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
isDigestQuery(),
digestVersion(),
acceptsTransient(),
- allowsOutOfRangeReads(),
+ PotentialTxnConflicts.ALLOW,
metadata(),
nowInSec,
columnFilter(),
- rowFilter(),
+ withoutReconciliation ? rowFilter().withoutReconciliation() : rowFilter(),
isRangeContinuation ? limits() : limits().withoutState(),
dataRange().forSubRange(range),
indexQueryPlan(),
isTrackingWarnings());
}
- public PartitionRangeReadCommand withNowInSec(long nowInSec)
+ public PartitionRangeReadCommand withTxnReadName(int txnReadName)
{
return create(serializedAtEpoch(),
isDigestQuery(),
digestVersion(),
acceptsTransient(),
- allowsOutOfRangeReads(),
+ potentialTxnConflicts(),
metadata(),
- nowInSec,
+ txnReadName,
columnFilter(),
rowFilter(),
limits(),
@@ -293,7 +291,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
isDigestQuery(),
digestVersion(),
acceptsTransient(),
- allowsOutOfRangeReads(),
+ potentialTxnConflicts(),
metadata(),
nowInSec(),
columnFilter(),
@@ -311,7 +309,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
true,
digestVersion(),
false,
- allowsOutOfRangeReads(),
+ potentialTxnConflicts(),
metadata(),
nowInSec(),
columnFilter(),
@@ -329,7 +327,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
false,
0,
true,
- allowsOutOfRangeReads(),
+ potentialTxnConflicts(),
metadata(),
nowInSec(),
columnFilter(),
@@ -347,7 +345,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
isDigestQuery(),
digestVersion(),
acceptsTransient(),
- allowsOutOfRangeReads(),
+ potentialTxnConflicts(),
metadata(),
nowInSec(),
columnFilter(),
@@ -365,7 +363,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
isDigestQuery(),
digestVersion(),
acceptsTransient(),
- allowsOutOfRangeReads(),
+ potentialTxnConflicts(),
metadata(),
nowInSec(),
columnFilter(),
@@ -601,7 +599,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
- boolean allowsOutOfRangeReads,
+ PotentialTxnConflicts potentialTxnConflicts,
TableMetadata metadata,
long nowInSec,
ColumnFilter columnFilter,
@@ -611,7 +609,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
throws IOException
{
DataRange range = DataRange.serializer.deserialize(in, version, metadata);
- return PartitionRangeReadCommand.create(serializedAtEpoch, isDigest, digestVersion, acceptsTransient, allowsOutOfRangeReads, metadata, nowInSec, columnFilter, rowFilter, limits, range, indexQueryPlan, false);
+ return PartitionRangeReadCommand.create(serializedAtEpoch, isDigest, digestVersion, acceptsTransient, potentialTxnConflicts, metadata, nowInSec, columnFilter, rowFilter, limits, range, indexQueryPlan, false);
}
}
@@ -629,7 +627,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
Index.QueryPlan indexQueryPlan,
boolean trackWarnings)
{
- super(metadata.epoch, isDigest, digestVersion, acceptsTransient, true, metadata, nowInSec, columnFilter, rowFilter, limits, dataRange, indexQueryPlan, trackWarnings);
+ super(metadata.epoch, isDigest, digestVersion, acceptsTransient, PotentialTxnConflicts.ALLOW, metadata, nowInSec, columnFilter, rowFilter, limits, dataRange, indexQueryPlan, trackWarnings);
}
@Override
diff --git a/src/java/org/apache/cassandra/db/PartitionRangeReadQuery.java b/src/java/org/apache/cassandra/db/PartitionRangeReadQuery.java
index d91930125d..934c33eafa 100644
--- a/src/java/org/apache/cassandra/db/PartitionRangeReadQuery.java
+++ b/src/java/org/apache/cassandra/db/PartitionRangeReadQuery.java
@@ -17,6 +17,7 @@
*/
package org.apache.cassandra.db;
+import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.filter.DataLimits;
import org.apache.cassandra.db.filter.RowFilter;
@@ -36,9 +37,10 @@ public interface PartitionRangeReadQuery extends ReadQuery
ColumnFilter columnFilter,
RowFilter rowFilter,
DataLimits limits,
- DataRange dataRange)
+ DataRange dataRange,
+ PotentialTxnConflicts potentialTxnConflicts)
{
- return PartitionRangeReadCommand.create(table, nowInSec, columnFilter, rowFilter, limits, dataRange);
+ return PartitionRangeReadCommand.create(table, nowInSec, columnFilter, rowFilter, limits, dataRange, potentialTxnConflicts);
}
/**
diff --git a/src/java/org/apache/cassandra/db/RangeTombstoneList.java b/src/java/org/apache/cassandra/db/RangeTombstoneList.java
index 8eee422be3..963985788a 100644
--- a/src/java/org/apache/cassandra/db/RangeTombstoneList.java
+++ b/src/java/org/apache/cassandra/db/RangeTombstoneList.java
@@ -22,14 +22,14 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
-import org.apache.cassandra.config.DatabaseDescriptor;
-import org.apache.cassandra.utils.AbstractIterator;
-import org.apache.cassandra.utils.CassandraUInt;
-
import com.google.common.collect.Iterators;
import org.apache.cassandra.cache.IMeasurableMemory;
-import org.apache.cassandra.db.rows.*;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.db.rows.Cell;
+import org.apache.cassandra.db.rows.EncodingStats;
+import org.apache.cassandra.utils.AbstractIterator;
+import org.apache.cassandra.utils.CassandraUInt;
import org.apache.cassandra.utils.ObjectSizes;
import org.apache.cassandra.utils.memory.ByteBufferCloner;
@@ -328,12 +328,13 @@ public class RangeTombstoneList implements Iterable, IMeasurable
markedAts[i] = timestamp;
}
- public void updateAllTimestampAndLocalDeletionTime(long timestamp, int localDeletionTime)
+ public void updateAllTimestampAndLocalDeletionTime(long timestamp, long localDeletionTime)
{
+ int unsignedLocalDeletionTime = Cell.deletionTimeLongToUnsignedInteger(localDeletionTime);
for (int i = 0; i < size; i++)
{
markedAts[i] = timestamp;
- delTimesUnsignedIntegers[i] = localDeletionTime;
+ delTimesUnsignedIntegers[i] = unsignedLocalDeletionTime;
}
}
diff --git a/src/java/org/apache/cassandra/db/ReadCommand.java b/src/java/org/apache/cassandra/db/ReadCommand.java
index d6b0f4734d..8b80044f18 100644
--- a/src/java/org/apache/cassandra/db/ReadCommand.java
+++ b/src/java/org/apache/cassandra/db/ReadCommand.java
@@ -90,6 +90,7 @@ import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.service.ClientWarn;
+import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tracing.Tracing;
@@ -100,6 +101,7 @@ import org.apache.cassandra.utils.NoSpamLogger;
import org.apache.cassandra.utils.ObjectSizes;
import org.apache.cassandra.utils.TimeUUID;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.filter;
import static org.apache.cassandra.db.partitions.UnfilteredPartitionIterators.MergeListener.NOOP;
@@ -119,6 +121,30 @@ public abstract class ReadCommand extends AbstractReadQuery
protected static final Logger logger = LoggerFactory.getLogger(ReadCommand.class);
public static final IVersionedSerializer serializer = new Serializer();
+ public enum PotentialTxnConflicts
+ {
+ /**
+ * Check for and raise an error if this operation should have been transactionally managed. For use
+ * by queries that aren't issued by a transaction system managing potential conflicts in contexts where
+ * conflicts would be a problem.
+ */
+ DISALLOW(false),
+
+ /**
+ * Don't check or raise an error if this operation could conflict with transactions. For use when the thing
+ * being managed doesn't support transactions or the operation is being done by a transaction that is already
+ * managing any potential conflicts.
+ */
+ ALLOW(true);
+
+ public final boolean allowed;
+
+ PotentialTxnConflicts(boolean allowed)
+ {
+ this.allowed = allowed;
+ }
+ }
+
// Expose the active command running so transitive calls can lookup this command.
// This is useful for a few reasons, but mainly because the CQL query is here.
private static final FastThreadLocal COMMAND = new FastThreadLocal<>();
@@ -128,7 +154,8 @@ public abstract class ReadCommand extends AbstractReadQuery
private final boolean isDigestQuery;
private final boolean acceptsTransient;
private final Epoch serializedAtEpoch;
- private boolean allowsOutOfRangeReads;
+ private final PotentialTxnConflicts potentialTxnConflicts;
+
// if a digest query, the version for which the digest is expected. Ignored if not a digest.
private int digestVersion;
@@ -147,7 +174,7 @@ public abstract class ReadCommand extends AbstractReadQuery
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
- boolean allowsOutOfRangeReads,
+ PotentialTxnConflicts potentialTxnConflicts,
TableMetadata metadata,
long nowInSec,
ColumnFilter columnFilter,
@@ -174,7 +201,7 @@ public abstract class ReadCommand extends AbstractReadQuery
boolean isDigestQuery,
int digestVersion,
boolean acceptsTransient,
- boolean allowsOutOfRangeReads,
+ PotentialTxnConflicts potentialTxnConflicts,
TableMetadata metadata,
long nowInSec,
ColumnFilter columnFilter,
@@ -193,7 +220,7 @@ public abstract class ReadCommand extends AbstractReadQuery
this.digestVersion = digestVersion;
this.acceptsTransient = acceptsTransient;
this.indexQueryPlan = indexQueryPlan;
- this.allowsOutOfRangeReads = allowsOutOfRangeReads;
+ this.potentialTxnConflicts = potentialTxnConflicts;
this.trackWarnings = trackWarnings;
this.serializedAtEpoch = serializedAtEpoch;
this.dataRange = dataRange;
@@ -345,7 +372,7 @@ public abstract class ReadCommand extends AbstractReadQuery
*/
public ReadCommand copyAsTransientQuery(Replica replica)
{
- Preconditions.checkArgument(replica.isTransient(),
+ checkArgument(replica.isTransient(),
"Can't make a transient request on a full replica: " + replica);
return copyAsTransientQuery();
}
@@ -367,7 +394,7 @@ public abstract class ReadCommand extends AbstractReadQuery
*/
public ReadCommand copyAsDigestQuery(Replica replica)
{
- Preconditions.checkArgument(replica.isFull(),
+ checkArgument(replica.isFull(),
"Can't make a digest request on a transient replica " + replica);
return copyAsDigestQuery();
}
@@ -462,6 +489,8 @@ public abstract class ReadCommand extends AbstractReadQuery
try
{
ColumnFamilyStore cfs = Keyspace.openAndGetStore(metadata());
+ if (!potentialTxnConflicts.allowed)
+ ConsensusRequestRouter.validateSafeToReadNonTransactionally(this);
Index.QueryPlan indexQueryPlan = indexQueryPlan();
Index.Searcher searcher = null;
@@ -550,15 +579,9 @@ public abstract class ReadCommand extends AbstractReadQuery
return ReadExecutionController.forCommand(this, false);
}
- public ReadCommand allowOutOfRangeReads()
+ public PotentialTxnConflicts potentialTxnConflicts()
{
- allowsOutOfRangeReads = true;
- return this;
- }
-
- public boolean allowsOutOfRangeReads()
- {
- return allowsOutOfRangeReads;
+ return potentialTxnConflicts;
}
/**
@@ -1250,7 +1273,7 @@ public abstract class ReadCommand extends AbstractReadQuery
private static final int HAS_INDEX = 0x04;
private static final int ACCEPTS_TRANSIENT = 0x08;
private static final int NEEDS_RECONCILIATION = 0x10;
- private static final int ALLOWS_OUT_OF_RANGE_READS = 0x20;
+ private static final int ALLOWS_POTENTIAL_TXN_CONFLICTS = 0x20;
private final SchemaProvider schema;
@@ -1315,14 +1338,14 @@ public abstract class ReadCommand extends AbstractReadQuery
return (flags & NEEDS_RECONCILIATION) != 0;
}
- private static int allowsOutOfRangeReadsFlag(boolean allowsOutOfRangeReads)
+ private static int potentialTxnConflicts(PotentialTxnConflicts potentialTxnConflicts)
{
- return allowsOutOfRangeReads ? ALLOWS_OUT_OF_RANGE_READS: 0;
+ return potentialTxnConflicts.allowed ? ALLOWS_POTENTIAL_TXN_CONFLICTS : 0;
}
- private static boolean allowsOutOfRangeReads(int flags)
+ private static PotentialTxnConflicts potentialTxnConflicts(int flags)
{
- return (flags & ALLOWS_OUT_OF_RANGE_READS) != 0;
+ return (flags & ALLOWS_POTENTIAL_TXN_CONFLICTS) != 0 ? PotentialTxnConflicts.ALLOW : PotentialTxnConflicts.DISALLOW;
}
public void serialize(ReadCommand command, DataOutputPlus out, int version) throws IOException
@@ -1333,7 +1356,7 @@ public abstract class ReadCommand extends AbstractReadQuery
| indexFlag(null != command.indexQueryPlan())
| acceptsTransientFlag(command.acceptsTransient())
| needsReconciliationFlag(command.rowFilter().needsReconciliation())
- | allowsOutOfRangeReadsFlag(command.allowsOutOfRangeReads)
+ | potentialTxnConflicts(command.potentialTxnConflicts)
);
if (command.isDigestQuery())
out.writeUnsignedVInt32(command.digestVersion());
@@ -1359,7 +1382,7 @@ public abstract class ReadCommand extends AbstractReadQuery
int flags = in.readByte();
boolean isDigest = isDigest(flags);
boolean acceptsTransient = acceptsTransient(flags);
- boolean allowsOutOfRangeReads = allowsOutOfRangeReads(flags);
+ PotentialTxnConflicts potentialTxnConflicts = potentialTxnConflicts(flags);
// Shouldn't happen or it's a user error (see comment above) but
// better complain loudly than doing the wrong thing.
if (isForThrift(flags))
@@ -1405,7 +1428,7 @@ public abstract class ReadCommand extends AbstractReadQuery
indexQueryPlan = indexGroup.queryPlanFor(rowFilter);
}
- return kind.selectionDeserializer.deserialize(in, version, schemaVersion, isDigest, digestVersion, acceptsTransient, allowsOutOfRangeReads, tableMetadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan);
+ return kind.selectionDeserializer.deserialize(in, version, schemaVersion, isDigest, digestVersion, acceptsTransient, potentialTxnConflicts, tableMetadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan);
}
private IndexMetadata deserializeIndexMetadata(DataInputPlus in, int version, TableMetadata metadata) throws IOException
diff --git a/src/java/org/apache/cassandra/db/ReadCommandVerbHandler.java b/src/java/org/apache/cassandra/db/ReadCommandVerbHandler.java
index 0094a0df92..8ff89f4e9b 100644
--- a/src/java/org/apache/cassandra/db/ReadCommandVerbHandler.java
+++ b/src/java/org/apache/cassandra/db/ReadCommandVerbHandler.java
@@ -28,6 +28,7 @@ import org.apache.cassandra.exceptions.CoordinatorBehindException;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.InvalidRoutingException;
import org.apache.cassandra.exceptions.QueryCancelledException;
+import org.apache.cassandra.exceptions.RetryOnDifferentSystemException;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.metrics.TCMMetrics;
import org.apache.cassandra.net.IVerbHandler;
@@ -42,6 +43,7 @@ import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.FBUtilities;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static org.apache.cassandra.exceptions.RequestFailureReason.RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM;
public class ReadCommandVerbHandler implements IVerbHandler
{
@@ -99,6 +101,13 @@ public class ReadCommandVerbHandler implements IVerbHandler
MessagingService.instance().send(reply, message.from());
return;
}
+ catch (RetryOnDifferentSystemException e)
+ {
+ logger.debug("Responding with retry on different system");
+ MessagingService.instance().respondWithFailure(RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM, message);
+ Tracing.trace("Payload application resulted in RetryOnDifferentSysten");
+ return;
+ }
catch (AssertionError t)
{
throw new AssertionError(String.format("Caught an error while trying to process the command: %s", command.toCQLString()), t);
@@ -164,7 +173,7 @@ public class ReadCommandVerbHandler implements IVerbHandler
// Some read commands may be sent using an older Epoch intentionally so validating using the current Epoch
// doesn't work
- if (command.allowsOutOfRangeReads())
+ if (command.potentialTxnConflicts().allowed)
return metadata;
if (command.isTopK())
diff --git a/src/java/org/apache/cassandra/db/ReadResponse.java b/src/java/org/apache/cassandra/db/ReadResponse.java
index d4906b2dd5..65e17a6920 100644
--- a/src/java/org/apache/cassandra/db/ReadResponse.java
+++ b/src/java/org/apache/cassandra/db/ReadResponse.java
@@ -17,14 +17,19 @@
*/
package org.apache.cassandra.db;
-import java.io.*;
+import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.db.filter.ColumnFilter;
-import org.apache.cassandra.db.partitions.*;
-import org.apache.cassandra.db.rows.*;
+import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
+import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
+import org.apache.cassandra.db.rows.DeserializationHelper;
+import org.apache.cassandra.db.rows.Rows;
+import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.io.util.DataInputPlus;
@@ -132,6 +137,37 @@ public abstract class ReadResponse
return sb.toString();
}
+ /**
+ * For range reads Accord generates multiple responses per node because each command store executes
+ * the reads independently. The responses are already sorted in token order so the iterators or digests can be
+ * merged and still produce a consistent result across different nodes.
+ *
+ * This can *only* be called from the node producing the results not the coordinator because isEmptyDigest is
+ * not serialized
+ */
+ public static ReadResponse merge(List responses, ReadCommand command)
+ {
+ if (responses.get(0).isDigestResponse())
+ {
+ Digest digest = Digest.forReadResponse();
+ for (ReadResponse response : responses)
+ digest.update(((DigestResponse)response).digest);
+ return new DigestResponse(ByteBuffer.wrap(digest.digest()));
+ }
+ else
+ {
+ List iterators = new ArrayList<>(responses.size());
+ for (ReadResponse response : responses)
+ iterators.add(response.makeIterator(command));
+
+ // Range responses will not respect the limit because each command store returns a separate response
+ // so we effectively deserialize and then reserialize in order to apply the limits
+ // Wasteful, but better than sending it to the coordinator to do it
+ UnfilteredPartitionIterator filtered = command.limits().filter(UnfilteredPartitionIterators.concat(iterators), 0, command.selectsFullPartition());
+ return new LocalDataResponse(filtered, command, NO_OP_REPAIRED_DATA_INFO);
+ }
+ }
+
protected static ByteBuffer makeDigest(UnfilteredPartitionIterator iterator, ReadCommand command)
{
Digest digest = Digest.forReadResponse();
diff --git a/src/java/org/apache/cassandra/db/SimpleBuilders.java b/src/java/org/apache/cassandra/db/SimpleBuilders.java
index af13cb9126..7139b6c8b2 100644
--- a/src/java/org/apache/cassandra/db/SimpleBuilders.java
+++ b/src/java/org/apache/cassandra/db/SimpleBuilders.java
@@ -26,6 +26,7 @@ import java.util.Map;
import java.util.Set;
import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.context.CounterContext;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CollectionType;
@@ -120,7 +121,7 @@ public abstract class SimpleBuilders
private final Map updateBuilders = new HashMap<>();
- private boolean allowPotentialTransactionConflicts = false;
+ private PotentialTxnConflicts potentialTxnConflicts = PotentialTxnConflicts.DISALLOW;
public MutationBuilder(String keyspaceName, DecoratedKey key)
{
@@ -128,9 +129,9 @@ public abstract class SimpleBuilders
this.key = key;
}
- public MutationBuilder allowPotentialTransactionConflicts()
+ public MutationBuilder allowPotentialTxnConflicts()
{
- allowPotentialTransactionConflicts = true;
+ potentialTxnConflicts = PotentialTxnConflicts.ALLOW;
return this;
}
@@ -162,9 +163,9 @@ public abstract class SimpleBuilders
assert !updateBuilders.isEmpty() : "Cannot create empty mutation";
if (updateBuilders.size() == 1)
- return new Mutation(updateBuilders.values().iterator().next().build(), allowPotentialTransactionConflicts);
+ return new Mutation(updateBuilders.values().iterator().next().build(), potentialTxnConflicts);
- Mutation.PartitionUpdateCollector mutationBuilder = new Mutation.PartitionUpdateCollector(keyspaceName, key, allowPotentialTransactionConflicts);
+ Mutation.PartitionUpdateCollector mutationBuilder = new Mutation.PartitionUpdateCollector(keyspaceName, key, potentialTxnConflicts);
for (PartitionUpdateBuilder builder : updateBuilders.values())
mutationBuilder.add(builder.build());
return mutationBuilder.build();
diff --git a/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java b/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
index cdff02aa4f..0b4d25a925 100644
--- a/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
+++ b/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
@@ -98,7 +98,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
- boolean allowsOutOfRangeReads,
+ PotentialTxnConflicts potentialTxnConflicts,
TableMetadata metadata,
long nowInSec,
ColumnFilter columnFilter,
@@ -110,7 +110,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
boolean trackWarnings,
DataRange dataRange)
{
- super(serializedAtEpoch, Kind.SINGLE_PARTITION, isDigest, digestVersion, acceptsTransient, allowsOutOfRangeReads, metadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan, trackWarnings, dataRange);
+ super(serializedAtEpoch, Kind.SINGLE_PARTITION, isDigest, digestVersion, acceptsTransient, potentialTxnConflicts, metadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan, trackWarnings, dataRange);
assert partitionKey.getPartitioner() == metadata.partitioner;
this.partitionKey = partitionKey;
this.clusteringIndexFilter = clusteringIndexFilter;
@@ -120,7 +120,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
- boolean allowsOutOfRangeReads,
+ PotentialTxnConflicts potentialTxnConflicts,
TableMetadata metadata,
long nowInSec,
ColumnFilter columnFilter,
@@ -154,7 +154,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
isDigest,
digestVersion,
acceptsTransient,
- allowsOutOfRangeReads,
+ potentialTxnConflicts,
metadata,
nowInSec,
columnFilter,
@@ -194,7 +194,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
false,
0,
false,
- false,
+ PotentialTxnConflicts.DISALLOW,
metadata,
nowInSec,
columnFilter,
@@ -206,6 +206,44 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
false);
}
+ /**
+ * Creates a new read command on a single partition.
+ *
+ * @param metadata the table to query.
+ * @param nowInSec the time in seconds to use are "now" for this query.
+ * @param columnFilter the column filter to use for the query.
+ * @param rowFilter the row filter to use for the query.
+ * @param limits the limits to use for the query.
+ * @param partitionKey the partition key for the partition to query.
+ * @param clusteringIndexFilter the clustering index filter to use for the query.
+ *
+ * @return a newly created read command.
+ */
+ public static SinglePartitionReadCommand create(TableMetadata metadata,
+ long nowInSec,
+ ColumnFilter columnFilter,
+ RowFilter rowFilter,
+ DataLimits limits,
+ DecoratedKey partitionKey,
+ ClusteringIndexFilter clusteringIndexFilter,
+ PotentialTxnConflicts potentialTxnConflicts)
+ {
+ return create(metadata.epoch,
+ false,
+ 0,
+ false,
+ potentialTxnConflicts,
+ metadata,
+ nowInSec,
+ columnFilter,
+ rowFilter,
+ limits,
+ partitionKey,
+ clusteringIndexFilter,
+ findIndexQueryPlan(metadata, rowFilter),
+ false);
+ }
+
/**
* Creates a new read command on a single partition.
*
@@ -373,7 +411,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
isDigestQuery(),
digestVersion(),
acceptsTransient(),
- allowsOutOfRangeReads(),
+ potentialTxnConflicts(),
metadata(),
nowInSec(),
columnFilter(),
@@ -392,7 +430,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
true,
digestVersion(),
acceptsTransient(),
- allowsOutOfRangeReads(),
+ potentialTxnConflicts(),
metadata(),
nowInSec(),
columnFilter(),
@@ -411,7 +449,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
false,
0,
true,
- allowsOutOfRangeReads(),
+ potentialTxnConflicts(),
metadata(),
nowInSec(),
columnFilter(),
@@ -430,7 +468,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
isDigestQuery(),
digestVersion(),
acceptsTransient(),
- allowsOutOfRangeReads(),
+ potentialTxnConflicts(),
metadata(),
nowInSec(),
columnFilter(),
@@ -442,24 +480,6 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
isTrackingWarnings());
}
- public SinglePartitionReadCommand withNowInSec(long nowInSec)
- {
- return create(serializedAtEpoch(),
- isDigestQuery(),
- digestVersion(),
- acceptsTransient(),
- allowsOutOfRangeReads(),
- metadata(),
- nowInSec,
- columnFilter(),
- rowFilter(),
- limits(),
- partitionKey(),
- clusteringIndexFilter(),
- indexQueryPlan(),
- isTrackingWarnings());
- }
-
@Override
public DecoratedKey partitionKey()
{
@@ -1274,24 +1294,21 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
}
/*
- * The execution method does not need to perform reconciliation so the read command
- * should execute in a mannager suited to not needing reconciliation. Such as when
- * executing transactionally at a single replica and doing an index scan where the index
- * scan should not return extra rows and expect post filtering at the coordinator.
+ * When running transactionally we need to use the txn system nowInSeconds, and set whether reconciliation
+ * should be performed based on whether it's part of a multiple replica read. We also allow potential txn conflicts
+ * because we manage those conflicts from the txn system
*/
- public SinglePartitionReadCommand withoutReconciliation()
+ public SinglePartitionReadCommand withTransactionalSettings(boolean withoutReconciliation, long nowInSeconds)
{
- if (rowFilter().isEmpty())
- return this;
return create(serializedAtEpoch(),
isDigestQuery(),
digestVersion(),
acceptsTransient(),
- allowsOutOfRangeReads(),
+ PotentialTxnConflicts.ALLOW,
metadata(),
- nowInSec(),
+ nowInSeconds,
columnFilter(),
- rowFilter().withoutReconciliation(),
+ withoutReconciliation ? rowFilter().withoutReconciliation() : rowFilter(),
limits(),
partitionKey(),
clusteringIndexFilter(),
@@ -1310,7 +1327,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
RowFilter rowFilter,
DataLimits limits,
List partitionKeys,
- ClusteringIndexFilter clusteringIndexFilter)
+ ClusteringIndexFilter clusteringIndexFilter,
+ PotentialTxnConflicts potentialTxnConflicts)
{
List commands = new ArrayList<>(partitionKeys.size());
for (DecoratedKey partitionKey : partitionKeys)
@@ -1321,7 +1339,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
rowFilter,
limits,
partitionKey,
- clusteringIndexFilter));
+ clusteringIndexFilter,
+ potentialTxnConflicts));
}
return create(commands, limits);
@@ -1377,7 +1396,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
- boolean allowsOutOfRangeReads,
+ PotentialTxnConflicts potentialTxnConflicts,
TableMetadata metadata,
long nowInSec,
ColumnFilter columnFilter,
@@ -1388,7 +1407,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
{
DecoratedKey key = metadata.partitioner.decorateKey(metadata.partitionKeyType.readBuffer(in, DatabaseDescriptor.getMaxValueSize()));
ClusteringIndexFilter filter = ClusteringIndexFilter.serializer.deserialize(in, version, metadata);
- return SinglePartitionReadCommand.create(serializedAtEpoch, isDigest, digestVersion, acceptsTransient, allowsOutOfRangeReads, metadata, nowInSec, columnFilter, rowFilter, limits, key, filter, indexQueryPlan, false);
+ return SinglePartitionReadCommand.create(serializedAtEpoch, isDigest, digestVersion, acceptsTransient, potentialTxnConflicts, metadata, nowInSec, columnFilter, rowFilter, limits, key, filter, indexQueryPlan, false);
}
}
@@ -1436,7 +1455,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
boolean trackWarnings,
DataRange dataRange)
{
- super(metadata.epoch, isDigest, digestVersion, acceptsTransient, true, metadata, nowInSec, columnFilter,
+ super(metadata.epoch, isDigest, digestVersion, acceptsTransient, PotentialTxnConflicts.ALLOW, metadata, nowInSec, columnFilter,
rowFilter, limits, partitionKey, clusteringIndexFilter, indexQueryPlan, trackWarnings, dataRange);
}
diff --git a/src/java/org/apache/cassandra/db/SinglePartitionReadQuery.java b/src/java/org/apache/cassandra/db/SinglePartitionReadQuery.java
index 5409cde8c4..0ec2961861 100644
--- a/src/java/org/apache/cassandra/db/SinglePartitionReadQuery.java
+++ b/src/java/org/apache/cassandra/db/SinglePartitionReadQuery.java
@@ -23,9 +23,9 @@ import java.util.List;
import java.util.stream.Collectors;
import com.google.common.collect.Iterables;
-
import org.apache.commons.lang3.tuple.Pair;
+import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.filter.DataLimits;
@@ -51,9 +51,10 @@ public interface SinglePartitionReadQuery extends ReadQuery
RowFilter rowFilter,
DataLimits limits,
List partitionKeys,
- ClusteringIndexFilter clusteringIndexFilter)
+ ClusteringIndexFilter clusteringIndexFilter,
+ PotentialTxnConflicts potentialTxnConflicts)
{
- return SinglePartitionReadCommand.Group.create(metadata, nowInSec, columnFilter, rowFilter, limits, partitionKeys, clusteringIndexFilter);
+ return SinglePartitionReadCommand.Group.create(metadata, nowInSec, columnFilter, rowFilter, limits, partitionKeys, clusteringIndexFilter, potentialTxnConflicts);
}
diff --git a/src/java/org/apache/cassandra/db/partitions/PartitionIterators.java b/src/java/org/apache/cassandra/db/partitions/PartitionIterators.java
index b8a86d5a1a..30f3e75e62 100644
--- a/src/java/org/apache/cassandra/db/partitions/PartitionIterators.java
+++ b/src/java/org/apache/cassandra/db/partitions/PartitionIterators.java
@@ -17,16 +17,16 @@
*/
package org.apache.cassandra.db.partitions;
-import java.util.*;
+import java.util.List;
import org.apache.cassandra.db.EmptyIterators;
+import org.apache.cassandra.db.SinglePartitionReadQuery;
+import org.apache.cassandra.db.rows.RowIterator;
+import org.apache.cassandra.db.rows.RowIterators;
import org.apache.cassandra.db.transform.MorePartitions;
import org.apache.cassandra.db.transform.Transformation;
import org.apache.cassandra.utils.AbstractIterator;
-import org.apache.cassandra.db.SinglePartitionReadQuery;
-import org.apache.cassandra.db.rows.*;
-
public abstract class PartitionIterators
{
private PartitionIterators() {}
@@ -57,7 +57,7 @@ public abstract class PartitionIterators
return Transformation.apply(toReturn, new Close());
}
- public static PartitionIterator concat(final List iterators)
+ public static PartitionIterator concat(final List extends PartitionIterator> iterators)
{
if (iterators.size() == 1)
return iterators.get(0);
diff --git a/src/java/org/apache/cassandra/db/partitions/PartitionUpdate.java b/src/java/org/apache/cassandra/db/partitions/PartitionUpdate.java
index 87e2154e52..011629576f 100644
--- a/src/java/org/apache/cassandra/db/partitions/PartitionUpdate.java
+++ b/src/java/org/apache/cassandra/db/partitions/PartitionUpdate.java
@@ -71,14 +71,14 @@ import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.metrics.TCMMetrics;
-import org.apache.cassandra.tcm.ClusterMetadata;
-import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.utils.btree.BTree;
import org.apache.cassandra.utils.btree.UpdateFunction;
import org.apache.cassandra.utils.vint.VIntCoding;
@@ -1161,7 +1161,7 @@ public class PartitionUpdate extends AbstractBTreePartition
return this;
}
- public Builder updateTimesAndPathsForAccord(@Nonnull Function cellToMaybeNewListPath, long newTimestamp, int newLocalDeletionTime)
+ public Builder updateTimesAndPathsForAccord(@Nonnull Function cellToMaybeNewListPath, long newTimestamp, long newLocalDeletionTime)
{
deletionInfo.updateAllTimestampAndLocalDeletionTime(newTimestamp - 1, newLocalDeletionTime);
tree = BTree.transformAndFilter(tree, (x) -> x.updateTimesAndPathsForAccord(cellToMaybeNewListPath, newTimestamp, newLocalDeletionTime));
diff --git a/src/java/org/apache/cassandra/db/partitions/UnfilteredPartitionIterators.java b/src/java/org/apache/cassandra/db/partitions/UnfilteredPartitionIterators.java
index e68603c9f3..fd7880e367 100644
--- a/src/java/org/apache/cassandra/db/partitions/UnfilteredPartitionIterators.java
+++ b/src/java/org/apache/cassandra/db/partitions/UnfilteredPartitionIterators.java
@@ -19,11 +19,21 @@ package org.apache.cassandra.db.partitions;
import java.io.IOError;
import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.NoSuchElementException;
-import org.apache.cassandra.db.*;
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.Digest;
+import org.apache.cassandra.db.EmptyIterators;
+import org.apache.cassandra.db.SinglePartitionReadCommand;
import org.apache.cassandra.db.filter.ColumnFilter;
-import org.apache.cassandra.db.rows.*;
+import org.apache.cassandra.db.rows.DeserializationHelper;
+import org.apache.cassandra.db.rows.LazilyInitializedUnfilteredRowIterator;
+import org.apache.cassandra.db.rows.UnfilteredRowIterator;
+import org.apache.cassandra.db.rows.UnfilteredRowIteratorSerializer;
+import org.apache.cassandra.db.rows.UnfilteredRowIterators;
import org.apache.cassandra.db.transform.FilteredPartitions;
import org.apache.cassandra.db.transform.MorePartitions;
import org.apache.cassandra.db.transform.Transformation;
diff --git a/src/java/org/apache/cassandra/db/rows/AbstractCell.java b/src/java/org/apache/cassandra/db/rows/AbstractCell.java
index d30489a109..0dbcfc4420 100644
--- a/src/java/org/apache/cassandra/db/rows/AbstractCell.java
+++ b/src/java/org/apache/cassandra/db/rows/AbstractCell.java
@@ -121,14 +121,14 @@ public abstract class AbstractCell extends Cell
}
@Override
- public ColumnData updateTimesAndPathsForAccord(@Nonnull Function cellToMaybeNewListPath, long newTimestamp, int newLocalDeletionTime)
+ public ColumnData updateTimesAndPathsForAccord(@Nonnull Function cellToMaybeNewListPath, long newTimestamp, long newLocalDeletionTime)
{
long localDeletionTime = localDeletionTime() != NO_DELETION_TIME ? newLocalDeletionTime : NO_DELETION_TIME;
return new BufferCell(column, isTombstone() ? newTimestamp - 1 : newTimestamp, ttl(), localDeletionTime, buffer(), path());
}
@Override
- public Cell> updateAllTimesWithNewCellPathForComplexColumnData(@Nonnull CellPath maybeNewPath, long newTimestamp, int newLocalDeletionTime)
+ public Cell> updateAllTimesWithNewCellPathForComplexColumnData(@Nonnull CellPath maybeNewPath, long newTimestamp, long newLocalDeletionTime)
{
long localDeletionTime = localDeletionTime() != NO_DELETION_TIME ? newLocalDeletionTime : NO_DELETION_TIME;
return new BufferCell(column, isTombstone() ? newTimestamp - 1 : newTimestamp, ttl(), localDeletionTime, buffer(), maybeNewPath);
diff --git a/src/java/org/apache/cassandra/db/rows/BTreeRow.java b/src/java/org/apache/cassandra/db/rows/BTreeRow.java
index ed445e8b6b..84ebdc00be 100644
--- a/src/java/org/apache/cassandra/db/rows/BTreeRow.java
+++ b/src/java/org/apache/cassandra/db/rows/BTreeRow.java
@@ -444,7 +444,7 @@ public class BTreeRow extends AbstractRow
}
@Override
- public Row updateTimesAndPathsForAccord(@Nonnull Function cellToMaybeNewListPath, long newTimestamp, int newLocalDeletionTime)
+ public Row updateTimesAndPathsForAccord(@Nonnull Function cellToMaybeNewListPath, long newTimestamp, long newLocalDeletionTime)
{
LivenessInfo newInfo = primaryKeyLivenessInfo.isEmpty() ? primaryKeyLivenessInfo : primaryKeyLivenessInfo.withUpdatedTimestampAndLocalDeletionTime(newTimestamp, newLocalDeletionTime);
// If the deletion is shadowable and the row has a timestamp, we'll forced the deletion timestamp to be less than the row one, so we
diff --git a/src/java/org/apache/cassandra/db/rows/ColumnData.java b/src/java/org/apache/cassandra/db/rows/ColumnData.java
index 8f055e1d14..c27d9bdabc 100644
--- a/src/java/org/apache/cassandra/db/rows/ColumnData.java
+++ b/src/java/org/apache/cassandra/db/rows/ColumnData.java
@@ -291,7 +291,7 @@ public abstract class ColumnData implements IMeasurableMemory
/**
* @param cellToMaybeNewListPath If the cell is a list append cell a new cell path is returned generated based on the Accord executeAt timestamp
*/
- public abstract ColumnData updateTimesAndPathsForAccord(@Nonnull Function cellToMaybeNewListPath, long newTimestamp, int newLocalDeletionTime);
+ public abstract ColumnData updateTimesAndPathsForAccord(@Nonnull Function cellToMaybeNewListPath, long newTimestamp, long newLocalDeletionTime);
/**
* List paths are time UUIDs that increment for each item in the list and for Accord and Paxos
@@ -299,7 +299,7 @@ public abstract class ColumnData implements IMeasurableMemory
*
* @param maybeNewPath If this cell is a list append for a non-frozen list (multi-cell) then it will be new path generated using the executeAt timestamp, otherwise it will be the existing path
*/
- public abstract ColumnData updateAllTimesWithNewCellPathForComplexColumnData(@Nonnull CellPath maybeNewPath, long newTimestamp, int newLocalDeletionTime);
+ public abstract ColumnData updateAllTimesWithNewCellPathForComplexColumnData(@Nonnull CellPath maybeNewPath, long newTimestamp, long newLocalDeletionTime);
public abstract ColumnData markCounterLocalToBeCleared();
diff --git a/src/java/org/apache/cassandra/db/rows/ComplexColumnData.java b/src/java/org/apache/cassandra/db/rows/ComplexColumnData.java
index f032fd5b6d..308c147f8a 100644
--- a/src/java/org/apache/cassandra/db/rows/ComplexColumnData.java
+++ b/src/java/org/apache/cassandra/db/rows/ComplexColumnData.java
@@ -267,7 +267,7 @@ public class ComplexColumnData extends ColumnData implements Iterable>
}
@Override
- public ColumnData updateTimesAndPathsForAccord(@Nonnull Function cellToMaybeNewListPath, long newTimestamp, int newLocalDeletionTime)
+ public ColumnData updateTimesAndPathsForAccord(@Nonnull Function cellToMaybeNewListPath, long newTimestamp, long newLocalDeletionTime)
{
DeletionTime newDeletion = complexDeletion.isLive() ? complexDeletion : DeletionTime.build(newTimestamp - 1, newLocalDeletionTime);
Function maybeNewListPath;
@@ -279,7 +279,7 @@ public class ComplexColumnData extends ColumnData implements Iterable>
}
@Override
- public ColumnData updateAllTimesWithNewCellPathForComplexColumnData(@Nonnull CellPath maybeNewPath, long newTimestamp, int newLocalDeletionTime)
+ public ColumnData updateAllTimesWithNewCellPathForComplexColumnData(@Nonnull CellPath maybeNewPath, long newTimestamp, long newLocalDeletionTime)
{
throw new UnsupportedOperationException();
}
diff --git a/src/java/org/apache/cassandra/db/rows/Row.java b/src/java/org/apache/cassandra/db/rows/Row.java
index 3820e8c3a4..cb48c4ce6f 100644
--- a/src/java/org/apache/cassandra/db/rows/Row.java
+++ b/src/java/org/apache/cassandra/db/rows/Row.java
@@ -312,7 +312,7 @@ public interface Row extends Unfiltered, Iterable, IMeasurableMemory
*/
public Row updateAllTimestamp(long newTimestamp);
- public Row updateTimesAndPathsForAccord(@Nonnull Function cellToMaybeNewListPath, long newTimestamp, int newLocalDeletionTime);
+ public Row updateTimesAndPathsForAccord(@Nonnull Function| cellToMaybeNewListPath, long newTimestamp, long newLocalDeletionTime);
/**
* Returns a copy of this row with the new deletion as row deletion if it is more recent
diff --git a/src/java/org/apache/cassandra/db/virtual/VirtualMutation.java b/src/java/org/apache/cassandra/db/virtual/VirtualMutation.java
index f07e0e3282..ee98da29c1 100644
--- a/src/java/org/apache/cassandra/db/virtual/VirtualMutation.java
+++ b/src/java/org/apache/cassandra/db/virtual/VirtualMutation.java
@@ -30,6 +30,7 @@ import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.IMutation;
import org.apache.cassandra.db.Mutation;
+import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.ClientState;
@@ -142,8 +143,8 @@ public final class VirtualMutation implements IMutation
* Accord doesn't support reading/writing virtual tables yet so updating them non-transactionally is always safe
*/
@Override
- public boolean allowsPotentialTransactionConflicts()
+ public PotentialTxnConflicts potentialTxnConflicts()
{
- return true;
+ return PotentialTxnConflicts.ALLOW;
}
}
diff --git a/src/java/org/apache/cassandra/dht/Murmur3Partitioner.java b/src/java/org/apache/cassandra/dht/Murmur3Partitioner.java
index 2885cd732b..c702a4d7c3 100644
--- a/src/java/org/apache/cassandra/dht/Murmur3Partitioner.java
+++ b/src/java/org/apache/cassandra/dht/Murmur3Partitioner.java
@@ -258,6 +258,7 @@ public class Murmur3Partitioner implements IPartitioner
// CASSANDRA-17109 Added the below checks, but paxos tests were not updated, rather than fix
// the paxos tests, disabling the checks for now. The current paxos tests bias twards MIN but
// not for MAX, which makes the test very flaky as when MAX is generated the test fails...
+ // TODO (mustfix): This was done as part of CEP-15 and needs to be added back
// if (token == MAXIMUM)
// throw new IllegalArgumentException("Cannot increase above MAXIMUM");
diff --git a/src/java/org/apache/cassandra/dht/NormalizedRanges.java b/src/java/org/apache/cassandra/dht/NormalizedRanges.java
index 8a92467d3b..c9a040a658 100644
--- a/src/java/org/apache/cassandra/dht/NormalizedRanges.java
+++ b/src/java/org/apache/cassandra/dht/NormalizedRanges.java
@@ -136,6 +136,9 @@ public class NormalizedRanges> extends AbstractList subtract(NormalizedRanges b)
{
+ if (b.isEmpty())
+ return this;
+
if (b.size() == 1 && b.get(0).isFull())
return NormalizedRanges.empty();
@@ -199,10 +202,9 @@ public class NormalizedRanges> extends AbstractList invert()
+ NormalizedRanges invert()
{
- if (isEmpty())
- return this;
+ checkState(!isEmpty());
List> result = new ArrayList<>(size() + 2);
T minValue = get(0).left.minValue();
diff --git a/src/java/org/apache/cassandra/dht/Range.java b/src/java/org/apache/cassandra/dht/Range.java
index 3407890a70..00204c66b4 100644
--- a/src/java/org/apache/cassandra/dht/Range.java
+++ b/src/java/org/apache/cassandra/dht/Range.java
@@ -35,6 +35,7 @@ import org.apache.commons.lang3.ObjectUtils;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.PartitionPosition;
+import org.apache.cassandra.dht.Token.KeyBound;
import org.apache.cassandra.dht.Token.TokenFactory;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
@@ -44,6 +45,8 @@ import org.apache.cassandra.tcm.serialization.MetadataSerializer;
import org.apache.cassandra.tcm.serialization.Version;
import org.apache.cassandra.utils.Pair;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
import static java.util.Collections.emptyList;
import static org.apache.cassandra.config.CassandraRelevantProperties.TEST_RANGE_EXPENSIVE_CHECKS;
@@ -776,4 +779,88 @@ public class Range> extends AbstractBounds implemen
return tokenSerializer.serializedSize(t, SERDE_VERSION);
}
}
+
+ /**
+ * Returns a Pair containing the intersection (or null) and the remainder of the bounds that is to the right of the
+ * range, the remainder to the left is discarded since it is assumed if you are checking for intersection of multiple ranges
+ * the ranges are being checked in order.
+ */
+ public static Pair, AbstractBounds> intersectionAndRemainder(AbstractBounds bounds, org.apache.cassandra.dht.Range range)
+ {
+ checkArgument((bounds.inclusiveRight() && bounds.inclusiveLeft()) || (bounds.left.compareTo(bounds.right) < 0 || bounds.right.isMinimum()), "Wrap around not handled");
+ boolean boundsInclusiveLeft = bounds.inclusiveLeft() || (bounds.left.getClass() == KeyBound.class && ((KeyBound)bounds.left).isMinimumBound);
+ boolean boundsInclusiveRight = bounds.inclusiveRight() || (bounds.right.getClass() == KeyBound.class && !((KeyBound)bounds.right).isMinimumBound);
+ Token boundsLeft = bounds.left.getToken();
+ Token boundsRight = bounds.right.getToken();
+ Token rangeLeft = range.left;
+ Token rangeRight = range.right;
+ checkState(rangeLeft.compareTo(rangeRight) < 0 || rangeRight.isMinimum(), "Wrap around is not handled");
+
+ // Completely before
+ int rightLeftCmp = boundsRight.compareTo(rangeLeft);
+ // Nothing is > min on the right
+ if (boundsRight.isMinimum())
+ rightLeftCmp = 1;
+ // Range left is not inclusive, doesn't matter whether the bound is inclusive/exclusive left
+ rightLeftCmp = rightLeftCmp == 0 ? -1 : rightLeftCmp;
+ if (rightLeftCmp < 0)
+ return Pair.create(null, null);
+
+ // Completely after
+ int leftRightCmp = boundsLeft.compareTo(rangeRight);
+ // Nothing is > min on the right
+ if (rangeRight.isMinimum())
+ leftRightCmp = -1;
+ // Fixed mismatched inclusivity
+ leftRightCmp = leftRightCmp == 0 && !boundsInclusiveLeft ? 1 : leftRightCmp;
+ if (leftRightCmp > 0)
+ return Pair.create(null, bounds);
+
+ int rightRightCmp = boundsRight.compareTo(rangeRight);
+ // min on the right is > than everything
+ if (rangeRight.isMinimum() && boundsRight.isMinimum())
+ rightRightCmp = 0;
+ else if (boundsRight.isMinimum())
+ rightRightCmp = 1;
+ else if (rangeRight.isMinimum())
+ rightRightCmp = -1;
+ // Fixed mismatched inclusivity
+ rightRightCmp = rightRightCmp == 0 && !boundsInclusiveRight ? -1 : rightRightCmp;
+
+ int leftLeftCmp = boundsLeft.compareTo(rangeLeft);
+ // Range left is not inclusive, doesn't matter whether the bound is inclusive/exclusive left
+ leftLeftCmp = leftLeftCmp == 0 ? -1 : leftLeftCmp;
+
+ // Fully contained
+ if (leftLeftCmp > 0 && rightRightCmp <= 0)
+ return Pair.create(bounds, null);
+ // Split by the right bound of the range (rightRightCmp is implicitly > 0 given the preceding condition)
+ else if (leftLeftCmp >= 0)
+ return bounds.split(rangeRight.maxKeyBound());
+ // Intersects but has some portion that needs to be discarded first
+ else
+ {
+ // Remove everything before the intersection
+ Pair, AbstractBounds> split = bounds.split(rangeLeft.maxKeyBound());
+ AbstractBounds intersectionAndRemainder = bounds;
+ if (split != null)
+ intersectionAndRemainder = split.right;
+ // There is a remainder
+ if (rightRightCmp > 0)
+ return intersectionAndRemainder.split(rangeRight.maxKeyBound());
+ // There is no remainder everything that
+ return Pair.create(intersectionAndRemainder, null);
+ }
+ }
+
+ public static int compareRightToken(Token a, Token b)
+ {
+ if (a.isMinimum() && b.isMinimum())
+ return 0;
+ if (a.isMinimum())
+ return 1;
+ if (b.isMinimum())
+ return 0;
+ return a.compareTo(b);
+ }
}
diff --git a/src/java/org/apache/cassandra/hints/HintsDispatcher.java b/src/java/org/apache/cassandra/hints/HintsDispatcher.java
index 6022f284df..faeefcf973 100644
--- a/src/java/org/apache/cassandra/hints/HintsDispatcher.java
+++ b/src/java/org/apache/cassandra/hints/HintsDispatcher.java
@@ -42,6 +42,8 @@ import org.apache.cassandra.concurrent.Stage;
import org.apache.cassandra.db.Mutation;
import org.apache.cassandra.exceptions.RequestFailure;
import org.apache.cassandra.exceptions.RequestFailureReason;
+import org.apache.cassandra.exceptions.RetryOnDifferentSystemException;
+import org.apache.cassandra.exceptions.WriteFailureException;
import org.apache.cassandra.exceptions.WriteTimeoutException;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.locator.InetAddressAndPort;
@@ -248,7 +250,9 @@ final class HintsDispatcher implements AutoCloseable
failedRetryDifferentSystem = true;
}
- if (failures > 0 || timeouts > 0 || failedRetryDifferentSystem)
+ // The batchlog Accord hints need to return abort if any hint needs to be retried and retry the whole page
+ // since we don't want hints to ping pong back and forth via hintsNeedingRehinting
+ if (failures > 0 || timeouts > 0 || failedRetryDifferentSystem || (isBatchLogHints && retryDifferentSystem > 0))
{
HintDiagnostics.pageFailureResult(this, success, failures, timeouts, retryDifferentSystem);
return Action.ABORT;
@@ -534,16 +538,26 @@ final class HintsDispatcher implements AutoCloseable
try
{
IAccordService accord = AccordService.instance();
- TxnResult.Kind kind = accord.getTxnResult(accordTxnResult, true, null, requestTime).kind();
+ TxnResult.Kind kind = accord.getTxnResult(accordTxnResult).kind();
if (kind == retry_new_protocol)
accordOutcome = RETRY_DIFFERENT_SYSTEM;
else
accordOutcome = SUCCESS;
}
+ catch (WriteTimeoutException | WriteFailureException | RetryOnDifferentSystemException e)
+ {
+ if (e instanceof RetryOnDifferentSystemException)
+ accordOutcome = RETRY_DIFFERENT_SYSTEM;
+ else
+ accordOutcome = TIMEOUT;
+ String msg = "Accord hint delivery transaction failed retriably";
+ if (noSpamLogger.getStatement(msg).shouldLog(Clock.Global.nanoTime()))
+ logger.error(msg, e);
+ }
catch (Exception e)
{
- accordOutcome = e instanceof WriteTimeoutException ? TIMEOUT : FAILURE;
- String msg = "Accord hint delivery transaction failed";
+ accordOutcome = FAILURE;
+ String msg = "Accord hint delivery transaction failed permanently";
if (noSpamLogger.getStatement(msg).shouldLog(Clock.Global.nanoTime()))
logger.error(msg, e);
}
diff --git a/src/java/org/apache/cassandra/index/internal/composites/CompositesSearcher.java b/src/java/org/apache/cassandra/index/internal/composites/CompositesSearcher.java
index 8f357027e9..6629c06256 100644
--- a/src/java/org/apache/cassandra/index/internal/composites/CompositesSearcher.java
+++ b/src/java/org/apache/cassandra/index/internal/composites/CompositesSearcher.java
@@ -21,17 +21,29 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
-import org.apache.cassandra.schema.TableMetadata;
-import org.apache.cassandra.db.*;
+import org.apache.cassandra.db.Clustering;
+import org.apache.cassandra.db.ClusteringComparator;
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.DeletionTime;
+import org.apache.cassandra.db.ReadCommand;
+import org.apache.cassandra.db.ReadExecutionController;
+import org.apache.cassandra.db.SinglePartitionReadCommand;
+import org.apache.cassandra.db.WriteContext;
import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter;
import org.apache.cassandra.db.filter.DataLimits;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
-import org.apache.cassandra.db.rows.*;
+import org.apache.cassandra.db.rows.Row;
+import org.apache.cassandra.db.rows.RowIterator;
+import org.apache.cassandra.db.rows.Rows;
+import org.apache.cassandra.db.rows.UnfilteredRowIterator;
+import org.apache.cassandra.db.rows.UnfilteredRowIterators;
import org.apache.cassandra.db.transform.Transformation;
+import org.apache.cassandra.index.Index;
import org.apache.cassandra.index.internal.CassandraIndex;
import org.apache.cassandra.index.internal.CassandraIndexSearcher;
import org.apache.cassandra.index.internal.IndexEntry;
+import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.btree.BTreeSet;
@@ -159,7 +171,7 @@ public class CompositesSearcher extends CassandraIndexSearcher
DataLimits.NONE,
partitionKey,
filter,
- null);
+ (Index.QueryPlan) null);
}
// by the next caller of next, or through closing this iterator is this come before.
diff --git a/src/java/org/apache/cassandra/index/internal/keys/KeysSearcher.java b/src/java/org/apache/cassandra/index/internal/keys/KeysSearcher.java
index 9baa6f6f47..98e4e02160 100644
--- a/src/java/org/apache/cassandra/index/internal/keys/KeysSearcher.java
+++ b/src/java/org/apache/cassandra/index/internal/keys/KeysSearcher.java
@@ -22,12 +22,22 @@ import java.nio.ByteBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.cassandra.db.*;
+import org.apache.cassandra.db.Clustering;
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.DeletionTime;
+import org.apache.cassandra.db.ReadCommand;
+import org.apache.cassandra.db.ReadExecutionController;
+import org.apache.cassandra.db.SinglePartitionReadCommand;
+import org.apache.cassandra.db.WriteContext;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.filter.DataLimits;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
-import org.apache.cassandra.db.rows.*;
+import org.apache.cassandra.db.rows.Row;
+import org.apache.cassandra.db.rows.RowIterator;
+import org.apache.cassandra.db.rows.Rows;
+import org.apache.cassandra.db.rows.UnfilteredRowIterator;
+import org.apache.cassandra.index.Index;
import org.apache.cassandra.index.internal.CassandraIndex;
import org.apache.cassandra.index.internal.CassandraIndexSearcher;
import org.apache.cassandra.schema.TableMetadata;
@@ -91,7 +101,7 @@ public class KeysSearcher extends CassandraIndexSearcher
DataLimits.NONE,
key,
command.clusteringIndexFilter(key),
- null);
+ (Index.QueryPlan) null);
// Otherwise, we close right away if empty, and if it's assigned to next it will be called either
// by the next caller of next, or through closing this iterator is this come before.
diff --git a/src/java/org/apache/cassandra/index/sai/utils/IndexTermType.java b/src/java/org/apache/cassandra/index/sai/utils/IndexTermType.java
index 279f7441e4..2f07f76ad4 100644
--- a/src/java/org/apache/cassandra/index/sai/utils/IndexTermType.java
+++ b/src/java/org/apache/cassandra/index/sai/utils/IndexTermType.java
@@ -788,7 +788,7 @@ public class IndexTermType
return CompositeType.getInstance(collection.nameComparator(), collection.valueComparator());
}
default:
- throw new IllegalArgumentException("Unsupported collection type: " + collection.kind + "; index type: " + indexType.name());
+ throw new IllegalArgumentException("Unsupported collection type: " + collection.kind);
}
}
diff --git a/src/java/org/apache/cassandra/metrics/TableMetrics.java b/src/java/org/apache/cassandra/metrics/TableMetrics.java
index 380333058c..d65bdebcdd 100644
--- a/src/java/org/apache/cassandra/metrics/TableMetrics.java
+++ b/src/java/org/apache/cassandra/metrics/TableMetrics.java
@@ -820,7 +820,7 @@ public class TableMetrics
accordRepairUnexpectedFailures = createTableMeter("AccordRepairUnexpectedFailures", cfs.keyspace.metric.rangeMigrationUnexpectedFailures);
accordRepairDependencyLimitFailures = createTableMeter("AccordRepairDependencyLimitFaiures", cfs.keyspace.metric.rangeMigrationDependencyLimitFailures);
mutationsRejectedOnWrongSystem = createTableMeter("MutationsRejectedOnWrongSystem", cfs.keyspace.metric.mutationsRejectedOnWrongSystem);
- readsRejectedOnWrongSystem = createTableMeter("ReadsRejectedOnWrongSystem", cfs.keyspace.metric.mutationsRejectedOnWrongSystem);
+ readsRejectedOnWrongSystem = createTableMeter("ReadsRejectedOnWrongSystem", cfs.keyspace.metric.readsRejectedOnWrongSystem);
repairsStarted = createTableCounter("RepairJobsStarted");
repairsCompleted = createTableCounter("RepairJobsCompleted");
diff --git a/src/java/org/apache/cassandra/schema/TableParams.java b/src/java/org/apache/cassandra/schema/TableParams.java
index d3d059ab79..7c3e52943c 100644
--- a/src/java/org/apache/cassandra/schema/TableParams.java
+++ b/src/java/org/apache/cassandra/schema/TableParams.java
@@ -27,6 +27,7 @@ import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
+import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.cql3.Attributes;
import org.apache.cassandra.cql3.CqlBuilder;
import org.apache.cassandra.exceptions.ConfigurationException;
@@ -244,6 +245,9 @@ public final class TableParams
if (cdc && memtable.factory().writesShouldSkipCommitLog())
fail("CDC cannot work if writes skip the commit log. Check your memtable configuration.");
+
+ if (transactionalMode.isTestMode() && !CassandraRelevantProperties.ACCORD_ALLOW_TEST_MODES.getBoolean())
+ fail("Transactional mode " + transactionalMode + " can't be used if " + CassandraRelevantProperties.ACCORD_ALLOW_TEST_MODES.getKey() + " is not set");
}
private static void fail(String format, Object... args)
diff --git a/src/java/org/apache/cassandra/service/AbstractWriteResponseHandler.java b/src/java/org/apache/cassandra/service/AbstractWriteResponseHandler.java
index 4219cad301..84dfe49b57 100644
--- a/src/java/org/apache/cassandra/service/AbstractWriteResponseHandler.java
+++ b/src/java/org/apache/cassandra/service/AbstractWriteResponseHandler.java
@@ -167,7 +167,8 @@ public abstract class AbstractWriteResponseHandler implements RequestCallback
// Retrying on the correct system might make this write succeed
if (transactionRetryErrors > 0)
throw new RetryOnDifferentSystemException();
- throw new CoordinatorBehindException("Write request failed due to coordinator behind");
+ if (coordinatorBehindErrors > 0)
+ throw new CoordinatorBehindException("Write request failed due to coordinator behind");
}
throw new WriteFailureException(replicaPlan.consistencyLevel(), ackCount(), blockFor(), writeType, getFailureReasonByEndpointMap());
diff --git a/src/java/org/apache/cassandra/service/StorageProxy.java b/src/java/org/apache/cassandra/service/StorageProxy.java
index 9c939269c9..c640263454 100644
--- a/src/java/org/apache/cassandra/service/StorageProxy.java
+++ b/src/java/org/apache/cassandra/service/StorageProxy.java
@@ -22,8 +22,10 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -43,8 +45,10 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.cache.CacheLoader;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
import com.google.common.util.concurrent.Uninterruptibles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -84,7 +88,6 @@ import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
import org.apache.cassandra.db.rows.RowIterator;
import org.apache.cassandra.db.view.ViewUtils;
import org.apache.cassandra.dht.AbstractBounds;
-import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.CasWriteTimeoutException;
import org.apache.cassandra.exceptions.CasWriteUnknownResultException;
@@ -138,9 +141,7 @@ import org.apache.cassandra.service.accord.IAccordService.AsyncTxnResult;
import org.apache.cassandra.service.accord.txn.TxnData;
import org.apache.cassandra.service.accord.txn.TxnDataKeyValue;
import org.apache.cassandra.service.accord.txn.TxnDataValue;
-import org.apache.cassandra.service.accord.txn.TxnKeyRead;
import org.apache.cassandra.service.accord.txn.TxnQuery;
-import org.apache.cassandra.service.accord.txn.TxnRangeRead;
import org.apache.cassandra.service.accord.txn.TxnRangeReadResult;
import org.apache.cassandra.service.accord.txn.TxnRead;
import org.apache.cassandra.service.accord.txn.TxnResult;
@@ -148,6 +149,7 @@ import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper.SplitConsumer;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper.SplitMutations;
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter;
+import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.SplitReads;
import org.apache.cassandra.service.consensus.migration.TransactionalMigrationFromMode;
import org.apache.cassandra.service.paxos.Ballot;
import org.apache.cassandra.service.paxos.Commit;
@@ -181,11 +183,11 @@ import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import static accord.primitives.Txn.Kind.Read;
import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.concat;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.db.ConsistencyLevel.SERIAL;
+import static org.apache.cassandra.db.partitions.PartitionIterators.singletonIterator;
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.casReadMetrics;
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.casWriteMetrics;
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.readMetrics;
@@ -213,6 +215,7 @@ import static org.apache.cassandra.service.consensus.migration.ConsensusMigratio
import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.ConsensusRoutingDecision;
import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.getTableMetadata;
import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.shouldReadEphemerally;
+import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.splitReadsIntoAccordAndNormal;
import static org.apache.cassandra.service.paxos.Ballot.Flag.GLOBAL;
import static org.apache.cassandra.service.paxos.Ballot.Flag.LOCAL;
import static org.apache.cassandra.service.paxos.BallotGenerator.Global.nextBallot;
@@ -1272,7 +1275,7 @@ public class StorageProxy implements StorageProxyMBean
{
writeMetrics.retryDifferentSystem.mark();
writeMetricsForLevel(consistencyLevel).retryDifferentSystem.mark();
- logger.debug("Retrying mutations on different system because some mutations were misrouted");
+ logger.debug("Retrying mutations on different system because some mutations were misrouted according to Cassandra");
Tracing.trace("Got {} from normal mutations, will retry", e);
continue;
}
@@ -1296,9 +1299,13 @@ public class StorageProxy implements StorageProxyMBean
if (accordResult != null)
{
IAccordService accord = AccordService.instance();
- TxnResult.Kind kind = accord.getTxnResult(accordResult, true, consistencyLevel, requestTime).kind();
- if (kind == retry_new_protocol)
+ TxnResult.Kind kind = accord.getTxnResult(accordResult).kind();
+ if (kind == retry_new_protocol && failure == null)
+ {
+ Tracing.trace("Accord returned retry new protocol");
+ logger.debug("Retrying mutations on different system because some mutations were misrouted according to Accord");
continue;
+ }
Tracing.trace("Successfully wrote Accord mutations");
}
}
@@ -1400,9 +1407,9 @@ public class StorageProxy implements StorageProxyMBean
boolean wroteToBatchLog = false;
while (true)
{
+ ClusterMetadata cm = ClusterMetadata.current();
// In case we hit an error in before/during splitting
attributeNonAccordLatency = true;
- ClusterMetadata cm = ClusterMetadata.current();
List wrappers = new ArrayList<>(mutations.size());
List accordMutations = new ArrayList<>(mutations.size());
BatchlogCleanup cleanup = new BatchlogCleanup(() -> asyncRemoveFromBatchlog(batchlogReplicaPlan, batchUUID, requestTime));
@@ -1506,7 +1513,7 @@ public class StorageProxy implements StorageProxyMBean
if (accordResult != null)
{
IAccordService accord = AccordService.instance();
- TxnResult.Kind kind = accord.getTxnResult(accordResult, true, consistencyLevel, requestTime).kind();
+ TxnResult.Kind kind = accord.getTxnResult(accordResult).kind();
if (kind == retry_new_protocol && failure == null)
continue;
Tracing.trace("Successfully wrote Accord mutations");
@@ -2121,7 +2128,7 @@ public class StorageProxy implements StorageProxyMBean
return consistencyLevel.isSerialConsistency()
? readWithConsensus(group, consistencyLevel, requestTime)
- : readRegular(group, consistencyLevel, requestTime);
+ : dispatchReadWithRetryOnDifferentSystem(group, consistencyLevel, ReadCoordinator.DEFAULT, requestTime);
}
public static boolean hasJoined()
@@ -2176,45 +2183,44 @@ public class StorageProxy implements StorageProxyMBean
return lastResult.serialReadResult;
}
- private static ConsistencyLevel consistencyLevelForAccordRead(ClusterMetadata cm, SinglePartitionReadCommand.Group group, @Nullable ConsistencyLevel consistencyLevel)
+ public static ConsistencyLevel consistencyLevelForAccordRead(ClusterMetadata cm, TableId tableId, SinglePartitionReadCommand.Group group, @Nullable ConsistencyLevel consistencyLevel)
{
// Null means no specific consistency behavior is required from Accord, it's functionally similar to
// reading at ONE if you are reading data that wasn't written via Accord
if (consistencyLevel == null)
return null;
- TableId tableId = group.queries.get(0).metadata().id;
TableParams tableParams = getTableMetadata(cm, tableId).params;
TransactionalMode mode = tableParams.transactionalMode;
TransactionalMigrationFromMode migrationFromMode = tableParams.transactionalMigrationFrom;
for (SinglePartitionReadCommand command : group.queries)
{
- // readCLForStrategy should return either null or the supplied consistency level
+ // readCLForMode should return either null or the supplied consistency level
// in which case we will read everything at that CL since Accord doesn't support per table
// read consistency
- ConsistencyLevel commitCL = mode.readCLForStrategy(migrationFromMode, consistencyLevel, cm, tableId, command.partitionKey().getToken());
- if (commitCL != null)
- return commitCL;
+ ConsistencyLevel readCL = mode.readCLForMode(migrationFromMode, consistencyLevel, cm, tableId, command.partitionKey().getToken());
+ if (readCL != null)
+ return readCL;
}
return null;
}
- public static AsyncTxnResult readWithAccord(ClusterMetadata cm, PartitionRangeReadCommand command, List> ranges, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) {
+ public static AsyncTxnResult readWithAccord(ClusterMetadata cm, PartitionRangeReadCommand command, AbstractBounds range, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
+ {
if (consistencyLevel != null && !IAccordService.SUPPORTED_READ_CONSISTENCY_LEVELS.contains(consistencyLevel))
throw new InvalidRequestException(consistencyLevel + " is not supported by Accord");
TableMetadata tableMetadata = getTableMetadata(cm, command.metadata().id);
TableParams tableParams = tableMetadata.params;
- Range readRange = new Range<>(command.dataRange().startKey().getToken(), command.dataRange().stopKey().getToken());
- consistencyLevel = tableParams.transactionalMode.readCLForStrategy(tableParams.transactionalMigrationFrom, consistencyLevel, cm, tableMetadata.id, readRange);
- TxnRead read = new TxnRangeRead(command, ranges, consistencyLevel);
+ consistencyLevel = tableParams.transactionalMode.readCLForMode(tableParams.transactionalMigrationFrom, consistencyLevel, cm, tableMetadata.id, command.dataRange().keyRange());
+ TxnRead read = TxnRead.createRangeRead(command, range, consistencyLevel);
Txn.Kind kind = shouldReadEphemerally(read.keys(), tableParams, Read);
Txn txn = new Txn.InMemory(kind, read.keys(), read, TxnQuery.RANGE_QUERY, null);
IAccordService accordService = AccordService.instance();
return accordService.coordinateAsync(tableMetadata.epoch.getEpoch(), txn, consistencyLevel, requestTime);
}
- private static ConsensusAttemptResult readWithAccord(ClusterMetadata cm, SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
+ private static AsyncTxnResult readWithAccordAsync(ClusterMetadata cm, SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
{
if (consistencyLevel != null && !IAccordService.SUPPORTED_READ_CONSISTENCY_LEVELS.contains(consistencyLevel))
throw new InvalidRequestException(consistencyLevel + " is not supported by Accord");
@@ -2223,20 +2229,25 @@ public class StorageProxy implements StorageProxyMBean
// level since Accord will manage reading safely
TableMetadata tableMetadata = getTableMetadata(cm, group.metadata().id);
TableParams tableParams = tableMetadata.params;
- consistencyLevel = consistencyLevelForAccordRead(cm, group, consistencyLevel);
- TxnKeyRead read = TxnKeyRead.createSerialRead(group.queries, consistencyLevel);
+ consistencyLevel = consistencyLevelForAccordRead(cm, group.queries.get(0).metadata().id, group, consistencyLevel);
+ TxnRead read = TxnRead.createSerialRead(group.queries, consistencyLevel);
Txn.Kind kind = shouldReadEphemerally(read.keys(), tableParams, Read);
Txn txn = new Txn.InMemory(kind, read.keys(), read, TxnQuery.ALL, null);
- AsyncTxnResult asyncTxnResult = AccordService.instance().coordinateAsync(tableMetadata.epoch.getEpoch(), txn, consistencyLevel, requestTime);
- return getConsensusAttemptResultFromAsyncTxnResult(asyncTxnResult, group.queries.size(), index -> group.queries.get(index).isReversed(), consistencyLevel, requestTime);
+ return AccordService.instance().coordinateAsync(tableMetadata.epoch.getEpoch(), txn, consistencyLevel, requestTime);
+ }
+
+ private static ConsensusAttemptResult readWithAccord(ClusterMetadata cm, SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
+ {
+ AsyncTxnResult asyncTxnResult = readWithAccordAsync(cm, group, consistencyLevel, requestTime);
+ return getConsensusAttemptResultFromAsyncTxnResult(asyncTxnResult, group.queries.size(), index -> group.queries.get(index).isReversed());
}
/*
* Used for both the SERIAL and non-SERIAL read path into Accord
*/
- public static ConsensusAttemptResult getConsensusAttemptResultFromAsyncTxnResult(AsyncTxnResult asyncTxnResult, int numQueries, IntPredicate isQueryReversed, ConsistencyLevel cl, Dispatcher.RequestTime requestTime)
+ public static ConsensusAttemptResult getConsensusAttemptResultFromAsyncTxnResult(AsyncTxnResult asyncTxnResult, int numQueries, IntPredicate isQueryReversed)
{
- TxnResult txnResult = AccordService.instance().getTxnResult(asyncTxnResult, false, cl, requestTime);
+ TxnResult txnResult = AccordService.instance().getTxnResult(asyncTxnResult);
// TODO (required): Converge on a single approach to RETRY_NEW_PROTOCOL, this works for now because reads don't support it anyways
if (txnResult.kind() == retry_new_protocol)
return RETRY_NEW_PROTOCOL;
@@ -2251,7 +2262,7 @@ public class StorageProxy implements StorageProxyMBean
else if (data.size() == 1)
{
TxnDataKeyValue value = ((TxnDataKeyValue)data.values().iterator().next());
- return serialReadResult(PartitionIterators.singletonIterator(value.rowIterator(isQueryReversed.test(0))));
+ return serialReadResult(singletonIterator(value.rowIterator(isQueryReversed.test(0))));
}
else
{
@@ -2263,7 +2274,7 @@ public class StorageProxy implements StorageProxyMBean
{
int queryIndex = e.getKey();
TxnDataKeyValue value = ((TxnDataKeyValue)e.getValue());
- partitionIterators.set(queryIndex, PartitionIterators.singletonIterator(value.rowIterator(isQueryReversed.test(queryIndex))));
+ partitionIterators.set(queryIndex, singletonIterator(value.rowIterator(isQueryReversed.test(queryIndex))));
}
return serialReadResult(partitionIterators.size() == 1 ? partitionIterators.get(0) : PartitionIterators.concat(partitionIterators));
}
@@ -2364,45 +2375,124 @@ public class StorageProxy implements StorageProxyMBean
}
}
+ public static PartitionIterator dispatchReadWithRetryOnDifferentSystem(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, ReadCoordinator coordinator, Dispatcher.RequestTime requestTime)
+ throws UnavailableException, ReadFailureException, ReadTimeoutException
+ {
+ while (true)
+ {
+ ClusterMetadata cm = ClusterMetadata.current();
+ try
+ {
+ SplitReads splitReads = splitReadsIntoAccordAndNormal(cm, group, coordinator, requestTime);
+ SinglePartitionReadCommand.Group accordReads = splitReads.accordReads;
+ AsyncTxnResult accordResult = accordReads != null ? readWithAccordAsync(cm, accordReads, consistencyLevel, requestTime) : null;
+ SinglePartitionReadCommand.Group normalReads = splitReads.normalReads;
+ Tracing.trace("Split reads into Accord {} and normal {}", accordReads, normalReads);
+
+ Throwable failure = null;
+ PartitionIterator normalPartitions = null;
+ try
+ {
+ if (normalReads != null)
+ {
+ normalPartitions = readRegular(normalReads, consistencyLevel, coordinator, requestTime);
+ Tracing.trace("Successfully executed normal reads");
+ }
+ }
+ catch (RetryOnDifferentSystemException e)
+ {
+ readMetrics.retryDifferentSystem.mark();
+ readMetricsForLevel(consistencyLevel).retryDifferentSystem.mark();
+ logger.debug("Retrying reads on different system because some reads were misrouted according to Accord");
+ Tracing.trace("Got {} from normal reads, will retry", e);
+ continue;
+ }
+ catch (CoordinatorBehindException e)
+ {
+ readMetrics.retryCoordinatorBehind.mark();
+ readMetricsForLevel(consistencyLevel).retryCoordinatorBehind.mark();
+ logger.debug("Retrying reads now that coordinator has caught up to cluster metadata");
+ Tracing.trace("Got {} from normal reads, will retry", e);
+ continue;
+ }
+ catch (Exception e)
+ {
+ failure = Throwables.merge(failure, e);
+ }
+
+ // Check if the Accord reads succeeded asynchronously
+ PartitionIterator accordPartitions = null;
+ try
+ {
+ if (accordResult != null)
+ {
+ ConsensusAttemptResult consensusResult = getConsensusAttemptResultFromAsyncTxnResult(accordResult, accordReads.queries.size(), index -> group.queries.get(index).isReversed());
+ if (consensusResult == RETRY_NEW_PROTOCOL)
+ {
+ readMetrics.retryDifferentSystem.mark();
+ readMetricsForLevel(consistencyLevel).retryDifferentSystem.mark();
+ Tracing.trace("Accord returned retry new protocol");
+ logger.debug("Retrying reads on different system because some reads were misrouted according to Accord");
+ continue;
+ }
+ Tracing.trace("Successfully executed Accord reads");
+ accordPartitions = consensusResult.serialReadResult;
+ }
+ }
+ catch (Exception e)
+ {
+ failure = Throwables.merge(failure, e);
+ }
+
+ if (failure != null)
+ throw unchecked(failure);
+
+ PartitionIterator resultIterator = null;
+ if (normalPartitions != null && (accordPartitions == null || !accordPartitions.hasNext()))
+ resultIterator = normalPartitions;
+ else if ((normalPartitions == null || !normalPartitions.hasNext()) && accordPartitions != null)
+ resultIterator = accordPartitions;
+ else
+ {
+ // Merge into partition key order
+ List partitions = new ArrayList<>(group.queries.size());
+ Iterator mergeIterator = Iterators.mergeSorted(ImmutableList.of(normalPartitions, accordPartitions), Comparator.comparing(RowIterator::partitionKey));
+ while (mergeIterator.hasNext())
+ {
+ partitions.add(singletonIterator(mergeIterator.next()));
+ }
+ resultIterator = PartitionIterators.concat(partitions);}
+ return maybeEnforceLimits(resultIterator, group);
+ }
+ catch (Exception t)
+ {
+ // Unexpected error so it would be helpful to have details
+ Tracing.trace("{}", getStackTraceAsToString(t));
+ throw t;
+ }
+ }
+ }
+
+ public static PartitionIterator maybeEnforceLimits(PartitionIterator iterator, SinglePartitionReadCommand.Group group)
+ {
+ // Note that the only difference between the command in a group must be the partition key on which
+ // they applied.
+ boolean enforceStrictLiveness = group.queries.get(0).metadata().enforceStrictLiveness();
+ // If we have more than one command, then despite each read command honoring the limit, the total result
+ // might not honor it and so we should enforce it
+ if (group.queries.size() > 1)
+ return group.limits().filter(iterator, group.nowInSec(), group.selectsFullPartition(), enforceStrictLiveness);
+ return iterator;
+ }
+
@SuppressWarnings("resource")
- public static PartitionIterator readRegular(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, ReadCoordinator coordinator, Dispatcher.RequestTime requestTime)
+ private static PartitionIterator readRegular(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, ReadCoordinator coordinator, Dispatcher.RequestTime requestTime)
throws UnavailableException, ReadFailureException, ReadTimeoutException
{
long start = nanoTime();
try
{
- ClusterMetadata cm = ClusterMetadata.current();
- TableId tableId = group.queries.get(0).metadata().id;
- // Returns null for local tables
- TableMetadata tableMetadata = getTableMetadata(cm, tableId);
- if (tableMetadata == null)
- tableMetadata = Schema.instance.localKeyspaces().getTableOrViewNullable(tableId);
- TableParams tableParams = tableMetadata.params;
-
- TransactionalMode transactionalMode = tableParams.transactionalMode;
-// TransactionalMigrationFromMode transactionalMigrationFromMode = tableParams.transactionalMigrationFrom;
- // TODO (required): Tests would fail with this and we need to add live migration support anyways so for now allow it
-// if (transactionalMigrationFromMode != TransactionalMigrationFromMode.none)
-// throw new UnsupportedOperationException("Live migration is not supported, can't safely read when migrating from " + transactionalMigrationFromMode + " to " + transactionalMode);
-
- PartitionIterator result;
- if (transactionalMode.nonSerialReadsThroughAccord && coordinator.isEventuallyConsistent())
- {
- ConsensusAttemptResult consensusAttemptResult = readWithAccord(cm, group, consistencyLevel, requestTime);
- checkState(!consensusAttemptResult.shouldRetryOnNewConsensusProtocol, "Live migration is not supported with non-SERIAL reads yet");
- result = consensusAttemptResult.serialReadResult;
- }
- else
- result = fetchRows(group.queries, consistencyLevel, coordinator, requestTime);
-
- // Note that the only difference between the command in a group must be the partition key on which
- // they applied.
- boolean enforceStrictLiveness = tableMetadata.enforceStrictLiveness();
- // If we have more than one command, then despite each read command honoring the limit, the total result
- // might not honor it and so we should enforce it
- if (group.queries.size() > 1)
- result = group.limits().filter(result, group.nowInSec(), group.selectsFullPartition(), enforceStrictLiveness);
- return result;
+ return fetchRows(group.queries, consistencyLevel, coordinator, requestTime);
}
catch (UnavailableException e)
{
@@ -2444,11 +2534,6 @@ public class StorageProxy implements StorageProxyMBean
}
}
- public static PartitionIterator readRegular(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
- {
- return readRegular(group, consistencyLevel, ReadCoordinator.DEFAULT, requestTime);
- }
-
public static void recordReadRegularAbort(ConsistencyLevel consistencyLevel, Throwable cause)
{
readMetrics.markAbort(cause);
@@ -2493,8 +2578,11 @@ public class StorageProxy implements StorageProxyMBean
* 3. Wait for a response from R replicas
* 4. If the digests (if any) match the data return the data
* 5. else carry out read repair by getting data from all the nodes.
+ *
+ * This should not be called directly because it bypasses statistics and error handling. It is public
+ * so it can be used by Accord to fetch rows and the statistics will be tracked by Accord.
*/
- private static PartitionIterator fetchRows(List commands,
+ public static PartitionIterator fetchRows(List commands,
ConsistencyLevel consistencyLevel,
ReadCoordinator coordinator,
Dispatcher.RequestTime requestTime)
diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java
index 3615f2c7e2..be6699f292 100644
--- a/src/java/org/apache/cassandra/service/StorageService.java
+++ b/src/java/org/apache/cassandra/service/StorageService.java
@@ -1677,14 +1677,12 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
}
@Override
- public void migrateConsensusProtocol(@Nonnull String targetProtocol,
- @Nonnull List keyspaceNames,
+ public void migrateConsensusProtocol(@Nonnull List keyspaceNames,
@Nullable List maybeTableNames,
@Nullable String maybeRangesStr)
{
- checkNotNull(targetProtocol, "targetProtocol is null");
checkArgument(!keyspaceNames.contains(SchemaConstants.METADATA_KEYSPACE_NAME));
- startMigrationToConsensusProtocol(targetProtocol, keyspaceNames, Optional.ofNullable(maybeTableNames), Optional.ofNullable(maybeRangesStr));
+ startMigrationToConsensusProtocol(keyspaceNames, Optional.ofNullable(maybeTableNames), Optional.ofNullable(maybeRangesStr));
}
@Override
diff --git a/src/java/org/apache/cassandra/service/StorageServiceMBean.java b/src/java/org/apache/cassandra/service/StorageServiceMBean.java
index deb0b249d3..73e9fcd680 100644
--- a/src/java/org/apache/cassandra/service/StorageServiceMBean.java
+++ b/src/java/org/apache/cassandra/service/StorageServiceMBean.java
@@ -1142,8 +1142,7 @@ public interface StorageServiceMBean extends NotificationEmitter
public String getBootstrapState();
void abortBootstrap(String nodeId, String endpoint);
- void migrateConsensusProtocol(@Nonnull String targetProtocol,
- @Nullable List keyspaceNames,
+ void migrateConsensusProtocol(@Nullable List keyspaceNames,
@Nullable List maybeTableNames,
@Nullable String maybeRangesStr);
diff --git a/src/java/org/apache/cassandra/service/accord/AccordCallback.java b/src/java/org/apache/cassandra/service/accord/AccordCallback.java
deleted file mode 100644
index 6e658948a4..0000000000
--- a/src/java/org/apache/cassandra/service/accord/AccordCallback.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.service.accord;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import accord.coordinate.Timeout;
-import accord.local.AgentExecutor;
-import accord.messages.Callback;
-import accord.messages.Reply;
-import accord.messages.SafeCallback;
-import org.apache.cassandra.exceptions.RequestFailure;
-import org.apache.cassandra.exceptions.RequestFailureReason;
-import org.apache.cassandra.locator.InetAddressAndPort;
-import org.apache.cassandra.net.Message;
-import org.apache.cassandra.net.RequestCallback;
-
-class AccordCallback extends SafeCallback implements RequestCallback
-{
- private static final Logger logger = LoggerFactory.getLogger(AccordCallback.class);
- private final AccordEndpointMapper endpointMapper;
-
- public AccordCallback(AgentExecutor executor, Callback callback, AccordEndpointMapper endpointMapper)
- {
- super(executor, callback);
- this.endpointMapper = endpointMapper;
- }
-
- @Override
- public void onResponse(Message msg)
- {
- logger.trace("Received response {} from {}", msg.payload, msg.from());
- success(endpointMapper.mappedId(msg.from()), msg.payload);
- }
-
- private static Throwable convertFailureMessage(RequestFailure failure)
- {
- return failure.reason == RequestFailureReason.TIMEOUT ?
- new Timeout(null, null) :
- new RuntimeException(failure.failure);
- }
-
- @Override
- public void onFailure(InetAddressAndPort from, RequestFailure failure)
- {
- logger.trace("Received failure {} from {} for {}", failure, from, this);
- // TODO (now): we should distinguish timeout failures with some placeholder Exception
- failure(endpointMapper.mappedId(from), convertFailureMessage(failure));
- }
-
- @Override
- public boolean trackLatencyForSnitch()
- {
- return true;
- }
-
- @Override
- public boolean invokeOnFailure()
- {
- return true;
- }
-}
diff --git a/src/java/org/apache/cassandra/service/accord/AccordConfigurationService.java b/src/java/org/apache/cassandra/service/accord/AccordConfigurationService.java
index 4cf16f0241..0dbf99635a 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordConfigurationService.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordConfigurationService.java
@@ -52,13 +52,16 @@ import org.apache.cassandra.repair.SharedContext;
import org.apache.cassandra.service.accord.AccordKeyspace.EpochDiskState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadataService;
+import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.listeners.ChangeListener;
+import org.apache.cassandra.tcm.membership.NodeState;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Simulate;
import org.apache.cassandra.utils.concurrent.AsyncPromise;
import org.apache.cassandra.utils.concurrent.Future;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
+import static org.apache.cassandra.service.accord.AccordTopology.tcmIdToAccord;
import static org.apache.cassandra.utils.Simulate.With.MONITORS;
// TODO: listen to FailureDetector and rearrange fast path accordingly
@@ -344,16 +347,22 @@ public class AccordConfigurationService extends AbstractConfigurationService= topology.epoch()) checkIfNodesRemoved(topology);
- else epochs.acknowledgeFuture(topology.epoch()).addCallback(() -> checkIfNodesRemoved(topology));
+ Set stillLiveNodes = metadata.directory.states.entrySet()
+ .stream()
+ .filter(e -> e.getValue() != NodeState.LEFT && e.getValue() != NodeState.LEAVING)
+ .map(e -> tcmIdToAccord(e.getKey()))
+ .collect(Collectors.toSet());
+ if (epochs.lastAcknowledged() >= topology.epoch()) checkIfNodesRemoved(topology, stillLiveNodes);
+ else epochs.acknowledgeFuture(topology.epoch()).addCallback(() -> checkIfNodesRemoved(topology, stillLiveNodes));
}
- private void checkIfNodesRemoved(Topology topology)
+ private void checkIfNodesRemoved(Topology topology, Set stillLiveNodes)
{
if (epochs.minEpoch() == topology.epoch()) return;
Topology previous = getTopologyForEpoch(topology.epoch() - 1);
// for all nodes removed, or pending removal, mark them as removed so we don't wait on their replies
- Sets.SetView removedNodes = Sets.difference(previous.nodes(), topology.nodes());
+ Set removedNodes = Sets.difference(previous.nodes(), topology.nodes());
+ removedNodes = Sets.filter(removedNodes, id -> !stillLiveNodes.contains(id));
// TODO (desired, efficiency): there should be no need to notify every epoch for every removed node
for (Node.Id removedNode : removedNodes)
{
@@ -420,29 +429,36 @@ public class AccordConfigurationService extends AbstractConfigurationService peers = new HashSet<>(ClusterMetadata.current().directory.allJoinedEndpoints());
- peers.remove(FBUtilities.getBroadcastAddressAndPort());
- if (peers.isEmpty())
- return;
- Topology topology;
- while ((topology =FetchTopology.fetch(SharedContext.Global.instance, peers, epoch).get()) == null) {}
- reportTopology(topology);
- }
- catch (InterruptedException e)
- {
- if (currentEpoch() >= epoch)
- return;
- Thread.currentThread().interrupt();
- throw new UncheckedInterruptedException(e);
- }
- catch (Throwable e)
- {
- if (currentEpoch() >= epoch)
- return;
- throw new RuntimeException(e.getCause());
- }
+ // It's not safe for this to block on CMS so for now pick a thread pool to handle it
+ Stage.ACCORD_MIGRATION.execute(() -> {
+ if (ClusterMetadata.current().epoch.getEpoch() < epoch)
+ ClusterMetadataService.instance().fetchLogFromCMS(Epoch.create(epoch));
+ try
+ {
+ Set peers = new HashSet<>(ClusterMetadata.current().directory.allJoinedEndpoints());
+ peers.remove(FBUtilities.getBroadcastAddressAndPort());
+ if (peers.isEmpty())
+ return;
+ Topology topology;
+ while ((topology = FetchTopology.fetch(SharedContext.Global.instance, peers, epoch).get()) == null)
+ {
+ }
+ reportTopology(topology);
+ }
+ catch (InterruptedException e)
+ {
+ if (currentEpoch() >= epoch)
+ return;
+ Thread.currentThread().interrupt();
+ throw new UncheckedInterruptedException(e);
+ }
+ catch (Throwable e)
+ {
+ if (currentEpoch() >= epoch)
+ return;
+ throw new RuntimeException(e.getCause());
+ }
+ });
}
@Override
diff --git a/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java b/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java
index fdcd4f5391..346b1015c5 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java
@@ -50,6 +50,7 @@ import accord.primitives.Ranges;
import accord.primitives.Routable.Domain;
import accord.primitives.RoutingKeys;
import accord.primitives.SaveStatus;
+import accord.primitives.Seekable;
import accord.primitives.Seekables;
import accord.primitives.Status;
import accord.primitives.Timestamp;
@@ -94,6 +95,16 @@ public class AccordObjectSizes
return EMPTY_RANGE_SIZE + key(range.start()) + key(range.end());
}
+ public static long seekable(Seekable seekable)
+ {
+ switch (seekable.domain())
+ {
+ default: throw new AssertionError();
+ case Key: return key((Key) seekable);
+ case Range: return range((Range) seekable);
+ }
+ }
+
private static final long EMPTY_RANGES_SIZE = measure(Ranges.of());
public static long ranges(Ranges ranges)
{
diff --git a/src/java/org/apache/cassandra/service/accord/AccordResponseVerbHandler.java b/src/java/org/apache/cassandra/service/accord/AccordResponseVerbHandler.java
index 16b48c8399..330043d351 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordResponseVerbHandler.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordResponseVerbHandler.java
@@ -62,6 +62,7 @@ class AccordResponseVerbHandler implements IVerbHandler
}
Node.Id from = endpointMapper.mappedId(message.from());
+ logger.trace("Receiving {} from {}", message.payload, message.from());
if (message.isFailureResponse())
{
Tracing.trace("Processing failure response from {}", message.from());
diff --git a/src/java/org/apache/cassandra/service/accord/AccordService.java b/src/java/org/apache/cassandra/service/accord/AccordService.java
index 0c89bcb734..fc6c68dd56 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordService.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordService.java
@@ -26,6 +26,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -118,11 +119,11 @@ import org.agrona.collections.Int2ObjectHashMap;
import org.apache.cassandra.concurrent.Shutdownable;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor;
-import org.apache.cassandra.cql3.statements.RequestValidations;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.WriteType;
import org.apache.cassandra.dht.AccordSplitter;
+import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.ReadTimeoutException;
import org.apache.cassandra.exceptions.RequestExecutionException;
import org.apache.cassandra.exceptions.RequestTimeoutException;
@@ -156,6 +157,7 @@ import org.apache.cassandra.service.accord.exceptions.ReadPreemptedException;
import org.apache.cassandra.service.accord.exceptions.WritePreemptedException;
import org.apache.cassandra.service.accord.interop.AccordInteropAdapter.AccordInteropFactory;
import org.apache.cassandra.service.accord.repair.RepairSyncPointAdapter;
+import org.apache.cassandra.service.accord.txn.RetryWithNewProtocolResult;
import org.apache.cassandra.service.accord.txn.TxnResult;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.service.consensus.migration.TableMigrationState;
@@ -609,12 +611,14 @@ public class AccordService implements IAccordService, Shutdownable
// Barriers can be needed just because it's an Accord managed range, but it could also be a migration back to Paxos
// in which case we do want to barrier the migrating/migrated ranges even though the target for the migration is not Accord
// In either case Accord should be aware of those ranges and not generate a topology mismatch
- if (tm.params.transactionalMode != TransactionalMode.off || tm.params.transactionalMigrationFrom.from != TransactionalMode.off)
+ if (tm.params.transactionalMode != TransactionalMode.off || tm.params.transactionalMigrationFrom.migratingFromAccord())
{
TableMigrationState tms = cm.consensusMigrationState.tableStates.get(tm.id);
// null is fine could be completely migrated or was always an Accord table on creation
if (tms == null)
return keysOrRanges;
+ // Use migratingAndMigratedRanges (not accordSafeToReadRanges) because barriers are allowed even if Accord can't perform
+ // a read because they are only finishing/recovering existing Accord transactions
Ranges migratingAndMigratedRanges = AccordTopology.toAccordRanges(tms.tableId, tms.migratingAndMigratedRanges);
return keysOrRanges.slice(migratingAndMigratedRanges);
}
@@ -739,6 +743,34 @@ public class AccordService implements IAccordService, Shutdownable
return node.topology();
}
+ private static Set txnDroppedTables(Seekables,?> keys)
+ {
+ Set tables = new HashSet<>();
+ for (Seekable seekable : keys)
+ {
+ switch (seekable.domain())
+ {
+ default:
+ throw new IllegalStateException("Unhandled domain " + seekable.domain());
+ case Key:
+ tables.add(((PartitionKey) seekable).table());
+ break;
+ case Range:
+ tables.add(((TokenRange) seekable).table());
+ break;
+ }
+ }
+
+ Iterator tablesIterator = tables.iterator();
+ while (tablesIterator.hasNext())
+ {
+ TableId table = tablesIterator.next();
+ if (Schema.instance.getTableMetadata(table) != null)
+ tablesIterator.remove();
+ }
+ return tables;
+ }
+
/**
* Consistency level is just echoed back in timeouts, in the future it may be used for interoperability
* with non-Accord operations.
@@ -747,7 +779,7 @@ public class AccordService implements IAccordService, Shutdownable
public @Nonnull TxnResult coordinate(long minEpoch, @Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, @Nonnull Dispatcher.RequestTime requestTime)
{
AsyncTxnResult asyncTxnResult = coordinateAsync(minEpoch, txn, consistencyLevel, requestTime);
- return getTxnResult(asyncTxnResult, txn.isWrite(), consistencyLevel, requestTime);
+ return getTxnResult(asyncTxnResult);
}
@Override
@@ -769,7 +801,8 @@ public class AccordService implements IAccordService, Shutdownable
metrics.keySize.update(txn.keys().size());
long deadlineNanos = requestTime.computeDeadline(DatabaseDescriptor.getTransactionTimeout(NANOSECONDS));
AsyncResult asyncResult = node.coordinate(txnId, txn, minEpoch, deadlineNanos);
- AsyncTxnResult asyncTxnResult = new AsyncTxnResult(txnId);
+ Seekables, ?> keys = txn.keys();
+ AsyncTxnResult asyncTxnResult = new AsyncTxnResult(txnId, minEpoch, consistencyLevel, txn.isWrite(), requestTime);
asyncResult.addCallback((success, failure) -> {
long durationNanos = nanoTime() - requestTime.startedAtNanos();
sharedMetrics.addNano(durationNanos);
@@ -786,6 +819,9 @@ public class AccordService implements IAccordService, Shutdownable
return;
}
+ sharedMetrics.failures.mark();
+ metrics.failures.mark();
+
if (cause instanceof Timeout)
{
// Don't mark the metric here, should be done in getTxnResult to ensure it only happens once
@@ -795,7 +831,6 @@ public class AccordService implements IAccordService, Shutdownable
}
if (cause instanceof Preempted || cause instanceof Invalidated)
{
- sharedMetrics.timeouts.mark();
metrics.preempted.mark();
//TODO need to improve
// Coordinator "could" query the accord state to see whats going on but that doesn't exist yet.
@@ -803,25 +838,39 @@ public class AccordService implements IAccordService, Shutdownable
asyncTxnResult.tryFailure(newPreempted(txnId, txn.isWrite(), consistencyLevel));
return;
}
- sharedMetrics.failures.mark();
+ // TODO (desired): It would be better to check if the topology mismatch is due to Accord ownership changes
+ // or the txn accessing tables that don't exist or something.
if (cause instanceof TopologyMismatch)
{
+ // Excluding bugs topology mismatch can occur because a table was dropped in between creating the txn
+ // and executing it.
+ // It could also race with the table stopping/starting being managed by Accord.
+ // The caller can retry if the table indeed exists and is managed by Accord.
+ Set txnDroppedTables = txnDroppedTables(keys);
+ Tracing.trace("Accord returned topology mismatch: " + cause.getMessage());
+ logger.debug("Accord returned topology mismatch", cause);
metrics.topologyMismatches.mark();
- asyncTxnResult.tryFailure(RequestValidations.invalidRequest(cause.getMessage()));
+ // Throw IRE in case the caller fails to check if the table still exists
+ if (!txnDroppedTables.isEmpty())
+ {
+ Tracing.trace("Accord txn uses dropped tables {}", txnDroppedTables);
+ logger.debug("Accord txn uses dropped tables {}", txnDroppedTables);
+ asyncTxnResult.setFailure(new InvalidRequestException("Accord transaction uses dropped tables"));
+ }
+ asyncTxnResult.setSuccess(RetryWithNewProtocolResult.instance);
return;
}
- metrics.failures.mark();
asyncTxnResult.tryFailure(new RuntimeException(cause));
});
return asyncTxnResult;
}
@Override
- public TxnResult getTxnResult(AsyncTxnResult asyncTxnResult, boolean isWrite, @Nullable ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
+ public TxnResult getTxnResult(AsyncTxnResult asyncTxnResult)
{
ClientRequestMetrics sharedMetrics;
AccordClientRequestMetrics metrics;
- if (isWrite)
+ if (asyncTxnResult.isWrite)
{
sharedMetrics = ClientRequestsMetricsHolder.writeMetrics;
metrics = accordWriteMetrics;
@@ -833,7 +882,7 @@ public class AccordService implements IAccordService, Shutdownable
}
try
{
- long deadlineNanos = requestTime.computeDeadline(DatabaseDescriptor.getTransactionTimeout(NANOSECONDS));
+ long deadlineNanos = asyncTxnResult.requestTime.computeDeadline(DatabaseDescriptor.getTransactionTimeout(NANOSECONDS));
TxnResult result = asyncTxnResult.get(deadlineNanos - nanoTime(), NANOSECONDS);
return result;
}
@@ -851,6 +900,8 @@ public class AccordService implements IAccordService, Shutdownable
}
else if (cause instanceof RuntimeException)
throw (RuntimeException) cause;
+ else if (cause instanceof InvalidRequestException)
+ throw ((InvalidRequestException)cause);
else
throw new RuntimeException(cause);
}
@@ -864,7 +915,7 @@ public class AccordService implements IAccordService, Shutdownable
{
metrics.timeouts.mark();
sharedMetrics.timeouts.mark();
- throw newTimeout(asyncTxnResult.txnId, isWrite, consistencyLevel);
+ throw newTimeout(asyncTxnResult.txnId, asyncTxnResult.isWrite, asyncTxnResult.consistencyLevel);
}
}
diff --git a/src/java/org/apache/cassandra/service/accord/AccordTopology.java b/src/java/org/apache/cassandra/service/accord/AccordTopology.java
index 83c74d82fa..8b51a923a1 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordTopology.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordTopology.java
@@ -18,17 +18,25 @@
package org.apache.cassandra.service.accord;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
-import accord.local.Node.Id;
-import accord.primitives.Ranges;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
+import accord.local.Node.Id;
+import accord.primitives.Ranges;
import accord.topology.Shard;
import accord.topology.Topology;
import accord.utils.Invariants;
@@ -38,7 +46,13 @@ import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.locator.InetAddressAndPort;
-import org.apache.cassandra.schema.*;
+import org.apache.cassandra.schema.Diff;
+import org.apache.cassandra.schema.DistributedSchema;
+import org.apache.cassandra.schema.KeyspaceMetadata;
+import org.apache.cassandra.schema.Keyspaces;
+import org.apache.cassandra.schema.ReplicationParams;
+import org.apache.cassandra.schema.TableId;
+import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.accord.api.AccordRoutingKey.SentinelKey;
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
import org.apache.cassandra.service.accord.fastpath.FastPathStrategy;
diff --git a/src/java/org/apache/cassandra/service/accord/AccordVerbHandler.java b/src/java/org/apache/cassandra/service/accord/AccordVerbHandler.java
index 0fdf3d9479..48cd307ccd 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordVerbHandler.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordVerbHandler.java
@@ -27,6 +27,9 @@ import accord.local.Node;
import accord.messages.Request;
import org.apache.cassandra.net.IVerbHandler;
import org.apache.cassandra.net.Message;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.ClusterMetadataService;
+import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.utils.NoSpamLogger;
public class AccordVerbHandler implements IVerbHandler
@@ -53,6 +56,7 @@ public class AccordVerbHandler implements IVerbHandler
}
logger.trace("Receiving {} from {}", message.payload, message.from());
+
T request = message.payload;
/*
@@ -62,14 +66,32 @@ public class AccordVerbHandler implements IVerbHandler
*/
Node.Id fromNodeId = endpointMapper.mappedId(message.from());
long waitForEpoch = request.waitForEpoch();
-
- if (node.topology().hasAtLeastEpoch(waitForEpoch))
+ ClusterMetadata cm = ClusterMetadata.current();
+ boolean cmUpToDate = ClusterMetadata.current().epoch.getEpoch() >= waitForEpoch;
+ if (node.topology().hasAtLeastEpoch(waitForEpoch) && cmUpToDate)
request.process(node, fromNodeId, message.header);
else
- node.withEpoch(waitForEpoch, (ignored, withEpochFailure) -> {
- if (withEpochFailure != null)
- throw new RuntimeException("Timed out waiting for epoch when processing message from " + fromNodeId + " to " + node + " message " + message, withEpochFailure);
- request.process(node, fromNodeId, message.header);
- });
+ {
+ // withEpoch does not reliably ensure that TCM is up to date, if Accord has the topology it won't
+ // wait for TCM to come up to date, so do it here in the verb handler
+ if (!cmUpToDate)
+ {
+ ClusterMetadataService.instance().fetchLogFromPeerOrCMSAsync(cm, message.from(), Epoch.create(waitForEpoch)).addCallback((success, failure) ->
+ node.withEpoch(waitForEpoch, (ignored, withEpochFailure) -> {
+ if (withEpochFailure != null)
+ throw new RuntimeException("Timed out waiting for epoch when processing message from " + fromNodeId + " to " + node + " message " + message, withEpochFailure);
+ request.process(node, fromNodeId, message.header);
+ })
+ );
+ }
+ else
+ {
+ node.withEpoch(waitForEpoch, (ignored, withEpochFailure) -> {
+ if (withEpochFailure != null)
+ throw new RuntimeException("Timed out waiting for epoch when processing message from " + fromNodeId + " to " + node + " message " + message, withEpochFailure);
+ request.process(node, fromNodeId, message.header);
+ });
+ }
+ }
}
}
diff --git a/src/java/org/apache/cassandra/service/accord/IAccordService.java b/src/java/org/apache/cassandra/service/accord/IAccordService.java
index 068c4c1b19..8750745993 100644
--- a/src/java/org/apache/cassandra/service/accord/IAccordService.java
+++ b/src/java/org/apache/cassandra/service/accord/IAccordService.java
@@ -110,17 +110,27 @@ public interface IAccordService
class AsyncTxnResult extends AsyncPromise
{
public final @Nonnull TxnId txnId;
+ public final long minEpoch;
+ @Nullable
+ public final ConsistencyLevel consistencyLevel;
+ public final boolean isWrite;
+ public final Dispatcher.RequestTime requestTime;
- public AsyncTxnResult(@Nonnull TxnId txnId)
+ public AsyncTxnResult(@Nonnull TxnId txnId, long minEpoch, @Nullable ConsistencyLevel consistencyLevel, boolean isWrite, @Nonnull Dispatcher.RequestTime requestTime)
{
checkNotNull(txnId);
+ checkNotNull(requestTime);
this.txnId = txnId;
+ this.minEpoch = minEpoch;
+ this.consistencyLevel = consistencyLevel;
+ this.isWrite = isWrite;
+ this.requestTime = requestTime;
}
}
@Nonnull
AsyncTxnResult coordinateAsync(long minEpoch, @Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime);
- TxnResult getTxnResult(AsyncTxnResult asyncTxnResult, boolean isWrite, @Nullable ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime);
+ TxnResult getTxnResult(AsyncTxnResult asyncTxnResult);
long currentEpoch();
@@ -236,7 +246,7 @@ public interface IAccordService
}
@Override
- public TxnResult getTxnResult(AsyncTxnResult asyncTxnResult, boolean isWrite, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
+ public TxnResult getTxnResult(AsyncTxnResult asyncTxnResult)
{
throw new UnsupportedOperationException("No accord transaction should be executed when accord.enabled = false in cassandra.yaml");
}
@@ -418,9 +428,9 @@ public interface IAccordService
}
@Override
- public TxnResult getTxnResult(AsyncTxnResult asyncTxnResult, boolean isWrite, @Nullable ConsistencyLevel consistencyLevel, RequestTime requestTime)
+ public TxnResult getTxnResult(AsyncTxnResult asyncTxnResult)
{
- return delegate.getTxnResult(asyncTxnResult, isWrite, consistencyLevel, requestTime);
+ return delegate.getTxnResult(asyncTxnResult);
}
@Override
diff --git a/src/java/org/apache/cassandra/service/accord/TokenRange.java b/src/java/org/apache/cassandra/service/accord/TokenRange.java
index 1f684fb4b8..8dd96cfaaa 100644
--- a/src/java/org/apache/cassandra/service/accord/TokenRange.java
+++ b/src/java/org/apache/cassandra/service/accord/TokenRange.java
@@ -34,10 +34,14 @@ import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
import org.apache.cassandra.service.accord.api.AccordRoutingKey.SentinelKey;
+import org.apache.cassandra.utils.ObjectSizes;
public class TokenRange extends Range.EndInclusive
{
- public TokenRange(AccordRoutingKey start, AccordRoutingKey end)
+ public static final long EMPTY_SIZE = ObjectSizes.measure(new TokenRange(SentinelKey.min(TableId.fromLong(0)), SentinelKey.max(TableId.fromLong(0))));
+
+ // Don't make this public use create or createUnsafe
+ private TokenRange(AccordRoutingKey start, AccordRoutingKey end)
{
super(start, end);
}
@@ -72,6 +76,11 @@ public class TokenRange extends Range.EndInclusive
return (AccordRoutingKey) super.end();
}
+ public long estimatedSizeOnHeap()
+ {
+ return EMPTY_SIZE + start().estimatedSizeOnHeap() + end().estimatedSizeOnHeap();
+ }
+
public boolean isFullRange()
{
return start().kindOfRoutingKey() == AccordRoutingKey.RoutingKeyKind.SENTINEL && end().kindOfRoutingKey() == AccordRoutingKey.RoutingKeyKind.SENTINEL;
@@ -94,7 +103,12 @@ public class TokenRange extends Range.EndInclusive
return new TokenRange((AccordRoutingKey) start, (AccordRoutingKey) end);
}
- public org.apache.cassandra.dht.Range toKeyspaceRange ()
+ /*
+ * This behaves quite incorrectly with MinTokenKey because it loses the inclusivity of MinTokenKey in the conversion.
+ * It's not a problem for cluster metadata and topology, but it's quite wrong for queries that convert from Bounds to
+ * Range.
+ */
+ public org.apache.cassandra.dht.Range toKeyspaceRange()
{
IPartitioner partitioner = DatabaseDescriptor.getPartitioner();
AccordRoutingKey start = start();
@@ -104,7 +118,6 @@ public class TokenRange extends Range.EndInclusive
return new org.apache.cassandra.dht.Range<>(left, right);
}
-
public static final Serializer serializer = new Serializer();
public static final class Serializer implements IVersionedSerializer
diff --git a/src/java/org/apache/cassandra/service/accord/api/AccordAgent.java b/src/java/org/apache/cassandra/service/accord/api/AccordAgent.java
index c5561be97c..8ee5957209 100644
--- a/src/java/org/apache/cassandra/service/accord/api/AccordAgent.java
+++ b/src/java/org/apache/cassandra/service/accord/api/AccordAgent.java
@@ -52,8 +52,8 @@ import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.metrics.AccordMetrics;
import org.apache.cassandra.net.ResponseContext;
import org.apache.cassandra.service.accord.AccordService;
-import org.apache.cassandra.service.accord.txn.TxnKeyRead;
import org.apache.cassandra.service.accord.txn.TxnQuery;
+import org.apache.cassandra.service.accord.txn.TxnRead;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.JVMStabilityInspector;
@@ -174,7 +174,7 @@ public class AccordAgent implements Agent
@Override
public Txn emptySystemTxn(Kind kind, Routable.Domain domain)
{
- return new Txn.InMemory(kind, domain == Key ? Keys.EMPTY : Ranges.EMPTY, TxnKeyRead.EMPTY, TxnQuery.UNSAFE_EMPTY, null);
+ return new Txn.InMemory(kind, domain == Key ? Keys.EMPTY : Ranges.EMPTY, TxnRead.empty(domain), TxnQuery.UNSAFE_EMPTY, null);
}
@Override
diff --git a/src/java/org/apache/cassandra/service/accord/api/AccordRoutingKey.java b/src/java/org/apache/cassandra/service/accord/api/AccordRoutingKey.java
index 9088faccc3..61454eab64 100644
--- a/src/java/org/apache/cassandra/service/accord/api/AccordRoutingKey.java
+++ b/src/java/org/apache/cassandra/service/accord/api/AccordRoutingKey.java
@@ -240,7 +240,7 @@ public abstract class AccordRoutingKey extends AccordRoutableKey implements Rout
public Range asRange()
{
checkState(!isMinMinSentinel, "It might be possible to support converting a minmin sentinel to a range, but it needs to be evaluated in the context where it is failing");
- return new TokenRange(new SentinelKey(table, isMinSentinel, true), this);
+ return TokenRange.create(new SentinelKey(table, isMinSentinel, true), this);
}
}
@@ -373,7 +373,7 @@ public abstract class AccordRoutingKey extends AccordRoutableKey implements Rout
? new SentinelKey(table, true, false)
: new TokenKey(table, token.decreaseSlightly());
- return new TokenRange(before, this);
+ return TokenRange.create(before, this);
}
public MinTokenKey(TableId tableId, Token token)
diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java
index 574d962a96..fb3d8a606c 100644
--- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java
+++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java
@@ -107,7 +107,7 @@ public class AccordInteropAdapter extends AbstractTxnAdapter
if (updateKind != AccordUpdate.Kind.UNRECOVERABLE_REPAIR && (consistencyLevel == null || consistencyLevel == ConsistencyLevel.ONE || txn.read().keys().isEmpty()))
return false;
- new AccordInteropExecution(node, txnId, txn, updateKind, route, txn.read().keys().toParticipants(), executeAt, deps, callback, executor, consistencyLevel, endpointMapper)
+ new AccordInteropExecution(node, txnId, txn, updateKind, route, executeAt, deps, callback, executor, consistencyLevel, endpointMapper)
.start();
return true;
}
diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java
index 0a6a10f5ae..0bc6cdbe32 100644
--- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java
+++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java
@@ -24,7 +24,6 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
@@ -80,19 +79,16 @@ import org.apache.cassandra.service.accord.AccordEndpointMapper;
import org.apache.cassandra.service.accord.TokenRange;
import org.apache.cassandra.service.accord.api.AccordAgent;
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
+import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
import org.apache.cassandra.service.accord.api.PartitionKey;
import org.apache.cassandra.service.accord.interop.AccordInteropReadCallback.MaximalCommitSender;
import org.apache.cassandra.service.accord.txn.AccordUpdate;
import org.apache.cassandra.service.accord.txn.TxnData;
-import org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind;
import org.apache.cassandra.service.accord.txn.TxnDataKeyValue;
import org.apache.cassandra.service.accord.txn.TxnDataRangeValue;
-import org.apache.cassandra.service.accord.txn.TxnKeyRead;
-import org.apache.cassandra.service.accord.txn.TxnRangeRead;
+import org.apache.cassandra.service.accord.txn.TxnRead;
import org.apache.cassandra.service.accord.txn.UnrecoverableRepairUpdate;
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter;
-import org.apache.cassandra.service.consensus.migration.ConsensusTableMigration;
-import org.apache.cassandra.service.consensus.migration.TableMigrationState;
import org.apache.cassandra.service.reads.ReadCoordinator;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Dispatcher;
@@ -151,7 +147,6 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen
private final TxnId txnId;
private final Txn txn;
private final FullRoute> route;
- private final Participants> readScope;
private final Timestamp executeAt;
private final Deps deps;
private final BiConsumer super Result, Throwable> callback;
@@ -169,7 +164,7 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen
private final Set contacted;
private final AccordUpdate.Kind updateKind;
- public AccordInteropExecution(Node node, TxnId txnId, Txn txn, AccordUpdate.Kind updateKind, FullRoute> route, Participants> readScope, Timestamp executeAt, Deps deps, BiConsumer super Result, Throwable> callback,
+ public AccordInteropExecution(Node node, TxnId txnId, Txn txn, AccordUpdate.Kind updateKind, FullRoute> route, Timestamp executeAt, Deps deps, BiConsumer super Result, Throwable> callback,
AgentExecutor executor, ConsistencyLevel consistencyLevel, AccordEndpointMapper endpointMapper)
{
requireArgument(!txn.read().keys().isEmpty() || updateKind == AccordUpdate.Kind.UNRECOVERABLE_REPAIR);
@@ -177,7 +172,6 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen
this.txnId = txnId;
this.txn = txn;
this.route = route;
- this.readScope = readScope;
this.executeAt = executeAt;
this.deps = deps;
this.callback = callback;
@@ -233,123 +227,125 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen
public void sendReadCommand(Message message, InetAddressAndPort to, RequestCallback callback)
{
Node.Id id = endpointMapper.mappedId(to);
- SinglePartitionReadCommand command = (SinglePartitionReadCommand) message.payload;
+ // TODO (nicetohave): It would be better to use the re-use the command from the transaction but it's fragile
+ // to try and figure out exactly what changed for things like read repair and short read protection
+ // Also this read scope doesn't reflect the contents of this particular read and is larger than it needs to be
// TODO (required): understand interop and whether StableFastPath is appropriate
- AccordInteropStableThenRead commit = new AccordInteropStableThenRead(id, allTopologies, txnId, Kind.StableFastPath, executeAt, txn, deps, route, command);
+ AccordInteropStableThenRead commit = new AccordInteropStableThenRead(id, allTopologies, txnId, Kind.StableFastPath, executeAt, txn, deps, route, message.payload);
node.send(id, commit, executor, new AccordInteropRead.ReadCallback(id, to, message, callback, this));
}
@Override
public void sendReadRepairMutation(Message message, InetAddressAndPort to, RequestCallback | | | | | | | | | | | | | | |