mirror of https://github.com/apache/cassandra
Live migration for non-serial reads
Patch by Ariel Weisberg; Reviewed by David Capwell for CASSANDRA-19439
This commit is contained in:
parent
cf9e34df45
commit
6522d52b0b
|
|
@ -1239,6 +1239,7 @@
|
||||||
<jvmarg value="-XX:SoftRefLRUPolicyMSPerMB=0" />
|
<jvmarg value="-XX:SoftRefLRUPolicyMSPerMB=0" />
|
||||||
<jvmarg value="-XX:ActiveProcessorCount=${cassandra.test.processorCount}" />
|
<jvmarg value="-XX:ActiveProcessorCount=${cassandra.test.processorCount}" />
|
||||||
<jvmarg value="-XX:HeapDumpPath=${build.test.dir}" />
|
<jvmarg value="-XX:HeapDumpPath=${build.test.dir}" />
|
||||||
|
<jvmarg value="-Dcassandra.test.accord.allow_test_modes=true"/>
|
||||||
<jvmarg value="-Dcassandra.test.driver.connection_timeout_ms=${test.driver.connection_timeout_ms}"/>
|
<jvmarg value="-Dcassandra.test.driver.connection_timeout_ms=${test.driver.connection_timeout_ms}"/>
|
||||||
<jvmarg value="-Dcassandra.test.driver.read_timeout_ms=${test.driver.read_timeout_ms}"/>
|
<jvmarg value="-Dcassandra.test.driver.read_timeout_ms=${test.driver.read_timeout_ms}"/>
|
||||||
<jvmarg value="-Dcassandra.memtable_row_overhead_computation_step=100"/>
|
<jvmarg value="-Dcassandra.memtable_row_overhead_computation_step=100"/>
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,7 @@
|
||||||
-Dcassandra.ring_delay_ms=10000
|
-Dcassandra.ring_delay_ms=10000
|
||||||
-Dcassandra.skip_sync=true
|
-Dcassandra.skip_sync=true
|
||||||
-Dcassandra.storagedir=$PROJECT_DIR$/data
|
-Dcassandra.storagedir=$PROJECT_DIR$/data
|
||||||
|
-Dcassandra.test.accord.allow_test_modes=true
|
||||||
-Dcassandra.strict.runtime.checks=true
|
-Dcassandra.strict.runtime.checks=true
|
||||||
-Dcassandra.test.flush_local_schema_changes=false
|
-Dcassandra.test.flush_local_schema_changes=false
|
||||||
-Dcassandra.test.messagingService.nonGracefulShutdown=true
|
-Dcassandra.test.messagingService.nonGracefulShutdown=true
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
@ -454,12 +453,12 @@ public class BatchlogManager implements BatchlogManagerMBean
|
||||||
if (accordResult != null)
|
if (accordResult != null)
|
||||||
{
|
{
|
||||||
IAccordService accord = AccordService.instance();
|
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)
|
if (kind == retry_new_protocol)
|
||||||
throw new RetryOnDifferentSystemException();
|
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("Failed replaying a batched mutation on Accord, will write a hint");
|
||||||
logger.trace("Failure was : {}", e.getMessage());
|
logger.trace("Failure was : {}", e.getMessage());
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import static org.apache.cassandra.utils.LocalizeString.toUpperCaseLocalized;
|
||||||
public enum CassandraRelevantProperties
|
public enum CassandraRelevantProperties
|
||||||
{
|
{
|
||||||
ACCORD_AGENT_CLASS("cassandra.test.accord.agent"),
|
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_COSTFACTOR(Invariants.KEY_PARANOIA_COSTFACTOR),
|
||||||
ACCORD_KEY_PARANOIA_CPU(Invariants.KEY_PARANOIA_CPU),
|
ACCORD_KEY_PARANOIA_CPU(Invariants.KEY_PARANOIA_CPU),
|
||||||
ACCORD_KEY_PARANOIA_MEMORY(Invariants.KEY_PARANOIA_MEMORY),
|
ACCORD_KEY_PARANOIA_MEMORY(Invariants.KEY_PARANOIA_MEMORY),
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ import org.apache.cassandra.db.Clustering;
|
||||||
import org.apache.cassandra.db.ConsistencyLevel;
|
import org.apache.cassandra.db.ConsistencyLevel;
|
||||||
import org.apache.cassandra.db.DecoratedKey;
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
import org.apache.cassandra.db.IMutation;
|
import org.apache.cassandra.db.IMutation;
|
||||||
|
import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
|
||||||
import org.apache.cassandra.db.RegularAndStaticColumns;
|
import org.apache.cassandra.db.RegularAndStaticColumns;
|
||||||
import org.apache.cassandra.db.Slice;
|
import org.apache.cassandra.db.Slice;
|
||||||
import org.apache.cassandra.db.Slices;
|
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
|
// 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
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import org.apache.cassandra.db.CounterMutation;
|
||||||
import org.apache.cassandra.db.DecoratedKey;
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
import org.apache.cassandra.db.IMutation;
|
import org.apache.cassandra.db.IMutation;
|
||||||
import org.apache.cassandra.db.Mutation;
|
import org.apache.cassandra.db.Mutation;
|
||||||
|
import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
|
||||||
import org.apache.cassandra.db.RegularAndStaticColumns;
|
import org.apache.cassandra.db.RegularAndStaticColumns;
|
||||||
import org.apache.cassandra.db.commitlog.CommitLogSegment;
|
import org.apache.cassandra.db.commitlog.CommitLogSegment;
|
||||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||||
|
|
@ -137,14 +138,14 @@ final class BatchUpdatesCollector implements UpdatesCollector
|
||||||
* @return a collection containing all the mutations.
|
* @return a collection containing all the mutations.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<IMutation> toMutations(ClientState state, boolean allowPotentialTxnConflicts)
|
public List<IMutation> toMutations(ClientState state, PotentialTxnConflicts potentialTxnConflicts)
|
||||||
{
|
{
|
||||||
List<IMutation> ms = new ArrayList<>();
|
List<IMutation> ms = new ArrayList<>();
|
||||||
for (Map<ByteBuffer, IMutationBuilder> ksMap : mutationBuilders.values())
|
for (Map<ByteBuffer, IMutationBuilder> ksMap : mutationBuilders.values())
|
||||||
{
|
{
|
||||||
for (IMutationBuilder builder : ksMap.values())
|
for (IMutationBuilder builder : ksMap.values())
|
||||||
{
|
{
|
||||||
IMutation mutation = builder.build(allowPotentialTxnConflicts);
|
IMutation mutation = builder.build(potentialTxnConflicts);
|
||||||
mutation.validateIndexedColumns(state);
|
mutation.validateIndexedColumns(state);
|
||||||
mutation.validateSize(MessagingService.current_version, CommitLogSegment.ENTRY_OVERHEAD_SIZE);
|
mutation.validateSize(MessagingService.current_version, CommitLogSegment.ENTRY_OVERHEAD_SIZE);
|
||||||
ms.add(mutation);
|
ms.add(mutation);
|
||||||
|
|
@ -182,7 +183,7 @@ final class BatchUpdatesCollector implements UpdatesCollector
|
||||||
/**
|
/**
|
||||||
* Build the immutable mutation
|
* Build the immutable mutation
|
||||||
*/
|
*/
|
||||||
IMutation build(boolean allowPotentialTxnConflicts);
|
IMutation build(PotentialTxnConflicts potentialTxnConflicts);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the builder for the given tableId
|
* Get the builder for the given tableId
|
||||||
|
|
@ -215,7 +216,7 @@ final class BatchUpdatesCollector implements UpdatesCollector
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mutation build(boolean allowPotentialTxnConflicts)
|
public Mutation build(PotentialTxnConflicts potentialTxnConflicts)
|
||||||
{
|
{
|
||||||
ImmutableMap.Builder<TableId, PartitionUpdate> updates = new ImmutableMap.Builder<>();
|
ImmutableMap.Builder<TableId, PartitionUpdate> updates = new ImmutableMap.Builder<>();
|
||||||
for (Map.Entry<TableId, PartitionUpdate.Builder> updateEntry : modifications.entrySet())
|
for (Map.Entry<TableId, PartitionUpdate.Builder> updateEntry : modifications.entrySet())
|
||||||
|
|
@ -223,7 +224,7 @@ final class BatchUpdatesCollector implements UpdatesCollector
|
||||||
PartitionUpdate update = updateEntry.getValue().build();
|
PartitionUpdate update = updateEntry.getValue().build();
|
||||||
updates.put(updateEntry.getKey(), update);
|
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)
|
public PartitionUpdate.Builder get(TableId tableId)
|
||||||
|
|
@ -263,9 +264,9 @@ final class BatchUpdatesCollector implements UpdatesCollector
|
||||||
return mutationBuilder.add(builder);
|
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)
|
public PartitionUpdate.Builder get(TableId id)
|
||||||
|
|
@ -297,7 +298,7 @@ final class BatchUpdatesCollector implements UpdatesCollector
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VirtualMutation build(boolean allowPotentialTxnConflicts)
|
public VirtualMutation build(PotentialTxnConflicts potentialTxnConflicts)
|
||||||
{
|
{
|
||||||
ImmutableMap.Builder<TableId, PartitionUpdate> updates = new ImmutableMap.Builder<>();
|
ImmutableMap.Builder<TableId, PartitionUpdate> updates = new ImmutableMap.Builder<>();
|
||||||
modifications.forEach((tableId, updateBuilder) -> updates.put(tableId, updateBuilder.build()));
|
modifications.forEach((tableId, updateBuilder) -> updates.put(tableId, updateBuilder.build()));
|
||||||
|
|
|
||||||
|
|
@ -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.TxnCondition;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnData;
|
import org.apache.cassandra.service.accord.txn.TxnData;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnDataKeyValue;
|
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.TxnQuery;
|
||||||
|
import org.apache.cassandra.service.accord.txn.TxnRead;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnReference;
|
import org.apache.cassandra.service.accord.txn.TxnReference;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnResult;
|
import org.apache.cassandra.service.accord.txn.TxnResult;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnUpdate;
|
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.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.getTableMetadata;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processed CAS conditions and update on potentially multiple rows of the same partition.
|
* 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
|
// 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
|
// level since Accord will manage reading safely
|
||||||
TableParams tableParams = getTableMetadata(cm, metadata.id).params;
|
TableParams tableParams = getTableMetadata(cm, metadata.id).params;
|
||||||
consistencyLevel = tableParams.transactionalMode.readCLForStrategy(tableParams.transactionalMigrationFrom, consistencyLevel, cm, metadata.id, readCommand.partitionKey().getToken());
|
consistencyLevel = tableParams.transactionalMode.readCLForMode(tableParams.transactionalMigrationFrom, consistencyLevel, cm, metadata.id, readCommand.partitionKey().getToken());
|
||||||
TxnKeyRead read = TxnKeyRead.createCasRead(readCommand, consistencyLevel);
|
TxnRead read = TxnRead.createCasRead(readCommand, consistencyLevel);
|
||||||
// In a CAS requesting only one key is supported and writes
|
// In a CAS requesting only one key is supported and writes
|
||||||
// can't be dependent on any data that is read (only conditions)
|
// can't be dependent on any data that is read (only conditions)
|
||||||
// so the only relevant keys are the read key
|
// 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
|
// since it is safe to match what non-SERIAL writes do
|
||||||
TableMetadata tableMetadata = getTableMetadata(cm, metadata.id);
|
TableMetadata tableMetadata = getTableMetadata(cm, metadata.id);
|
||||||
TableParams tableParams = tableMetadata.params;
|
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
|
// CAS requires using the new txn timestamp to correctly linearize some kinds of updates
|
||||||
return new TxnUpdate(createWriteFragments(clientState), createCondition(), commitConsistencyLevel, false);
|
return new TxnUpdate(createWriteFragments(clientState), createCondition(), commitConsistencyLevel, false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ import org.apache.cassandra.db.ConsistencyLevel;
|
||||||
import org.apache.cassandra.db.DecoratedKey;
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
import org.apache.cassandra.db.IMutation;
|
import org.apache.cassandra.db.IMutation;
|
||||||
import org.apache.cassandra.db.Keyspace;
|
import org.apache.cassandra.db.Keyspace;
|
||||||
|
import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
|
||||||
import org.apache.cassandra.db.ReadExecutionController;
|
import org.apache.cassandra.db.ReadExecutionController;
|
||||||
import org.apache.cassandra.db.RegularAndStaticColumns;
|
import org.apache.cassandra.db.RegularAndStaticColumns;
|
||||||
import org.apache.cassandra.db.SinglePartitionReadCommand;
|
import org.apache.cassandra.db.SinglePartitionReadCommand;
|
||||||
|
|
@ -870,7 +871,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
|
||||||
SingleTableSinglePartitionUpdatesCollector collector = new SingleTableSinglePartitionUpdatesCollector(metadata, updatedColumns);
|
SingleTableSinglePartitionUpdatesCollector collector = new SingleTableSinglePartitionUpdatesCollector(metadata, updatedColumns);
|
||||||
addUpdates(collector, keys, state, options, local, timestamp, nowInSeconds, requestTime, constructingAccordBaseUpdate);
|
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
|
// 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
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -878,7 +879,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
|
||||||
SingleTableUpdatesCollector collector = new SingleTableUpdatesCollector(metadata, updatedColumns, perPartitionKeyCounts);
|
SingleTableUpdatesCollector collector = new SingleTableUpdatesCollector(metadata, updatedColumns, perPartitionKeyCounts);
|
||||||
addUpdates(collector, keys, state, options, local, timestamp, nowInSeconds, requestTime, constructingAccordBaseUpdate);
|
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
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ import org.apache.cassandra.db.DecoratedKey;
|
||||||
import org.apache.cassandra.db.Keyspace;
|
import org.apache.cassandra.db.Keyspace;
|
||||||
import org.apache.cassandra.db.PartitionPosition;
|
import org.apache.cassandra.db.PartitionPosition;
|
||||||
import org.apache.cassandra.db.PartitionRangeReadQuery;
|
import org.apache.cassandra.db.PartitionRangeReadQuery;
|
||||||
|
import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
|
||||||
import org.apache.cassandra.db.ReadExecutionController;
|
import org.apache.cassandra.db.ReadExecutionController;
|
||||||
import org.apache.cassandra.db.ReadQuery;
|
import org.apache.cassandra.db.ReadQuery;
|
||||||
import org.apache.cassandra.db.SinglePartitionReadCommand;
|
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())
|
if (options.isReadThresholdsEnabled())
|
||||||
query.trackWarnings();
|
query.trackWarnings();
|
||||||
|
|
@ -437,7 +438,8 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
|
||||||
getLimit(options),
|
getLimit(options),
|
||||||
getPerPartitionLimit(options),
|
getPerPartitionLimit(options),
|
||||||
options.getPageSize(),
|
options.getPageSize(),
|
||||||
getAggregationSpec(options));
|
getAggregationSpec(options),
|
||||||
|
PotentialTxnConflicts.DISALLOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadQuery getQuery(QueryOptions options,
|
public ReadQuery getQuery(QueryOptions options,
|
||||||
|
|
@ -447,18 +449,20 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
|
||||||
int userLimit,
|
int userLimit,
|
||||||
int perPartitionLimit,
|
int perPartitionLimit,
|
||||||
int pageSize,
|
int pageSize,
|
||||||
AggregationSpecification aggregationSpec)
|
AggregationSpecification aggregationSpec,
|
||||||
|
PotentialTxnConflicts potentialTxnConflicts)
|
||||||
{
|
{
|
||||||
DataLimits limit = getDataLimits(userLimit, perPartitionLimit, pageSize, aggregationSpec);
|
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,
|
public ReadQuery getQuery(QueryOptions options,
|
||||||
ClientState state,
|
ClientState state,
|
||||||
ColumnFilter columnFilter,
|
ColumnFilter columnFilter,
|
||||||
long nowInSec,
|
long nowInSec,
|
||||||
DataLimits limit)
|
DataLimits limit,
|
||||||
|
PotentialTxnConflicts potentialTxnConflicts)
|
||||||
{
|
{
|
||||||
RowFilter rowFilter = getRowFilter(options, state);
|
RowFilter rowFilter = getRowFilter(options, state);
|
||||||
|
|
||||||
|
|
@ -467,13 +471,13 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
|
||||||
if (restrictions.usesSecondaryIndexing() && !SchemaConstants.isLocalSystemKeyspace(table.keyspace))
|
if (restrictions.usesSecondaryIndexing() && !SchemaConstants.isLocalSystemKeyspace(table.keyspace))
|
||||||
Guardrails.nonPartitionRestrictedIndexQueryEnabled.ensureEnabled(state);
|
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())
|
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,
|
private ResultMessage.Rows execute(ReadQuery query,
|
||||||
|
|
@ -657,7 +661,8 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
|
||||||
userLimit,
|
userLimit,
|
||||||
userPerPartitionLimit,
|
userPerPartitionLimit,
|
||||||
pageSize,
|
pageSize,
|
||||||
aggregationSpec);
|
aggregationSpec,
|
||||||
|
PotentialTxnConflicts.ALLOW);
|
||||||
|
|
||||||
try (ReadExecutionController executionController = query.executionController())
|
try (ReadExecutionController executionController = query.executionController())
|
||||||
{
|
{
|
||||||
|
|
@ -704,7 +709,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
|
|
||||||
Selectors selectors = selection.newSelectors(options);
|
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<DecoratedKey, List<Row>> result = Collections.emptyMap();
|
Map<DecoratedKey, List<Row>> result = Collections.emptyMap();
|
||||||
try (ReadExecutionController executionController = query.executionController())
|
try (ReadExecutionController executionController = query.executionController())
|
||||||
|
|
@ -778,7 +783,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReadQuery getSliceCommands(QueryOptions options, ClientState state, ColumnFilter columnFilter,
|
private ReadQuery getSliceCommands(QueryOptions options, ClientState state, ColumnFilter columnFilter,
|
||||||
RowFilter rowFilter, DataLimits limit, long nowInSec)
|
RowFilter rowFilter, DataLimits limit, long nowInSec, PotentialTxnConflicts potentialTxnConflicts)
|
||||||
{
|
{
|
||||||
Collection<ByteBuffer> keys = restrictions.getPartitionKeys(options, state);
|
Collection<ByteBuffer> keys = restrictions.getPartitionKeys(options, state);
|
||||||
if (keys.isEmpty())
|
if (keys.isEmpty())
|
||||||
|
|
@ -801,7 +806,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
|
||||||
}
|
}
|
||||||
|
|
||||||
SinglePartitionReadQuery.Group<? extends SinglePartitionReadQuery> group =
|
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.
|
// If there's a secondary index that the commands can use, have it validate the request parameters.
|
||||||
group.maybeValidateIndex();
|
group.maybeValidateIndex();
|
||||||
|
|
@ -855,7 +860,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReadQuery getRangeCommand(QueryOptions options, ClientState state, ColumnFilter columnFilter,
|
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);
|
ClusteringIndexFilter clusteringIndexFilter = makeClusteringIndexFilter(options, state, columnFilter);
|
||||||
if (clusteringIndexFilter == null)
|
if (clusteringIndexFilter == null)
|
||||||
|
|
@ -868,7 +873,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
|
||||||
return ReadQuery.empty(table);
|
return ReadQuery.empty(table);
|
||||||
|
|
||||||
ReadQuery command =
|
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.
|
// If there's a secondary index that the command can use, have it validate the request parameters.
|
||||||
command.maybeValidateIndex();
|
command.maybeValidateIndex();
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import org.apache.cassandra.db.CounterMutation;
|
||||||
import org.apache.cassandra.db.DecoratedKey;
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
import org.apache.cassandra.db.IMutation;
|
import org.apache.cassandra.db.IMutation;
|
||||||
import org.apache.cassandra.db.Mutation;
|
import org.apache.cassandra.db.Mutation;
|
||||||
|
import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
|
||||||
import org.apache.cassandra.db.RegularAndStaticColumns;
|
import org.apache.cassandra.db.RegularAndStaticColumns;
|
||||||
import org.apache.cassandra.db.commitlog.CommitLogSegment;
|
import org.apache.cassandra.db.commitlog.CommitLogSegment;
|
||||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||||
|
|
@ -78,16 +79,16 @@ final class SingleTableSinglePartitionUpdatesCollector implements UpdatesCollect
|
||||||
* Returns a collection containing all the mutations.
|
* Returns a collection containing all the mutations.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<IMutation> toMutations(ClientState state, boolean allowPotentialTxnConflicts)
|
public List<IMutation> toMutations(ClientState state, PotentialTxnConflicts potentialTxnConflicts)
|
||||||
{
|
{
|
||||||
// it is possible that a modification statement does not create any mutations
|
// 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
|
// for example: DELETE FROM some_table WHERE part_key = 1 AND clust_key < 3 AND clust_key > 5
|
||||||
if (builder == null)
|
if (builder == null)
|
||||||
return Collections.emptyList();
|
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;
|
IMutation mutation;
|
||||||
|
|
||||||
|
|
@ -96,7 +97,7 @@ final class SingleTableSinglePartitionUpdatesCollector implements UpdatesCollect
|
||||||
else if (metadata.isCounter())
|
else if (metadata.isCounter())
|
||||||
mutation = new CounterMutation(new Mutation(builder.build()), counterConsistencyLevel);
|
mutation = new CounterMutation(new Mutation(builder.build()), counterConsistencyLevel);
|
||||||
else
|
else
|
||||||
mutation = new Mutation(builder.build(), allowPotentialTxnConflicts);
|
mutation = new Mutation(builder.build(), potentialTxnConflicts);
|
||||||
|
|
||||||
mutation.validateIndexedColumns(state);
|
mutation.validateIndexedColumns(state);
|
||||||
mutation.validateSize(MessagingService.current_version, CommitLogSegment.ENTRY_OVERHEAD_SIZE);
|
mutation.validateSize(MessagingService.current_version, CommitLogSegment.ENTRY_OVERHEAD_SIZE);
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import org.apache.cassandra.db.CounterMutation;
|
||||||
import org.apache.cassandra.db.DecoratedKey;
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
import org.apache.cassandra.db.IMutation;
|
import org.apache.cassandra.db.IMutation;
|
||||||
import org.apache.cassandra.db.Mutation;
|
import org.apache.cassandra.db.Mutation;
|
||||||
|
import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
|
||||||
import org.apache.cassandra.db.RegularAndStaticColumns;
|
import org.apache.cassandra.db.RegularAndStaticColumns;
|
||||||
import org.apache.cassandra.db.commitlog.CommitLogSegment;
|
import org.apache.cassandra.db.commitlog.CommitLogSegment;
|
||||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||||
|
|
@ -95,24 +96,24 @@ final class SingleTableUpdatesCollector implements UpdatesCollector
|
||||||
* @return a collection containing all the mutations.
|
* @return a collection containing all the mutations.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<IMutation> toMutations(ClientState state, boolean allowPotentialTxnConflicts)
|
public List<IMutation> toMutations(ClientState state, PotentialTxnConflicts potentialTxnConflicts)
|
||||||
{
|
{
|
||||||
if (puBuilders.size() == 1)
|
if (puBuilders.size() == 1)
|
||||||
{
|
{
|
||||||
PartitionUpdate.Builder builder = puBuilders.values().iterator().next();
|
PartitionUpdate.Builder builder = puBuilders.values().iterator().next();
|
||||||
return Collections.singletonList(createMutation(state, builder, allowPotentialTxnConflicts));
|
return Collections.singletonList(createMutation(state, builder, potentialTxnConflicts));
|
||||||
}
|
}
|
||||||
List<IMutation> ms = new ArrayList<>(puBuilders.size());
|
List<IMutation> ms = new ArrayList<>(puBuilders.size());
|
||||||
for (PartitionUpdate.Builder builder : puBuilders.values())
|
for (PartitionUpdate.Builder builder : puBuilders.values())
|
||||||
{
|
{
|
||||||
IMutation mutation = createMutation(state, builder, allowPotentialTxnConflicts);
|
IMutation mutation = createMutation(state, builder, potentialTxnConflicts);
|
||||||
ms.add(mutation);
|
ms.add(mutation);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ms;
|
return ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IMutation createMutation(ClientState state, PartitionUpdate.Builder builder, boolean allowPotentialTxnConflicts)
|
private IMutation createMutation(ClientState state, PartitionUpdate.Builder builder, PotentialTxnConflicts potentialTxnConflicts)
|
||||||
{
|
{
|
||||||
IMutation mutation;
|
IMutation mutation;
|
||||||
|
|
||||||
|
|
@ -121,7 +122,7 @@ final class SingleTableUpdatesCollector implements UpdatesCollector
|
||||||
else if (metadata.isCounter())
|
else if (metadata.isCounter())
|
||||||
mutation = new CounterMutation(new Mutation(builder.build()), counterConsistencyLevel);
|
mutation = new CounterMutation(new Mutation(builder.build()), counterConsistencyLevel);
|
||||||
else
|
else
|
||||||
mutation = new Mutation(builder.build(), allowPotentialTxnConflicts);
|
mutation = new Mutation(builder.build(), potentialTxnConflicts);
|
||||||
|
|
||||||
mutation.validateIndexedColumns(state);
|
mutation.validateIndexedColumns(state);
|
||||||
mutation.validateSize(MessagingService.current_version, CommitLogSegment.ENTRY_OVERHEAD_SIZE);
|
mutation.validateSize(MessagingService.current_version, CommitLogSegment.ENTRY_OVERHEAD_SIZE);
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
import accord.api.Key;
|
import accord.api.Key;
|
||||||
import accord.primitives.Keys;
|
import accord.primitives.Keys;
|
||||||
|
import accord.primitives.Routable.Domain;
|
||||||
import accord.primitives.Txn;
|
import accord.primitives.Txn;
|
||||||
import org.agrona.collections.Int2ObjectHashMap;
|
import org.agrona.collections.Int2ObjectHashMap;
|
||||||
import org.apache.cassandra.audit.AuditLogContext;
|
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.ReferenceOperation;
|
||||||
import org.apache.cassandra.cql3.transactions.RowDataReference;
|
import org.apache.cassandra.cql3.transactions.RowDataReference;
|
||||||
import org.apache.cassandra.cql3.transactions.SelectReferenceSource;
|
import org.apache.cassandra.cql3.transactions.SelectReferenceSource;
|
||||||
|
import org.apache.cassandra.db.ConsistencyLevel;
|
||||||
import org.apache.cassandra.db.SinglePartitionReadCommand;
|
import org.apache.cassandra.db.SinglePartitionReadCommand;
|
||||||
import org.apache.cassandra.db.SinglePartitionReadQuery;
|
import org.apache.cassandra.db.SinglePartitionReadQuery;
|
||||||
import org.apache.cassandra.db.filter.DataLimits;
|
import org.apache.cassandra.db.filter.DataLimits;
|
||||||
import org.apache.cassandra.db.marshal.AbstractType;
|
import org.apache.cassandra.db.marshal.AbstractType;
|
||||||
|
import org.apache.cassandra.dht.Token;
|
||||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||||
import org.apache.cassandra.schema.ColumnMetadata;
|
import org.apache.cassandra.schema.ColumnMetadata;
|
||||||
import org.apache.cassandra.schema.Schema;
|
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.ClientState;
|
||||||
import org.apache.cassandra.service.QueryState;
|
import org.apache.cassandra.service.QueryState;
|
||||||
import org.apache.cassandra.service.accord.AccordService;
|
import org.apache.cassandra.service.accord.AccordService;
|
||||||
import org.apache.cassandra.service.accord.api.AccordRoutableKey;
|
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.AccordUpdate;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnCondition;
|
import org.apache.cassandra.service.accord.txn.TxnCondition;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnData;
|
import org.apache.cassandra.service.accord.txn.TxnData;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnDataKeyValue;
|
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.TxnNamedRead;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnQuery;
|
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.TxnReference;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnResult;
|
import org.apache.cassandra.service.accord.txn.TxnResult;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnUpdate;
|
import org.apache.cassandra.service.accord.txn.TxnUpdate;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnWrite;
|
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.tcm.Epoch;
|
||||||
import org.apache.cassandra.transport.Dispatcher;
|
import org.apache.cassandra.transport.Dispatcher;
|
||||||
import org.apache.cassandra.transport.messages.ResultMessage;
|
import org.apache.cassandra.transport.messages.ResultMessage;
|
||||||
import org.apache.cassandra.utils.FBUtilities;
|
import org.apache.cassandra.utils.FBUtilities;
|
||||||
|
|
||||||
import static accord.primitives.Txn.Kind.Read;
|
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.checkFalse;
|
||||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
|
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
|
||||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
|
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.RETURNING;
|
||||||
import static org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind.USER;
|
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.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.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;
|
import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.shouldReadEphemerally;
|
||||||
|
|
||||||
public class TransactionStatement implements CQLStatement.CompositeCQLStatement, CQLStatement.ReturningCQLStatement
|
public class TransactionStatement implements CQLStatement.CompositeCQLStatement, CQLStatement.ReturningCQLStatement
|
||||||
|
|
@ -261,8 +272,8 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
|
||||||
for (NamedSelect select : assignments)
|
for (NamedSelect select : assignments)
|
||||||
{
|
{
|
||||||
TxnNamedRead read = createNamedRead(select, options, state);
|
TxnNamedRead read = createNamedRead(select, options, state);
|
||||||
|
keyConsumer.accept((Key)read.key());
|
||||||
minEpoch = Math.max(minEpoch, read.command().metadata().epoch.getEpoch());
|
minEpoch = Math.max(minEpoch, read.command().metadata().epoch.getEpoch());
|
||||||
keyConsumer.accept(read.key());
|
|
||||||
reads.add(read);
|
reads.add(read);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,7 +281,7 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
|
||||||
{
|
{
|
||||||
for (TxnNamedRead read : createNamedReads(returningSelect, options, state))
|
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());
|
minEpoch = Math.max(minEpoch, read.command().metadata().epoch.getEpoch());
|
||||||
reads.add(read);
|
reads.add(read);
|
||||||
}
|
}
|
||||||
|
|
@ -279,8 +290,11 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
|
||||||
if (autoReads != null)
|
if (autoReads != null)
|
||||||
{
|
{
|
||||||
for (NamedSelect select : autoReads.values())
|
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;
|
return reads;
|
||||||
|
|
@ -301,15 +315,15 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
|
||||||
return new TxnCondition.BooleanGroup(TxnCondition.Kind.AND, result);
|
return new TxnCondition.BooleanGroup(TxnCondition.Kind.AND, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<TxnWrite.Fragment> createWriteFragments(ClientState state, QueryOptions options, Map<Integer, NamedSelect> autoReads, Consumer<Key> keyConsumer)
|
List<TxnWrite.Fragment> createWriteFragments(ClientState state, QueryOptions options, Map<Integer, NamedSelect> autoReads, Set<Key> keys)
|
||||||
{
|
{
|
||||||
List<TxnWrite.Fragment> fragments = new ArrayList<>(updates.size());
|
List<TxnWrite.Fragment> fragments = new ArrayList<>(updates.size());
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
for (ModificationStatement modification : updates)
|
for (ModificationStatement modification : updates)
|
||||||
{
|
{
|
||||||
TxnWrite.Fragment fragment = modification.getTxnWriteFragment(idx, state, options);
|
TxnWrite.Fragment fragment = modification.getTxnWriteFragment(idx, state, options);
|
||||||
|
keys.add(fragment.key);
|
||||||
minEpoch = Math.max(minEpoch, fragment.baseUpdate.metadata().epoch.getEpoch());
|
minEpoch = Math.max(minEpoch, fragment.baseUpdate.metadata().epoch.getEpoch());
|
||||||
keyConsumer.accept(fragment.key);
|
|
||||||
fragments.add(fragment);
|
fragments.add(fragment);
|
||||||
|
|
||||||
if (modification.allReferenceOperations().stream().anyMatch(ReferenceOperation::requiresRead))
|
if (modification.allReferenceOperations().stream().anyMatch(ReferenceOperation::requiresRead))
|
||||||
|
|
@ -325,9 +339,12 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
|
||||||
return fragments;
|
return fragments;
|
||||||
}
|
}
|
||||||
|
|
||||||
AccordUpdate createUpdate(ClientState state, QueryOptions options, Map<Integer, NamedSelect> autoReads, Consumer<Key> keyConsumer)
|
AccordUpdate createUpdate(ClusterMetadata cm, ClientState state, QueryOptions options, Map<Integer, NamedSelect> autoReads, Set<Key> 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<TxnWrite.Fragment> writeFragments = createWriteFragments(state, options, autoReads, keys);
|
||||||
|
ConsistencyLevel commitCL = consistencyLevelForAccordCommit(cm, keys, options.getConsistency());
|
||||||
|
return new TxnUpdate(writeFragments, createCondition(options), commitCL, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys toKeys(SortedSet<Key> keySet)
|
Keys toKeys(SortedSet<Key> keySet)
|
||||||
|
|
@ -335,10 +352,90 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
|
||||||
return new Keys(keySet);
|
return new Keys(keySet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ConsistencyLevel consistencyLevelForAccordRead(ClusterMetadata cm, Set<Key> 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<Key> 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
|
@VisibleForTesting
|
||||||
public Txn createTxn(ClientState state, QueryOptions options)
|
public Txn createTxn(ClientState state, QueryOptions options)
|
||||||
{
|
{
|
||||||
SortedSet<Key> keySet = new TreeSet<>();
|
SortedSet<Key> keySet = new TreeSet<>();
|
||||||
|
ClusterMetadata cm = ClusterMetadata.current();
|
||||||
|
|
||||||
if (updates.isEmpty())
|
if (updates.isEmpty())
|
||||||
{
|
{
|
||||||
|
|
@ -346,18 +443,17 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
|
||||||
Preconditions.checkState(conditions.isEmpty(), "No condition should exist without updates present");
|
Preconditions.checkState(conditions.isEmpty(), "No condition should exist without updates present");
|
||||||
List<TxnNamedRead> reads = createNamedReads(options, state, null, keySet::add);
|
List<TxnNamedRead> reads = createNamedReads(options, state, null, keySet::add);
|
||||||
Keys txnKeys = toKeys(keySet);
|
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);
|
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);
|
return new Txn.InMemory(kind, txnKeys, read, TxnQuery.ALL, null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Int2ObjectHashMap<NamedSelect> autoReads = new Int2ObjectHashMap<>();
|
Int2ObjectHashMap<NamedSelect> autoReads = new Int2ObjectHashMap<>();
|
||||||
AccordUpdate update = createUpdate(state, options, autoReads, keySet::add);
|
AccordUpdate update = createUpdate(cm, state, options, autoReads, keySet);
|
||||||
List<TxnNamedRead> reads = createNamedReads(options, state, autoReads, keySet::add);
|
List<TxnNamedRead> reads = createNamedReads(options, state, autoReads, keySet::add);
|
||||||
Keys txnKeys = toKeys(keySet);
|
TxnRead read = createTxnRead(reads, null, Domain.Key);
|
||||||
TxnKeyRead read = createTxnRead(reads, null);
|
return new Txn.InMemory(toKeys(keySet), read, TxnQuery.ALL, update);
|
||||||
return new Txn.InMemory(txnKeys, read, TxnQuery.ALL, update);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import java.util.List;
|
||||||
import org.apache.cassandra.db.ConsistencyLevel;
|
import org.apache.cassandra.db.ConsistencyLevel;
|
||||||
import org.apache.cassandra.db.DecoratedKey;
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
import org.apache.cassandra.db.IMutation;
|
import org.apache.cassandra.db.IMutation;
|
||||||
|
import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
|
||||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||||
import org.apache.cassandra.schema.TableMetadata;
|
import org.apache.cassandra.schema.TableMetadata;
|
||||||
import org.apache.cassandra.service.ClientState;
|
import org.apache.cassandra.service.ClientState;
|
||||||
|
|
@ -30,5 +31,5 @@ import org.apache.cassandra.service.ClientState;
|
||||||
public interface UpdatesCollector
|
public interface UpdatesCollector
|
||||||
{
|
{
|
||||||
PartitionUpdate.Builder getPartitionUpdateBuilder(TableMetadata metadata, DecoratedKey dk, ConsistencyLevel consistency);
|
PartitionUpdate.Builder getPartitionUpdateBuilder(TableMetadata metadata, DecoratedKey dk, ConsistencyLevel consistency);
|
||||||
List<IMutation> toMutations(ClientState state, boolean allowPotentialTxnConflicts);
|
List<IMutation> toMutations(ClientState state, PotentialTxnConflicts allowPotentialTxnConflicts);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -595,7 +595,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
|
||||||
MemtableParams.get(attrs.getString(TableParams.Option.MEMTABLE.toString()));
|
MemtableParams.get(attrs.getString(TableParams.Option.MEMTABLE.toString()));
|
||||||
Guardrails.tableProperties.guard(attrs.updatedProperties(), attrs::removeProperty, state);
|
Guardrails.tableProperties.guard(attrs.updatedProperties(), attrs::removeProperty, state);
|
||||||
|
|
||||||
validateDefaultTimeToLive(attrs.asNewTableParams());
|
validateDefaultTimeToLive(attrs.asNewTableParams(keyspaceName));
|
||||||
}
|
}
|
||||||
|
|
||||||
private TableParams validateAndUpdateTransactionalMigration(boolean isCounter, TableParams prev, TableParams next)
|
private TableParams validateAndUpdateTransactionalMigration(boolean isCounter, TableParams prev, TableParams next)
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,7 @@ public final class CopyTableStatement extends AlterSchemaStatement
|
||||||
.sum();
|
.sum();
|
||||||
Guardrails.tables.guard(totalUserTables + 1, targetTableName, false, state);
|
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)
|
private void maybeCopyIndexes(TableMetadata.Builder builder, TableMetadata sourceTableMeta, KeyspaceMetadata targetKeyspaceMeta)
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,20 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.cassandra.cql3.statements.schema;
|
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 java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import org.apache.cassandra.audit.AuditLogContext;
|
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.IResource;
|
||||||
import org.apache.cassandra.auth.Permission;
|
import org.apache.cassandra.auth.Permission;
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
import org.apache.cassandra.cql3.*;
|
|
||||||
import org.apache.cassandra.cql3.constraints.ColumnConstraints;
|
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.cql3.functions.masking.ColumnMask;
|
||||||
import org.apache.cassandra.db.guardrails.Guardrails;
|
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.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.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.service.ClientState;
|
||||||
import org.apache.cassandra.tcm.ClusterMetadata;
|
|
||||||
import org.apache.cassandra.service.reads.repair.ReadRepairStrategy;
|
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;
|
||||||
import org.apache.cassandra.transport.Event.SchemaChange.Change;
|
import org.apache.cassandra.transport.Event.SchemaChange.Change;
|
||||||
import org.apache.cassandra.transport.Event.SchemaChange.Target;
|
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.lang.String.format;
|
||||||
import static java.util.Comparator.comparing;
|
import static java.util.Comparator.comparing;
|
||||||
|
|
||||||
import static com.google.common.collect.Iterables.concat;
|
|
||||||
|
|
||||||
public final class CreateTableStatement extends AlterSchemaStatement
|
public final class CreateTableStatement extends AlterSchemaStatement
|
||||||
{
|
{
|
||||||
private final String tableName;
|
private final String tableName;
|
||||||
|
|
@ -189,7 +215,7 @@ public final class CreateTableStatement extends AlterSchemaStatement
|
||||||
if (useCompactStorage)
|
if (useCompactStorage)
|
||||||
Guardrails.compactTablesEnabled.ensureEnabled(state);
|
Guardrails.compactTablesEnabled.ensureEnabled(state);
|
||||||
|
|
||||||
validateDefaultTimeToLive(attrs.asNewTableParams());
|
validateDefaultTimeToLive(attrs.asNewTableParams(keyspaceName));
|
||||||
|
|
||||||
rawColumns.forEach((name, raw) -> raw.validate(state, name));
|
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)
|
public TableMetadata.Builder builder(Types types, UserFunctions functions)
|
||||||
{
|
{
|
||||||
attrs.validate();
|
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
|
// use a TreeMap to preserve ordering across JDK versions (see CASSANDRA-9492) - important for stable unit tests
|
||||||
Map<ColumnIdentifier, ColumnProperties> columns = new TreeMap<>(comparing(o -> o.bytes));
|
Map<ColumnIdentifier, ColumnProperties> columns = new TreeMap<>(comparing(o -> o.bytes));
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,12 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.cassandra.cql3.statements.schema;
|
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.ImmutableSet;
|
||||||
import com.google.common.collect.Iterables;
|
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.audit.AuditLogEntryType;
|
||||||
import org.apache.cassandra.auth.Permission;
|
import org.apache.cassandra.auth.Permission;
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
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.restrictions.StatementRestrictions;
|
||||||
import org.apache.cassandra.cql3.selection.RawSelector;
|
import org.apache.cassandra.cql3.selection.RawSelector;
|
||||||
import org.apache.cassandra.cql3.selection.Selectable;
|
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.db.view.View;
|
||||||
import org.apache.cassandra.exceptions.AlreadyExistsException;
|
import org.apache.cassandra.exceptions.AlreadyExistsException;
|
||||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
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.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.service.ClientState;
|
||||||
import org.apache.cassandra.tcm.ClusterMetadata;
|
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||||
import org.apache.cassandra.transport.Event.SchemaChange;
|
import org.apache.cassandra.transport.Event.SchemaChange;
|
||||||
import org.apache.cassandra.transport.Event.SchemaChange.Change;
|
import org.apache.cassandra.transport.Event.SchemaChange.Change;
|
||||||
import org.apache.cassandra.transport.Event.SchemaChange.Target;
|
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.concat;
|
||||||
import static com.google.common.collect.Iterables.filter;
|
import static com.google.common.collect.Iterables.filter;
|
||||||
import static com.google.common.collect.Iterables.transform;
|
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;
|
import static org.apache.cassandra.config.CassandraRelevantProperties.MV_ALLOW_FILTERING_NONKEY_COLUMNS_UNSAFE;
|
||||||
|
|
||||||
public final class CreateViewStatement extends AlterSchemaStatement
|
public final class CreateViewStatement extends AlterSchemaStatement
|
||||||
|
|
@ -334,7 +348,7 @@ public final class CreateViewStatement extends AlterSchemaStatement
|
||||||
else if (!builder.hasId())
|
else if (!builder.hasId())
|
||||||
builder.id(TableId.get(metadata));
|
builder.id(TableId.get(metadata));
|
||||||
|
|
||||||
builder.params(attrs.asNewTableParams())
|
builder.params(attrs.asNewTableParams(keyspaceName))
|
||||||
.kind(TableMetadata.Kind.VIEW);
|
.kind(TableMetadata.Kind.VIEW);
|
||||||
|
|
||||||
partitionKeyColumns.stream()
|
partitionKeyColumns.stream()
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ import org.apache.cassandra.schema.CachingParams;
|
||||||
import org.apache.cassandra.schema.CompactionParams;
|
import org.apache.cassandra.schema.CompactionParams;
|
||||||
import org.apache.cassandra.schema.CompressionParams;
|
import org.apache.cassandra.schema.CompressionParams;
|
||||||
import org.apache.cassandra.schema.MemtableParams;
|
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.TableId;
|
||||||
import org.apache.cassandra.schema.TableParams;
|
import org.apache.cassandra.schema.TableParams;
|
||||||
import org.apache.cassandra.schema.TableParams.Option;
|
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 org.apache.cassandra.service.reads.repair.ReadRepairStrategy;
|
||||||
|
|
||||||
import static java.lang.String.format;
|
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
|
public final class TableAttributes extends PropertyDefinitions
|
||||||
{
|
{
|
||||||
|
|
@ -65,10 +84,10 @@ public final class TableAttributes extends PropertyDefinitions
|
||||||
build(TableParams.builder()).validate();
|
build(TableParams.builder()).validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
TableParams asNewTableParams()
|
TableParams asNewTableParams(String keyspaceName)
|
||||||
{
|
{
|
||||||
TableParams.Builder builder = TableParams.builder();
|
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());
|
builder.transactionalMode(DatabaseDescriptor.defaultTransactionalMode());
|
||||||
return build(builder);
|
return build(builder);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import com.google.common.collect.PeekingIterator;
|
||||||
import com.google.common.util.concurrent.Striped;
|
import com.google.common.util.concurrent.Striped;
|
||||||
|
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
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.context.CounterContext;
|
||||||
import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter;
|
import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter;
|
||||||
import org.apache.cassandra.db.filter.ColumnFilter;
|
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
|
* anyways and it's safe to continue non-transactionally updating them
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean allowsPotentialTransactionConflicts()
|
public PotentialTxnConflicts potentialTxnConflicts()
|
||||||
{
|
{
|
||||||
return true;
|
return PotentialTxnConflicts.ALLOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void grabCounterLocks(Keyspace keyspace, List<Lock> locks) throws WriteTimeoutException
|
private void grabCounterLocks(Keyspace keyspace, List<Lock> locks) throws WriteTimeoutException
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import java.util.function.Supplier;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
|
import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
|
||||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||||
import org.apache.cassandra.schema.TableId;
|
import org.apache.cassandra.schema.TableId;
|
||||||
import org.apache.cassandra.service.ClientState;
|
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.
|
* 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
|
// Construct replacement mutation that is identical except it only includes updates for the specified tables
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ import java.util.Iterator;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
|
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
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.ObjectSizes;
|
||||||
import org.apache.cassandra.utils.memory.ByteBufferCloner;
|
import org.apache.cassandra.utils.memory.ByteBufferCloner;
|
||||||
|
|
||||||
|
|
@ -230,7 +231,7 @@ public class MutableDeletionInfo implements DeletionInfo
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeletionInfo updateAllTimestampAndLocalDeletionTime(long timestamp, int localDeletionTime)
|
public DeletionInfo updateAllTimestampAndLocalDeletionTime(long timestamp, long localDeletionTime)
|
||||||
{
|
{
|
||||||
if (partitionDeletion.markedForDeleteAt() != Long.MIN_VALUE)
|
if (partitionDeletion.markedForDeleteAt() != Long.MIN_VALUE)
|
||||||
partitionDeletion = DeletionTime.build(timestamp, localDeletionTime);
|
partitionDeletion = DeletionTime.build(timestamp, localDeletionTime);
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.cassandra.concurrent.Stage;
|
import org.apache.cassandra.concurrent.Stage;
|
||||||
import org.apache.cassandra.config.CassandraRelevantProperties;
|
import org.apache.cassandra.config.CassandraRelevantProperties;
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
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.commitlog.CommitLog;
|
||||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||||
import org.apache.cassandra.db.rows.DeserializationHelper;
|
import org.apache.cassandra.db.rows.DeserializationHelper;
|
||||||
|
|
@ -98,31 +99,31 @@ public class Mutation implements IMutation, Supplier<Mutation>
|
||||||
// that are only safe to write to from a transaction system.
|
// 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
|
// 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
|
// 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)
|
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<TableId, PartitionUpdate> modifications, long approxCreatedAtNanos, boolean allowPotentialTransactionConflicts)
|
public Mutation(String keyspaceName, DecoratedKey key, ImmutableMap<TableId, PartitionUpdate> 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<TableId, PartitionUpdate> modifications, long approxCreatedAtNanos, boolean cdcEnabled, boolean allowPotentialTransactionConflicts)
|
public Mutation(String keyspaceName, DecoratedKey key, ImmutableMap<TableId, PartitionUpdate> modifications, long approxCreatedAtNanos, boolean cdcEnabled, PotentialTxnConflicts potentialTxnConflicts)
|
||||||
{
|
{
|
||||||
this.keyspaceName = keyspaceName;
|
this.keyspaceName = keyspaceName;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.modifications = modifications;
|
this.modifications = modifications;
|
||||||
this.cdcEnabled = cdcEnabled;
|
this.cdcEnabled = cdcEnabled;
|
||||||
this.approxCreatedAtNanos = approxCreatedAtNanos;
|
this.approxCreatedAtNanos = approxCreatedAtNanos;
|
||||||
this.allowPotentialTransactionConflicts = allowPotentialTransactionConflicts;
|
this.potentialTxnConflicts = potentialTxnConflicts;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean cdcEnabled(Iterable<PartitionUpdate> modifications)
|
private static boolean cdcEnabled(Iterable<PartitionUpdate> modifications)
|
||||||
|
|
@ -156,7 +157,7 @@ public class Mutation implements IMutation, Supplier<Mutation>
|
||||||
|
|
||||||
Map<TableId, PartitionUpdate> updates = builder.build();
|
Map<TableId, PartitionUpdate> updates = builder.build();
|
||||||
checkState(!updates.isEmpty(), "Updates should not be empty");
|
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)
|
public @Nullable Mutation without(TableId tableId)
|
||||||
|
|
@ -248,12 +249,12 @@ public class Mutation implements IMutation, Supplier<Mutation>
|
||||||
Set<TableId> updatedTables = new HashSet<>();
|
Set<TableId> updatedTables = new HashSet<>();
|
||||||
String ks = null;
|
String ks = null;
|
||||||
DecoratedKey key = null;
|
DecoratedKey key = null;
|
||||||
Boolean allowPotentialTransactionConflicts = null;
|
PotentialTxnConflicts potentialTxnConflicts = null;
|
||||||
for (Mutation mutation : mutations)
|
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");
|
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());
|
updatedTables.addAll(mutation.modifications.keySet());
|
||||||
if (ks != null && !ks.equals(mutation.keyspaceName))
|
if (ks != null && !ks.equals(mutation.keyspaceName))
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
|
|
@ -280,7 +281,7 @@ public class Mutation implements IMutation, Supplier<Mutation>
|
||||||
modifications.put(table, updates.size() == 1 ? updates.get(0) : PartitionUpdate.merge(updates));
|
modifications.put(table, updates.size() == 1 ? updates.get(0) : PartitionUpdate.merge(updates));
|
||||||
updates.clear();
|
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()
|
public Future<?> applyFuture()
|
||||||
|
|
@ -339,24 +340,24 @@ public class Mutation implements IMutation, Supplier<Mutation>
|
||||||
|
|
||||||
public void allowPotentialTransactionConflicts()
|
public void allowPotentialTransactionConflicts()
|
||||||
{
|
{
|
||||||
allowPotentialTransactionConflicts = true;
|
potentialTxnConflicts = PotentialTxnConflicts.ALLOW;
|
||||||
Arrays.fill(cachedSerializations, null);
|
Arrays.fill(cachedSerializations, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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()
|
public String toString()
|
||||||
|
|
@ -443,7 +444,7 @@ public class Mutation implements IMutation, Supplier<Mutation>
|
||||||
* being handled by the caller
|
* being handled by the caller
|
||||||
* @return this builder
|
* @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.
|
* 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<Mutation>
|
||||||
if (version >= VERSION_51)
|
if (version >= VERSION_51)
|
||||||
{
|
{
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
flags |= allowPotentialTransactionConflictsFlag(mutation.allowPotentialTransactionConflicts);
|
flags |= potentialTxnConflictsFlag(mutation.potentialTxnConflicts);
|
||||||
out.write(flags);
|
out.write(flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -583,11 +584,11 @@ public class Mutation implements IMutation, Supplier<Mutation>
|
||||||
{
|
{
|
||||||
teeIn = new TeeDataInputPlus(in, dob, CACHEABLE_MUTATION_SIZE_LIMIT);
|
teeIn = new TeeDataInputPlus(in, dob, CACHEABLE_MUTATION_SIZE_LIMIT);
|
||||||
|
|
||||||
boolean allowPotentialTransactionConflicts = false;
|
PotentialTxnConflicts potentialTxnConflicts = PotentialTxnConflicts.DISALLOW;
|
||||||
if (version >= VERSION_51)
|
if (version >= VERSION_51)
|
||||||
{
|
{
|
||||||
int flags = teeIn.readByte();
|
int flags = teeIn.readByte();
|
||||||
allowPotentialTransactionConflicts = allowPotentialTransactionConflicts(flags);
|
potentialTxnConflicts = potentialTxnConflicts(flags);
|
||||||
}
|
}
|
||||||
int size = teeIn.readUnsignedVInt32();
|
int size = teeIn.readUnsignedVInt32();
|
||||||
assert size > 0;
|
assert size > 0;
|
||||||
|
|
@ -595,7 +596,7 @@ public class Mutation implements IMutation, Supplier<Mutation>
|
||||||
PartitionUpdate update = PartitionUpdate.serializer.deserialize(teeIn, version, flag);
|
PartitionUpdate update = PartitionUpdate.serializer.deserialize(teeIn, version, flag);
|
||||||
if (size == 1)
|
if (size == 1)
|
||||||
{
|
{
|
||||||
m = new Mutation(update, allowPotentialTransactionConflicts);
|
m = new Mutation(update, potentialTxnConflicts);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -608,7 +609,7 @@ public class Mutation implements IMutation, Supplier<Mutation>
|
||||||
update = PartitionUpdate.serializer.deserialize(teeIn, version, flag);
|
update = PartitionUpdate.serializer.deserialize(teeIn, version, flag);
|
||||||
modifications.put(update.metadata().id, update);
|
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
|
//Only cache serializations that don't hit the limit
|
||||||
|
|
@ -708,18 +709,18 @@ public class Mutation implements IMutation, Supplier<Mutation>
|
||||||
private final long approxCreatedAtNanos = approxTime.now();
|
private final long approxCreatedAtNanos = approxTime.now();
|
||||||
private boolean empty = true;
|
private boolean empty = true;
|
||||||
|
|
||||||
private boolean allowPotentialTransactionConflicts;
|
private PotentialTxnConflicts potentialTxnConflicts;
|
||||||
|
|
||||||
public PartitionUpdateCollector(String keyspaceName, DecoratedKey key)
|
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.keyspaceName = keyspaceName;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.allowPotentialTransactionConflicts = allowPotentialTransactionConflicts;
|
this.potentialTxnConflicts = potentialTxnConflicts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PartitionUpdateCollector add(PartitionUpdate partitionUpdate)
|
public PartitionUpdateCollector add(PartitionUpdate partitionUpdate)
|
||||||
|
|
@ -753,7 +754,7 @@ public class Mutation implements IMutation, Supplier<Mutation>
|
||||||
|
|
||||||
public Mutation build()
|
public Mutation build()
|
||||||
{
|
{
|
||||||
return new Mutation(keyspaceName, key, modifications.build(), approxCreatedAtNanos, allowPotentialTransactionConflicts);
|
return new Mutation(keyspaceName, key, modifications.build(), approxCreatedAtNanos, potentialTxnConflicts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,10 @@ package org.apache.cassandra.db;
|
||||||
|
|
||||||
import org.apache.cassandra.exceptions.WriteTimeoutException;
|
import org.apache.cassandra.exceptions.WriteTimeoutException;
|
||||||
import org.apache.cassandra.locator.InetAddressAndPort;
|
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 org.apache.cassandra.tracing.Tracing;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
boolean isDigest,
|
boolean isDigest,
|
||||||
int digestVersion,
|
int digestVersion,
|
||||||
boolean acceptsTransient,
|
boolean acceptsTransient,
|
||||||
boolean allowOutOfRangeReads,
|
PotentialTxnConflicts potentialTxnConflicts,
|
||||||
TableMetadata metadata,
|
TableMetadata metadata,
|
||||||
long nowInSec,
|
long nowInSec,
|
||||||
ColumnFilter columnFilter,
|
ColumnFilter columnFilter,
|
||||||
|
|
@ -82,7 +82,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
Index.QueryPlan indexQueryPlan,
|
Index.QueryPlan indexQueryPlan,
|
||||||
boolean trackWarnings)
|
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());
|
this.requestedSlices = dataRange.clusteringIndexFilter.getSlices(metadata());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,7 +90,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
boolean isDigest,
|
boolean isDigest,
|
||||||
int digestVersion,
|
int digestVersion,
|
||||||
boolean acceptsTransient,
|
boolean acceptsTransient,
|
||||||
boolean allowsOutOfRangeReads,
|
PotentialTxnConflicts potentialTxnConflicts,
|
||||||
TableMetadata metadata,
|
TableMetadata metadata,
|
||||||
long nowInSec,
|
long nowInSec,
|
||||||
ColumnFilter columnFilter,
|
ColumnFilter columnFilter,
|
||||||
|
|
@ -118,7 +118,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
isDigest,
|
isDigest,
|
||||||
digestVersion,
|
digestVersion,
|
||||||
acceptsTransient,
|
acceptsTransient,
|
||||||
allowsOutOfRangeReads,
|
potentialTxnConflicts,
|
||||||
metadata,
|
metadata,
|
||||||
nowInSec,
|
nowInSec,
|
||||||
columnFilter,
|
columnFilter,
|
||||||
|
|
@ -140,7 +140,30 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
false,
|
false,
|
||||||
0,
|
0,
|
||||||
false,
|
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,
|
false,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
potentialTxnConflicts,
|
||||||
metadata,
|
metadata,
|
||||||
nowInSec,
|
nowInSec,
|
||||||
columnFilter,
|
columnFilter,
|
||||||
|
|
@ -165,7 +188,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
false,
|
false,
|
||||||
0,
|
0,
|
||||||
false,
|
false,
|
||||||
false,
|
PotentialTxnConflicts.DISALLOW,
|
||||||
metadata,
|
metadata,
|
||||||
nowInSec,
|
nowInSec,
|
||||||
ColumnFilter.all(metadata),
|
ColumnFilter.all(metadata),
|
||||||
|
|
@ -212,7 +235,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
isDigestQuery(),
|
isDigestQuery(),
|
||||||
digestVersion(),
|
digestVersion(),
|
||||||
acceptsTransient(),
|
acceptsTransient(),
|
||||||
allowsOutOfRangeReads(),
|
potentialTxnConflicts(),
|
||||||
metadata(),
|
metadata(),
|
||||||
nowInSec(),
|
nowInSec(),
|
||||||
columnFilter(),
|
columnFilter(),
|
||||||
|
|
@ -223,32 +246,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
isTrackingWarnings());
|
isTrackingWarnings());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
public PartitionRangeReadCommand withTransactionalSettings(long nowInSec, AbstractBounds<PartitionPosition> range, boolean isRangeContinuation, boolean withoutReconciliation)
|
||||||
* 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<PartitionPosition> range, boolean isRangeContinuation)
|
|
||||||
{
|
{
|
||||||
// If we're not a continuation of whatever range we've previously queried, we should ignore the states of the
|
// 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
|
// 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(),
|
isDigestQuery(),
|
||||||
digestVersion(),
|
digestVersion(),
|
||||||
acceptsTransient(),
|
acceptsTransient(),
|
||||||
allowsOutOfRangeReads(),
|
PotentialTxnConflicts.ALLOW,
|
||||||
metadata(),
|
metadata(),
|
||||||
nowInSec,
|
nowInSec,
|
||||||
columnFilter(),
|
columnFilter(),
|
||||||
rowFilter(),
|
withoutReconciliation ? rowFilter().withoutReconciliation() : rowFilter(),
|
||||||
isRangeContinuation ? limits() : limits().withoutState(),
|
isRangeContinuation ? limits() : limits().withoutState(),
|
||||||
dataRange().forSubRange(range),
|
dataRange().forSubRange(range),
|
||||||
indexQueryPlan(),
|
indexQueryPlan(),
|
||||||
isTrackingWarnings());
|
isTrackingWarnings());
|
||||||
}
|
}
|
||||||
|
|
||||||
public PartitionRangeReadCommand withNowInSec(long nowInSec)
|
public PartitionRangeReadCommand withTxnReadName(int txnReadName)
|
||||||
{
|
{
|
||||||
return create(serializedAtEpoch(),
|
return create(serializedAtEpoch(),
|
||||||
isDigestQuery(),
|
isDigestQuery(),
|
||||||
digestVersion(),
|
digestVersion(),
|
||||||
acceptsTransient(),
|
acceptsTransient(),
|
||||||
allowsOutOfRangeReads(),
|
potentialTxnConflicts(),
|
||||||
metadata(),
|
metadata(),
|
||||||
nowInSec,
|
txnReadName,
|
||||||
columnFilter(),
|
columnFilter(),
|
||||||
rowFilter(),
|
rowFilter(),
|
||||||
limits(),
|
limits(),
|
||||||
|
|
@ -293,7 +291,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
isDigestQuery(),
|
isDigestQuery(),
|
||||||
digestVersion(),
|
digestVersion(),
|
||||||
acceptsTransient(),
|
acceptsTransient(),
|
||||||
allowsOutOfRangeReads(),
|
potentialTxnConflicts(),
|
||||||
metadata(),
|
metadata(),
|
||||||
nowInSec(),
|
nowInSec(),
|
||||||
columnFilter(),
|
columnFilter(),
|
||||||
|
|
@ -311,7 +309,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
true,
|
true,
|
||||||
digestVersion(),
|
digestVersion(),
|
||||||
false,
|
false,
|
||||||
allowsOutOfRangeReads(),
|
potentialTxnConflicts(),
|
||||||
metadata(),
|
metadata(),
|
||||||
nowInSec(),
|
nowInSec(),
|
||||||
columnFilter(),
|
columnFilter(),
|
||||||
|
|
@ -329,7 +327,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
false,
|
false,
|
||||||
0,
|
0,
|
||||||
true,
|
true,
|
||||||
allowsOutOfRangeReads(),
|
potentialTxnConflicts(),
|
||||||
metadata(),
|
metadata(),
|
||||||
nowInSec(),
|
nowInSec(),
|
||||||
columnFilter(),
|
columnFilter(),
|
||||||
|
|
@ -347,7 +345,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
isDigestQuery(),
|
isDigestQuery(),
|
||||||
digestVersion(),
|
digestVersion(),
|
||||||
acceptsTransient(),
|
acceptsTransient(),
|
||||||
allowsOutOfRangeReads(),
|
potentialTxnConflicts(),
|
||||||
metadata(),
|
metadata(),
|
||||||
nowInSec(),
|
nowInSec(),
|
||||||
columnFilter(),
|
columnFilter(),
|
||||||
|
|
@ -365,7 +363,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
isDigestQuery(),
|
isDigestQuery(),
|
||||||
digestVersion(),
|
digestVersion(),
|
||||||
acceptsTransient(),
|
acceptsTransient(),
|
||||||
allowsOutOfRangeReads(),
|
potentialTxnConflicts(),
|
||||||
metadata(),
|
metadata(),
|
||||||
nowInSec(),
|
nowInSec(),
|
||||||
columnFilter(),
|
columnFilter(),
|
||||||
|
|
@ -601,7 +599,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
boolean isDigest,
|
boolean isDigest,
|
||||||
int digestVersion,
|
int digestVersion,
|
||||||
boolean acceptsTransient,
|
boolean acceptsTransient,
|
||||||
boolean allowsOutOfRangeReads,
|
PotentialTxnConflicts potentialTxnConflicts,
|
||||||
TableMetadata metadata,
|
TableMetadata metadata,
|
||||||
long nowInSec,
|
long nowInSec,
|
||||||
ColumnFilter columnFilter,
|
ColumnFilter columnFilter,
|
||||||
|
|
@ -611,7 +609,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
DataRange range = DataRange.serializer.deserialize(in, version, metadata);
|
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,
|
Index.QueryPlan indexQueryPlan,
|
||||||
boolean trackWarnings)
|
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
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.cassandra.db;
|
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.ColumnFilter;
|
||||||
import org.apache.cassandra.db.filter.DataLimits;
|
import org.apache.cassandra.db.filter.DataLimits;
|
||||||
import org.apache.cassandra.db.filter.RowFilter;
|
import org.apache.cassandra.db.filter.RowFilter;
|
||||||
|
|
@ -36,9 +37,10 @@ public interface PartitionRangeReadQuery extends ReadQuery
|
||||||
ColumnFilter columnFilter,
|
ColumnFilter columnFilter,
|
||||||
RowFilter rowFilter,
|
RowFilter rowFilter,
|
||||||
DataLimits limits,
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,14 @@ import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
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 com.google.common.collect.Iterators;
|
||||||
|
|
||||||
import org.apache.cassandra.cache.IMeasurableMemory;
|
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.ObjectSizes;
|
||||||
import org.apache.cassandra.utils.memory.ByteBufferCloner;
|
import org.apache.cassandra.utils.memory.ByteBufferCloner;
|
||||||
|
|
||||||
|
|
@ -328,12 +328,13 @@ public class RangeTombstoneList implements Iterable<RangeTombstone>, IMeasurable
|
||||||
markedAts[i] = timestamp;
|
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++)
|
for (int i = 0; i < size; i++)
|
||||||
{
|
{
|
||||||
markedAts[i] = timestamp;
|
markedAts[i] = timestamp;
|
||||||
delTimesUnsignedIntegers[i] = localDeletionTime;
|
delTimesUnsignedIntegers[i] = unsignedLocalDeletionTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ import org.apache.cassandra.schema.TableId;
|
||||||
import org.apache.cassandra.schema.TableMetadata;
|
import org.apache.cassandra.schema.TableMetadata;
|
||||||
import org.apache.cassandra.service.ActiveRepairService;
|
import org.apache.cassandra.service.ActiveRepairService;
|
||||||
import org.apache.cassandra.service.ClientWarn;
|
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.ClusterMetadata;
|
||||||
import org.apache.cassandra.tcm.Epoch;
|
import org.apache.cassandra.tcm.Epoch;
|
||||||
import org.apache.cassandra.tracing.Tracing;
|
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.ObjectSizes;
|
||||||
import org.apache.cassandra.utils.TimeUUID;
|
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.any;
|
||||||
import static com.google.common.collect.Iterables.filter;
|
import static com.google.common.collect.Iterables.filter;
|
||||||
import static org.apache.cassandra.db.partitions.UnfilteredPartitionIterators.MergeListener.NOOP;
|
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);
|
protected static final Logger logger = LoggerFactory.getLogger(ReadCommand.class);
|
||||||
public static final IVersionedSerializer<ReadCommand> serializer = new Serializer();
|
public static final IVersionedSerializer<ReadCommand> 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.
|
// 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.
|
// This is useful for a few reasons, but mainly because the CQL query is here.
|
||||||
private static final FastThreadLocal<ReadCommand> COMMAND = new FastThreadLocal<>();
|
private static final FastThreadLocal<ReadCommand> COMMAND = new FastThreadLocal<>();
|
||||||
|
|
@ -128,7 +154,8 @@ public abstract class ReadCommand extends AbstractReadQuery
|
||||||
private final boolean isDigestQuery;
|
private final boolean isDigestQuery;
|
||||||
private final boolean acceptsTransient;
|
private final boolean acceptsTransient;
|
||||||
private final Epoch serializedAtEpoch;
|
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.
|
// if a digest query, the version for which the digest is expected. Ignored if not a digest.
|
||||||
private int digestVersion;
|
private int digestVersion;
|
||||||
|
|
||||||
|
|
@ -147,7 +174,7 @@ public abstract class ReadCommand extends AbstractReadQuery
|
||||||
boolean isDigest,
|
boolean isDigest,
|
||||||
int digestVersion,
|
int digestVersion,
|
||||||
boolean acceptsTransient,
|
boolean acceptsTransient,
|
||||||
boolean allowsOutOfRangeReads,
|
PotentialTxnConflicts potentialTxnConflicts,
|
||||||
TableMetadata metadata,
|
TableMetadata metadata,
|
||||||
long nowInSec,
|
long nowInSec,
|
||||||
ColumnFilter columnFilter,
|
ColumnFilter columnFilter,
|
||||||
|
|
@ -174,7 +201,7 @@ public abstract class ReadCommand extends AbstractReadQuery
|
||||||
boolean isDigestQuery,
|
boolean isDigestQuery,
|
||||||
int digestVersion,
|
int digestVersion,
|
||||||
boolean acceptsTransient,
|
boolean acceptsTransient,
|
||||||
boolean allowsOutOfRangeReads,
|
PotentialTxnConflicts potentialTxnConflicts,
|
||||||
TableMetadata metadata,
|
TableMetadata metadata,
|
||||||
long nowInSec,
|
long nowInSec,
|
||||||
ColumnFilter columnFilter,
|
ColumnFilter columnFilter,
|
||||||
|
|
@ -193,7 +220,7 @@ public abstract class ReadCommand extends AbstractReadQuery
|
||||||
this.digestVersion = digestVersion;
|
this.digestVersion = digestVersion;
|
||||||
this.acceptsTransient = acceptsTransient;
|
this.acceptsTransient = acceptsTransient;
|
||||||
this.indexQueryPlan = indexQueryPlan;
|
this.indexQueryPlan = indexQueryPlan;
|
||||||
this.allowsOutOfRangeReads = allowsOutOfRangeReads;
|
this.potentialTxnConflicts = potentialTxnConflicts;
|
||||||
this.trackWarnings = trackWarnings;
|
this.trackWarnings = trackWarnings;
|
||||||
this.serializedAtEpoch = serializedAtEpoch;
|
this.serializedAtEpoch = serializedAtEpoch;
|
||||||
this.dataRange = dataRange;
|
this.dataRange = dataRange;
|
||||||
|
|
@ -345,7 +372,7 @@ public abstract class ReadCommand extends AbstractReadQuery
|
||||||
*/
|
*/
|
||||||
public ReadCommand copyAsTransientQuery(Replica replica)
|
public ReadCommand copyAsTransientQuery(Replica replica)
|
||||||
{
|
{
|
||||||
Preconditions.checkArgument(replica.isTransient(),
|
checkArgument(replica.isTransient(),
|
||||||
"Can't make a transient request on a full replica: " + replica);
|
"Can't make a transient request on a full replica: " + replica);
|
||||||
return copyAsTransientQuery();
|
return copyAsTransientQuery();
|
||||||
}
|
}
|
||||||
|
|
@ -367,7 +394,7 @@ public abstract class ReadCommand extends AbstractReadQuery
|
||||||
*/
|
*/
|
||||||
public ReadCommand copyAsDigestQuery(Replica replica)
|
public ReadCommand copyAsDigestQuery(Replica replica)
|
||||||
{
|
{
|
||||||
Preconditions.checkArgument(replica.isFull(),
|
checkArgument(replica.isFull(),
|
||||||
"Can't make a digest request on a transient replica " + replica);
|
"Can't make a digest request on a transient replica " + replica);
|
||||||
return copyAsDigestQuery();
|
return copyAsDigestQuery();
|
||||||
}
|
}
|
||||||
|
|
@ -462,6 +489,8 @@ public abstract class ReadCommand extends AbstractReadQuery
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ColumnFamilyStore cfs = Keyspace.openAndGetStore(metadata());
|
ColumnFamilyStore cfs = Keyspace.openAndGetStore(metadata());
|
||||||
|
if (!potentialTxnConflicts.allowed)
|
||||||
|
ConsensusRequestRouter.validateSafeToReadNonTransactionally(this);
|
||||||
Index.QueryPlan indexQueryPlan = indexQueryPlan();
|
Index.QueryPlan indexQueryPlan = indexQueryPlan();
|
||||||
|
|
||||||
Index.Searcher searcher = null;
|
Index.Searcher searcher = null;
|
||||||
|
|
@ -550,15 +579,9 @@ public abstract class ReadCommand extends AbstractReadQuery
|
||||||
return ReadExecutionController.forCommand(this, false);
|
return ReadExecutionController.forCommand(this, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadCommand allowOutOfRangeReads()
|
public PotentialTxnConflicts potentialTxnConflicts()
|
||||||
{
|
{
|
||||||
allowsOutOfRangeReads = true;
|
return potentialTxnConflicts;
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean allowsOutOfRangeReads()
|
|
||||||
{
|
|
||||||
return allowsOutOfRangeReads;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1250,7 +1273,7 @@ public abstract class ReadCommand extends AbstractReadQuery
|
||||||
private static final int HAS_INDEX = 0x04;
|
private static final int HAS_INDEX = 0x04;
|
||||||
private static final int ACCEPTS_TRANSIENT = 0x08;
|
private static final int ACCEPTS_TRANSIENT = 0x08;
|
||||||
private static final int NEEDS_RECONCILIATION = 0x10;
|
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;
|
private final SchemaProvider schema;
|
||||||
|
|
||||||
|
|
@ -1315,14 +1338,14 @@ public abstract class ReadCommand extends AbstractReadQuery
|
||||||
return (flags & NEEDS_RECONCILIATION) != 0;
|
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
|
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())
|
| indexFlag(null != command.indexQueryPlan())
|
||||||
| acceptsTransientFlag(command.acceptsTransient())
|
| acceptsTransientFlag(command.acceptsTransient())
|
||||||
| needsReconciliationFlag(command.rowFilter().needsReconciliation())
|
| needsReconciliationFlag(command.rowFilter().needsReconciliation())
|
||||||
| allowsOutOfRangeReadsFlag(command.allowsOutOfRangeReads)
|
| potentialTxnConflicts(command.potentialTxnConflicts)
|
||||||
);
|
);
|
||||||
if (command.isDigestQuery())
|
if (command.isDigestQuery())
|
||||||
out.writeUnsignedVInt32(command.digestVersion());
|
out.writeUnsignedVInt32(command.digestVersion());
|
||||||
|
|
@ -1359,7 +1382,7 @@ public abstract class ReadCommand extends AbstractReadQuery
|
||||||
int flags = in.readByte();
|
int flags = in.readByte();
|
||||||
boolean isDigest = isDigest(flags);
|
boolean isDigest = isDigest(flags);
|
||||||
boolean acceptsTransient = acceptsTransient(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
|
// Shouldn't happen or it's a user error (see comment above) but
|
||||||
// better complain loudly than doing the wrong thing.
|
// better complain loudly than doing the wrong thing.
|
||||||
if (isForThrift(flags))
|
if (isForThrift(flags))
|
||||||
|
|
@ -1405,7 +1428,7 @@ public abstract class ReadCommand extends AbstractReadQuery
|
||||||
indexQueryPlan = indexGroup.queryPlanFor(rowFilter);
|
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
|
private IndexMetadata deserializeIndexMetadata(DataInputPlus in, int version, TableMetadata metadata) throws IOException
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import org.apache.cassandra.exceptions.CoordinatorBehindException;
|
||||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||||
import org.apache.cassandra.exceptions.InvalidRoutingException;
|
import org.apache.cassandra.exceptions.InvalidRoutingException;
|
||||||
import org.apache.cassandra.exceptions.QueryCancelledException;
|
import org.apache.cassandra.exceptions.QueryCancelledException;
|
||||||
|
import org.apache.cassandra.exceptions.RetryOnDifferentSystemException;
|
||||||
import org.apache.cassandra.locator.Replica;
|
import org.apache.cassandra.locator.Replica;
|
||||||
import org.apache.cassandra.metrics.TCMMetrics;
|
import org.apache.cassandra.metrics.TCMMetrics;
|
||||||
import org.apache.cassandra.net.IVerbHandler;
|
import org.apache.cassandra.net.IVerbHandler;
|
||||||
|
|
@ -42,6 +43,7 @@ import org.apache.cassandra.tracing.Tracing;
|
||||||
import org.apache.cassandra.utils.FBUtilities;
|
import org.apache.cassandra.utils.FBUtilities;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
|
import static org.apache.cassandra.exceptions.RequestFailureReason.RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM;
|
||||||
|
|
||||||
public class ReadCommandVerbHandler implements IVerbHandler<ReadCommand>
|
public class ReadCommandVerbHandler implements IVerbHandler<ReadCommand>
|
||||||
{
|
{
|
||||||
|
|
@ -99,6 +101,13 @@ public class ReadCommandVerbHandler implements IVerbHandler<ReadCommand>
|
||||||
MessagingService.instance().send(reply, message.from());
|
MessagingService.instance().send(reply, message.from());
|
||||||
return;
|
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)
|
catch (AssertionError t)
|
||||||
{
|
{
|
||||||
throw new AssertionError(String.format("Caught an error while trying to process the command: %s", command.toCQLString()), 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<ReadCommand>
|
||||||
|
|
||||||
// Some read commands may be sent using an older Epoch intentionally so validating using the current Epoch
|
// Some read commands may be sent using an older Epoch intentionally so validating using the current Epoch
|
||||||
// doesn't work
|
// doesn't work
|
||||||
if (command.allowsOutOfRangeReads())
|
if (command.potentialTxnConflicts().allowed)
|
||||||
return metadata;
|
return metadata;
|
||||||
|
|
||||||
if (command.isTopK())
|
if (command.isTopK())
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,19 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.cassandra.db;
|
package org.apache.cassandra.db;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import org.apache.cassandra.db.filter.ColumnFilter;
|
import org.apache.cassandra.db.filter.ColumnFilter;
|
||||||
import org.apache.cassandra.db.partitions.*;
|
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
||||||
import org.apache.cassandra.db.rows.*;
|
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.IVersionedSerializer;
|
||||||
import org.apache.cassandra.io.util.DataInputBuffer;
|
import org.apache.cassandra.io.util.DataInputBuffer;
|
||||||
import org.apache.cassandra.io.util.DataInputPlus;
|
import org.apache.cassandra.io.util.DataInputPlus;
|
||||||
|
|
@ -132,6 +137,37 @@ public abstract class ReadResponse
|
||||||
return sb.toString();
|
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<ReadResponse> 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<UnfilteredPartitionIterator> 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)
|
protected static ByteBuffer makeDigest(UnfilteredPartitionIterator iterator, ReadCommand command)
|
||||||
{
|
{
|
||||||
Digest digest = Digest.forReadResponse();
|
Digest digest = Digest.forReadResponse();
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
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.context.CounterContext;
|
||||||
import org.apache.cassandra.db.marshal.AbstractType;
|
import org.apache.cassandra.db.marshal.AbstractType;
|
||||||
import org.apache.cassandra.db.marshal.CollectionType;
|
import org.apache.cassandra.db.marshal.CollectionType;
|
||||||
|
|
@ -120,7 +121,7 @@ public abstract class SimpleBuilders
|
||||||
|
|
||||||
private final Map<TableId, PartitionUpdateBuilder> updateBuilders = new HashMap<>();
|
private final Map<TableId, PartitionUpdateBuilder> updateBuilders = new HashMap<>();
|
||||||
|
|
||||||
private boolean allowPotentialTransactionConflicts = false;
|
private PotentialTxnConflicts potentialTxnConflicts = PotentialTxnConflicts.DISALLOW;
|
||||||
|
|
||||||
public MutationBuilder(String keyspaceName, DecoratedKey key)
|
public MutationBuilder(String keyspaceName, DecoratedKey key)
|
||||||
{
|
{
|
||||||
|
|
@ -128,9 +129,9 @@ public abstract class SimpleBuilders
|
||||||
this.key = key;
|
this.key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MutationBuilder allowPotentialTransactionConflicts()
|
public MutationBuilder allowPotentialTxnConflicts()
|
||||||
{
|
{
|
||||||
allowPotentialTransactionConflicts = true;
|
potentialTxnConflicts = PotentialTxnConflicts.ALLOW;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,9 +163,9 @@ public abstract class SimpleBuilders
|
||||||
assert !updateBuilders.isEmpty() : "Cannot create empty mutation";
|
assert !updateBuilders.isEmpty() : "Cannot create empty mutation";
|
||||||
|
|
||||||
if (updateBuilders.size() == 1)
|
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())
|
for (PartitionUpdateBuilder builder : updateBuilders.values())
|
||||||
mutationBuilder.add(builder.build());
|
mutationBuilder.add(builder.build());
|
||||||
return mutationBuilder.build();
|
return mutationBuilder.build();
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
boolean isDigest,
|
boolean isDigest,
|
||||||
int digestVersion,
|
int digestVersion,
|
||||||
boolean acceptsTransient,
|
boolean acceptsTransient,
|
||||||
boolean allowsOutOfRangeReads,
|
PotentialTxnConflicts potentialTxnConflicts,
|
||||||
TableMetadata metadata,
|
TableMetadata metadata,
|
||||||
long nowInSec,
|
long nowInSec,
|
||||||
ColumnFilter columnFilter,
|
ColumnFilter columnFilter,
|
||||||
|
|
@ -110,7 +110,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
boolean trackWarnings,
|
boolean trackWarnings,
|
||||||
DataRange dataRange)
|
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;
|
assert partitionKey.getPartitioner() == metadata.partitioner;
|
||||||
this.partitionKey = partitionKey;
|
this.partitionKey = partitionKey;
|
||||||
this.clusteringIndexFilter = clusteringIndexFilter;
|
this.clusteringIndexFilter = clusteringIndexFilter;
|
||||||
|
|
@ -120,7 +120,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
boolean isDigest,
|
boolean isDigest,
|
||||||
int digestVersion,
|
int digestVersion,
|
||||||
boolean acceptsTransient,
|
boolean acceptsTransient,
|
||||||
boolean allowsOutOfRangeReads,
|
PotentialTxnConflicts potentialTxnConflicts,
|
||||||
TableMetadata metadata,
|
TableMetadata metadata,
|
||||||
long nowInSec,
|
long nowInSec,
|
||||||
ColumnFilter columnFilter,
|
ColumnFilter columnFilter,
|
||||||
|
|
@ -154,7 +154,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
isDigest,
|
isDigest,
|
||||||
digestVersion,
|
digestVersion,
|
||||||
acceptsTransient,
|
acceptsTransient,
|
||||||
allowsOutOfRangeReads,
|
potentialTxnConflicts,
|
||||||
metadata,
|
metadata,
|
||||||
nowInSec,
|
nowInSec,
|
||||||
columnFilter,
|
columnFilter,
|
||||||
|
|
@ -194,7 +194,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
false,
|
false,
|
||||||
0,
|
0,
|
||||||
false,
|
false,
|
||||||
false,
|
PotentialTxnConflicts.DISALLOW,
|
||||||
metadata,
|
metadata,
|
||||||
nowInSec,
|
nowInSec,
|
||||||
columnFilter,
|
columnFilter,
|
||||||
|
|
@ -206,6 +206,44 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
false);
|
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.
|
* Creates a new read command on a single partition.
|
||||||
*
|
*
|
||||||
|
|
@ -373,7 +411,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
isDigestQuery(),
|
isDigestQuery(),
|
||||||
digestVersion(),
|
digestVersion(),
|
||||||
acceptsTransient(),
|
acceptsTransient(),
|
||||||
allowsOutOfRangeReads(),
|
potentialTxnConflicts(),
|
||||||
metadata(),
|
metadata(),
|
||||||
nowInSec(),
|
nowInSec(),
|
||||||
columnFilter(),
|
columnFilter(),
|
||||||
|
|
@ -392,7 +430,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
true,
|
true,
|
||||||
digestVersion(),
|
digestVersion(),
|
||||||
acceptsTransient(),
|
acceptsTransient(),
|
||||||
allowsOutOfRangeReads(),
|
potentialTxnConflicts(),
|
||||||
metadata(),
|
metadata(),
|
||||||
nowInSec(),
|
nowInSec(),
|
||||||
columnFilter(),
|
columnFilter(),
|
||||||
|
|
@ -411,7 +449,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
false,
|
false,
|
||||||
0,
|
0,
|
||||||
true,
|
true,
|
||||||
allowsOutOfRangeReads(),
|
potentialTxnConflicts(),
|
||||||
metadata(),
|
metadata(),
|
||||||
nowInSec(),
|
nowInSec(),
|
||||||
columnFilter(),
|
columnFilter(),
|
||||||
|
|
@ -430,7 +468,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
isDigestQuery(),
|
isDigestQuery(),
|
||||||
digestVersion(),
|
digestVersion(),
|
||||||
acceptsTransient(),
|
acceptsTransient(),
|
||||||
allowsOutOfRangeReads(),
|
potentialTxnConflicts(),
|
||||||
metadata(),
|
metadata(),
|
||||||
nowInSec(),
|
nowInSec(),
|
||||||
columnFilter(),
|
columnFilter(),
|
||||||
|
|
@ -442,24 +480,6 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
isTrackingWarnings());
|
isTrackingWarnings());
|
||||||
}
|
}
|
||||||
|
|
||||||
public SinglePartitionReadCommand withNowInSec(long nowInSec)
|
|
||||||
{
|
|
||||||
return create(serializedAtEpoch(),
|
|
||||||
isDigestQuery(),
|
|
||||||
digestVersion(),
|
|
||||||
acceptsTransient(),
|
|
||||||
allowsOutOfRangeReads(),
|
|
||||||
metadata(),
|
|
||||||
nowInSec,
|
|
||||||
columnFilter(),
|
|
||||||
rowFilter(),
|
|
||||||
limits(),
|
|
||||||
partitionKey(),
|
|
||||||
clusteringIndexFilter(),
|
|
||||||
indexQueryPlan(),
|
|
||||||
isTrackingWarnings());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DecoratedKey partitionKey()
|
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
|
* When running transactionally we need to use the txn system nowInSeconds, and set whether reconciliation
|
||||||
* should execute in a mannager suited to not needing reconciliation. Such as when
|
* should be performed based on whether it's part of a multiple replica read. We also allow potential txn conflicts
|
||||||
* executing transactionally at a single replica and doing an index scan where the index
|
* because we manage those conflicts from the txn system
|
||||||
* scan should not return extra rows and expect post filtering at the coordinator.
|
|
||||||
*/
|
*/
|
||||||
public SinglePartitionReadCommand withoutReconciliation()
|
public SinglePartitionReadCommand withTransactionalSettings(boolean withoutReconciliation, long nowInSeconds)
|
||||||
{
|
{
|
||||||
if (rowFilter().isEmpty())
|
|
||||||
return this;
|
|
||||||
return create(serializedAtEpoch(),
|
return create(serializedAtEpoch(),
|
||||||
isDigestQuery(),
|
isDigestQuery(),
|
||||||
digestVersion(),
|
digestVersion(),
|
||||||
acceptsTransient(),
|
acceptsTransient(),
|
||||||
allowsOutOfRangeReads(),
|
PotentialTxnConflicts.ALLOW,
|
||||||
metadata(),
|
metadata(),
|
||||||
nowInSec(),
|
nowInSeconds,
|
||||||
columnFilter(),
|
columnFilter(),
|
||||||
rowFilter().withoutReconciliation(),
|
withoutReconciliation ? rowFilter().withoutReconciliation() : rowFilter(),
|
||||||
limits(),
|
limits(),
|
||||||
partitionKey(),
|
partitionKey(),
|
||||||
clusteringIndexFilter(),
|
clusteringIndexFilter(),
|
||||||
|
|
@ -1310,7 +1327,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
RowFilter rowFilter,
|
RowFilter rowFilter,
|
||||||
DataLimits limits,
|
DataLimits limits,
|
||||||
List<DecoratedKey> partitionKeys,
|
List<DecoratedKey> partitionKeys,
|
||||||
ClusteringIndexFilter clusteringIndexFilter)
|
ClusteringIndexFilter clusteringIndexFilter,
|
||||||
|
PotentialTxnConflicts potentialTxnConflicts)
|
||||||
{
|
{
|
||||||
List<SinglePartitionReadCommand> commands = new ArrayList<>(partitionKeys.size());
|
List<SinglePartitionReadCommand> commands = new ArrayList<>(partitionKeys.size());
|
||||||
for (DecoratedKey partitionKey : partitionKeys)
|
for (DecoratedKey partitionKey : partitionKeys)
|
||||||
|
|
@ -1321,7 +1339,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
rowFilter,
|
rowFilter,
|
||||||
limits,
|
limits,
|
||||||
partitionKey,
|
partitionKey,
|
||||||
clusteringIndexFilter));
|
clusteringIndexFilter,
|
||||||
|
potentialTxnConflicts));
|
||||||
}
|
}
|
||||||
|
|
||||||
return create(commands, limits);
|
return create(commands, limits);
|
||||||
|
|
@ -1377,7 +1396,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
boolean isDigest,
|
boolean isDigest,
|
||||||
int digestVersion,
|
int digestVersion,
|
||||||
boolean acceptsTransient,
|
boolean acceptsTransient,
|
||||||
boolean allowsOutOfRangeReads,
|
PotentialTxnConflicts potentialTxnConflicts,
|
||||||
TableMetadata metadata,
|
TableMetadata metadata,
|
||||||
long nowInSec,
|
long nowInSec,
|
||||||
ColumnFilter columnFilter,
|
ColumnFilter columnFilter,
|
||||||
|
|
@ -1388,7 +1407,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
||||||
{
|
{
|
||||||
DecoratedKey key = metadata.partitioner.decorateKey(metadata.partitionKeyType.readBuffer(in, DatabaseDescriptor.getMaxValueSize()));
|
DecoratedKey key = metadata.partitioner.decorateKey(metadata.partitionKeyType.readBuffer(in, DatabaseDescriptor.getMaxValueSize()));
|
||||||
ClusteringIndexFilter filter = ClusteringIndexFilter.serializer.deserialize(in, version, metadata);
|
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,
|
boolean trackWarnings,
|
||||||
DataRange dataRange)
|
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);
|
rowFilter, limits, partitionKey, clusteringIndexFilter, indexQueryPlan, trackWarnings, dataRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
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.ClusteringIndexFilter;
|
||||||
import org.apache.cassandra.db.filter.ColumnFilter;
|
import org.apache.cassandra.db.filter.ColumnFilter;
|
||||||
import org.apache.cassandra.db.filter.DataLimits;
|
import org.apache.cassandra.db.filter.DataLimits;
|
||||||
|
|
@ -51,9 +51,10 @@ public interface SinglePartitionReadQuery extends ReadQuery
|
||||||
RowFilter rowFilter,
|
RowFilter rowFilter,
|
||||||
DataLimits limits,
|
DataLimits limits,
|
||||||
List<DecoratedKey> partitionKeys,
|
List<DecoratedKey> 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,16 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.cassandra.db.partitions;
|
package org.apache.cassandra.db.partitions;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.cassandra.db.EmptyIterators;
|
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.MorePartitions;
|
||||||
import org.apache.cassandra.db.transform.Transformation;
|
import org.apache.cassandra.db.transform.Transformation;
|
||||||
import org.apache.cassandra.utils.AbstractIterator;
|
import org.apache.cassandra.utils.AbstractIterator;
|
||||||
|
|
||||||
import org.apache.cassandra.db.SinglePartitionReadQuery;
|
|
||||||
import org.apache.cassandra.db.rows.*;
|
|
||||||
|
|
||||||
public abstract class PartitionIterators
|
public abstract class PartitionIterators
|
||||||
{
|
{
|
||||||
private PartitionIterators() {}
|
private PartitionIterators() {}
|
||||||
|
|
@ -57,7 +57,7 @@ public abstract class PartitionIterators
|
||||||
return Transformation.apply(toReturn, new Close());
|
return Transformation.apply(toReturn, new Close());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PartitionIterator concat(final List<PartitionIterator> iterators)
|
public static PartitionIterator concat(final List<? extends PartitionIterator> iterators)
|
||||||
{
|
{
|
||||||
if (iterators.size() == 1)
|
if (iterators.size() == 1)
|
||||||
return iterators.get(0);
|
return iterators.get(0);
|
||||||
|
|
|
||||||
|
|
@ -71,14 +71,14 @@ import org.apache.cassandra.io.util.DataInputPlus;
|
||||||
import org.apache.cassandra.io.util.DataOutputBuffer;
|
import org.apache.cassandra.io.util.DataOutputBuffer;
|
||||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||||
import org.apache.cassandra.metrics.TCMMetrics;
|
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.net.MessagingService;
|
||||||
import org.apache.cassandra.schema.ColumnMetadata;
|
import org.apache.cassandra.schema.ColumnMetadata;
|
||||||
import org.apache.cassandra.schema.Schema;
|
import org.apache.cassandra.schema.Schema;
|
||||||
import org.apache.cassandra.schema.TableId;
|
import org.apache.cassandra.schema.TableId;
|
||||||
import org.apache.cassandra.schema.TableMetadata;
|
import org.apache.cassandra.schema.TableMetadata;
|
||||||
import org.apache.cassandra.service.ClientState;
|
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.BTree;
|
||||||
import org.apache.cassandra.utils.btree.UpdateFunction;
|
import org.apache.cassandra.utils.btree.UpdateFunction;
|
||||||
import org.apache.cassandra.utils.vint.VIntCoding;
|
import org.apache.cassandra.utils.vint.VIntCoding;
|
||||||
|
|
@ -1161,7 +1161,7 @@ public class PartitionUpdate extends AbstractBTreePartition
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder updateTimesAndPathsForAccord(@Nonnull Function<Cell, CellPath> cellToMaybeNewListPath, long newTimestamp, int newLocalDeletionTime)
|
public Builder updateTimesAndPathsForAccord(@Nonnull Function<Cell, CellPath> cellToMaybeNewListPath, long newTimestamp, long newLocalDeletionTime)
|
||||||
{
|
{
|
||||||
deletionInfo.updateAllTimestampAndLocalDeletionTime(newTimestamp - 1, newLocalDeletionTime);
|
deletionInfo.updateAllTimestampAndLocalDeletionTime(newTimestamp - 1, newLocalDeletionTime);
|
||||||
tree = BTree.<Row, Row>transformAndFilter(tree, (x) -> x.updateTimesAndPathsForAccord(cellToMaybeNewListPath, newTimestamp, newLocalDeletionTime));
|
tree = BTree.<Row, Row>transformAndFilter(tree, (x) -> x.updateTimesAndPathsForAccord(cellToMaybeNewListPath, newTimestamp, newLocalDeletionTime));
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,21 @@ package org.apache.cassandra.db.partitions;
|
||||||
|
|
||||||
import java.io.IOError;
|
import java.io.IOError;
|
||||||
import java.io.IOException;
|
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.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.FilteredPartitions;
|
||||||
import org.apache.cassandra.db.transform.MorePartitions;
|
import org.apache.cassandra.db.transform.MorePartitions;
|
||||||
import org.apache.cassandra.db.transform.Transformation;
|
import org.apache.cassandra.db.transform.Transformation;
|
||||||
|
|
|
||||||
|
|
@ -121,14 +121,14 @@ public abstract class AbstractCell<V> extends Cell<V>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ColumnData updateTimesAndPathsForAccord(@Nonnull Function<Cell, CellPath> cellToMaybeNewListPath, long newTimestamp, int newLocalDeletionTime)
|
public ColumnData updateTimesAndPathsForAccord(@Nonnull Function<Cell, CellPath> cellToMaybeNewListPath, long newTimestamp, long newLocalDeletionTime)
|
||||||
{
|
{
|
||||||
long localDeletionTime = localDeletionTime() != NO_DELETION_TIME ? newLocalDeletionTime : NO_DELETION_TIME;
|
long localDeletionTime = localDeletionTime() != NO_DELETION_TIME ? newLocalDeletionTime : NO_DELETION_TIME;
|
||||||
return new BufferCell(column, isTombstone() ? newTimestamp - 1 : newTimestamp, ttl(), localDeletionTime, buffer(), path());
|
return new BufferCell(column, isTombstone() ? newTimestamp - 1 : newTimestamp, ttl(), localDeletionTime, buffer(), path());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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;
|
long localDeletionTime = localDeletionTime() != NO_DELETION_TIME ? newLocalDeletionTime : NO_DELETION_TIME;
|
||||||
return new BufferCell(column, isTombstone() ? newTimestamp - 1 : newTimestamp, ttl(), localDeletionTime, buffer(), maybeNewPath);
|
return new BufferCell(column, isTombstone() ? newTimestamp - 1 : newTimestamp, ttl(), localDeletionTime, buffer(), maybeNewPath);
|
||||||
|
|
|
||||||
|
|
@ -444,7 +444,7 @@ public class BTreeRow extends AbstractRow
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Row updateTimesAndPathsForAccord(@Nonnull Function<Cell, CellPath> cellToMaybeNewListPath, long newTimestamp, int newLocalDeletionTime)
|
public Row updateTimesAndPathsForAccord(@Nonnull Function<Cell, CellPath> cellToMaybeNewListPath, long newTimestamp, long newLocalDeletionTime)
|
||||||
{
|
{
|
||||||
LivenessInfo newInfo = primaryKeyLivenessInfo.isEmpty() ? primaryKeyLivenessInfo : primaryKeyLivenessInfo.withUpdatedTimestampAndLocalDeletionTime(newTimestamp, 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
|
// 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
|
||||||
|
|
|
||||||
|
|
@ -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
|
* @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<Cell, CellPath> cellToMaybeNewListPath, long newTimestamp, int newLocalDeletionTime);
|
public abstract ColumnData updateTimesAndPathsForAccord(@Nonnull Function<Cell, CellPath> cellToMaybeNewListPath, long newTimestamp, long newLocalDeletionTime);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List paths are time UUIDs that increment for each item in the list and for Accord and Paxos
|
* 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
|
* @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();
|
public abstract ColumnData markCounterLocalToBeCleared();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,7 @@ public class ComplexColumnData extends ColumnData implements Iterable<Cell<?>>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ColumnData updateTimesAndPathsForAccord(@Nonnull Function<Cell, CellPath> cellToMaybeNewListPath, long newTimestamp, int newLocalDeletionTime)
|
public ColumnData updateTimesAndPathsForAccord(@Nonnull Function<Cell, CellPath> cellToMaybeNewListPath, long newTimestamp, long newLocalDeletionTime)
|
||||||
{
|
{
|
||||||
DeletionTime newDeletion = complexDeletion.isLive() ? complexDeletion : DeletionTime.build(newTimestamp - 1, newLocalDeletionTime);
|
DeletionTime newDeletion = complexDeletion.isLive() ? complexDeletion : DeletionTime.build(newTimestamp - 1, newLocalDeletionTime);
|
||||||
Function<Cell, CellPath> maybeNewListPath;
|
Function<Cell, CellPath> maybeNewListPath;
|
||||||
|
|
@ -279,7 +279,7 @@ public class ComplexColumnData extends ColumnData implements Iterable<Cell<?>>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ColumnData updateAllTimesWithNewCellPathForComplexColumnData(@Nonnull CellPath maybeNewPath, long newTimestamp, int newLocalDeletionTime)
|
public ColumnData updateAllTimesWithNewCellPathForComplexColumnData(@Nonnull CellPath maybeNewPath, long newTimestamp, long newLocalDeletionTime)
|
||||||
{
|
{
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -312,7 +312,7 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
||||||
*/
|
*/
|
||||||
public Row updateAllTimestamp(long newTimestamp);
|
public Row updateAllTimestamp(long newTimestamp);
|
||||||
|
|
||||||
public Row updateTimesAndPathsForAccord(@Nonnull Function<Cell, CellPath> cellToMaybeNewListPath, long newTimestamp, int newLocalDeletionTime);
|
public Row updateTimesAndPathsForAccord(@Nonnull Function<Cell, CellPath> cellToMaybeNewListPath, long newTimestamp, long newLocalDeletionTime);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a copy of this row with the new deletion as row deletion if it is more recent
|
* Returns a copy of this row with the new deletion as row deletion if it is more recent
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
import org.apache.cassandra.db.DecoratedKey;
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
import org.apache.cassandra.db.IMutation;
|
import org.apache.cassandra.db.IMutation;
|
||||||
import org.apache.cassandra.db.Mutation;
|
import org.apache.cassandra.db.Mutation;
|
||||||
|
import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
|
||||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||||
import org.apache.cassandra.schema.TableId;
|
import org.apache.cassandra.schema.TableId;
|
||||||
import org.apache.cassandra.service.ClientState;
|
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
|
* Accord doesn't support reading/writing virtual tables yet so updating them non-transactionally is always safe
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean allowsPotentialTransactionConflicts()
|
public PotentialTxnConflicts potentialTxnConflicts()
|
||||||
{
|
{
|
||||||
return true;
|
return PotentialTxnConflicts.ALLOW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -258,6 +258,7 @@ public class Murmur3Partitioner implements IPartitioner
|
||||||
// CASSANDRA-17109 Added the below checks, but paxos tests were not updated, rather than fix
|
// 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
|
// 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...
|
// 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)
|
// if (token == MAXIMUM)
|
||||||
// throw new IllegalArgumentException("Cannot increase above MAXIMUM");
|
// throw new IllegalArgumentException("Cannot increase above MAXIMUM");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,9 @@ public class NormalizedRanges<T extends RingPosition<T>> extends AbstractList<Ra
|
||||||
|
|
||||||
public NormalizedRanges<T> subtract(NormalizedRanges<T> b)
|
public NormalizedRanges<T> subtract(NormalizedRanges<T> b)
|
||||||
{
|
{
|
||||||
|
if (b.isEmpty())
|
||||||
|
return this;
|
||||||
|
|
||||||
if (b.size() == 1 && b.get(0).isFull())
|
if (b.size() == 1 && b.get(0).isFull())
|
||||||
return NormalizedRanges.empty();
|
return NormalizedRanges.empty();
|
||||||
|
|
||||||
|
|
@ -199,10 +202,9 @@ public class NormalizedRanges<T extends RingPosition<T>> extends AbstractList<Ra
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public NormalizedRanges<T> invert()
|
NormalizedRanges<T> invert()
|
||||||
{
|
{
|
||||||
if (isEmpty())
|
checkState(!isEmpty());
|
||||||
return this;
|
|
||||||
|
|
||||||
List<Range<T>> result = new ArrayList<>(size() + 2);
|
List<Range<T>> result = new ArrayList<>(size() + 2);
|
||||||
T minValue = get(0).left.minValue();
|
T minValue = get(0).left.minValue();
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import org.apache.commons.lang3.ObjectUtils;
|
||||||
|
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
import org.apache.cassandra.db.PartitionPosition;
|
import org.apache.cassandra.db.PartitionPosition;
|
||||||
|
import org.apache.cassandra.dht.Token.KeyBound;
|
||||||
import org.apache.cassandra.dht.Token.TokenFactory;
|
import org.apache.cassandra.dht.Token.TokenFactory;
|
||||||
import org.apache.cassandra.io.util.DataInputPlus;
|
import org.apache.cassandra.io.util.DataInputPlus;
|
||||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
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.tcm.serialization.Version;
|
||||||
import org.apache.cassandra.utils.Pair;
|
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 java.util.Collections.emptyList;
|
||||||
import static org.apache.cassandra.config.CassandraRelevantProperties.TEST_RANGE_EXPENSIVE_CHECKS;
|
import static org.apache.cassandra.config.CassandraRelevantProperties.TEST_RANGE_EXPENSIVE_CHECKS;
|
||||||
|
|
||||||
|
|
@ -776,4 +779,88 @@ public class Range<T extends RingPosition<T>> extends AbstractBounds<T> implemen
|
||||||
return tokenSerializer.serializedSize(t, SERDE_VERSION);
|
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<PartitionPosition>, AbstractBounds<PartitionPosition>> intersectionAndRemainder(AbstractBounds<PartitionPosition> bounds, org.apache.cassandra.dht.Range<Token> 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<PartitionPosition>, AbstractBounds<PartitionPosition>> split = bounds.split(rangeLeft.maxKeyBound());
|
||||||
|
AbstractBounds<PartitionPosition> 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ import org.apache.cassandra.concurrent.Stage;
|
||||||
import org.apache.cassandra.db.Mutation;
|
import org.apache.cassandra.db.Mutation;
|
||||||
import org.apache.cassandra.exceptions.RequestFailure;
|
import org.apache.cassandra.exceptions.RequestFailure;
|
||||||
import org.apache.cassandra.exceptions.RequestFailureReason;
|
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.exceptions.WriteTimeoutException;
|
||||||
import org.apache.cassandra.io.util.File;
|
import org.apache.cassandra.io.util.File;
|
||||||
import org.apache.cassandra.locator.InetAddressAndPort;
|
import org.apache.cassandra.locator.InetAddressAndPort;
|
||||||
|
|
@ -248,7 +250,9 @@ final class HintsDispatcher implements AutoCloseable
|
||||||
failedRetryDifferentSystem = true;
|
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);
|
HintDiagnostics.pageFailureResult(this, success, failures, timeouts, retryDifferentSystem);
|
||||||
return Action.ABORT;
|
return Action.ABORT;
|
||||||
|
|
@ -534,16 +538,26 @@ final class HintsDispatcher implements AutoCloseable
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IAccordService accord = AccordService.instance();
|
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)
|
if (kind == retry_new_protocol)
|
||||||
accordOutcome = RETRY_DIFFERENT_SYSTEM;
|
accordOutcome = RETRY_DIFFERENT_SYSTEM;
|
||||||
else
|
else
|
||||||
accordOutcome = SUCCESS;
|
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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
accordOutcome = e instanceof WriteTimeoutException ? TIMEOUT : FAILURE;
|
accordOutcome = FAILURE;
|
||||||
String msg = "Accord hint delivery transaction failed";
|
String msg = "Accord hint delivery transaction failed permanently";
|
||||||
if (noSpamLogger.getStatement(msg).shouldLog(Clock.Global.nanoTime()))
|
if (noSpamLogger.getStatement(msg).shouldLog(Clock.Global.nanoTime()))
|
||||||
logger.error(msg, e);
|
logger.error(msg, e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,17 +21,29 @@ import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.cassandra.schema.TableMetadata;
|
import org.apache.cassandra.db.Clustering;
|
||||||
import org.apache.cassandra.db.*;
|
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.ClusteringIndexNamesFilter;
|
||||||
import org.apache.cassandra.db.filter.DataLimits;
|
import org.apache.cassandra.db.filter.DataLimits;
|
||||||
import org.apache.cassandra.db.filter.RowFilter;
|
import org.apache.cassandra.db.filter.RowFilter;
|
||||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
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.db.transform.Transformation;
|
||||||
|
import org.apache.cassandra.index.Index;
|
||||||
import org.apache.cassandra.index.internal.CassandraIndex;
|
import org.apache.cassandra.index.internal.CassandraIndex;
|
||||||
import org.apache.cassandra.index.internal.CassandraIndexSearcher;
|
import org.apache.cassandra.index.internal.CassandraIndexSearcher;
|
||||||
import org.apache.cassandra.index.internal.IndexEntry;
|
import org.apache.cassandra.index.internal.IndexEntry;
|
||||||
|
import org.apache.cassandra.schema.TableMetadata;
|
||||||
import org.apache.cassandra.utils.btree.BTreeSet;
|
import org.apache.cassandra.utils.btree.BTreeSet;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -159,7 +171,7 @@ public class CompositesSearcher extends CassandraIndexSearcher
|
||||||
DataLimits.NONE,
|
DataLimits.NONE,
|
||||||
partitionKey,
|
partitionKey,
|
||||||
filter,
|
filter,
|
||||||
null);
|
(Index.QueryPlan) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// by the next caller of next, or through closing this iterator is this come before.
|
// by the next caller of next, or through closing this iterator is this come before.
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,22 @@ import java.nio.ByteBuffer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.ColumnFilter;
|
||||||
import org.apache.cassandra.db.filter.DataLimits;
|
import org.apache.cassandra.db.filter.DataLimits;
|
||||||
import org.apache.cassandra.db.filter.RowFilter;
|
import org.apache.cassandra.db.filter.RowFilter;
|
||||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
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.CassandraIndex;
|
||||||
import org.apache.cassandra.index.internal.CassandraIndexSearcher;
|
import org.apache.cassandra.index.internal.CassandraIndexSearcher;
|
||||||
import org.apache.cassandra.schema.TableMetadata;
|
import org.apache.cassandra.schema.TableMetadata;
|
||||||
|
|
@ -91,7 +101,7 @@ public class KeysSearcher extends CassandraIndexSearcher
|
||||||
DataLimits.NONE,
|
DataLimits.NONE,
|
||||||
key,
|
key,
|
||||||
command.clusteringIndexFilter(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
|
// 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.
|
// by the next caller of next, or through closing this iterator is this come before.
|
||||||
|
|
|
||||||
|
|
@ -788,7 +788,7 @@ public class IndexTermType
|
||||||
return CompositeType.getInstance(collection.nameComparator(), collection.valueComparator());
|
return CompositeType.getInstance(collection.nameComparator(), collection.valueComparator());
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unsupported collection type: " + collection.kind + "; index type: " + indexType.name());
|
throw new IllegalArgumentException("Unsupported collection type: " + collection.kind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -820,7 +820,7 @@ public class TableMetrics
|
||||||
accordRepairUnexpectedFailures = createTableMeter("AccordRepairUnexpectedFailures", cfs.keyspace.metric.rangeMigrationUnexpectedFailures);
|
accordRepairUnexpectedFailures = createTableMeter("AccordRepairUnexpectedFailures", cfs.keyspace.metric.rangeMigrationUnexpectedFailures);
|
||||||
accordRepairDependencyLimitFailures = createTableMeter("AccordRepairDependencyLimitFaiures", cfs.keyspace.metric.rangeMigrationDependencyLimitFailures);
|
accordRepairDependencyLimitFailures = createTableMeter("AccordRepairDependencyLimitFaiures", cfs.keyspace.metric.rangeMigrationDependencyLimitFailures);
|
||||||
mutationsRejectedOnWrongSystem = createTableMeter("MutationsRejectedOnWrongSystem", cfs.keyspace.metric.mutationsRejectedOnWrongSystem);
|
mutationsRejectedOnWrongSystem = createTableMeter("MutationsRejectedOnWrongSystem", cfs.keyspace.metric.mutationsRejectedOnWrongSystem);
|
||||||
readsRejectedOnWrongSystem = createTableMeter("ReadsRejectedOnWrongSystem", cfs.keyspace.metric.mutationsRejectedOnWrongSystem);
|
readsRejectedOnWrongSystem = createTableMeter("ReadsRejectedOnWrongSystem", cfs.keyspace.metric.readsRejectedOnWrongSystem);
|
||||||
|
|
||||||
repairsStarted = createTableCounter("RepairJobsStarted");
|
repairsStarted = createTableCounter("RepairJobsStarted");
|
||||||
repairsCompleted = createTableCounter("RepairJobsCompleted");
|
repairsCompleted = createTableCounter("RepairJobsCompleted");
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import com.google.common.base.Objects;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
import org.apache.cassandra.config.CassandraRelevantProperties;
|
||||||
import org.apache.cassandra.cql3.Attributes;
|
import org.apache.cassandra.cql3.Attributes;
|
||||||
import org.apache.cassandra.cql3.CqlBuilder;
|
import org.apache.cassandra.cql3.CqlBuilder;
|
||||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||||
|
|
@ -244,6 +245,9 @@ public final class TableParams
|
||||||
|
|
||||||
if (cdc && memtable.factory().writesShouldSkipCommitLog())
|
if (cdc && memtable.factory().writesShouldSkipCommitLog())
|
||||||
fail("CDC cannot work if writes skip the commit log. Check your memtable configuration.");
|
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)
|
private static void fail(String format, Object... args)
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,8 @@ public abstract class AbstractWriteResponseHandler<T> implements RequestCallback
|
||||||
// Retrying on the correct system might make this write succeed
|
// Retrying on the correct system might make this write succeed
|
||||||
if (transactionRetryErrors > 0)
|
if (transactionRetryErrors > 0)
|
||||||
throw new RetryOnDifferentSystemException();
|
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());
|
throw new WriteFailureException(replicaPlan.consistencyLevel(), ackCount(), blockFor(), writeType, getFailureReasonByEndpointMap());
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,10 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
@ -43,8 +45,10 @@ import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Iterators;
|
||||||
import com.google.common.util.concurrent.Uninterruptibles;
|
import com.google.common.util.concurrent.Uninterruptibles;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.rows.RowIterator;
|
||||||
import org.apache.cassandra.db.view.ViewUtils;
|
import org.apache.cassandra.db.view.ViewUtils;
|
||||||
import org.apache.cassandra.dht.AbstractBounds;
|
import org.apache.cassandra.dht.AbstractBounds;
|
||||||
import org.apache.cassandra.dht.Range;
|
|
||||||
import org.apache.cassandra.dht.Token;
|
import org.apache.cassandra.dht.Token;
|
||||||
import org.apache.cassandra.exceptions.CasWriteTimeoutException;
|
import org.apache.cassandra.exceptions.CasWriteTimeoutException;
|
||||||
import org.apache.cassandra.exceptions.CasWriteUnknownResultException;
|
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.TxnData;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnDataKeyValue;
|
import org.apache.cassandra.service.accord.txn.TxnDataKeyValue;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnDataValue;
|
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.TxnQuery;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnRangeRead;
|
|
||||||
import org.apache.cassandra.service.accord.txn.TxnRangeReadResult;
|
import org.apache.cassandra.service.accord.txn.TxnRangeReadResult;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnRead;
|
import org.apache.cassandra.service.accord.txn.TxnRead;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnResult;
|
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.SplitConsumer;
|
||||||
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper.SplitMutations;
|
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;
|
||||||
|
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.SplitReads;
|
||||||
import org.apache.cassandra.service.consensus.migration.TransactionalMigrationFromMode;
|
import org.apache.cassandra.service.consensus.migration.TransactionalMigrationFromMode;
|
||||||
import org.apache.cassandra.service.paxos.Ballot;
|
import org.apache.cassandra.service.paxos.Ballot;
|
||||||
import org.apache.cassandra.service.paxos.Commit;
|
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 accord.primitives.Txn.Kind.Read;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
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 com.google.common.collect.Iterables.concat;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
import static org.apache.cassandra.db.ConsistencyLevel.SERIAL;
|
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.casReadMetrics;
|
||||||
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.casWriteMetrics;
|
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.casWriteMetrics;
|
||||||
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.readMetrics;
|
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.ConsensusRoutingDecision;
|
||||||
import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.getTableMetadata;
|
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.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.GLOBAL;
|
||||||
import static org.apache.cassandra.service.paxos.Ballot.Flag.LOCAL;
|
import static org.apache.cassandra.service.paxos.Ballot.Flag.LOCAL;
|
||||||
import static org.apache.cassandra.service.paxos.BallotGenerator.Global.nextBallot;
|
import static org.apache.cassandra.service.paxos.BallotGenerator.Global.nextBallot;
|
||||||
|
|
@ -1272,7 +1275,7 @@ public class StorageProxy implements StorageProxyMBean
|
||||||
{
|
{
|
||||||
writeMetrics.retryDifferentSystem.mark();
|
writeMetrics.retryDifferentSystem.mark();
|
||||||
writeMetricsForLevel(consistencyLevel).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);
|
Tracing.trace("Got {} from normal mutations, will retry", e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1296,9 +1299,13 @@ public class StorageProxy implements StorageProxyMBean
|
||||||
if (accordResult != null)
|
if (accordResult != null)
|
||||||
{
|
{
|
||||||
IAccordService accord = AccordService.instance();
|
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)
|
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;
|
continue;
|
||||||
|
}
|
||||||
Tracing.trace("Successfully wrote Accord mutations");
|
Tracing.trace("Successfully wrote Accord mutations");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1400,9 +1407,9 @@ public class StorageProxy implements StorageProxyMBean
|
||||||
boolean wroteToBatchLog = false;
|
boolean wroteToBatchLog = false;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
ClusterMetadata cm = ClusterMetadata.current();
|
||||||
// In case we hit an error in before/during splitting
|
// In case we hit an error in before/during splitting
|
||||||
attributeNonAccordLatency = true;
|
attributeNonAccordLatency = true;
|
||||||
ClusterMetadata cm = ClusterMetadata.current();
|
|
||||||
List<WriteResponseHandlerWrapper> wrappers = new ArrayList<>(mutations.size());
|
List<WriteResponseHandlerWrapper> wrappers = new ArrayList<>(mutations.size());
|
||||||
List<Mutation> accordMutations = new ArrayList<>(mutations.size());
|
List<Mutation> accordMutations = new ArrayList<>(mutations.size());
|
||||||
BatchlogCleanup cleanup = new BatchlogCleanup(() -> asyncRemoveFromBatchlog(batchlogReplicaPlan, batchUUID, requestTime));
|
BatchlogCleanup cleanup = new BatchlogCleanup(() -> asyncRemoveFromBatchlog(batchlogReplicaPlan, batchUUID, requestTime));
|
||||||
|
|
@ -1506,7 +1513,7 @@ public class StorageProxy implements StorageProxyMBean
|
||||||
if (accordResult != null)
|
if (accordResult != null)
|
||||||
{
|
{
|
||||||
IAccordService accord = AccordService.instance();
|
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)
|
if (kind == retry_new_protocol && failure == null)
|
||||||
continue;
|
continue;
|
||||||
Tracing.trace("Successfully wrote Accord mutations");
|
Tracing.trace("Successfully wrote Accord mutations");
|
||||||
|
|
@ -2121,7 +2128,7 @@ public class StorageProxy implements StorageProxyMBean
|
||||||
|
|
||||||
return consistencyLevel.isSerialConsistency()
|
return consistencyLevel.isSerialConsistency()
|
||||||
? readWithConsensus(group, consistencyLevel, requestTime)
|
? readWithConsensus(group, consistencyLevel, requestTime)
|
||||||
: readRegular(group, consistencyLevel, requestTime);
|
: dispatchReadWithRetryOnDifferentSystem(group, consistencyLevel, ReadCoordinator.DEFAULT, requestTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasJoined()
|
public static boolean hasJoined()
|
||||||
|
|
@ -2176,45 +2183,44 @@ public class StorageProxy implements StorageProxyMBean
|
||||||
return lastResult.serialReadResult;
|
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
|
// 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
|
// reading at ONE if you are reading data that wasn't written via Accord
|
||||||
if (consistencyLevel == null)
|
if (consistencyLevel == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
TableId tableId = group.queries.get(0).metadata().id;
|
|
||||||
TableParams tableParams = getTableMetadata(cm, tableId).params;
|
TableParams tableParams = getTableMetadata(cm, tableId).params;
|
||||||
TransactionalMode mode = tableParams.transactionalMode;
|
TransactionalMode mode = tableParams.transactionalMode;
|
||||||
TransactionalMigrationFromMode migrationFromMode = tableParams.transactionalMigrationFrom;
|
TransactionalMigrationFromMode migrationFromMode = tableParams.transactionalMigrationFrom;
|
||||||
for (SinglePartitionReadCommand command : group.queries)
|
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
|
// in which case we will read everything at that CL since Accord doesn't support per table
|
||||||
// read consistency
|
// read consistency
|
||||||
ConsistencyLevel commitCL = mode.readCLForStrategy(migrationFromMode, consistencyLevel, cm, tableId, command.partitionKey().getToken());
|
ConsistencyLevel readCL = mode.readCLForMode(migrationFromMode, consistencyLevel, cm, tableId, command.partitionKey().getToken());
|
||||||
if (commitCL != null)
|
if (readCL != null)
|
||||||
return commitCL;
|
return readCL;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AsyncTxnResult readWithAccord(ClusterMetadata cm, PartitionRangeReadCommand command, List<AbstractBounds<PartitionPosition>> ranges, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) {
|
public static AsyncTxnResult readWithAccord(ClusterMetadata cm, PartitionRangeReadCommand command, AbstractBounds<PartitionPosition> range, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
|
||||||
|
{
|
||||||
if (consistencyLevel != null && !IAccordService.SUPPORTED_READ_CONSISTENCY_LEVELS.contains(consistencyLevel))
|
if (consistencyLevel != null && !IAccordService.SUPPORTED_READ_CONSISTENCY_LEVELS.contains(consistencyLevel))
|
||||||
throw new InvalidRequestException(consistencyLevel + " is not supported by Accord");
|
throw new InvalidRequestException(consistencyLevel + " is not supported by Accord");
|
||||||
|
|
||||||
TableMetadata tableMetadata = getTableMetadata(cm, command.metadata().id);
|
TableMetadata tableMetadata = getTableMetadata(cm, command.metadata().id);
|
||||||
TableParams tableParams = tableMetadata.params;
|
TableParams tableParams = tableMetadata.params;
|
||||||
Range<Token> readRange = new Range<>(command.dataRange().startKey().getToken(), command.dataRange().stopKey().getToken());
|
consistencyLevel = tableParams.transactionalMode.readCLForMode(tableParams.transactionalMigrationFrom, consistencyLevel, cm, tableMetadata.id, command.dataRange().keyRange());
|
||||||
consistencyLevel = tableParams.transactionalMode.readCLForStrategy(tableParams.transactionalMigrationFrom, consistencyLevel, cm, tableMetadata.id, readRange);
|
TxnRead read = TxnRead.createRangeRead(command, range, consistencyLevel);
|
||||||
TxnRead read = new TxnRangeRead(command, ranges, consistencyLevel);
|
|
||||||
Txn.Kind kind = shouldReadEphemerally(read.keys(), tableParams, Read);
|
Txn.Kind kind = shouldReadEphemerally(read.keys(), tableParams, Read);
|
||||||
Txn txn = new Txn.InMemory(kind, read.keys(), read, TxnQuery.RANGE_QUERY, null);
|
Txn txn = new Txn.InMemory(kind, read.keys(), read, TxnQuery.RANGE_QUERY, null);
|
||||||
IAccordService accordService = AccordService.instance();
|
IAccordService accordService = AccordService.instance();
|
||||||
return accordService.coordinateAsync(tableMetadata.epoch.getEpoch(), txn, consistencyLevel, requestTime);
|
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))
|
if (consistencyLevel != null && !IAccordService.SUPPORTED_READ_CONSISTENCY_LEVELS.contains(consistencyLevel))
|
||||||
throw new InvalidRequestException(consistencyLevel + " is not supported by Accord");
|
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
|
// level since Accord will manage reading safely
|
||||||
TableMetadata tableMetadata = getTableMetadata(cm, group.metadata().id);
|
TableMetadata tableMetadata = getTableMetadata(cm, group.metadata().id);
|
||||||
TableParams tableParams = tableMetadata.params;
|
TableParams tableParams = tableMetadata.params;
|
||||||
consistencyLevel = consistencyLevelForAccordRead(cm, group, consistencyLevel);
|
consistencyLevel = consistencyLevelForAccordRead(cm, group.queries.get(0).metadata().id, group, consistencyLevel);
|
||||||
TxnKeyRead read = TxnKeyRead.createSerialRead(group.queries, consistencyLevel);
|
TxnRead read = TxnRead.createSerialRead(group.queries, consistencyLevel);
|
||||||
Txn.Kind kind = shouldReadEphemerally(read.keys(), tableParams, Read);
|
Txn.Kind kind = shouldReadEphemerally(read.keys(), tableParams, Read);
|
||||||
Txn txn = new Txn.InMemory(kind, read.keys(), read, TxnQuery.ALL, null);
|
Txn txn = new Txn.InMemory(kind, read.keys(), read, TxnQuery.ALL, null);
|
||||||
AsyncTxnResult asyncTxnResult = AccordService.instance().coordinateAsync(tableMetadata.epoch.getEpoch(), txn, consistencyLevel, requestTime);
|
return AccordService.instance().coordinateAsync(tableMetadata.epoch.getEpoch(), txn, consistencyLevel, requestTime);
|
||||||
return getConsensusAttemptResultFromAsyncTxnResult(asyncTxnResult, group.queries.size(), index -> group.queries.get(index).isReversed(), 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
|
* 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
|
// 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)
|
if (txnResult.kind() == retry_new_protocol)
|
||||||
return RETRY_NEW_PROTOCOL;
|
return RETRY_NEW_PROTOCOL;
|
||||||
|
|
@ -2251,7 +2262,7 @@ public class StorageProxy implements StorageProxyMBean
|
||||||
else if (data.size() == 1)
|
else if (data.size() == 1)
|
||||||
{
|
{
|
||||||
TxnDataKeyValue value = ((TxnDataKeyValue)data.values().iterator().next());
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -2263,7 +2274,7 @@ public class StorageProxy implements StorageProxyMBean
|
||||||
{
|
{
|
||||||
int queryIndex = e.getKey();
|
int queryIndex = e.getKey();
|
||||||
TxnDataKeyValue value = ((TxnDataKeyValue)e.getValue());
|
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));
|
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<PartitionIterator> partitions = new ArrayList<>(group.queries.size());
|
||||||
|
Iterator<RowIterator> 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")
|
@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
|
throws UnavailableException, ReadFailureException, ReadTimeoutException
|
||||||
{
|
{
|
||||||
long start = nanoTime();
|
long start = nanoTime();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ClusterMetadata cm = ClusterMetadata.current();
|
return fetchRows(group.queries, consistencyLevel, coordinator, requestTime);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
catch (UnavailableException e)
|
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)
|
public static void recordReadRegularAbort(ConsistencyLevel consistencyLevel, Throwable cause)
|
||||||
{
|
{
|
||||||
readMetrics.markAbort(cause);
|
readMetrics.markAbort(cause);
|
||||||
|
|
@ -2493,8 +2578,11 @@ public class StorageProxy implements StorageProxyMBean
|
||||||
* 3. Wait for a response from R replicas
|
* 3. Wait for a response from R replicas
|
||||||
* 4. If the digests (if any) match the data return the data
|
* 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.
|
* 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<SinglePartitionReadCommand> commands,
|
public static PartitionIterator fetchRows(List<SinglePartitionReadCommand> commands,
|
||||||
ConsistencyLevel consistencyLevel,
|
ConsistencyLevel consistencyLevel,
|
||||||
ReadCoordinator coordinator,
|
ReadCoordinator coordinator,
|
||||||
Dispatcher.RequestTime requestTime)
|
Dispatcher.RequestTime requestTime)
|
||||||
|
|
|
||||||
|
|
@ -1677,14 +1677,12 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void migrateConsensusProtocol(@Nonnull String targetProtocol,
|
public void migrateConsensusProtocol(@Nonnull List<String> keyspaceNames,
|
||||||
@Nonnull List<String> keyspaceNames,
|
|
||||||
@Nullable List<String> maybeTableNames,
|
@Nullable List<String> maybeTableNames,
|
||||||
@Nullable String maybeRangesStr)
|
@Nullable String maybeRangesStr)
|
||||||
{
|
{
|
||||||
checkNotNull(targetProtocol, "targetProtocol is null");
|
|
||||||
checkArgument(!keyspaceNames.contains(SchemaConstants.METADATA_KEYSPACE_NAME));
|
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
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1142,8 +1142,7 @@ public interface StorageServiceMBean extends NotificationEmitter
|
||||||
public String getBootstrapState();
|
public String getBootstrapState();
|
||||||
void abortBootstrap(String nodeId, String endpoint);
|
void abortBootstrap(String nodeId, String endpoint);
|
||||||
|
|
||||||
void migrateConsensusProtocol(@Nonnull String targetProtocol,
|
void migrateConsensusProtocol(@Nullable List<String> keyspaceNames,
|
||||||
@Nullable List<String> keyspaceNames,
|
|
||||||
@Nullable List<String> maybeTableNames,
|
@Nullable List<String> maybeTableNames,
|
||||||
@Nullable String maybeRangesStr);
|
@Nullable String maybeRangesStr);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<T extends Reply> extends SafeCallback<T> implements RequestCallback<T>
|
|
||||||
{
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AccordCallback.class);
|
|
||||||
private final AccordEndpointMapper endpointMapper;
|
|
||||||
|
|
||||||
public AccordCallback(AgentExecutor executor, Callback<T> callback, AccordEndpointMapper endpointMapper)
|
|
||||||
{
|
|
||||||
super(executor, callback);
|
|
||||||
this.endpointMapper = endpointMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponse(Message<T> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -52,13 +52,16 @@ import org.apache.cassandra.repair.SharedContext;
|
||||||
import org.apache.cassandra.service.accord.AccordKeyspace.EpochDiskState;
|
import org.apache.cassandra.service.accord.AccordKeyspace.EpochDiskState;
|
||||||
import org.apache.cassandra.tcm.ClusterMetadata;
|
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||||
import org.apache.cassandra.tcm.ClusterMetadataService;
|
import org.apache.cassandra.tcm.ClusterMetadataService;
|
||||||
|
import org.apache.cassandra.tcm.Epoch;
|
||||||
import org.apache.cassandra.tcm.listeners.ChangeListener;
|
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.FBUtilities;
|
||||||
import org.apache.cassandra.utils.Simulate;
|
import org.apache.cassandra.utils.Simulate;
|
||||||
import org.apache.cassandra.utils.concurrent.AsyncPromise;
|
import org.apache.cassandra.utils.concurrent.AsyncPromise;
|
||||||
import org.apache.cassandra.utils.concurrent.Future;
|
import org.apache.cassandra.utils.concurrent.Future;
|
||||||
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
|
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;
|
import static org.apache.cassandra.utils.Simulate.With.MONITORS;
|
||||||
|
|
||||||
// TODO: listen to FailureDetector and rearrange fast path accordingly
|
// TODO: listen to FailureDetector and rearrange fast path accordingly
|
||||||
|
|
@ -344,16 +347,22 @@ public class AccordConfigurationService extends AbstractConfigurationService<Acc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reportTopology(topology);
|
reportTopology(topology);
|
||||||
if (epochs.lastAcknowledged() >= topology.epoch()) checkIfNodesRemoved(topology);
|
Set<Node.Id> stillLiveNodes = metadata.directory.states.entrySet()
|
||||||
else epochs.acknowledgeFuture(topology.epoch()).addCallback(() -> checkIfNodesRemoved(topology));
|
.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<Node.Id> stillLiveNodes)
|
||||||
{
|
{
|
||||||
if (epochs.minEpoch() == topology.epoch()) return;
|
if (epochs.minEpoch() == topology.epoch()) return;
|
||||||
Topology previous = getTopologyForEpoch(topology.epoch() - 1);
|
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
|
// for all nodes removed, or pending removal, mark them as removed so we don't wait on their replies
|
||||||
Sets.SetView<Node.Id> removedNodes = Sets.difference(previous.nodes(), topology.nodes());
|
Set<Node.Id> 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
|
// TODO (desired, efficiency): there should be no need to notify every epoch for every removed node
|
||||||
for (Node.Id removedNode : removedNodes)
|
for (Node.Id removedNode : removedNodes)
|
||||||
{
|
{
|
||||||
|
|
@ -420,29 +429,36 @@ public class AccordConfigurationService extends AbstractConfigurationService<Acc
|
||||||
@Override
|
@Override
|
||||||
protected void fetchTopologyInternal(long epoch)
|
protected void fetchTopologyInternal(long epoch)
|
||||||
{
|
{
|
||||||
try
|
// It's not safe for this to block on CMS so for now pick a thread pool to handle it
|
||||||
{
|
Stage.ACCORD_MIGRATION.execute(() -> {
|
||||||
Set<InetAddressAndPort> peers = new HashSet<>(ClusterMetadata.current().directory.allJoinedEndpoints());
|
if (ClusterMetadata.current().epoch.getEpoch() < epoch)
|
||||||
peers.remove(FBUtilities.getBroadcastAddressAndPort());
|
ClusterMetadataService.instance().fetchLogFromCMS(Epoch.create(epoch));
|
||||||
if (peers.isEmpty())
|
try
|
||||||
return;
|
{
|
||||||
Topology topology;
|
Set<InetAddressAndPort> peers = new HashSet<>(ClusterMetadata.current().directory.allJoinedEndpoints());
|
||||||
while ((topology =FetchTopology.fetch(SharedContext.Global.instance, peers, epoch).get()) == null) {}
|
peers.remove(FBUtilities.getBroadcastAddressAndPort());
|
||||||
reportTopology(topology);
|
if (peers.isEmpty())
|
||||||
}
|
return;
|
||||||
catch (InterruptedException e)
|
Topology topology;
|
||||||
{
|
while ((topology = FetchTopology.fetch(SharedContext.Global.instance, peers, epoch).get()) == null)
|
||||||
if (currentEpoch() >= epoch)
|
{
|
||||||
return;
|
}
|
||||||
Thread.currentThread().interrupt();
|
reportTopology(topology);
|
||||||
throw new UncheckedInterruptedException(e);
|
}
|
||||||
}
|
catch (InterruptedException e)
|
||||||
catch (Throwable e)
|
{
|
||||||
{
|
if (currentEpoch() >= epoch)
|
||||||
if (currentEpoch() >= epoch)
|
return;
|
||||||
return;
|
Thread.currentThread().interrupt();
|
||||||
throw new RuntimeException(e.getCause());
|
throw new UncheckedInterruptedException(e);
|
||||||
}
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
if (currentEpoch() >= epoch)
|
||||||
|
return;
|
||||||
|
throw new RuntimeException(e.getCause());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ import accord.primitives.Ranges;
|
||||||
import accord.primitives.Routable.Domain;
|
import accord.primitives.Routable.Domain;
|
||||||
import accord.primitives.RoutingKeys;
|
import accord.primitives.RoutingKeys;
|
||||||
import accord.primitives.SaveStatus;
|
import accord.primitives.SaveStatus;
|
||||||
|
import accord.primitives.Seekable;
|
||||||
import accord.primitives.Seekables;
|
import accord.primitives.Seekables;
|
||||||
import accord.primitives.Status;
|
import accord.primitives.Status;
|
||||||
import accord.primitives.Timestamp;
|
import accord.primitives.Timestamp;
|
||||||
|
|
@ -94,6 +95,16 @@ public class AccordObjectSizes
|
||||||
return EMPTY_RANGE_SIZE + key(range.start()) + key(range.end());
|
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());
|
private static final long EMPTY_RANGES_SIZE = measure(Ranges.of());
|
||||||
public static long ranges(Ranges ranges)
|
public static long ranges(Ranges ranges)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ class AccordResponseVerbHandler<T extends Reply> implements IVerbHandler<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
Node.Id from = endpointMapper.mappedId(message.from());
|
Node.Id from = endpointMapper.mappedId(message.from());
|
||||||
|
logger.trace("Receiving {} from {}", message.payload, message.from());
|
||||||
if (message.isFailureResponse())
|
if (message.isFailureResponse())
|
||||||
{
|
{
|
||||||
Tracing.trace("Processing failure response from {}", message.from());
|
Tracing.trace("Processing failure response from {}", message.from());
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
@ -118,11 +119,11 @@ import org.agrona.collections.Int2ObjectHashMap;
|
||||||
import org.apache.cassandra.concurrent.Shutdownable;
|
import org.apache.cassandra.concurrent.Shutdownable;
|
||||||
import org.apache.cassandra.config.CassandraRelevantProperties;
|
import org.apache.cassandra.config.CassandraRelevantProperties;
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
import org.apache.cassandra.cql3.statements.RequestValidations;
|
|
||||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
import org.apache.cassandra.db.ConsistencyLevel;
|
import org.apache.cassandra.db.ConsistencyLevel;
|
||||||
import org.apache.cassandra.db.WriteType;
|
import org.apache.cassandra.db.WriteType;
|
||||||
import org.apache.cassandra.dht.AccordSplitter;
|
import org.apache.cassandra.dht.AccordSplitter;
|
||||||
|
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||||
import org.apache.cassandra.exceptions.ReadTimeoutException;
|
import org.apache.cassandra.exceptions.ReadTimeoutException;
|
||||||
import org.apache.cassandra.exceptions.RequestExecutionException;
|
import org.apache.cassandra.exceptions.RequestExecutionException;
|
||||||
import org.apache.cassandra.exceptions.RequestTimeoutException;
|
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.exceptions.WritePreemptedException;
|
||||||
import org.apache.cassandra.service.accord.interop.AccordInteropAdapter.AccordInteropFactory;
|
import org.apache.cassandra.service.accord.interop.AccordInteropAdapter.AccordInteropFactory;
|
||||||
import org.apache.cassandra.service.accord.repair.RepairSyncPointAdapter;
|
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.accord.txn.TxnResult;
|
||||||
import org.apache.cassandra.service.consensus.TransactionalMode;
|
import org.apache.cassandra.service.consensus.TransactionalMode;
|
||||||
import org.apache.cassandra.service.consensus.migration.TableMigrationState;
|
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
|
// 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 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
|
// 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);
|
TableMigrationState tms = cm.consensusMigrationState.tableStates.get(tm.id);
|
||||||
// null is fine could be completely migrated or was always an Accord table on creation
|
// null is fine could be completely migrated or was always an Accord table on creation
|
||||||
if (tms == null)
|
if (tms == null)
|
||||||
return keysOrRanges;
|
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);
|
Ranges migratingAndMigratedRanges = AccordTopology.toAccordRanges(tms.tableId, tms.migratingAndMigratedRanges);
|
||||||
return keysOrRanges.slice(migratingAndMigratedRanges);
|
return keysOrRanges.slice(migratingAndMigratedRanges);
|
||||||
}
|
}
|
||||||
|
|
@ -739,6 +743,34 @@ public class AccordService implements IAccordService, Shutdownable
|
||||||
return node.topology();
|
return node.topology();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Set<TableId> txnDroppedTables(Seekables<?,?> keys)
|
||||||
|
{
|
||||||
|
Set<TableId> 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<TableId> 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
|
* Consistency level is just echoed back in timeouts, in the future it may be used for interoperability
|
||||||
* with non-Accord operations.
|
* 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)
|
public @Nonnull TxnResult coordinate(long minEpoch, @Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, @Nonnull Dispatcher.RequestTime requestTime)
|
||||||
{
|
{
|
||||||
AsyncTxnResult asyncTxnResult = coordinateAsync(minEpoch, txn, consistencyLevel, requestTime);
|
AsyncTxnResult asyncTxnResult = coordinateAsync(minEpoch, txn, consistencyLevel, requestTime);
|
||||||
return getTxnResult(asyncTxnResult, txn.isWrite(), consistencyLevel, requestTime);
|
return getTxnResult(asyncTxnResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -769,7 +801,8 @@ public class AccordService implements IAccordService, Shutdownable
|
||||||
metrics.keySize.update(txn.keys().size());
|
metrics.keySize.update(txn.keys().size());
|
||||||
long deadlineNanos = requestTime.computeDeadline(DatabaseDescriptor.getTransactionTimeout(NANOSECONDS));
|
long deadlineNanos = requestTime.computeDeadline(DatabaseDescriptor.getTransactionTimeout(NANOSECONDS));
|
||||||
AsyncResult<Result> asyncResult = node.coordinate(txnId, txn, minEpoch, deadlineNanos);
|
AsyncResult<Result> 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) -> {
|
asyncResult.addCallback((success, failure) -> {
|
||||||
long durationNanos = nanoTime() - requestTime.startedAtNanos();
|
long durationNanos = nanoTime() - requestTime.startedAtNanos();
|
||||||
sharedMetrics.addNano(durationNanos);
|
sharedMetrics.addNano(durationNanos);
|
||||||
|
|
@ -786,6 +819,9 @@ public class AccordService implements IAccordService, Shutdownable
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sharedMetrics.failures.mark();
|
||||||
|
metrics.failures.mark();
|
||||||
|
|
||||||
if (cause instanceof Timeout)
|
if (cause instanceof Timeout)
|
||||||
{
|
{
|
||||||
// Don't mark the metric here, should be done in getTxnResult to ensure it only happens once
|
// 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)
|
if (cause instanceof Preempted || cause instanceof Invalidated)
|
||||||
{
|
{
|
||||||
sharedMetrics.timeouts.mark();
|
|
||||||
metrics.preempted.mark();
|
metrics.preempted.mark();
|
||||||
//TODO need to improve
|
//TODO need to improve
|
||||||
// Coordinator "could" query the accord state to see whats going on but that doesn't exist yet.
|
// 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));
|
asyncTxnResult.tryFailure(newPreempted(txnId, txn.isWrite(), consistencyLevel));
|
||||||
return;
|
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)
|
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<TableId> txnDroppedTables = txnDroppedTables(keys);
|
||||||
|
Tracing.trace("Accord returned topology mismatch: " + cause.getMessage());
|
||||||
|
logger.debug("Accord returned topology mismatch", cause);
|
||||||
metrics.topologyMismatches.mark();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
metrics.failures.mark();
|
|
||||||
asyncTxnResult.tryFailure(new RuntimeException(cause));
|
asyncTxnResult.tryFailure(new RuntimeException(cause));
|
||||||
});
|
});
|
||||||
return asyncTxnResult;
|
return asyncTxnResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TxnResult getTxnResult(AsyncTxnResult asyncTxnResult, boolean isWrite, @Nullable ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
|
public TxnResult getTxnResult(AsyncTxnResult asyncTxnResult)
|
||||||
{
|
{
|
||||||
ClientRequestMetrics sharedMetrics;
|
ClientRequestMetrics sharedMetrics;
|
||||||
AccordClientRequestMetrics metrics;
|
AccordClientRequestMetrics metrics;
|
||||||
if (isWrite)
|
if (asyncTxnResult.isWrite)
|
||||||
{
|
{
|
||||||
sharedMetrics = ClientRequestsMetricsHolder.writeMetrics;
|
sharedMetrics = ClientRequestsMetricsHolder.writeMetrics;
|
||||||
metrics = accordWriteMetrics;
|
metrics = accordWriteMetrics;
|
||||||
|
|
@ -833,7 +882,7 @@ public class AccordService implements IAccordService, Shutdownable
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
long deadlineNanos = requestTime.computeDeadline(DatabaseDescriptor.getTransactionTimeout(NANOSECONDS));
|
long deadlineNanos = asyncTxnResult.requestTime.computeDeadline(DatabaseDescriptor.getTransactionTimeout(NANOSECONDS));
|
||||||
TxnResult result = asyncTxnResult.get(deadlineNanos - nanoTime(), NANOSECONDS);
|
TxnResult result = asyncTxnResult.get(deadlineNanos - nanoTime(), NANOSECONDS);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -851,6 +900,8 @@ public class AccordService implements IAccordService, Shutdownable
|
||||||
}
|
}
|
||||||
else if (cause instanceof RuntimeException)
|
else if (cause instanceof RuntimeException)
|
||||||
throw (RuntimeException) cause;
|
throw (RuntimeException) cause;
|
||||||
|
else if (cause instanceof InvalidRequestException)
|
||||||
|
throw ((InvalidRequestException)cause);
|
||||||
else
|
else
|
||||||
throw new RuntimeException(cause);
|
throw new RuntimeException(cause);
|
||||||
}
|
}
|
||||||
|
|
@ -864,7 +915,7 @@ public class AccordService implements IAccordService, Shutdownable
|
||||||
{
|
{
|
||||||
metrics.timeouts.mark();
|
metrics.timeouts.mark();
|
||||||
sharedMetrics.timeouts.mark();
|
sharedMetrics.timeouts.mark();
|
||||||
throw newTimeout(asyncTxnResult.txnId, isWrite, consistencyLevel);
|
throw newTimeout(asyncTxnResult.txnId, asyncTxnResult.isWrite, asyncTxnResult.consistencyLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,17 +18,25 @@
|
||||||
|
|
||||||
package org.apache.cassandra.service.accord;
|
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.ExecutionException;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.stream.Collectors;
|
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.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
|
import accord.local.Node.Id;
|
||||||
|
import accord.primitives.Ranges;
|
||||||
import accord.topology.Shard;
|
import accord.topology.Shard;
|
||||||
import accord.topology.Topology;
|
import accord.topology.Topology;
|
||||||
import accord.utils.Invariants;
|
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.Range;
|
||||||
import org.apache.cassandra.dht.Token;
|
import org.apache.cassandra.dht.Token;
|
||||||
import org.apache.cassandra.locator.InetAddressAndPort;
|
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.SentinelKey;
|
||||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
||||||
import org.apache.cassandra.service.accord.fastpath.FastPathStrategy;
|
import org.apache.cassandra.service.accord.fastpath.FastPathStrategy;
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@ import accord.local.Node;
|
||||||
import accord.messages.Request;
|
import accord.messages.Request;
|
||||||
import org.apache.cassandra.net.IVerbHandler;
|
import org.apache.cassandra.net.IVerbHandler;
|
||||||
import org.apache.cassandra.net.Message;
|
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;
|
import org.apache.cassandra.utils.NoSpamLogger;
|
||||||
|
|
||||||
public class AccordVerbHandler<T extends Request> implements IVerbHandler<T>
|
public class AccordVerbHandler<T extends Request> implements IVerbHandler<T>
|
||||||
|
|
@ -53,6 +56,7 @@ public class AccordVerbHandler<T extends Request> implements IVerbHandler<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.trace("Receiving {} from {}", message.payload, message.from());
|
logger.trace("Receiving {} from {}", message.payload, message.from());
|
||||||
|
|
||||||
T request = message.payload;
|
T request = message.payload;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -62,14 +66,32 @@ public class AccordVerbHandler<T extends Request> implements IVerbHandler<T>
|
||||||
*/
|
*/
|
||||||
Node.Id fromNodeId = endpointMapper.mappedId(message.from());
|
Node.Id fromNodeId = endpointMapper.mappedId(message.from());
|
||||||
long waitForEpoch = request.waitForEpoch();
|
long waitForEpoch = request.waitForEpoch();
|
||||||
|
ClusterMetadata cm = ClusterMetadata.current();
|
||||||
if (node.topology().hasAtLeastEpoch(waitForEpoch))
|
boolean cmUpToDate = ClusterMetadata.current().epoch.getEpoch() >= waitForEpoch;
|
||||||
|
if (node.topology().hasAtLeastEpoch(waitForEpoch) && cmUpToDate)
|
||||||
request.process(node, fromNodeId, message.header);
|
request.process(node, fromNodeId, message.header);
|
||||||
else
|
else
|
||||||
node.withEpoch(waitForEpoch, (ignored, withEpochFailure) -> {
|
{
|
||||||
if (withEpochFailure != null)
|
// withEpoch does not reliably ensure that TCM is up to date, if Accord has the topology it won't
|
||||||
throw new RuntimeException("Timed out waiting for epoch when processing message from " + fromNodeId + " to " + node + " message " + message, withEpochFailure);
|
// wait for TCM to come up to date, so do it here in the verb handler
|
||||||
request.process(node, fromNodeId, message.header);
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,17 +110,27 @@ public interface IAccordService
|
||||||
class AsyncTxnResult extends AsyncPromise<TxnResult>
|
class AsyncTxnResult extends AsyncPromise<TxnResult>
|
||||||
{
|
{
|
||||||
public final @Nonnull TxnId txnId;
|
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(txnId);
|
||||||
|
checkNotNull(requestTime);
|
||||||
this.txnId = txnId;
|
this.txnId = txnId;
|
||||||
|
this.minEpoch = minEpoch;
|
||||||
|
this.consistencyLevel = consistencyLevel;
|
||||||
|
this.isWrite = isWrite;
|
||||||
|
this.requestTime = requestTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
AsyncTxnResult coordinateAsync(long minEpoch, @Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime);
|
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();
|
long currentEpoch();
|
||||||
|
|
||||||
|
|
@ -236,7 +246,7 @@ public interface IAccordService
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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");
|
throw new UnsupportedOperationException("No accord transaction should be executed when accord.enabled = false in cassandra.yaml");
|
||||||
}
|
}
|
||||||
|
|
@ -418,9 +428,9 @@ public interface IAccordService
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,14 @@ import org.apache.cassandra.io.util.DataOutputPlus;
|
||||||
import org.apache.cassandra.schema.TableId;
|
import org.apache.cassandra.schema.TableId;
|
||||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
|
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
|
||||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.SentinelKey;
|
import org.apache.cassandra.service.accord.api.AccordRoutingKey.SentinelKey;
|
||||||
|
import org.apache.cassandra.utils.ObjectSizes;
|
||||||
|
|
||||||
public class TokenRange extends Range.EndInclusive
|
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);
|
super(start, end);
|
||||||
}
|
}
|
||||||
|
|
@ -72,6 +76,11 @@ public class TokenRange extends Range.EndInclusive
|
||||||
return (AccordRoutingKey) super.end();
|
return (AccordRoutingKey) super.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long estimatedSizeOnHeap()
|
||||||
|
{
|
||||||
|
return EMPTY_SIZE + start().estimatedSizeOnHeap() + end().estimatedSizeOnHeap();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isFullRange()
|
public boolean isFullRange()
|
||||||
{
|
{
|
||||||
return start().kindOfRoutingKey() == AccordRoutingKey.RoutingKeyKind.SENTINEL && end().kindOfRoutingKey() == AccordRoutingKey.RoutingKeyKind.SENTINEL;
|
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);
|
return new TokenRange((AccordRoutingKey) start, (AccordRoutingKey) end);
|
||||||
}
|
}
|
||||||
|
|
||||||
public org.apache.cassandra.dht.Range<Token> 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<Token> toKeyspaceRange()
|
||||||
{
|
{
|
||||||
IPartitioner partitioner = DatabaseDescriptor.getPartitioner();
|
IPartitioner partitioner = DatabaseDescriptor.getPartitioner();
|
||||||
AccordRoutingKey start = start();
|
AccordRoutingKey start = start();
|
||||||
|
|
@ -104,7 +118,6 @@ public class TokenRange extends Range.EndInclusive
|
||||||
return new org.apache.cassandra.dht.Range<>(left, right);
|
return new org.apache.cassandra.dht.Range<>(left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static final Serializer serializer = new Serializer();
|
public static final Serializer serializer = new Serializer();
|
||||||
|
|
||||||
public static final class Serializer implements IVersionedSerializer<TokenRange>
|
public static final class Serializer implements IVersionedSerializer<TokenRange>
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,8 @@ import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
import org.apache.cassandra.metrics.AccordMetrics;
|
import org.apache.cassandra.metrics.AccordMetrics;
|
||||||
import org.apache.cassandra.net.ResponseContext;
|
import org.apache.cassandra.net.ResponseContext;
|
||||||
import org.apache.cassandra.service.accord.AccordService;
|
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.TxnQuery;
|
||||||
|
import org.apache.cassandra.service.accord.txn.TxnRead;
|
||||||
import org.apache.cassandra.utils.Clock;
|
import org.apache.cassandra.utils.Clock;
|
||||||
import org.apache.cassandra.utils.JVMStabilityInspector;
|
import org.apache.cassandra.utils.JVMStabilityInspector;
|
||||||
|
|
||||||
|
|
@ -174,7 +174,7 @@ public class AccordAgent implements Agent
|
||||||
@Override
|
@Override
|
||||||
public Txn emptySystemTxn(Kind kind, Routable.Domain domain)
|
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
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -240,7 +240,7 @@ public abstract class AccordRoutingKey extends AccordRoutableKey implements Rout
|
||||||
public Range asRange()
|
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");
|
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 SentinelKey(table, true, false)
|
||||||
: new TokenKey(table, token.decreaseSlightly());
|
: new TokenKey(table, token.decreaseSlightly());
|
||||||
|
|
||||||
return new TokenRange(before, this);
|
return TokenRange.create(before, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MinTokenKey(TableId tableId, Token token)
|
public MinTokenKey(TableId tableId, Token token)
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ public class AccordInteropAdapter extends AbstractTxnAdapter
|
||||||
if (updateKind != AccordUpdate.Kind.UNRECOVERABLE_REPAIR && (consistencyLevel == null || consistencyLevel == ConsistencyLevel.ONE || txn.read().keys().isEmpty()))
|
if (updateKind != AccordUpdate.Kind.UNRECOVERABLE_REPAIR && (consistencyLevel == null || consistencyLevel == ConsistencyLevel.ONE || txn.read().keys().isEmpty()))
|
||||||
return false;
|
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();
|
.start();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.BiConsumer;
|
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.TokenRange;
|
||||||
import org.apache.cassandra.service.accord.api.AccordAgent;
|
import org.apache.cassandra.service.accord.api.AccordAgent;
|
||||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
|
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.api.PartitionKey;
|
||||||
import org.apache.cassandra.service.accord.interop.AccordInteropReadCallback.MaximalCommitSender;
|
import org.apache.cassandra.service.accord.interop.AccordInteropReadCallback.MaximalCommitSender;
|
||||||
import org.apache.cassandra.service.accord.txn.AccordUpdate;
|
import org.apache.cassandra.service.accord.txn.AccordUpdate;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnData;
|
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.TxnDataKeyValue;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnDataRangeValue;
|
import org.apache.cassandra.service.accord.txn.TxnDataRangeValue;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnKeyRead;
|
import org.apache.cassandra.service.accord.txn.TxnRead;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnRangeRead;
|
|
||||||
import org.apache.cassandra.service.accord.txn.UnrecoverableRepairUpdate;
|
import org.apache.cassandra.service.accord.txn.UnrecoverableRepairUpdate;
|
||||||
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter;
|
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.service.reads.ReadCoordinator;
|
||||||
import org.apache.cassandra.tcm.ClusterMetadata;
|
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||||
import org.apache.cassandra.transport.Dispatcher;
|
import org.apache.cassandra.transport.Dispatcher;
|
||||||
|
|
@ -151,7 +147,6 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen
|
||||||
private final TxnId txnId;
|
private final TxnId txnId;
|
||||||
private final Txn txn;
|
private final Txn txn;
|
||||||
private final FullRoute<?> route;
|
private final FullRoute<?> route;
|
||||||
private final Participants<?> readScope;
|
|
||||||
private final Timestamp executeAt;
|
private final Timestamp executeAt;
|
||||||
private final Deps deps;
|
private final Deps deps;
|
||||||
private final BiConsumer<? super Result, Throwable> callback;
|
private final BiConsumer<? super Result, Throwable> callback;
|
||||||
|
|
@ -169,7 +164,7 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen
|
||||||
private final Set<InetAddressAndPort> contacted;
|
private final Set<InetAddressAndPort> contacted;
|
||||||
private final AccordUpdate.Kind updateKind;
|
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)
|
AgentExecutor executor, ConsistencyLevel consistencyLevel, AccordEndpointMapper endpointMapper)
|
||||||
{
|
{
|
||||||
requireArgument(!txn.read().keys().isEmpty() || updateKind == AccordUpdate.Kind.UNRECOVERABLE_REPAIR);
|
requireArgument(!txn.read().keys().isEmpty() || updateKind == AccordUpdate.Kind.UNRECOVERABLE_REPAIR);
|
||||||
|
|
@ -177,7 +172,6 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen
|
||||||
this.txnId = txnId;
|
this.txnId = txnId;
|
||||||
this.txn = txn;
|
this.txn = txn;
|
||||||
this.route = route;
|
this.route = route;
|
||||||
this.readScope = readScope;
|
|
||||||
this.executeAt = executeAt;
|
this.executeAt = executeAt;
|
||||||
this.deps = deps;
|
this.deps = deps;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
|
@ -233,123 +227,125 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen
|
||||||
public void sendReadCommand(Message<ReadCommand> message, InetAddressAndPort to, RequestCallback<ReadResponse> callback)
|
public void sendReadCommand(Message<ReadCommand> message, InetAddressAndPort to, RequestCallback<ReadResponse> callback)
|
||||||
{
|
{
|
||||||
Node.Id id = endpointMapper.mappedId(to);
|
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
|
// 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));
|
node.send(id, commit, executor, new AccordInteropRead.ReadCallback(id, to, message, callback, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendReadRepairMutation(Message<Mutation> message, InetAddressAndPort to, RequestCallback<Object> callback)
|
public void sendReadRepairMutation(Message<Mutation> message, InetAddressAndPort to, RequestCallback<Object> callback)
|
||||||
{
|
{
|
||||||
requireArgument(message.payload.allowsPotentialTransactionConflicts());
|
requireArgument(message.payload.potentialTxnConflicts().allowed);
|
||||||
|
requireArgument(message.payload.getTableIds().size() == 1);
|
||||||
Node.Id id = endpointMapper.mappedId(to);
|
Node.Id id = endpointMapper.mappedId(to);
|
||||||
|
Participants<?> readScope = Participants.singleton(txn.read().keys().domain(), new TokenKey(message.payload.getTableIds().iterator().next(), message.payload.key().getToken()));
|
||||||
AccordInteropReadRepair readRepair = new AccordInteropReadRepair(id, executes, txnId, readScope, executeAt.epoch(), message.payload);
|
AccordInteropReadRepair readRepair = new AccordInteropReadRepair(id, executes, txnId, readScope, executeAt.epoch(), message.payload);
|
||||||
node.send(id, readRepair, executor, new AccordInteropReadRepair.ReadRepairCallback(id, to, message, callback, this));
|
node.send(id, readRepair, executor, new AccordInteropReadRepair.ReadRepairCallback(id, to, message, callback, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<AsyncChain<Data>> keyReadChains(int nowInSeconds, Dispatcher.RequestTime requestTime)
|
private List<AsyncChain<Data>> readChains(Dispatcher.RequestTime requestTime)
|
||||||
{
|
{
|
||||||
TxnKeyRead read = (TxnKeyRead) txn.read();
|
TxnRead read = (TxnRead) txn.read();
|
||||||
List<AsyncChain<Data>> results = new ArrayList<>();
|
|
||||||
Seekables<?, ?> keys = txn.read().keys();
|
Seekables<?, ?> keys = txn.read().keys();
|
||||||
|
switch (keys.domain())
|
||||||
|
{
|
||||||
|
case Key:
|
||||||
|
return keyReadChains(read, keys, requestTime);
|
||||||
|
case Range:
|
||||||
|
return rangeReadChains(read, keys, requestTime);
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unhandled domain " + keys.domain());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<AsyncChain<Data>> keyReadChains(TxnRead read, Seekables<?, ?> keys, Dispatcher.RequestTime requestTime)
|
||||||
|
{
|
||||||
|
ClusterMetadata cm = ClusterMetadata.current();
|
||||||
|
List<AsyncChain<Data>> results = new ArrayList<>();
|
||||||
keys.forEach(key -> {
|
keys.forEach(key -> {
|
||||||
read.forEachWithKey((PartitionKey) key, fragment -> {
|
read.forEachWithKey((PartitionKey) key, fragment -> {
|
||||||
SinglePartitionReadCommand command = (SinglePartitionReadCommand) fragment.command();
|
SinglePartitionReadCommand command = (SinglePartitionReadCommand) fragment.command();
|
||||||
|
|
||||||
// This should only rarely occur when coordinators start a transaction in a migrating range
|
// This should only rarely occur when coordinators start a transaction in a migrating range
|
||||||
// because they haven't yet updated their cluster metadata.
|
// because they haven't yet updated their cluster metadata.
|
||||||
// It would be harmless to do the read, because it will be rejected in `TxnQuery` anyways,
|
// It would be harmless to do the read, because it will be rejected in `TxnQuery` anyways,
|
||||||
// but it's faster to skip the read
|
// but it's faster to skip the read
|
||||||
TableMigrationState tms = ConsensusTableMigration.getTableMigrationState(command.metadata().id);
|
AccordClientRequestMetrics metrics = txn.kind().isWrite() ? accordWriteMetrics : accordReadMetrics;
|
||||||
AccordClientRequestMetrics metrics = txn.kind().isWrite() ? accordWriteMetrics : accordReadMetrics;
|
// TODO (required): This doesn't use the metadata from the correct epoch
|
||||||
if (ConsensusRequestRouter.instance.isKeyInMigratingOrMigratedRangeFromAccord(command.metadata(), tms, command.partitionKey()))
|
if (!ConsensusRequestRouter.instance.isKeyManagedByAccordForReadAndWrite(cm, command.metadata().id, command.partitionKey()))
|
||||||
{
|
{
|
||||||
metrics.migrationSkippedReads.mark();
|
metrics.migrationSkippedReads.mark();
|
||||||
results.add(AsyncChains.success(TxnData.emptyPartition(fragment.txnDataName(), command)));
|
results.add(AsyncChains.success(TxnData.emptyPartition(fragment.txnDataName(), command)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Group group = Group.one(command);
|
||||||
|
results.add(AsyncChains.ofCallable(Stage.ACCORD_MIGRATION.executor(), () -> {
|
||||||
|
TxnData result = new TxnData();
|
||||||
|
// Enforcing limits is redundant since we only have a group of size 1, but checking anyways
|
||||||
|
// documents the requirement here
|
||||||
|
try (PartitionIterator iterator = StorageProxy.maybeEnforceLimits(StorageProxy.fetchRows(group.queries, consistencyLevel, this, requestTime), group))
|
||||||
|
{
|
||||||
|
if (iterator.hasNext())
|
||||||
|
{
|
||||||
|
try (RowIterator partition = iterator.next())
|
||||||
|
{
|
||||||
|
TxnDataKeyValue value = new TxnDataKeyValue(partition);
|
||||||
|
if (value.hasRows() || command.selectsFullPartition())
|
||||||
|
result.put(fragment.txnDataName(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<AsyncChain<Data>> rangeReadChains(TxnRead read, Seekables<?, ?> keys, Dispatcher.RequestTime requestTime)
|
||||||
|
{
|
||||||
|
List<AsyncChain<Data>> results = new ArrayList<>();
|
||||||
|
keys.forEach(key -> {
|
||||||
|
read.forEachWithKey(key, fragment -> {
|
||||||
|
PartitionRangeReadCommand command = ((PartitionRangeReadCommand) fragment.command()).withTxnReadName(fragment.txnDataName());
|
||||||
|
|
||||||
|
// TODO (required): To make migration work we need to validate that the range is all on Accord
|
||||||
|
|
||||||
Group group = Group.one(command.withNowInSec(nowInSeconds));
|
|
||||||
results.add(AsyncChains.ofCallable(Stage.ACCORD_MIGRATION.executor(), () -> {
|
results.add(AsyncChains.ofCallable(Stage.ACCORD_MIGRATION.executor(), () -> {
|
||||||
TxnData result = new TxnData();
|
TxnData result = new TxnData();
|
||||||
try (PartitionIterator iterator = StorageProxy.readRegular(group, consistencyLevel, this, requestTime))
|
try (PartitionIterator iterator = StorageProxy.getRangeSlice(command, consistencyLevel, this, requestTime))
|
||||||
{
|
{
|
||||||
if (iterator.hasNext())
|
TxnDataRangeValue value = new TxnDataRangeValue();
|
||||||
|
while (iterator.hasNext())
|
||||||
{
|
{
|
||||||
try (RowIterator partition = iterator.next())
|
try (RowIterator partition = iterator.next())
|
||||||
{
|
{
|
||||||
TxnDataKeyValue value = new TxnDataKeyValue(partition);
|
FilteredPartition filtered = FilteredPartition.create(partition);
|
||||||
if (value.hasRows() || command.selectsFullPartition())
|
if (filtered.hasRows() || command.selectsFullPartition())
|
||||||
result.put(fragment.txnDataName(), value);
|
value.add(filtered);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
result.put(fragment.txnDataName(), value);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<AsyncChain<Data>> rangeReadChains(long nowInSeconds, Dispatcher.RequestTime requestTime)
|
|
||||||
{
|
|
||||||
TxnRangeRead read = (TxnRangeRead) txn.read();
|
|
||||||
Seekables<?, ?> keys = txn.read().keys();
|
|
||||||
List<AsyncChain<Data>> results = new ArrayList<>();
|
|
||||||
keys.forEach(key -> {
|
|
||||||
TokenRange range = (TokenRange)key;
|
|
||||||
PartitionRangeReadCommand command = read.commandForSubrange(range, nowInSeconds);
|
|
||||||
|
|
||||||
// This should only rarely occur when coordinators start a transaction in a migrating range
|
|
||||||
// because they haven't yet updated their cluster metadata.
|
|
||||||
// It would be harmless to do the read, because it will be rejected in `TxnQuery` anyways,
|
|
||||||
// but it's faster to skip the read
|
|
||||||
// TODO (required): To make migration work we need to validate that the range is all on Accord
|
|
||||||
// if any part isn't we should reject the read
|
|
||||||
// TableMigrationState tms = ConsensusTableMigration.getTableMigrationState(command.metadata().id);
|
|
||||||
// AccordClientRequestMetrics metrics = txn.kind().isWrite() ? accordWriteMetrics : accordReadMetrics;
|
|
||||||
// if (ConsensusRequestRouter.instance.isKeyInMigratingOrMigratedRangeFromAccord(command.metadata(), tms, command.partitionKey()))
|
|
||||||
// {
|
|
||||||
// metrics.migrationSkippedReads.mark();
|
|
||||||
// results.add(AsyncChains.success(TxnData.emptyPartition(fragment.txnDataName(), command)));
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
results.add(AsyncChains.ofCallable(Stage.ACCORD_MIGRATION.executor(), () -> {
|
|
||||||
TxnData result = new TxnData();
|
|
||||||
try (PartitionIterator iterator = StorageProxy.getRangeSlice(command, consistencyLevel, this, requestTime))
|
|
||||||
{
|
|
||||||
TxnDataRangeValue value = new TxnDataRangeValue();
|
|
||||||
while (iterator.hasNext())
|
|
||||||
{
|
|
||||||
try (RowIterator partition = iterator.next())
|
|
||||||
{
|
|
||||||
FilteredPartition filtered = FilteredPartition.create(partition);
|
|
||||||
if (filtered.hasRows() || command.selectsFullPartition())
|
|
||||||
value.add(filtered);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.put(TxnData.txnDataName(TxnDataNameKind.USER), value);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AsyncChain<Data> readChains()
|
private AsyncChain<Data> readChains()
|
||||||
{
|
{
|
||||||
int nowInSeconds = (int) TimeUnit.MICROSECONDS.toSeconds(executeAt.hlc());
|
|
||||||
// TODO (expected): use normal query nano time
|
// TODO (expected): use normal query nano time
|
||||||
Dispatcher.RequestTime requestTime = Dispatcher.RequestTime.forImmediateExecution();
|
Dispatcher.RequestTime requestTime = Dispatcher.RequestTime.forImmediateExecution();
|
||||||
|
|
||||||
List<AsyncChain<Data>> results;
|
List<AsyncChain<Data>> results = readChains(requestTime);
|
||||||
if (txn.keys().domain().isKey())
|
|
||||||
results = keyReadChains(nowInSeconds, requestTime);
|
|
||||||
else
|
|
||||||
results = rangeReadChains(nowInSeconds, requestTime);
|
|
||||||
|
|
||||||
if (results.isEmpty())
|
if (results.isEmpty())
|
||||||
return AsyncChains.success(new TxnData());
|
return AsyncChains.success(new TxnData());
|
||||||
|
|
||||||
|
|
@ -431,9 +427,20 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReadCommand maybeAllowOutOfRangeReads(ReadCommand readCommand)
|
public ReadCommand maybeAllowOutOfRangeReads(ReadCommand readCommand, ConsistencyLevel cl)
|
||||||
{
|
{
|
||||||
return readCommand.allowOutOfRangeReads();
|
// Reading from a single coordinator so there is no reconciliation at the coordinator and filtering/limits
|
||||||
|
// need to be pushed down to query execution
|
||||||
|
boolean withoutReconciliation = cl == null || cl == ConsistencyLevel.ONE;
|
||||||
|
// Really just want to enable allowPotentialTxnConflicts without changing anything else
|
||||||
|
// but didn't want to add another method for constructing a modified read command
|
||||||
|
if (readCommand instanceof SinglePartitionReadCommand)
|
||||||
|
return ((SinglePartitionReadCommand)readCommand).withTransactionalSettings(withoutReconciliation, readCommand.nowInSec());
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PartitionRangeReadCommand rangeCommand = ((PartitionRangeReadCommand)readCommand);
|
||||||
|
return rangeCommand.withTransactionalSettings(readCommand.nowInSec(), rangeCommand.dataRange().keyRange(), true, withoutReconciliation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide request callbacks with a way to send maximal commits on Insufficient responses
|
// Provide request callbacks with a way to send maximal commits on Insufficient responses
|
||||||
|
|
|
||||||
|
|
@ -19,22 +19,34 @@
|
||||||
package org.apache.cassandra.service.accord.interop;
|
package org.apache.cassandra.service.accord.interop;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import accord.api.Data;
|
import accord.api.Data;
|
||||||
import accord.local.Node;
|
import accord.local.Node;
|
||||||
import accord.local.SafeCommandStore;
|
import accord.local.SafeCommandStore;
|
||||||
import accord.messages.ReadData;
|
|
||||||
import accord.messages.MessageType;
|
import accord.messages.MessageType;
|
||||||
|
import accord.messages.ReadData;
|
||||||
import accord.primitives.PartialTxn;
|
import accord.primitives.PartialTxn;
|
||||||
import accord.primitives.Participants;
|
import accord.primitives.Participants;
|
||||||
|
import accord.primitives.Range;
|
||||||
import accord.primitives.Ranges;
|
import accord.primitives.Ranges;
|
||||||
|
import accord.primitives.Routables.Slice;
|
||||||
import accord.primitives.Timestamp;
|
import accord.primitives.Timestamp;
|
||||||
import accord.primitives.TxnId;
|
import accord.primitives.TxnId;
|
||||||
import accord.topology.Topologies;
|
import accord.topology.Topologies;
|
||||||
import accord.utils.async.AsyncChain;
|
import accord.utils.async.AsyncChain;
|
||||||
import accord.utils.async.AsyncChains;
|
import accord.utils.async.AsyncChains;
|
||||||
import org.apache.cassandra.concurrent.Stage;
|
import org.apache.cassandra.concurrent.Stage;
|
||||||
|
import org.apache.cassandra.db.PartitionRangeReadCommand;
|
||||||
|
import org.apache.cassandra.db.ReadCommand;
|
||||||
import org.apache.cassandra.db.ReadCommandVerbHandler;
|
import org.apache.cassandra.db.ReadCommandVerbHandler;
|
||||||
import org.apache.cassandra.db.ReadResponse;
|
import org.apache.cassandra.db.ReadResponse;
|
||||||
import org.apache.cassandra.db.SinglePartitionReadCommand;
|
import org.apache.cassandra.db.SinglePartitionReadCommand;
|
||||||
|
|
@ -46,17 +58,26 @@ import org.apache.cassandra.locator.InetAddressAndPort;
|
||||||
import org.apache.cassandra.net.Message;
|
import org.apache.cassandra.net.Message;
|
||||||
import org.apache.cassandra.net.RequestCallback;
|
import org.apache.cassandra.net.RequestCallback;
|
||||||
import org.apache.cassandra.service.accord.AccordMessageSink.AccordMessageType;
|
import org.apache.cassandra.service.accord.AccordMessageSink.AccordMessageType;
|
||||||
|
import org.apache.cassandra.service.accord.TokenRange;
|
||||||
|
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
|
||||||
|
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
||||||
import org.apache.cassandra.service.accord.serializers.CommandSerializers;
|
import org.apache.cassandra.service.accord.serializers.CommandSerializers;
|
||||||
import org.apache.cassandra.service.accord.serializers.KeySerializers;
|
import org.apache.cassandra.service.accord.serializers.KeySerializers;
|
||||||
import org.apache.cassandra.service.accord.serializers.ReadDataSerializers;
|
import org.apache.cassandra.service.accord.serializers.ReadDataSerializers;
|
||||||
import org.apache.cassandra.service.accord.serializers.ReadDataSerializers.ReadDataSerializer;
|
import org.apache.cassandra.service.accord.serializers.ReadDataSerializers.ReadDataSerializer;
|
||||||
|
import org.apache.cassandra.service.accord.txn.TxnNamedRead;
|
||||||
|
import org.apache.cassandra.service.accord.txn.TxnRead;
|
||||||
|
import org.apache.cassandra.utils.Pair;
|
||||||
|
|
||||||
import static accord.primitives.SaveStatus.PreApplied;
|
import static accord.primitives.SaveStatus.PreApplied;
|
||||||
import static accord.primitives.SaveStatus.ReadyToExecute;
|
import static accord.primitives.SaveStatus.ReadyToExecute;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
public class AccordInteropRead extends ReadData
|
public class AccordInteropRead extends ReadData
|
||||||
{
|
{
|
||||||
public static final IVersionedSerializer<AccordInteropRead> requestSerializer = new ReadDataSerializer<>()
|
|
||||||
|
public static final IVersionedSerializer<AccordInteropRead> requestSerializer = new ReadDataSerializer<AccordInteropRead>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void serialize(AccordInteropRead read, DataOutputPlus out, int version) throws IOException
|
public void serialize(AccordInteropRead read, DataOutputPlus out, int version) throws IOException
|
||||||
|
|
@ -64,7 +85,7 @@ public class AccordInteropRead extends ReadData
|
||||||
CommandSerializers.txnId.serialize(read.txnId, out, version);
|
CommandSerializers.txnId.serialize(read.txnId, out, version);
|
||||||
KeySerializers.participants.serialize(read.scope, out, version);
|
KeySerializers.participants.serialize(read.scope, out, version);
|
||||||
out.writeUnsignedVInt(read.executeAtEpoch);
|
out.writeUnsignedVInt(read.executeAtEpoch);
|
||||||
SinglePartitionReadCommand.serializer.serialize(read.command, out, version);
|
ReadCommand.serializer.serialize(read.command, out, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -73,7 +94,7 @@ public class AccordInteropRead extends ReadData
|
||||||
TxnId txnId = CommandSerializers.txnId.deserialize(in, version);
|
TxnId txnId = CommandSerializers.txnId.deserialize(in, version);
|
||||||
Participants<?> scope = KeySerializers.participants.deserialize(in, version);
|
Participants<?> scope = KeySerializers.participants.deserialize(in, version);
|
||||||
long executeAtEpoch = in.readUnsignedVInt();
|
long executeAtEpoch = in.readUnsignedVInt();
|
||||||
SinglePartitionReadCommand command = (SinglePartitionReadCommand) SinglePartitionReadCommand.serializer.deserialize(in, version);
|
ReadCommand command = ReadCommand.serializer.deserialize(in, version);
|
||||||
return new AccordInteropRead(txnId, scope, executeAtEpoch, command);
|
return new AccordInteropRead(txnId, scope, executeAtEpoch, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,7 +104,7 @@ public class AccordInteropRead extends ReadData
|
||||||
return CommandSerializers.txnId.serializedSize(read.txnId, version)
|
return CommandSerializers.txnId.serializedSize(read.txnId, version)
|
||||||
+ KeySerializers.participants.serializedSize(read.scope, version)
|
+ KeySerializers.participants.serializedSize(read.scope, version)
|
||||||
+ TypeSizes.sizeofUnsignedVInt(read.executeAtEpoch)
|
+ TypeSizes.sizeofUnsignedVInt(read.executeAtEpoch)
|
||||||
+ SinglePartitionReadCommand.serializer.serializedSize(read.command, version);
|
+ ReadCommand.serializer.serializedSize(read.command, version);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -91,12 +112,15 @@ public class AccordInteropRead extends ReadData
|
||||||
|
|
||||||
private static class LocalReadData implements Data
|
private static class LocalReadData implements Data
|
||||||
{
|
{
|
||||||
|
private static final Comparator<Pair<AccordRoutingKey, ReadResponse>> RESPONSE_COMPARATOR = Comparator.comparing(Pair::left);
|
||||||
|
|
||||||
static final IVersionedSerializer<LocalReadData> serializer = new IVersionedSerializer<>()
|
static final IVersionedSerializer<LocalReadData> serializer = new IVersionedSerializer<>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void serialize(LocalReadData data, DataOutputPlus out, int version) throws IOException
|
public void serialize(LocalReadData data, DataOutputPlus out, int version) throws IOException
|
||||||
{
|
{
|
||||||
ReadResponse.serializer.serialize(data.response, out, version);
|
data.ensureRemoteResponse();
|
||||||
|
ReadResponse.serializer.serialize(data.remoteResponse, out, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -108,27 +132,76 @@ public class AccordInteropRead extends ReadData
|
||||||
@Override
|
@Override
|
||||||
public long serializedSize(LocalReadData data, int version)
|
public long serializedSize(LocalReadData data, int version)
|
||||||
{
|
{
|
||||||
return ReadResponse.serializer.serializedSize(data.response, version);
|
data.ensureRemoteResponse();
|
||||||
|
return ReadResponse.serializer.serializedSize(data.remoteResponse, version);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final ReadResponse response;
|
// Will be null at coordinator
|
||||||
|
List<Pair<AccordRoutingKey, ReadResponse>> localResponses;
|
||||||
|
// Will be null at coordinator
|
||||||
|
final ReadCommand readCommand;
|
||||||
|
// Will be not null at coordinator, but null at the node creating the response until it serialized
|
||||||
|
ReadResponse remoteResponse;
|
||||||
|
|
||||||
public LocalReadData(ReadResponse response)
|
public LocalReadData(@Nullable AccordRoutingKey start, @Nonnull ReadResponse response, @Nonnull ReadCommand readCommand)
|
||||||
{
|
{
|
||||||
this.response = response;
|
checkNotNull(response, "response is null");
|
||||||
|
checkNotNull(readCommand, "readCommand is null");
|
||||||
|
localResponses = ImmutableList.of(Pair.create(start, response));
|
||||||
|
this.readCommand = readCommand;
|
||||||
|
this.remoteResponse = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalReadData(@Nonnull ReadResponse remoteResponse)
|
||||||
|
{
|
||||||
|
checkNotNull(remoteResponse);
|
||||||
|
this.remoteResponse = remoteResponse;
|
||||||
|
readCommand = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "LocalReadData{" + response + '}';
|
if (localResponses != null)
|
||||||
|
return "LocalReadData{" + localResponses + '}';
|
||||||
|
else
|
||||||
|
return "LocalReadData{" + remoteResponse + '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Data merge(Data data)
|
public Data merge(Data data)
|
||||||
{
|
{
|
||||||
throw new IllegalStateException("Should only ever be a single partition");
|
checkState(remoteResponse == null, "Already serialized");
|
||||||
|
checkState(readCommand.isRangeRequest(), "Should only ever be a single partition");
|
||||||
|
LocalReadData other = (LocalReadData)data;
|
||||||
|
checkState(readCommand == other.readCommand, "Should share the same ReadCommand");
|
||||||
|
if (localResponses.size() == 1)
|
||||||
|
{
|
||||||
|
List<Pair<AccordRoutingKey, ReadResponse>> merged = new ArrayList<>();
|
||||||
|
merged.add(localResponses.get(0));
|
||||||
|
localResponses = merged;
|
||||||
|
}
|
||||||
|
localResponses.addAll(other.localResponses);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureRemoteResponse()
|
||||||
|
{
|
||||||
|
if (remoteResponse != null)
|
||||||
|
return;
|
||||||
|
// Range reads will be spread across command stores and need to be merged in token order
|
||||||
|
List<Pair<AccordRoutingKey, ReadResponse>> responses = localResponses;
|
||||||
|
if (responses.size() == 1)
|
||||||
|
{
|
||||||
|
remoteResponse = responses.get(0).right;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
responses = new ArrayList(responses);
|
||||||
|
Collections.sort(responses, RESPONSE_COMPARATOR);
|
||||||
|
remoteResponse = ReadResponse.merge(Lists.transform(responses, Pair::right), readCommand);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,21 +215,21 @@ public class AccordInteropRead extends ReadData
|
||||||
@Override
|
@Override
|
||||||
ReadResponse convertResponse(ReadOk ok)
|
ReadResponse convertResponse(ReadOk ok)
|
||||||
{
|
{
|
||||||
return ((LocalReadData) ok.data).response;
|
return ((LocalReadData)ok.data).remoteResponse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final ExecuteOn EXECUTE_ON = new ExecuteOn(ReadyToExecute, PreApplied);
|
private static final ExecuteOn EXECUTE_ON = new ExecuteOn(ReadyToExecute, PreApplied);
|
||||||
|
|
||||||
final SinglePartitionReadCommand command;
|
protected final ReadCommand command;
|
||||||
|
|
||||||
public AccordInteropRead(Node.Id to, Topologies topologies, TxnId txnId, Participants<?> scope, long executeAtEpoch, SinglePartitionReadCommand command)
|
public AccordInteropRead(Node.Id to, Topologies topologies, TxnId txnId, Participants<?> scope, long executeAtEpoch, ReadCommand command)
|
||||||
{
|
{
|
||||||
super(to, topologies, txnId, scope, executeAtEpoch);
|
super(to, topologies, txnId, scope, executeAtEpoch);
|
||||||
this.command = command;
|
this.command = command;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccordInteropRead(TxnId txnId, Participants<?> scope, long executeAtEpoch, SinglePartitionReadCommand command)
|
public AccordInteropRead(TxnId txnId, Participants<?> scope, long executeAtEpoch, ReadCommand command)
|
||||||
{
|
{
|
||||||
super(txnId, scope, executeAtEpoch);
|
super(txnId, scope, executeAtEpoch);
|
||||||
this.command = command;
|
this.command = command;
|
||||||
|
|
@ -171,8 +244,42 @@ public class AccordInteropRead extends ReadData
|
||||||
@Override
|
@Override
|
||||||
protected AsyncChain<Data> beginRead(SafeCommandStore safeStore, Timestamp executeAt, PartialTxn txn, Ranges unavailable)
|
protected AsyncChain<Data> beginRead(SafeCommandStore safeStore, Timestamp executeAt, PartialTxn txn, Ranges unavailable)
|
||||||
{
|
{
|
||||||
// TODO (required): subtract unavailable ranges, either from read or from response (or on coordinator)
|
TxnRead txnRead = (TxnRead)txn.read();
|
||||||
return AsyncChains.ofCallable(Stage.READ.executor(), () -> new LocalReadData(ReadCommandVerbHandler.instance.doRead(command, false)));
|
Ranges ranges = safeStore.ranges().allAt(executeAt).without(unavailable).intersecting(scope, Slice.Minimal);
|
||||||
|
long nowInSeconds = TxnNamedRead.nowInSeconds(executeAt);
|
||||||
|
List<AsyncChain<Data>> chains = new ArrayList<>(ranges.size());
|
||||||
|
for (Range r : ranges)
|
||||||
|
{
|
||||||
|
ReadCommand readCommand = this.command;
|
||||||
|
AccordRoutingKey routingKey = null;
|
||||||
|
final ReadCommand readCommandFinal;
|
||||||
|
if (readCommand.isRangeRequest())
|
||||||
|
{
|
||||||
|
// This path can have a subrange we have never seen before provided by short read protection or read repair so we need to
|
||||||
|
// calculate the intersection with this instance of the command store and the actual command if it is not empty we
|
||||||
|
// will need to execute it
|
||||||
|
TokenRange commandRange = TxnNamedRead.boundsAsAccordRange(readCommand.dataRange().keyRange(), readCommand.metadata().id);
|
||||||
|
Range intersection = commandRange.intersection(r);
|
||||||
|
if (intersection == null)
|
||||||
|
continue;
|
||||||
|
readCommandFinal = TxnNamedRead.commandForSubrange((PartitionRangeReadCommand) readCommand, intersection, txnRead.cassandraConsistencyLevel(), nowInSeconds);
|
||||||
|
routingKey = ((TokenRange)r).start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SinglePartitionReadCommand singlePartitionReadCommand = ((SinglePartitionReadCommand)readCommand);
|
||||||
|
if (!r.contains(new TokenKey(singlePartitionReadCommand.metadata().id, singlePartitionReadCommand.partitionKey().getToken())))
|
||||||
|
continue;
|
||||||
|
readCommandFinal = ((SinglePartitionReadCommand)readCommand).withTransactionalSettings(TxnNamedRead.readsWithoutReconciliation(txnRead.cassandraConsistencyLevel()), nowInSeconds);
|
||||||
|
}
|
||||||
|
AccordRoutingKey routingKeyFinal = routingKey;
|
||||||
|
chains.add(AsyncChains.ofCallable(Stage.READ.executor(), () -> new LocalReadData(routingKeyFinal, ReadCommandVerbHandler.instance.doRead(readCommandFinal, false), readCommand)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chains.isEmpty())
|
||||||
|
return AsyncChains.success(null);
|
||||||
|
|
||||||
|
return AsyncChains.reduce(chains, Data::merge);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ public abstract class AccordInteropReadCallback<T> implements Callback<ReadReply
|
||||||
|
|
||||||
abstract T convertResponse(ReadOk ok);
|
abstract T convertResponse(ReadOk ok);
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onSuccess(Node.Id from, ReadReply reply)
|
public void onSuccess(Node.Id from, ReadReply reply)
|
||||||
{
|
{
|
||||||
Invariants.requireArgument(from.equals(id));
|
Invariants.requireArgument(from.equals(id));
|
||||||
|
|
@ -78,6 +79,7 @@ public abstract class AccordInteropReadCallback<T> implements Callback<ReadReply
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onFailure(Node.Id from, Throwable failure)
|
public void onFailure(Node.Id from, Throwable failure)
|
||||||
{
|
{
|
||||||
RequestFailure requestFailure;
|
RequestFailure requestFailure;
|
||||||
|
|
@ -90,6 +92,7 @@ public abstract class AccordInteropReadCallback<T> implements Callback<ReadReply
|
||||||
wrapped.onFailure(endpoint, requestFailure);
|
wrapped.onFailure(endpoint, requestFailure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean onCallbackFailure(Node.Id from, Throwable failure)
|
public boolean onCallbackFailure(Node.Id from, Throwable failure)
|
||||||
{
|
{
|
||||||
wrapped.onFailure(endpoint, RequestFailure.UNKNOWN);
|
wrapped.onFailure(endpoint, RequestFailure.UNKNOWN);
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ import accord.primitives.Timestamp;
|
||||||
import accord.primitives.Txn;
|
import accord.primitives.Txn;
|
||||||
import accord.primitives.TxnId;
|
import accord.primitives.TxnId;
|
||||||
import accord.topology.Topologies;
|
import accord.topology.Topologies;
|
||||||
import org.apache.cassandra.db.SinglePartitionReadCommand;
|
import org.apache.cassandra.db.ReadCommand;
|
||||||
import org.apache.cassandra.db.TypeSizes;
|
import org.apache.cassandra.db.TypeSizes;
|
||||||
import org.apache.cassandra.io.IVersionedSerializer;
|
import org.apache.cassandra.io.IVersionedSerializer;
|
||||||
import org.apache.cassandra.io.util.DataInputPlus;
|
import org.apache.cassandra.io.util.DataInputPlus;
|
||||||
|
|
@ -78,7 +78,7 @@ public class AccordInteropStableThenRead extends AccordInteropRead
|
||||||
DepsSerializers.partialDeps.serialize(read.partialDeps, out, version);
|
DepsSerializers.partialDeps.serialize(read.partialDeps, out, version);
|
||||||
if (read.kind.withTxn == HasTxn)
|
if (read.kind.withTxn == HasTxn)
|
||||||
KeySerializers.fullRoute.serialize(read.route, out, version);
|
KeySerializers.fullRoute.serialize(read.route, out, version);
|
||||||
SinglePartitionReadCommand.serializer.serialize(read.command, out, version);
|
ReadCommand.serializer.serialize(read.command, out, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -92,7 +92,7 @@ public class AccordInteropStableThenRead extends AccordInteropRead
|
||||||
PartialTxn partialTxn = kind.withTxn == NoTxn ? null : CommandSerializers.nullablePartialTxn.deserialize(in, version);
|
PartialTxn partialTxn = kind.withTxn == NoTxn ? null : CommandSerializers.nullablePartialTxn.deserialize(in, version);
|
||||||
PartialDeps partialDeps = kind.withDeps == NoDeps ? null : DepsSerializers.partialDeps.deserialize(in, version);
|
PartialDeps partialDeps = kind.withDeps == NoDeps ? null : DepsSerializers.partialDeps.deserialize(in, version);
|
||||||
FullRoute < ?> route = kind.withTxn == HasTxn ? KeySerializers.fullRoute.deserialize(in, version) : null;
|
FullRoute < ?> route = kind.withTxn == HasTxn ? KeySerializers.fullRoute.deserialize(in, version) : null;
|
||||||
SinglePartitionReadCommand command = (SinglePartitionReadCommand) SinglePartitionReadCommand.serializer.deserialize(in, version);
|
ReadCommand command = ReadCommand.serializer.deserialize(in, version);
|
||||||
return new AccordInteropStableThenRead(txnId, scope, kind, minEpoch, executeAt, partialTxn, partialDeps, route, command);
|
return new AccordInteropStableThenRead(txnId, scope, kind, minEpoch, executeAt, partialTxn, partialDeps, route, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,7 +107,7 @@ public class AccordInteropStableThenRead extends AccordInteropRead
|
||||||
+ (read.kind.withTxn == NoTxn ? 0 : CommandSerializers.nullablePartialTxn.serializedSize(read.partialTxn, version))
|
+ (read.kind.withTxn == NoTxn ? 0 : CommandSerializers.nullablePartialTxn.serializedSize(read.partialTxn, version))
|
||||||
+ (read.kind.withDeps != HasDeps ? 0 : DepsSerializers.partialDeps.serializedSize(read.partialDeps, version))
|
+ (read.kind.withDeps != HasDeps ? 0 : DepsSerializers.partialDeps.serializedSize(read.partialDeps, version))
|
||||||
+ (read.kind.withTxn != HasTxn ? 0 : KeySerializers.fullRoute.serializedSize(read.route, version))
|
+ (read.kind.withTxn != HasTxn ? 0 : KeySerializers.fullRoute.serializedSize(read.route, version))
|
||||||
+ SinglePartitionReadCommand.serializer.serializedSize(read.command, version);
|
+ ReadCommand.serializer.serializedSize(read.command, version);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -121,18 +121,18 @@ public class AccordInteropStableThenRead extends AccordInteropRead
|
||||||
public final @Nullable PartialDeps partialDeps;
|
public final @Nullable PartialDeps partialDeps;
|
||||||
public final @Nullable FullRoute<?> route;
|
public final @Nullable FullRoute<?> route;
|
||||||
|
|
||||||
public AccordInteropStableThenRead(Node.Id to, Topologies topologies, TxnId txnId, Commit.Kind kind, Timestamp executeAt, Txn txn, Deps deps, FullRoute<?> route, SinglePartitionReadCommand command)
|
public AccordInteropStableThenRead(Node.Id to, Topologies topologies, TxnId txnId, Commit.Kind kind, Timestamp executeAt, Txn txn, Deps deps, FullRoute<?> route, ReadCommand command)
|
||||||
{
|
{
|
||||||
super(to, topologies, txnId, route, executeAt.epoch(), command);
|
super(to, topologies, txnId, route, executeAt.epoch(), command);
|
||||||
this.kind = kind;
|
this.kind = kind;
|
||||||
this.minEpoch = topologies.oldestEpoch();
|
this.minEpoch = topologies.oldestEpoch();
|
||||||
this.executeAt = executeAt;
|
this.executeAt = executeAt;
|
||||||
this.partialTxn = kind.withTxn.select(txn, scope, topologies, txnId, to);
|
this.partialTxn = kind.withTxn.select(txn, route, topologies, txnId, to);
|
||||||
this.partialDeps = kind.withDeps.select(deps, scope);
|
this.partialDeps = kind.withDeps.select(deps, route);
|
||||||
this.route = kind.withTxn.select(route);
|
this.route = kind.withTxn.select(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccordInteropStableThenRead(TxnId txnId, Participants<?> scope, Commit.Kind kind, long minEpoch, Timestamp executeAt, @Nullable PartialTxn partialTxn, @Nullable PartialDeps partialDeps, @Nullable FullRoute<?> route, SinglePartitionReadCommand command)
|
public AccordInteropStableThenRead(TxnId txnId, Participants<?> scope, Commit.Kind kind, long minEpoch, Timestamp executeAt, @Nullable PartialTxn partialTxn, @Nullable PartialDeps partialDeps, @Nullable FullRoute<?> route, ReadCommand command)
|
||||||
{
|
{
|
||||||
super(txnId, scope, executeAt.epoch(), command);
|
super(txnId, scope, executeAt.epoch(), command);
|
||||||
this.minEpoch = minEpoch;
|
this.minEpoch = minEpoch;
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ import accord.primitives.Ranges;
|
||||||
import accord.primitives.RoutableKey;
|
import accord.primitives.RoutableKey;
|
||||||
import accord.primitives.Route;
|
import accord.primitives.Route;
|
||||||
import accord.primitives.RoutingKeys;
|
import accord.primitives.RoutingKeys;
|
||||||
|
import accord.primitives.Seekable;
|
||||||
import accord.primitives.Seekables;
|
import accord.primitives.Seekables;
|
||||||
import accord.primitives.Unseekables;
|
import accord.primitives.Unseekables;
|
||||||
import accord.primitives.Unseekables.UnseekablesKind;
|
import accord.primitives.Unseekables.UnseekablesKind;
|
||||||
|
|
@ -418,6 +419,51 @@ public class KeySerializers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final IVersionedSerializer<Seekable> seekable = new IVersionedSerializer<>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void serialize(Seekable seekable, DataOutputPlus out, int version) throws IOException
|
||||||
|
{
|
||||||
|
switch (seekable.domain())
|
||||||
|
{
|
||||||
|
default: throw new AssertionError();
|
||||||
|
case Key:
|
||||||
|
out.writeByte(0);
|
||||||
|
PartitionKey.serializer.serialize((PartitionKey) seekable, out, version);
|
||||||
|
break;
|
||||||
|
case Range:
|
||||||
|
out.writeByte(1);
|
||||||
|
TokenRange.serializer.serialize((TokenRange) seekable, out, version);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Seekable deserialize(DataInputPlus in, int version) throws IOException
|
||||||
|
{
|
||||||
|
byte b = in.readByte();
|
||||||
|
switch (b)
|
||||||
|
{
|
||||||
|
default: throw new IOException("Corrupted input: expected byte 1 or 2, received " + b);
|
||||||
|
case 0: return PartitionKey.serializer.deserialize(in, version);
|
||||||
|
case 1: return TokenRange.serializer.deserialize(in, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long serializedSize(Seekable seekable, int version)
|
||||||
|
{
|
||||||
|
switch (seekable.domain())
|
||||||
|
{
|
||||||
|
default: throw new AssertionError();
|
||||||
|
case Key:
|
||||||
|
return 1 + PartitionKey.serializer.serializedSize((PartitionKey) seekable, version);
|
||||||
|
case Range:
|
||||||
|
return 1 + TokenRange.serializer.serializedSize((TokenRange) seekable, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public static class AbstractSeekablesSerializer implements IVersionedSerializer<Seekables<?, ?>>
|
public static class AbstractSeekablesSerializer implements IVersionedSerializer<Seekables<?, ?>>
|
||||||
{
|
{
|
||||||
final IVersionedSerializer<Keys> keys;
|
final IVersionedSerializer<Keys> keys;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,14 @@ import java.util.stream.Collectors;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.Iterators;
|
import com.google.common.collect.Iterators;
|
||||||
|
|
||||||
|
import accord.api.Key;
|
||||||
import accord.primitives.Keys;
|
import accord.primitives.Keys;
|
||||||
|
import accord.primitives.Range;
|
||||||
|
import accord.primitives.Ranges;
|
||||||
|
import accord.primitives.Routable.Domain;
|
||||||
|
import accord.primitives.Seekable;
|
||||||
|
import accord.primitives.Seekables;
|
||||||
|
import org.apache.cassandra.service.accord.TokenRange;
|
||||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -38,30 +45,55 @@ public abstract class AbstractKeySorted<T> implements Iterable<T>
|
||||||
{
|
{
|
||||||
public static final String ITEMS_OUT_OF_ORDER_MESSAGE = "Items are out of order ([%s] %s >= [%s] %s)";
|
public static final String ITEMS_OUT_OF_ORDER_MESSAGE = "Items are out of order ([%s] %s >= [%s] %s)";
|
||||||
|
|
||||||
protected final Keys itemKeys;
|
protected final Seekables itemKeys;
|
||||||
protected final T[] items;
|
protected final T[] items;
|
||||||
|
|
||||||
public AbstractKeySorted(T[] items)
|
public AbstractKeySorted(T[] items, Domain domain)
|
||||||
{
|
{
|
||||||
this.items = items;
|
this.items = items;
|
||||||
this.itemKeys = extractItemKeys();
|
this.itemKeys = extractItemKeys(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AbstractKeySorted(List<T> items)
|
public AbstractKeySorted(List<T> items, Domain domain)
|
||||||
{
|
{
|
||||||
T[] arr = newArray(items.size());
|
T[] arr = newArray(items.size());
|
||||||
items.toArray(arr);
|
items.toArray(arr);
|
||||||
Arrays.sort(arr, this::compare);
|
|
||||||
this.items = arr;
|
this.items = arr;
|
||||||
this.itemKeys = extractItemKeys();
|
switch (domain)
|
||||||
|
{
|
||||||
|
case Key:
|
||||||
|
Arrays.sort(arr, this::compareKey);
|
||||||
|
break;
|
||||||
|
case Range:
|
||||||
|
Arrays.sort(arr, this::compareRange);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unhandled domain " + domain);
|
||||||
|
}
|
||||||
|
this.itemKeys = extractItemKeys(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Keys extractItemKeys()
|
private Seekables extractItemKeys(Domain domain)
|
||||||
{
|
{
|
||||||
PartitionKey[] keys = new PartitionKey[items.length];
|
switch (domain)
|
||||||
for (int i = 0 ; i < keys.length ; ++i)
|
{
|
||||||
keys[i] = getKey(items[i]);
|
case Key:
|
||||||
return Keys.ofSorted(keys);
|
if (items.length == 0)
|
||||||
|
return Keys.EMPTY;
|
||||||
|
PartitionKey[] keys = new PartitionKey[items.length];
|
||||||
|
for (int i = 0 ; i < keys.length; i++)
|
||||||
|
keys[i] = (PartitionKey)getKey(items[i]);
|
||||||
|
return Keys.ofSorted(keys);
|
||||||
|
case Range:
|
||||||
|
if (items.length == 0)
|
||||||
|
return Ranges.EMPTY;
|
||||||
|
TokenRange[] ranges = new TokenRange[items.length];
|
||||||
|
for (int i = 0 ; i < ranges.length; i++)
|
||||||
|
ranges[i] = (TokenRange)getKey(items[i]);
|
||||||
|
return Ranges.ofSortedAndDeoverlapped(ranges);
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unhandled domain " + domain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -94,7 +126,7 @@ public abstract class AbstractKeySorted<T> implements Iterable<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public Keys keys()
|
public Seekables keys()
|
||||||
{
|
{
|
||||||
return itemKeys;
|
return itemKeys;
|
||||||
}
|
}
|
||||||
|
|
@ -104,25 +136,51 @@ public abstract class AbstractKeySorted<T> implements Iterable<T>
|
||||||
*/
|
*/
|
||||||
abstract int compareNonKeyFields(T left, T right);
|
abstract int compareNonKeyFields(T left, T right);
|
||||||
|
|
||||||
abstract PartitionKey getKey(T item);
|
abstract Seekable getKey(T item);
|
||||||
abstract T[] newArray(int size);
|
abstract T[] newArray(int size);
|
||||||
|
|
||||||
public int compare(T left, T right)
|
abstract Domain domain();
|
||||||
|
|
||||||
|
public int compareKey(T left, T right)
|
||||||
{
|
{
|
||||||
int cmp = getKey(left).compareTo(getKey(right));
|
int cmp = ((PartitionKey)getKey(left)).compareTo(((PartitionKey)getKey(right)));
|
||||||
|
return cmp != 0 ? cmp : compareNonKeyFields(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compareRange(T left, T right)
|
||||||
|
{
|
||||||
|
int cmp = ((TokenRange)getKey(left)).compareTo(((TokenRange)getKey(right)));
|
||||||
return cmp != 0 ? cmp : compareNonKeyFields(left, right);
|
return cmp != 0 ? cmp : compareNonKeyFields(left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void validateOrder()
|
void validateOrder()
|
||||||
{
|
{
|
||||||
for (int i = 1; i < items.length; i++)
|
Domain domain = getKey(items[0]).domain();
|
||||||
|
switch (domain)
|
||||||
{
|
{
|
||||||
T prev = items[i-1];
|
case Key:
|
||||||
T next = items[i];
|
for (int i = 1; i < items.length; i++)
|
||||||
|
{
|
||||||
|
T prev = items[i-1];
|
||||||
|
T next = items[i];
|
||||||
|
|
||||||
if (compare(prev, next) >= 0)
|
if (compareKey(prev, next) >= 0)
|
||||||
throw new IllegalStateException(String.format(ITEMS_OUT_OF_ORDER_MESSAGE, i - 1, prev, i, next));
|
throw new IllegalStateException(String.format(ITEMS_OUT_OF_ORDER_MESSAGE, i - 1, prev, i, next));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Range:
|
||||||
|
for (int i = 1; i < items.length; i++)
|
||||||
|
{
|
||||||
|
T prev = items[i-1];
|
||||||
|
T next = items[i];
|
||||||
|
|
||||||
|
if (compareRange(prev, next) >= 0)
|
||||||
|
throw new IllegalStateException(String.format(ITEMS_OUT_OF_ORDER_MESSAGE, i - 1, prev, i, next));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unhandled domain " + domain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,16 +189,55 @@ public abstract class AbstractKeySorted<T> implements Iterable<T>
|
||||||
return items.length;
|
return items.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void forEachWithKey(PartitionKey key, Consumer<T> consumer)
|
public void forEachWithKey(Seekable key, Consumer<T> consumer)
|
||||||
{
|
{
|
||||||
for (int i = firstPossibleKeyIdx(key); i < items.length && getKey(items[i]).equals(key); i++)
|
switch (key.domain())
|
||||||
consumer.accept(items[i]);
|
{
|
||||||
|
case Key:
|
||||||
|
for (int i = firstPossibleKeyIdx((PartitionKey) key); i < items.length; i++)
|
||||||
|
{
|
||||||
|
Key itemKey = (Key)getKey(items[i]);
|
||||||
|
if (key.equals(itemKey))
|
||||||
|
consumer.accept(items[i]);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Range:
|
||||||
|
TokenRange range = (TokenRange) key;
|
||||||
|
for (int i = firstPossibleRangeIdx(range); i < items.length; i++)
|
||||||
|
{
|
||||||
|
Range itemRange = (Range) getKey(items[i]);
|
||||||
|
if (range.compareIntersecting(itemRange) == 0)
|
||||||
|
consumer.accept(items[i]);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unhandled domain " + key.domain());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int firstPossibleRangeIdx(TokenRange range)
|
||||||
|
{
|
||||||
|
int idx = Arrays.binarySearch(items, range, (l, r) -> {
|
||||||
|
Range itemRange = (Range)getKey((T) l);
|
||||||
|
if (itemRange.compareIntersecting((TokenRange)r) == 0)
|
||||||
|
return 1;
|
||||||
|
if (((TokenRange) r).end().compareTo(itemRange.end()) > 0)
|
||||||
|
return 1;
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return -1 - idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int firstPossibleKeyIdx(PartitionKey key)
|
private int firstPossibleKeyIdx(PartitionKey key)
|
||||||
{
|
{
|
||||||
int idx = Arrays.binarySearch(items, key, (l, r) -> {
|
int idx = Arrays.binarySearch(items, key, (l, r) -> {
|
||||||
PartitionKey lk = getKey((T) l);
|
PartitionKey lk = (PartitionKey) getKey((T) l);
|
||||||
PartitionKey rk = (PartitionKey) r;
|
PartitionKey rk = (PartitionKey) r;
|
||||||
int cmp = lk.compareTo(rk);
|
int cmp = lk.compareTo(rk);
|
||||||
return cmp != 0 ? cmp : 1;
|
return cmp != 0 ? cmp : 1;
|
||||||
|
|
|
||||||
|
|
@ -18,22 +18,16 @@
|
||||||
|
|
||||||
package org.apache.cassandra.service.accord.txn;
|
package org.apache.cassandra.service.accord.txn;
|
||||||
|
|
||||||
import org.apache.cassandra.tcm.Epoch;
|
|
||||||
import org.apache.cassandra.utils.ObjectSizes;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Potentially returned by any transaction that tries to execute in an Epoch
|
* Potentially returned by any transaction that tries to execute in an Epoch
|
||||||
* where the range has migrated away from Accord
|
* where the range has migrated away from Accord
|
||||||
*/
|
*/
|
||||||
public class RetryWithNewProtocolResult implements TxnResult
|
public class RetryWithNewProtocolResult implements TxnResult
|
||||||
{
|
{
|
||||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new RetryWithNewProtocolResult(null));
|
public static final RetryWithNewProtocolResult instance = new RetryWithNewProtocolResult();
|
||||||
|
|
||||||
public final Epoch epoch;
|
private RetryWithNewProtocolResult()
|
||||||
|
|
||||||
RetryWithNewProtocolResult(Epoch epoch)
|
|
||||||
{
|
{
|
||||||
this.epoch = epoch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -45,6 +39,6 @@ public class RetryWithNewProtocolResult implements TxnResult
|
||||||
@Override
|
@Override
|
||||||
public long estimatedSizeOnHeap()
|
public long estimatedSizeOnHeap()
|
||||||
{
|
{
|
||||||
return EMPTY_SIZE + epoch.estimatedSizeOnHeap();
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,254 +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.txn;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
|
|
||||||
import accord.api.Data;
|
|
||||||
import accord.api.DataStore;
|
|
||||||
import accord.api.Read;
|
|
||||||
import accord.local.SafeCommandStore;
|
|
||||||
import accord.primitives.Keys;
|
|
||||||
import accord.primitives.Participants;
|
|
||||||
import accord.primitives.Ranges;
|
|
||||||
import accord.primitives.Seekable;
|
|
||||||
import accord.primitives.Timestamp;
|
|
||||||
import accord.utils.async.AsyncChain;
|
|
||||||
import accord.utils.async.AsyncChains;
|
|
||||||
import org.apache.cassandra.db.ConsistencyLevel;
|
|
||||||
import org.apache.cassandra.db.SinglePartitionReadCommand;
|
|
||||||
import org.apache.cassandra.io.util.DataInputPlus;
|
|
||||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
|
||||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
|
||||||
import org.apache.cassandra.utils.ObjectSizes;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
import static org.apache.cassandra.service.accord.AccordSerializers.consistencyLevelSerializer;
|
|
||||||
import static org.apache.cassandra.service.accord.IAccordService.SUPPORTED_READ_CONSISTENCY_LEVELS;
|
|
||||||
import static org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind.CAS_READ;
|
|
||||||
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.utils.ArraySerializers.deserializeArray;
|
|
||||||
import static org.apache.cassandra.utils.ArraySerializers.serializeArray;
|
|
||||||
import static org.apache.cassandra.utils.ArraySerializers.serializedArraySize;
|
|
||||||
import static org.apache.cassandra.utils.NullableSerializer.deserializeNullable;
|
|
||||||
import static org.apache.cassandra.utils.NullableSerializer.serializeNullable;
|
|
||||||
import static org.apache.cassandra.utils.NullableSerializer.serializedNullableSize;
|
|
||||||
|
|
||||||
public class TxnKeyRead extends AbstractKeySorted<TxnNamedRead> implements TxnRead
|
|
||||||
{
|
|
||||||
public static final TxnKeyRead EMPTY = new TxnKeyRead(new TxnNamedRead[0], null);
|
|
||||||
private static final long EMPTY_SIZE = ObjectSizes.measure(EMPTY);
|
|
||||||
private static final Comparator<TxnNamedRead> TXN_NAMED_READ_KEY_COMPARATOR = Comparator.comparing(TxnNamedRead::key);
|
|
||||||
|
|
||||||
// Cassandra's consistency level used by Accord to safely read data written outside of Accord
|
|
||||||
@Nullable
|
|
||||||
private final ConsistencyLevel cassandraConsistencyLevel;
|
|
||||||
|
|
||||||
private TxnKeyRead(@Nonnull TxnNamedRead[] items, @Nullable ConsistencyLevel cassandraConsistencyLevel)
|
|
||||||
{
|
|
||||||
super(items);
|
|
||||||
checkNotNull(items, "items is null");
|
|
||||||
checkArgument(cassandraConsistencyLevel == null || SUPPORTED_READ_CONSISTENCY_LEVELS.contains(cassandraConsistencyLevel), "Unsupported consistency level for read");
|
|
||||||
this.cassandraConsistencyLevel = cassandraConsistencyLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TxnKeyRead(@Nonnull List<TxnNamedRead> items, @Nullable ConsistencyLevel cassandraConsistencyLevel)
|
|
||||||
{
|
|
||||||
super(items);
|
|
||||||
checkNotNull(items, "items is null");
|
|
||||||
checkArgument(cassandraConsistencyLevel == null || SUPPORTED_READ_CONSISTENCY_LEVELS.contains(cassandraConsistencyLevel), "Unsupported consistency level for read");
|
|
||||||
this.cassandraConsistencyLevel = cassandraConsistencyLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TxnKeyRead createTxnRead(@Nonnull List<TxnNamedRead> items, @Nullable ConsistencyLevel consistencyLevel)
|
|
||||||
{
|
|
||||||
items.sort(Comparator.comparing(TxnNamedRead::key));
|
|
||||||
return new TxnKeyRead(items, consistencyLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TxnKeyRead createSerialRead(List<SinglePartitionReadCommand> readCommands, ConsistencyLevel consistencyLevel)
|
|
||||||
{
|
|
||||||
List<TxnNamedRead> reads = new ArrayList<>(readCommands.size());
|
|
||||||
for (int i = 0; i < readCommands.size(); i++)
|
|
||||||
reads.add(new TxnNamedRead(txnDataName(USER, i), readCommands.get(i)));
|
|
||||||
reads.sort(TXN_NAMED_READ_KEY_COMPARATOR);
|
|
||||||
return new TxnKeyRead(reads, consistencyLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TxnKeyRead createCasRead(SinglePartitionReadCommand readCommand, ConsistencyLevel consistencyLevel)
|
|
||||||
{
|
|
||||||
TxnNamedRead read = new TxnNamedRead(txnDataName(CAS_READ), readCommand);
|
|
||||||
return new TxnKeyRead(ImmutableList.of(read), consistencyLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A read that declares it will read from keys but doesn't actually read any data so dependent transactions will
|
|
||||||
// still be applied first
|
|
||||||
public static TxnKeyRead createNoOpRead(Keys keys)
|
|
||||||
{
|
|
||||||
List<TxnNamedRead> reads = new ArrayList<>(keys.size());
|
|
||||||
for (int i = 0; i < keys.size(); i++)
|
|
||||||
reads.add(new TxnNamedRead(txnDataName(USER, i), (PartitionKey)keys.get(i), null));
|
|
||||||
return new TxnKeyRead(reads, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long estimatedSizeOnHeap()
|
|
||||||
{
|
|
||||||
long size = EMPTY_SIZE;
|
|
||||||
for (TxnNamedRead read : items)
|
|
||||||
size += read.estimatedSizeOnHeap();
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
int compareNonKeyFields(TxnNamedRead left, TxnNamedRead right)
|
|
||||||
{
|
|
||||||
return Integer.compare(left.txnDataName(), right.txnDataName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
PartitionKey getKey(TxnNamedRead read)
|
|
||||||
{
|
|
||||||
return read.key();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
TxnNamedRead[] newArray(int size)
|
|
||||||
{
|
|
||||||
return new TxnNamedRead[size];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Keys keys()
|
|
||||||
{
|
|
||||||
return itemKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ConsistencyLevel cassandraConsistencyLevel()
|
|
||||||
{
|
|
||||||
return cassandraConsistencyLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Read slice(Ranges ranges)
|
|
||||||
{
|
|
||||||
return intersecting(itemKeys.slice(ranges));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Read intersecting(Participants<?> participants)
|
|
||||||
{
|
|
||||||
return intersecting(itemKeys.intersecting(participants));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Read intersecting(Keys select)
|
|
||||||
{
|
|
||||||
Keys keys = itemKeys.intersecting(select);
|
|
||||||
List<TxnNamedRead> reads = new ArrayList<>(keys.size());
|
|
||||||
|
|
||||||
for (TxnNamedRead read : items)
|
|
||||||
if (keys.contains(read.key()))
|
|
||||||
reads.add(read);
|
|
||||||
|
|
||||||
return createTxnRead(reads, cassandraConsistencyLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Read merge(Read read)
|
|
||||||
{
|
|
||||||
List<TxnNamedRead> reads = new ArrayList<>(items.length);
|
|
||||||
Collections.addAll(reads, items);
|
|
||||||
|
|
||||||
for (TxnNamedRead namedRead : (TxnKeyRead) read)
|
|
||||||
if (!reads.contains(namedRead))
|
|
||||||
reads.add(namedRead);
|
|
||||||
|
|
||||||
return createTxnRead(reads, cassandraConsistencyLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unmemoize()
|
|
||||||
{
|
|
||||||
for (TxnNamedRead read : items)
|
|
||||||
read.unmemoize();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AsyncChain<Data> read(Seekable key, SafeCommandStore safeStore, Timestamp executeAt, DataStore store)
|
|
||||||
{
|
|
||||||
// Set to null since we don't need it and interop can pass in null
|
|
||||||
safeStore = null;
|
|
||||||
|
|
||||||
List<AsyncChain<Data>> results = new ArrayList<>();
|
|
||||||
forEachWithKey((PartitionKey) key, read -> results.add(read.read(cassandraConsistencyLevel, executeAt)));
|
|
||||||
|
|
||||||
if (results.isEmpty())
|
|
||||||
// Result type must match everywhere
|
|
||||||
return AsyncChains.success(new TxnData());
|
|
||||||
|
|
||||||
if (results.size() == 1)
|
|
||||||
return results.get(0);
|
|
||||||
|
|
||||||
return AsyncChains.reduce(results, Data::merge);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Kind kind()
|
|
||||||
{
|
|
||||||
return Kind.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final TxnReadSerializer<TxnKeyRead> serializer = new TxnReadSerializer<TxnKeyRead>()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void serialize(TxnKeyRead read, DataOutputPlus out, int version) throws IOException
|
|
||||||
{
|
|
||||||
serializeArray(read.items, out, version, TxnNamedRead.serializer);
|
|
||||||
serializeNullable(read.cassandraConsistencyLevel, out, version, consistencyLevelSerializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TxnKeyRead deserialize(DataInputPlus in, int version) throws IOException
|
|
||||||
{
|
|
||||||
TxnNamedRead[] items = deserializeArray(in, version, TxnNamedRead.serializer, TxnNamedRead[]::new);
|
|
||||||
ConsistencyLevel consistencyLevel = deserializeNullable(in, version, consistencyLevelSerializer);
|
|
||||||
return new TxnKeyRead(items, consistencyLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long serializedSize(TxnKeyRead read, int version)
|
|
||||||
{
|
|
||||||
long size = 0;
|
|
||||||
size += serializedArraySize(read.items, version, TxnNamedRead.serializer);
|
|
||||||
size += serializedNullableSize(read.cassandraConsistencyLevel, version, consistencyLevelSerializer);
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -29,6 +29,8 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import accord.api.Data;
|
import accord.api.Data;
|
||||||
|
import accord.primitives.Range;
|
||||||
|
import accord.primitives.Seekable;
|
||||||
import accord.primitives.Timestamp;
|
import accord.primitives.Timestamp;
|
||||||
import accord.utils.async.AsyncChain;
|
import accord.utils.async.AsyncChain;
|
||||||
import accord.utils.async.AsyncChains;
|
import accord.utils.async.AsyncChains;
|
||||||
|
|
@ -36,20 +38,38 @@ import accord.utils.async.AsyncResults;
|
||||||
import org.apache.cassandra.concurrent.DebuggableTask;
|
import org.apache.cassandra.concurrent.DebuggableTask;
|
||||||
import org.apache.cassandra.concurrent.Stage;
|
import org.apache.cassandra.concurrent.Stage;
|
||||||
import org.apache.cassandra.db.ConsistencyLevel;
|
import org.apache.cassandra.db.ConsistencyLevel;
|
||||||
|
import org.apache.cassandra.db.PartitionPosition;
|
||||||
|
import org.apache.cassandra.db.PartitionRangeReadCommand;
|
||||||
import org.apache.cassandra.db.ReadCommand;
|
import org.apache.cassandra.db.ReadCommand;
|
||||||
import org.apache.cassandra.db.ReadExecutionController;
|
import org.apache.cassandra.db.ReadExecutionController;
|
||||||
import org.apache.cassandra.db.SinglePartitionReadCommand;
|
import org.apache.cassandra.db.SinglePartitionReadCommand;
|
||||||
import org.apache.cassandra.db.TypeSizes;
|
import org.apache.cassandra.db.TypeSizes;
|
||||||
|
import org.apache.cassandra.db.partitions.FilteredPartition;
|
||||||
import org.apache.cassandra.db.partitions.PartitionIterator;
|
import org.apache.cassandra.db.partitions.PartitionIterator;
|
||||||
|
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
||||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
|
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
|
||||||
|
import org.apache.cassandra.db.rows.RowIterator;
|
||||||
|
import org.apache.cassandra.dht.AbstractBounds;
|
||||||
|
import org.apache.cassandra.dht.Token;
|
||||||
|
import org.apache.cassandra.dht.Token.KeyBound;
|
||||||
import org.apache.cassandra.io.IVersionedSerializer;
|
import org.apache.cassandra.io.IVersionedSerializer;
|
||||||
import org.apache.cassandra.io.util.DataInputPlus;
|
import org.apache.cassandra.io.util.DataInputPlus;
|
||||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||||
|
import org.apache.cassandra.schema.TableId;
|
||||||
|
import org.apache.cassandra.service.accord.AccordObjectSizes;
|
||||||
|
import org.apache.cassandra.service.accord.TokenRange;
|
||||||
|
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
|
||||||
|
import org.apache.cassandra.service.accord.api.AccordRoutingKey.MinTokenKey;
|
||||||
|
import org.apache.cassandra.service.accord.api.AccordRoutingKey.SentinelKey;
|
||||||
|
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
||||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||||
|
import org.apache.cassandra.service.accord.serializers.KeySerializers;
|
||||||
|
import org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind;
|
||||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||||
import org.apache.cassandra.utils.MonotonicClock;
|
import org.apache.cassandra.utils.MonotonicClock;
|
||||||
import org.apache.cassandra.utils.ObjectSizes;
|
import org.apache.cassandra.utils.ObjectSizes;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static org.apache.cassandra.utils.ByteBufferUtil.readWithVIntLength;
|
import static org.apache.cassandra.utils.ByteBufferUtil.readWithVIntLength;
|
||||||
import static org.apache.cassandra.utils.ByteBufferUtil.serializedSizeWithVIntLength;
|
import static org.apache.cassandra.utils.ByteBufferUtil.serializedSizeWithVIntLength;
|
||||||
import static org.apache.cassandra.utils.ByteBufferUtil.writeWithVIntLength;
|
import static org.apache.cassandra.utils.ByteBufferUtil.writeWithVIntLength;
|
||||||
|
|
@ -59,10 +79,10 @@ public class TxnNamedRead extends AbstractSerialized<ReadCommand>
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final Logger logger = LoggerFactory.getLogger(TxnNamedRead.class);
|
private static final Logger logger = LoggerFactory.getLogger(TxnNamedRead.class);
|
||||||
|
|
||||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new TxnNamedRead(0, null, null));
|
private static final long EMPTY_SIZE = ObjectSizes.measure(new TxnNamedRead(0, null, (ByteBuffer) null));
|
||||||
|
|
||||||
private final int name;
|
private final int name;
|
||||||
private final PartitionKey key;
|
private final Seekable key;
|
||||||
|
|
||||||
public TxnNamedRead(int name, @Nullable SinglePartitionReadCommand value)
|
public TxnNamedRead(int name, @Nullable SinglePartitionReadCommand value)
|
||||||
{
|
{
|
||||||
|
|
@ -71,7 +91,51 @@ public class TxnNamedRead extends AbstractSerialized<ReadCommand>
|
||||||
this.key = new PartitionKey(value.metadata().id, value.partitionKey());
|
this.key = new PartitionKey(value.metadata().id, value.partitionKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
public TxnNamedRead(int name, PartitionKey key, ByteBuffer bytes)
|
public static TokenRange boundsAsAccordRange(AbstractBounds<PartitionPosition> range, TableId tableId)
|
||||||
|
{
|
||||||
|
// Should already have been unwrapped
|
||||||
|
checkState(!AbstractBounds.strictlyWrapsAround(range.left, range.right));
|
||||||
|
|
||||||
|
// Read commands can contain a mix of different kinds of bounds to facilitate paging
|
||||||
|
// and we need to communicate that to Accord as its own ranges. This uses
|
||||||
|
// TokenKey, SentinelKey, and MinTokenKey and sticks exclusively with left exclusive/right inclusive
|
||||||
|
// ranges rather add more types of ranges to the mix
|
||||||
|
// MinTokenKey allows emulating inclusive left and exclusive right with Range
|
||||||
|
boolean inclusiveLeft = range.inclusiveLeft();
|
||||||
|
PartitionPosition startPP = range.left;
|
||||||
|
boolean startIsMinKeyBound = startPP.getClass() == KeyBound.class ? ((KeyBound)startPP).isMinimumBound : false;
|
||||||
|
Token startToken = startPP.getToken();
|
||||||
|
AccordRoutingKey startAccordRoutingKey;
|
||||||
|
if (startToken.isMinimum() && inclusiveLeft)
|
||||||
|
startAccordRoutingKey = SentinelKey.min(tableId);
|
||||||
|
else if (inclusiveLeft || startIsMinKeyBound)
|
||||||
|
startAccordRoutingKey = new MinTokenKey(tableId, startToken);
|
||||||
|
else
|
||||||
|
startAccordRoutingKey = new TokenKey(tableId, startToken);
|
||||||
|
|
||||||
|
boolean inclusiveRight = range.inclusiveRight();
|
||||||
|
PartitionPosition endPP = range.right;
|
||||||
|
boolean endIsMinKeyBound = endPP.getClass() == KeyBound.class ? ((KeyBound)endPP).isMinimumBound : false;
|
||||||
|
Token stopToken = range.right.getToken();
|
||||||
|
AccordRoutingKey stopAccordRoutingKey;
|
||||||
|
if (stopToken.isMinimum())
|
||||||
|
stopAccordRoutingKey = SentinelKey.max(tableId);
|
||||||
|
else if (inclusiveRight && !endIsMinKeyBound)
|
||||||
|
stopAccordRoutingKey = new TokenKey(tableId, stopToken);
|
||||||
|
else
|
||||||
|
stopAccordRoutingKey = new MinTokenKey(tableId, stopToken);
|
||||||
|
return TokenRange.create(startAccordRoutingKey, stopAccordRoutingKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TxnNamedRead(int name, AbstractBounds<PartitionPosition> range, PartitionRangeReadCommand value)
|
||||||
|
{
|
||||||
|
super(value);
|
||||||
|
TableId tableId = value.metadata().id;
|
||||||
|
this.name = name;
|
||||||
|
this.key = boundsAsAccordRange(range, tableId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TxnNamedRead(int name, Seekable key, ByteBuffer bytes)
|
||||||
{
|
{
|
||||||
super(bytes);
|
super(bytes);
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
@ -80,13 +144,16 @@ public class TxnNamedRead extends AbstractSerialized<ReadCommand>
|
||||||
|
|
||||||
public long estimatedSizeOnHeap()
|
public long estimatedSizeOnHeap()
|
||||||
{
|
{
|
||||||
return EMPTY_SIZE + key.estimatedSizeOnHeap() + (bytes() != null ? ByteBufferUtil.estimatedSizeOnHeap(bytes()) : 0);
|
long size = EMPTY_SIZE;
|
||||||
|
size += AccordObjectSizes.seekable(key);
|
||||||
|
size += (bytes() != null ? ByteBufferUtil.estimatedSizeOnHeap(bytes()) : 0);
|
||||||
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IVersionedSerializer<ReadCommand> serializer()
|
protected IVersionedSerializer<ReadCommand> serializer()
|
||||||
{
|
{
|
||||||
return SinglePartitionReadCommand.serializer;
|
return ReadCommand.serializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -108,7 +175,7 @@ public class TxnNamedRead extends AbstractSerialized<ReadCommand>
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "TxnNamedRead{name='" + name + '\'' + ", key=" + key + ", update=" + get() + '}';
|
return "TxnNamedRead{name='" + name + '\'' + ", keys=" + key + ", update=" + get() + '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
public int txnDataName()
|
public int txnDataName()
|
||||||
|
|
@ -116,39 +183,58 @@ public class TxnNamedRead extends AbstractSerialized<ReadCommand>
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PartitionKey key()
|
public Seekable key()
|
||||||
{
|
{
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AsyncChain<Data> read(ConsistencyLevel consistencyLevel, Timestamp executeAt)
|
public static long nowInSeconds(Timestamp executeAt)
|
||||||
{
|
{
|
||||||
SinglePartitionReadCommand command = (SinglePartitionReadCommand) get();
|
return TimeUnit.MICROSECONDS.toSeconds(executeAt.hlc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsyncChain<Data> read(ConsistencyLevel consistencyLevel, Seekable key, Timestamp executeAt)
|
||||||
|
{
|
||||||
|
ReadCommand command = get();
|
||||||
if (command == null)
|
if (command == null)
|
||||||
return AsyncResults.success(TxnData.NOOP_DATA);
|
return AsyncResults.success(TxnData.NOOP_DATA);
|
||||||
|
|
||||||
// TODO (required, safety): before release, double check reasoning that this is safe
|
// TODO (required, safety): before release, double check reasoning that this is safe
|
||||||
// AccordCommandsForKey cfk = ((SafeAccordCommandStore)safeStore).commandsForKey(key);
|
// AccordCommandsForKey cfk = ((SafeAccordCommandStore)safeStore).commandsForKey(key);
|
||||||
// int nowInSeconds = cfk.nowInSecondsFor(executeAt, isForWriteTxn);
|
// int nowInSeconds = cfk.nowInSecondsFor(executeAt, isForWriteTxn);
|
||||||
// It's fine for our nowInSeconds to lag slightly our insertion timestamp, as to the user
|
// It's fine for our nowInSeconds to lag slightly our insertion timestamp, as to the user
|
||||||
// this simply looks like the transaction witnessed TTL'd data and the data then expired
|
// this simply looks like the transaction witnessed TTL'd data and the data then expired
|
||||||
// immediately after the transaction executed, and this simplifies things a great deal
|
// immediately after the transaction executed, and this simplifies things a great deal
|
||||||
int nowInSeconds = (int) TimeUnit.MICROSECONDS.toSeconds(executeAt.hlc());
|
long nowInSeconds = nowInSeconds(executeAt);
|
||||||
if (consistencyLevel == null || consistencyLevel == ConsistencyLevel.ONE)
|
|
||||||
command = command.withoutReconciliation();
|
boolean withoutReconciliation = readsWithoutReconciliation(consistencyLevel);
|
||||||
return performLocalRead(command, nowInSeconds);
|
switch (key.domain())
|
||||||
|
{
|
||||||
|
case Key:
|
||||||
|
return performLocalKeyRead(((SinglePartitionReadCommand) command).withTransactionalSettings(withoutReconciliation, nowInSeconds));
|
||||||
|
case Range:
|
||||||
|
return performLocalRangeRead(((PartitionRangeReadCommand) command), key.asRange(), consistencyLevel, nowInSeconds);
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unhandled domain " + key.domain());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean readsWithoutReconciliation(ConsistencyLevel consistencyLevel)
|
||||||
|
{
|
||||||
|
boolean withoutReconciliation = consistencyLevel == null || consistencyLevel == ConsistencyLevel.ONE;
|
||||||
|
return withoutReconciliation;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public ReadCommand command()
|
public ReadCommand command()
|
||||||
{
|
{
|
||||||
return get();
|
return get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private AsyncChain<Data> performLocalRead(SinglePartitionReadCommand command, int nowInSeconds)
|
private AsyncChain<Data> performLocalKeyRead(SinglePartitionReadCommand read)
|
||||||
{
|
{
|
||||||
Callable<Data> readCallable = () ->
|
Callable<Data> readCallable = () ->
|
||||||
{
|
{
|
||||||
SinglePartitionReadCommand read = command.withNowInSec(nowInSeconds);
|
|
||||||
|
|
||||||
try (ReadExecutionController controller = read.executionController();
|
try (ReadExecutionController controller = read.executionController();
|
||||||
PartitionIterator iterator = UnfilteredPartitionIterators.filter(read.executeLocally(controller), read.nowInSec()))
|
PartitionIterator iterator = UnfilteredPartitionIterators.filter(read.executeLocally(controller), read.nowInSec()))
|
||||||
{
|
{
|
||||||
|
|
@ -201,19 +287,132 @@ public class TxnNamedRead extends AbstractSerialized<ReadCommand>
|
||||||
@Override
|
@Override
|
||||||
public String description()
|
public String description()
|
||||||
{
|
{
|
||||||
return command.toCQLString();
|
return read.toCQLString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PartitionRangeReadCommand commandForSubrange(PartitionRangeReadCommand command, Range r, ConsistencyLevel consistencyLevel, long nowInSeconds)
|
||||||
|
{
|
||||||
|
AbstractBounds<PartitionPosition> bounds = command.dataRange().keyRange();
|
||||||
|
PartitionPosition startPP = bounds.left;
|
||||||
|
PartitionPosition endPP = bounds.right;
|
||||||
|
TokenKey startTokenKey = new TokenKey(command.metadata().id, startPP.getToken());
|
||||||
|
AccordRoutingKey startRoutingKey = ((AccordRoutingKey)r.start());
|
||||||
|
AccordRoutingKey endRoutingKey = ((AccordRoutingKey)r.end());
|
||||||
|
Token subRangeStartToken = startRoutingKey.getClass() == SentinelKey.class ? startPP.getToken() : ((AccordRoutingKey)r.start()).asTokenKey().token();
|
||||||
|
Token subRangeEndToken = endRoutingKey.getClass() == SentinelKey.class ? endPP.getToken() : ((AccordRoutingKey)r.end()).asTokenKey().token();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The way ranges/bounds work for range queries is that the beginning and ending bounds from the command
|
||||||
|
* could be tokens (and min/max key bounds) or actual keys depending on the bounds of the top level query we
|
||||||
|
* are running and where we are in paging. We need to preserve whatever is in the command in case it is a
|
||||||
|
* key and not a token, or it's a token but might be a min/max key bound.
|
||||||
|
*
|
||||||
|
* Then Accord will further subdivide the range in the command so need to inject additional bounds in the middle
|
||||||
|
* that match the range ownership of Accord.
|
||||||
|
*
|
||||||
|
* The command still contains the original bound and then the Accord range passed in determines what subset of
|
||||||
|
* that bound we want. We have to make sure to use the bounds from the command if it is the start or end instead
|
||||||
|
* of a key bound created from the Accord range since it could be a real key or min/max bound.
|
||||||
|
*
|
||||||
|
* When we are dealing with a bound created by Accord's further subdivision we use a maxKeyBound (exclusive)
|
||||||
|
* for both beginning and end because Bounds is left and right inclusive while Range is only left inclusive.
|
||||||
|
* We only use TokenRange with Accord which matches the left/right inclusivity of Cassandra's Range.
|
||||||
|
*
|
||||||
|
* That means the Range we get from Accord overlaps the previous Range on the left which when converted to a Bound
|
||||||
|
* would potentially read the same Token twice. So the left needs to be a maxKeyBound to exclude the data that isn't
|
||||||
|
* owned here and to avoid potentially reading the same data twice. The right bound also needs to be a maxKeyBound since Range
|
||||||
|
* is right inclusive so every partition we find needs to be < the right bound.
|
||||||
|
*/
|
||||||
|
boolean isFirstSubrange = startPP.getToken().equals(subRangeStartToken);
|
||||||
|
PartitionPosition subRangeStartPP = isFirstSubrange ? startPP : subRangeStartToken.maxKeyBound();
|
||||||
|
PartitionPosition subRangeEndPP = endPP.getToken().equals(subRangeEndToken) ? endPP : subRangeEndToken.maxKeyBound();
|
||||||
|
// Need to preserve the fact it is a bounds for paging to work, a range is not left inclusive and will not start from where we left off
|
||||||
|
AbstractBounds<PartitionPosition> subRange = isFirstSubrange ? bounds.withNewRight(subRangeEndPP) : new org.apache.cassandra.dht.Range(subRangeStartPP, subRangeEndPP);
|
||||||
|
return command.withTransactionalSettings(nowInSeconds, subRange, startTokenKey.equals(r.start()), readsWithoutReconciliation(consistencyLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AsyncChain<Data> performLocalRangeRead(PartitionRangeReadCommand command, Range r, ConsistencyLevel consistencyLevel, long nowInSeconds)
|
||||||
|
{
|
||||||
|
PartitionRangeReadCommand read = commandForSubrange(command, r, consistencyLevel, nowInSeconds);
|
||||||
|
Callable<Data> readCallable = () ->
|
||||||
|
{
|
||||||
|
try (ReadExecutionController controller = read.executionController();
|
||||||
|
UnfilteredPartitionIterator partition = read.executeLocally(controller);
|
||||||
|
PartitionIterator iterator = UnfilteredPartitionIterators.filter(partition, read.nowInSec()))
|
||||||
|
{
|
||||||
|
TxnData result = new TxnData();
|
||||||
|
TxnDataRangeValue value = new TxnDataRangeValue();
|
||||||
|
while(iterator.hasNext())
|
||||||
|
{
|
||||||
|
try (RowIterator rows = iterator.next())
|
||||||
|
{
|
||||||
|
FilteredPartition filtered = FilteredPartition.create(rows);
|
||||||
|
if (filtered.hasRows() || read.selectsFullPartition())
|
||||||
|
{
|
||||||
|
value.add(filtered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.put(TxnData.txnDataName(TxnDataNameKind.USER), value);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return AsyncChains.ofCallable(Stage.READ.executor(), readCallable, (callable, receiver) ->
|
||||||
|
new DebuggableTask.RunnableDebuggableTask()
|
||||||
|
{
|
||||||
|
private final long approxCreationTimeNanos = MonotonicClock.Global.approxTime.now();
|
||||||
|
private volatile long approxStartTimeNanos;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
approxStartTimeNanos = MonotonicClock.Global.approxTime.now();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Data call = callable.call();
|
||||||
|
receiver.accept(call, null);
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
logger.debug("AsyncChain Callable threw an Exception", t);
|
||||||
|
receiver.accept(null, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long creationTimeNanos()
|
||||||
|
{
|
||||||
|
return approxCreationTimeNanos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long startTimeNanos()
|
||||||
|
{
|
||||||
|
return approxStartTimeNanos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description()
|
||||||
|
{
|
||||||
|
return command.toCQLString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static final IVersionedSerializer<TxnNamedRead> serializer = new IVersionedSerializer<>()
|
static final IVersionedSerializer<TxnNamedRead> serializer = new IVersionedSerializer<>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void serialize(TxnNamedRead read, DataOutputPlus out, int version) throws IOException
|
public void serialize(TxnNamedRead read, DataOutputPlus out, int version) throws IOException
|
||||||
{
|
{
|
||||||
out.writeInt(read.name);
|
out.writeInt(read.name);
|
||||||
PartitionKey.serializer.serialize(read.key, out, version);
|
KeySerializers.seekable.serialize(read.key, out, version);
|
||||||
|
|
||||||
if (read.bytes() != null)
|
if (read.bytes() != null)
|
||||||
{
|
{
|
||||||
out.write(0);
|
out.write(0);
|
||||||
|
|
@ -229,7 +428,7 @@ public class TxnNamedRead extends AbstractSerialized<ReadCommand>
|
||||||
public TxnNamedRead deserialize(DataInputPlus in, int version) throws IOException
|
public TxnNamedRead deserialize(DataInputPlus in, int version) throws IOException
|
||||||
{
|
{
|
||||||
int name = in.readInt();
|
int name = in.readInt();
|
||||||
PartitionKey key = PartitionKey.serializer.deserialize(in, version);
|
Seekable key = KeySerializers.seekable.deserialize(in, version);
|
||||||
ByteBuffer bytes = in.readByte() == 1 ? null : readWithVIntLength(in);
|
ByteBuffer bytes = in.readByte() == 1 ? null : readWithVIntLength(in);
|
||||||
return new TxnNamedRead(name, key, bytes);
|
return new TxnNamedRead(name, key, bytes);
|
||||||
}
|
}
|
||||||
|
|
@ -239,8 +438,8 @@ public class TxnNamedRead extends AbstractSerialized<ReadCommand>
|
||||||
{
|
{
|
||||||
long size = 0;
|
long size = 0;
|
||||||
size += TypeSizes.sizeof(read.name);
|
size += TypeSizes.sizeof(read.name);
|
||||||
size += PartitionKey.serializer.serializedSize(read.key, version);
|
size += KeySerializers.seekable.serializedSize(read.key, version);
|
||||||
size += TypeSizes.BOOL_SIZE; // is null
|
size += TypeSizes.BYTE_SIZE; // is null
|
||||||
if (read.bytes() != null)
|
if (read.bytes() != null)
|
||||||
size += serializedSizeWithVIntLength(read.bytes());
|
size += serializedSizeWithVIntLength(read.bytes());
|
||||||
return size;
|
return size;
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import accord.api.Query;
|
||||||
import accord.api.Read;
|
import accord.api.Read;
|
||||||
import accord.api.Result;
|
import accord.api.Result;
|
||||||
import accord.api.Update;
|
import accord.api.Update;
|
||||||
|
import accord.primitives.Ranges;
|
||||||
import accord.primitives.Seekables;
|
import accord.primitives.Seekables;
|
||||||
import accord.primitives.Timestamp;
|
import accord.primitives.Timestamp;
|
||||||
import accord.primitives.TxnId;
|
import accord.primitives.TxnId;
|
||||||
|
|
@ -41,22 +42,27 @@ import org.apache.cassandra.io.IVersionedSerializer;
|
||||||
import org.apache.cassandra.io.util.DataInputPlus;
|
import org.apache.cassandra.io.util.DataInputPlus;
|
||||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||||
import org.apache.cassandra.metrics.ClientRequestsMetricsHolder;
|
import org.apache.cassandra.metrics.ClientRequestsMetricsHolder;
|
||||||
|
import org.apache.cassandra.service.accord.TokenRange;
|
||||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind;
|
import org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind;
|
||||||
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter;
|
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter;
|
||||||
import org.apache.cassandra.tcm.Epoch;
|
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||||
import org.apache.cassandra.utils.ObjectSizes;
|
import org.apache.cassandra.utils.ObjectSizes;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind.CAS_READ;
|
import static org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind.CAS_READ;
|
||||||
import static org.apache.cassandra.service.accord.txn.TxnData.txnDataName;
|
import static org.apache.cassandra.service.accord.txn.TxnData.txnDataName;
|
||||||
|
|
||||||
public abstract class TxnQuery implements Query
|
public abstract class TxnQuery implements Query
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by transaction statements which will have Accord pass back to the C* coordinator code all the data that is
|
* Used by transaction statements which will have Accord pass back to the C* coordinator code all the data that is
|
||||||
* read even if it is not returned as part of the result to the client. TxnDataName.returning() will fetch the data
|
* read even if it is not returned as part of the result to the client. TxnDataName.returning() will fetch the data
|
||||||
* that is returned from TxnData.
|
* that is returned from TxnData.
|
||||||
|
*
|
||||||
|
* Also used by SERIAL key reads, and non-SERIAL key reads when they are executed on Accord.
|
||||||
*/
|
*/
|
||||||
public static final TxnQuery ALL = new TxnQuery()
|
public static final TxnQuery ALL = new TxnQuery()
|
||||||
{
|
{
|
||||||
|
|
@ -118,7 +124,7 @@ public abstract class TxnQuery implements Query
|
||||||
return new TxnData();
|
return new TxnData();
|
||||||
else if (txnData.isEmpty())
|
else if (txnData.isEmpty())
|
||||||
{
|
{
|
||||||
TxnKeyRead txnKeyRead = (TxnKeyRead)read;
|
TxnRead txnKeyRead = (TxnRead)read;
|
||||||
SinglePartitionReadCommand command = (SinglePartitionReadCommand) txnKeyRead.iterator().next().get();
|
SinglePartitionReadCommand command = (SinglePartitionReadCommand) txnKeyRead.iterator().next().get();
|
||||||
// For CAS must return a non-empty result to indicate error even if there was no partition found
|
// For CAS must return a non-empty result to indicate error even if there was no partition found
|
||||||
return TxnData.of(txnDataName(CAS_READ), new TxnDataKeyValue(EmptyIterators.row(command.metadata(), command.partitionKey(), command.isReversed())));
|
return TxnData.of(txnDataName(CAS_READ), new TxnDataKeyValue(EmptyIterators.row(command.metadata(), command.partitionKey(), command.isReversed())));
|
||||||
|
|
@ -132,7 +138,7 @@ public abstract class TxnQuery implements Query
|
||||||
/**
|
/**
|
||||||
* UNSAFE_EMPTY doesn't validate that the range is owned by Accord so you want to be careful and use NONE
|
* UNSAFE_EMPTY doesn't validate that the range is owned by Accord so you want to be careful and use NONE
|
||||||
* if your transaction simply doesn't have results because that will validate that Accord owns the range
|
* if your transaction simply doesn't have results because that will validate that Accord owns the range
|
||||||
* for things like blind writes. Empty is used by Accord for things like sync points which may need to exeucte
|
* for things like blind writes. Empty is used by Accord for things like sync points which may need to execute
|
||||||
* for ranges Accord used to manage, but no longer does.
|
* for ranges Accord used to manage, but no longer does.
|
||||||
*/
|
*/
|
||||||
public static final TxnQuery UNSAFE_EMPTY = new TxnQuery()
|
public static final TxnQuery UNSAFE_EMPTY = new TxnQuery()
|
||||||
|
|
@ -175,8 +181,8 @@ public abstract class TxnQuery implements Query
|
||||||
|
|
||||||
private Result concat(TxnData data, Read read)
|
private Result concat(TxnData data, Read read)
|
||||||
{
|
{
|
||||||
TxnRangeRead txnRead = (TxnRangeRead) read;
|
TxnRead txnRead = (TxnRead) read;
|
||||||
PartitionRangeReadCommand command = (PartitionRangeReadCommand) txnRead.get();
|
PartitionRangeReadCommand command = (PartitionRangeReadCommand) txnRead.iterator().next().get();
|
||||||
TxnDataRangeValue value = (TxnDataRangeValue) data.get(txnDataName(TxnDataNameKind.USER));
|
TxnDataRangeValue value = (TxnDataRangeValue) data.get(txnDataName(TxnDataNameKind.USER));
|
||||||
Supplier<PartitionIterator> source = value.toPartitionIterator(command.isReversed());
|
Supplier<PartitionIterator> source = value.toPartitionIterator(command.isReversed());
|
||||||
// Because the query was split across multiple command stores the pushed down limit won't be sufficient
|
// Because the query was split across multiple command stores the pushed down limit won't be sufficient
|
||||||
|
|
@ -200,14 +206,17 @@ public abstract class TxnQuery implements Query
|
||||||
@Override
|
@Override
|
||||||
public Result compute(TxnId txnId, Timestamp executeAt, Seekables<?, ?> keys, @Nullable Data data, @Nullable Read read, @Nullable Update update)
|
public Result compute(TxnId txnId, Timestamp executeAt, Seekables<?, ?> keys, @Nullable Data data, @Nullable Read read, @Nullable Update update)
|
||||||
{
|
{
|
||||||
Epoch epoch = Epoch.create(executeAt.epoch());
|
// TODO (required): This is not the cluster metadata of the current transaction
|
||||||
if (transactionIsInMigratingOrMigratedRange(epoch, keys))
|
ClusterMetadata clusterMetadata = ClusterMetadata.current();
|
||||||
|
checkState(clusterMetadata.epoch.getEpoch() >= executeAt.epoch(), "TCM epoch %d is < executeAt epoch %d", clusterMetadata.epoch.getEpoch(), executeAt.epoch());
|
||||||
|
boolean reads = read != null && !read.keys().isEmpty();
|
||||||
|
if (transactionShouldBeBlocked(clusterMetadata, reads, keys))
|
||||||
{
|
{
|
||||||
if (txnId.isWrite())
|
if (txnId.isWrite())
|
||||||
ClientRequestsMetricsHolder.accordWriteMetrics.accordMigrationRejects.mark();
|
ClientRequestsMetricsHolder.accordWriteMetrics.accordMigrationRejects.mark();
|
||||||
else
|
else
|
||||||
ClientRequestsMetricsHolder.accordReadMetrics.accordMigrationRejects.mark();
|
ClientRequestsMetricsHolder.accordReadMetrics.accordMigrationRejects.mark();
|
||||||
return new RetryWithNewProtocolResult(epoch);
|
return RetryWithNewProtocolResult.instance;
|
||||||
}
|
}
|
||||||
return doCompute(txnId, executeAt, keys, data, read, update);
|
return doCompute(txnId, executeAt, keys, data, read, update);
|
||||||
}
|
}
|
||||||
|
|
@ -249,19 +258,56 @@ public abstract class TxnQuery implements Query
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static boolean transactionIsInMigratingOrMigratedRange(Epoch epoch, Seekables<?, ?> keys)
|
private static boolean transactionShouldBeBlocked(ClusterMetadata clusterMetadata, boolean reads, Seekables<?, ?> keys)
|
||||||
{
|
{
|
||||||
// TODO (required): This is going to be problematic when we presumably support range reads and don't validate them
|
// TxnQuery needs to be smart enough to allow blind writes through for the non-transactional use cases during migration
|
||||||
// Whatever this transaction might be it isn't one supported for migration anyways
|
// This also allows blind write TransactionStatement to run before TransactionStatemetns with reads can run,
|
||||||
if (!keys.domain().isKey())
|
// but this is harmless since we only promise that TransactionStatement works when migrated to Accord.
|
||||||
return false;
|
// TODO (lowpri): This could look at read keys vs write keys to see if it can run in more cases
|
||||||
|
if (reads)
|
||||||
|
return !transactionIsSafeToReadAndWrite(clusterMetadata, keys);
|
||||||
|
else
|
||||||
|
return !transactionIsSafeToWrite(clusterMetadata, keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean transactionIsSafeToReadAndWrite(ClusterMetadata clusterMetadata, Seekables<?, ?> keys)
|
||||||
|
{
|
||||||
|
switch (keys.domain())
|
||||||
|
{
|
||||||
|
case Key:
|
||||||
|
for (PartitionKey partitionKey : (Seekables<PartitionKey, ?>)keys)
|
||||||
|
{
|
||||||
|
// TODO (required): This is looking at ClusterMetadata, but not the ClusterMetadata for the specified epoch, just that epoch or later. Need to store ConsensusMigrationState in the global Topologies Accord stores for itself.
|
||||||
|
if (!ConsensusRequestRouter.instance.isKeyManagedByAccordForReadAndWrite(clusterMetadata, partitionKey.table(), partitionKey.partitionKey()))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Range:
|
||||||
|
for (accord.primitives.Range range : (Ranges)keys)
|
||||||
|
{
|
||||||
|
TokenRange tokenRange = (TokenRange)range;
|
||||||
|
// TODO (required): This is looking at ClusterMetadata, but not the ClusterMetadata for the specified epoch, just that epoch or later. Need to store ConsensusMigrationState in the global Topologies Accord stores for itself.
|
||||||
|
if (!ConsensusRequestRouter.instance.isRangeManagedByAccordForReadAndWrite(clusterMetadata, tokenRange.table(), tokenRange))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unsupported domain " + keys.domain());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean transactionIsSafeToWrite(ClusterMetadata clusterMetadata, Seekables<?, ?> keys)
|
||||||
|
{
|
||||||
|
checkState(keys.domain().isKey(), "Only key transactions are supported for writes");
|
||||||
|
|
||||||
for (PartitionKey partitionKey : (Seekables<PartitionKey, ?>)keys)
|
for (PartitionKey partitionKey : (Seekables<PartitionKey, ?>)keys)
|
||||||
{
|
{
|
||||||
// TODO (required): This is looking at ClusterMetadata, but not the ClusterMetadata for the specified epoch, just that epoch or later. Need to store ConsensusMigrationState in the global Topologies Accord stores for itself.
|
// TODO (required): This is looking at ClusterMetadata, but not the ClusterMetadata for the specified epoch, just that epoch or later. Need to store ConsensusMigrationState in the global Topologies Accord stores for itself.
|
||||||
if (ConsensusRequestRouter.instance.isKeyInMigratingOrMigratedRangeFromAccord(epoch, partitionKey.table(), partitionKey.partitionKey()))
|
if (!ConsensusRequestRouter.instance.isKeyManagedByAccordForWrite(clusterMetadata, partitionKey.table(), partitionKey.partitionKey()))
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,387 +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.txn;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import accord.api.Data;
|
|
||||||
import accord.api.DataStore;
|
|
||||||
import accord.api.Read;
|
|
||||||
import accord.local.SafeCommandStore;
|
|
||||||
import accord.primitives.AbstractRanges.UnionMode;
|
|
||||||
import accord.primitives.Participants;
|
|
||||||
import accord.primitives.Range;
|
|
||||||
import accord.primitives.Ranges;
|
|
||||||
import accord.primitives.Routable.Domain;
|
|
||||||
import accord.primitives.Routables.Slice;
|
|
||||||
import accord.primitives.Seekable;
|
|
||||||
import accord.primitives.Seekables;
|
|
||||||
import accord.primitives.Timestamp;
|
|
||||||
import accord.utils.async.AsyncChain;
|
|
||||||
import accord.utils.async.AsyncChains;
|
|
||||||
import org.apache.cassandra.concurrent.DebuggableTask;
|
|
||||||
import org.apache.cassandra.concurrent.Stage;
|
|
||||||
import org.apache.cassandra.db.ConsistencyLevel;
|
|
||||||
import org.apache.cassandra.db.PartitionPosition;
|
|
||||||
import org.apache.cassandra.db.PartitionRangeReadCommand;
|
|
||||||
import org.apache.cassandra.db.ReadCommand;
|
|
||||||
import org.apache.cassandra.db.ReadExecutionController;
|
|
||||||
import org.apache.cassandra.db.partitions.FilteredPartition;
|
|
||||||
import org.apache.cassandra.db.partitions.PartitionIterator;
|
|
||||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
|
||||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
|
|
||||||
import org.apache.cassandra.db.rows.RowIterator;
|
|
||||||
import org.apache.cassandra.dht.AbstractBounds;
|
|
||||||
import org.apache.cassandra.dht.Token;
|
|
||||||
import org.apache.cassandra.dht.Token.KeyBound;
|
|
||||||
import org.apache.cassandra.io.IVersionedSerializer;
|
|
||||||
import org.apache.cassandra.io.util.DataInputPlus;
|
|
||||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
|
||||||
import org.apache.cassandra.schema.TableId;
|
|
||||||
import org.apache.cassandra.service.accord.AccordObjectSizes;
|
|
||||||
import org.apache.cassandra.service.accord.TokenRange;
|
|
||||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
|
|
||||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.MinTokenKey;
|
|
||||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.SentinelKey;
|
|
||||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
|
||||||
import org.apache.cassandra.service.accord.serializers.KeySerializers;
|
|
||||||
import org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind;
|
|
||||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
|
||||||
import org.apache.cassandra.utils.MonotonicClock;
|
|
||||||
import org.apache.cassandra.utils.ObjectSizes;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
|
||||||
import static org.apache.cassandra.service.accord.AccordSerializers.consistencyLevelSerializer;
|
|
||||||
import static org.apache.cassandra.service.accord.IAccordService.SUPPORTED_READ_CONSISTENCY_LEVELS;
|
|
||||||
import static org.apache.cassandra.utils.ByteBufferUtil.readWithVIntLength;
|
|
||||||
import static org.apache.cassandra.utils.ByteBufferUtil.serializedSizeWithVIntLength;
|
|
||||||
import static org.apache.cassandra.utils.ByteBufferUtil.writeWithVIntLength;
|
|
||||||
import static org.apache.cassandra.utils.NullableSerializer.deserializeNullable;
|
|
||||||
import static org.apache.cassandra.utils.NullableSerializer.serializeNullable;
|
|
||||||
import static org.apache.cassandra.utils.NullableSerializer.serializedNullableSize;
|
|
||||||
|
|
||||||
public class TxnRangeRead extends AbstractSerialized<ReadCommand> implements TxnRead
|
|
||||||
{
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(TxnRangeRead.class);
|
|
||||||
|
|
||||||
public static final TxnRangeRead EMPTY = new TxnRangeRead(null, null, (Ranges)null);
|
|
||||||
private static final long EMPTY_SIZE = ObjectSizes.measure(EMPTY);
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
private final ConsistencyLevel cassandraConsistencyLevel;
|
|
||||||
@Nonnull
|
|
||||||
private final Ranges covering;
|
|
||||||
|
|
||||||
public TxnRangeRead(@Nonnull PartitionRangeReadCommand command, @Nonnull List<AbstractBounds<PartitionPosition>> ranges, @Nonnull ConsistencyLevel cassandraConsistencyLevel)
|
|
||||||
{
|
|
||||||
super(command);
|
|
||||||
checkArgument(cassandraConsistencyLevel == null || SUPPORTED_READ_CONSISTENCY_LEVELS.contains(cassandraConsistencyLevel), "Unsupported consistency level for read");
|
|
||||||
this.cassandraConsistencyLevel = cassandraConsistencyLevel;
|
|
||||||
TableId tableId = command.metadata().id;
|
|
||||||
|
|
||||||
TokenRange[] accordRanges = new TokenRange[ranges.size()];
|
|
||||||
for (int i = 0; i < ranges.size(); i++)
|
|
||||||
{
|
|
||||||
AbstractBounds<PartitionPosition> range = ranges.get(i);
|
|
||||||
// Should already have been unwrapped
|
|
||||||
checkState(!AbstractBounds.strictlyWrapsAround(range.left, range.right));
|
|
||||||
|
|
||||||
// Read commands can contain a mix of different kinds of bounds to facilitate paging
|
|
||||||
// and we need to communicate that to Accord as its own ranges. This uses
|
|
||||||
// TokenKey, SentinelKey, and MinTokenKey and sticks exclusively with left exclusive/right inclusive
|
|
||||||
// ranges rather add more types of ranges to the mix
|
|
||||||
// MinTokenKey allows emulating inclusive left and exclusive right with Range
|
|
||||||
boolean inclusiveLeft = range.inclusiveLeft();
|
|
||||||
PartitionPosition startPP = range.left;
|
|
||||||
boolean startIsMinKeyBound = startPP.getClass() == KeyBound.class ? ((KeyBound)startPP).isMinimumBound : false;
|
|
||||||
Token startToken = startPP.getToken();
|
|
||||||
AccordRoutingKey startAccordRoutingKey;
|
|
||||||
if (startToken.isMinimum() && inclusiveLeft)
|
|
||||||
startAccordRoutingKey = SentinelKey.min(tableId);
|
|
||||||
else if (inclusiveLeft || startIsMinKeyBound)
|
|
||||||
startAccordRoutingKey = new MinTokenKey(tableId, startToken);
|
|
||||||
else
|
|
||||||
startAccordRoutingKey = new TokenKey(tableId, startToken);
|
|
||||||
|
|
||||||
boolean inclusiveRight = range.inclusiveRight();
|
|
||||||
PartitionPosition endPP = range.right;
|
|
||||||
boolean endIsMinKeyBound = endPP.getClass() == KeyBound.class ? ((KeyBound)endPP).isMinimumBound : false;
|
|
||||||
Token stopToken = range.right.getToken();
|
|
||||||
AccordRoutingKey stopAccordRoutingKey;
|
|
||||||
if (stopToken.isMinimum())
|
|
||||||
stopAccordRoutingKey = SentinelKey.max(tableId);
|
|
||||||
else if (inclusiveRight && !endIsMinKeyBound)
|
|
||||||
stopAccordRoutingKey = new TokenKey(tableId, stopToken);
|
|
||||||
else
|
|
||||||
stopAccordRoutingKey = new MinTokenKey(tableId, stopToken);
|
|
||||||
accordRanges[i] = TokenRange.create(startAccordRoutingKey, stopAccordRoutingKey);
|
|
||||||
}
|
|
||||||
covering = Ranges.of(accordRanges);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TxnRangeRead(@Nonnull ByteBuffer commandBytes, @Nonnull ConsistencyLevel cassandraConsistencyLevel, @Nonnull Ranges covering)
|
|
||||||
{
|
|
||||||
super(commandBytes);
|
|
||||||
checkArgument(cassandraConsistencyLevel == null || SUPPORTED_READ_CONSISTENCY_LEVELS.contains(cassandraConsistencyLevel), "Unsupported consistency level for read");
|
|
||||||
this.cassandraConsistencyLevel = cassandraConsistencyLevel;
|
|
||||||
this.covering = covering;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Seekables<?, ?> keys()
|
|
||||||
{
|
|
||||||
return covering;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AsyncChain<Data> read(Seekable range, SafeCommandStore commandStore, Timestamp executeAt, DataStore store)
|
|
||||||
{
|
|
||||||
// Set to null since we don't need it and interop can pass in null
|
|
||||||
commandStore = null;
|
|
||||||
|
|
||||||
// It's fine for our nowInSeconds to lag slightly our insertion timestamp, as to the user
|
|
||||||
// this simply looks like the transaction witnessed TTL'd data and the data then expired
|
|
||||||
// immediately after the transaction executed, and this simplifies things a great deal
|
|
||||||
long nowInSeconds = TimeUnit.MICROSECONDS.toSeconds(executeAt.hlc());
|
|
||||||
|
|
||||||
List<AsyncChain<Data>> results = new ArrayList<>();
|
|
||||||
PartitionRangeReadCommand command = (PartitionRangeReadCommand)get();
|
|
||||||
if (cassandraConsistencyLevel == null || cassandraConsistencyLevel == ConsistencyLevel.ONE)
|
|
||||||
command = command.withoutReconciliation();
|
|
||||||
|
|
||||||
Ranges intersecting = covering.slice(Ranges.of(range.asRange()), Slice.Minimal);
|
|
||||||
for (Range subRange : intersecting)
|
|
||||||
results.add(performLocalRead(command, subRange, nowInSeconds));
|
|
||||||
if (results.isEmpty())
|
|
||||||
// Result type must match everywhere
|
|
||||||
return AsyncChains.success(new TxnData());
|
|
||||||
|
|
||||||
return AsyncChains.reduce(results, Data::merge);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PartitionRangeReadCommand commandForSubrange(Range r, long nowInSeconds)
|
|
||||||
{
|
|
||||||
return commandForSubrange((PartitionRangeReadCommand) get(), r, nowInSeconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PartitionRangeReadCommand commandForSubrange(PartitionRangeReadCommand command, Range r, long nowInSeconds)
|
|
||||||
{
|
|
||||||
AbstractBounds<PartitionPosition> bounds = command.dataRange().keyRange();
|
|
||||||
PartitionPosition startPP = bounds.left;
|
|
||||||
PartitionPosition endPP = bounds.right;
|
|
||||||
TokenKey startTokenKey = new TokenKey(command.metadata().id, startPP.getToken());
|
|
||||||
AccordRoutingKey startRoutingKey = ((AccordRoutingKey)r.start());
|
|
||||||
AccordRoutingKey endRoutingKey = ((AccordRoutingKey)r.end());
|
|
||||||
Token subRangeStartToken = startRoutingKey.getClass() == SentinelKey.class ? startPP.getToken() : ((AccordRoutingKey)r.start()).asTokenKey().token();
|
|
||||||
Token subRangeEndToken = endRoutingKey.getClass() == SentinelKey.class ? endPP.getToken() : ((AccordRoutingKey)r.end()).asTokenKey().token();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The way ranges/bounds work for range queries is that the beginning and ending bounds from the command
|
|
||||||
* could be tokens (and min/max key bounds) or actual keys depending on the bounds of the top level query we
|
|
||||||
* are running and where we are in paging. We need to preserve whatever is in the command in case it is a
|
|
||||||
* key and not a token, or it's a token but might be a min/max key bound.
|
|
||||||
*
|
|
||||||
* Then Accord will further subdivide the range in the command so need to inject additional bounds in the middle
|
|
||||||
* that match the range ownership of Accord.
|
|
||||||
*
|
|
||||||
* The command still contains the original bound and then the Accord range passed in determines what subset of
|
|
||||||
* that bound we want. We have to make sure to use the bounds from the command if it is the start or end instead
|
|
||||||
* of a key bound created from the Accord range since it could be a real key or min/max bound.
|
|
||||||
*
|
|
||||||
* When we are dealing with a bound created by Accord's further subdivision we use a maxKeyBound (exclusive)
|
|
||||||
* for both beginning and end because Bounds is left and right inclusive while Range is only left inclusive.
|
|
||||||
* We only use TokenRange with Accord which matches the left/right inclusivity of Cassandra's Range.
|
|
||||||
*
|
|
||||||
* That means the Range we get from Accord overlaps the previous Range on the left which when converted to a Bound
|
|
||||||
* would potentially read the same Token twice. So the left needs to be a maxKeyBound to exclude the data that isn't
|
|
||||||
* owned here and to avoid potentially reading the same data twice. The right bound also needs to be a maxKeyBound since Range
|
|
||||||
* is right inclusive so every partition we find needs to be < the right bound.
|
|
||||||
*/
|
|
||||||
boolean isFirstSubrange = startPP.getToken().equals(subRangeStartToken);
|
|
||||||
PartitionPosition subRangeStartPP = isFirstSubrange ? startPP : subRangeStartToken.maxKeyBound();
|
|
||||||
PartitionPosition subRangeEndPP = endPP.getToken().equals(subRangeEndToken) ? endPP : subRangeEndToken.maxKeyBound();
|
|
||||||
// Need to preserve the fact it is a bounds for paging to work, a range is not left inclusive and will not start from where we left off
|
|
||||||
AbstractBounds<PartitionPosition> subRange = isFirstSubrange ? bounds.withNewRight(subRangeEndPP) : new org.apache.cassandra.dht.Range(subRangeStartPP, subRangeEndPP);
|
|
||||||
return command.forSubRangeWithNowInSeconds(nowInSeconds, subRange, startTokenKey.equals(r.start()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private AsyncChain<Data> performLocalRead(PartitionRangeReadCommand command, Range r, long nowInSeconds)
|
|
||||||
{
|
|
||||||
Callable<Data> readCallable = () ->
|
|
||||||
{
|
|
||||||
PartitionRangeReadCommand read = commandForSubrange(command, r, nowInSeconds);
|
|
||||||
|
|
||||||
try (ReadExecutionController controller = read.executionController();
|
|
||||||
UnfilteredPartitionIterator partition = read.executeLocally(controller);
|
|
||||||
PartitionIterator iterator = UnfilteredPartitionIterators.filter(partition, read.nowInSec()))
|
|
||||||
{
|
|
||||||
TxnData result = new TxnData();
|
|
||||||
TxnDataRangeValue value = new TxnDataRangeValue();
|
|
||||||
while(iterator.hasNext())
|
|
||||||
{
|
|
||||||
try (RowIterator rows = iterator.next())
|
|
||||||
{
|
|
||||||
FilteredPartition filtered = FilteredPartition.create(rows);
|
|
||||||
if (filtered.hasRows() || read.selectsFullPartition())
|
|
||||||
{
|
|
||||||
value.add(filtered);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.put(TxnData.txnDataName(TxnDataNameKind.USER), value);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return AsyncChains.ofCallable(Stage.READ.executor(), readCallable, (callable, receiver) ->
|
|
||||||
new DebuggableTask.RunnableDebuggableTask()
|
|
||||||
{
|
|
||||||
private final long approxCreationTimeNanos = MonotonicClock.Global.approxTime.now();
|
|
||||||
private volatile long approxStartTimeNanos;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
approxStartTimeNanos = MonotonicClock.Global.approxTime.now();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Data call = callable.call();
|
|
||||||
receiver.accept(call, null);
|
|
||||||
}
|
|
||||||
catch (Throwable t)
|
|
||||||
{
|
|
||||||
logger.debug("AsyncChain Callable threw an Exception", t);
|
|
||||||
receiver.accept(null, t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long creationTimeNanos()
|
|
||||||
{
|
|
||||||
return approxCreationTimeNanos;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long startTimeNanos()
|
|
||||||
{
|
|
||||||
return approxStartTimeNanos;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String description()
|
|
||||||
{
|
|
||||||
return command.toCQLString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Read slice(Ranges ranges)
|
|
||||||
{
|
|
||||||
return new TxnRangeRead(bytes(), cassandraConsistencyLevel, covering.slice(ranges, Slice.Minimal));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Read intersecting(Participants<?> participants)
|
|
||||||
{
|
|
||||||
checkArgument(participants.domain() == Domain.Range, "Can only intersect with ranges");
|
|
||||||
return new TxnRangeRead(bytes(), cassandraConsistencyLevel, covering.intersecting(participants, Slice.Minimal));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Read merge(Read other)
|
|
||||||
{
|
|
||||||
Ranges newCovering = ((TxnRangeRead)other).covering.union(UnionMode.MERGE_OVERLAPPING, covering);
|
|
||||||
return new TxnRangeRead(bytes(), cassandraConsistencyLevel, newCovering);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Kind kind()
|
|
||||||
{
|
|
||||||
return Kind.range;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long estimatedSizeOnHeap()
|
|
||||||
{
|
|
||||||
long size = EMPTY_SIZE;
|
|
||||||
size += ByteBufferUtil.estimatedSizeOnHeap(bytes());
|
|
||||||
size += AccordObjectSizes.ranges(covering);
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PartitionRangeReadCommand command()
|
|
||||||
{
|
|
||||||
return (PartitionRangeReadCommand) get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final TxnReadSerializer<TxnRangeRead> serializer = new TxnReadSerializer<TxnRangeRead>()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void serialize(TxnRangeRead read, DataOutputPlus out, int version) throws IOException
|
|
||||||
{
|
|
||||||
serializeNullable(read.cassandraConsistencyLevel, out, version, consistencyLevelSerializer);
|
|
||||||
KeySerializers.ranges.serialize(read.covering, out, version);
|
|
||||||
writeWithVIntLength(read.bytes(), out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TxnRangeRead deserialize(DataInputPlus in, int version) throws IOException
|
|
||||||
{
|
|
||||||
ConsistencyLevel consistencyLevel = deserializeNullable(in, version, consistencyLevelSerializer);
|
|
||||||
Ranges covering = KeySerializers.ranges.deserialize(in, version);
|
|
||||||
ByteBuffer commandBytes = readWithVIntLength(in);
|
|
||||||
return new TxnRangeRead(commandBytes, consistencyLevel, covering);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long serializedSize(TxnRangeRead read, int version)
|
|
||||||
{
|
|
||||||
long size = 0;
|
|
||||||
size += KeySerializers.ranges.serializedSize(read.covering, version);
|
|
||||||
size += serializedNullableSize(read.cassandraConsistencyLevel, version, consistencyLevelSerializer);
|
|
||||||
size += serializedSizeWithVIntLength(read.bytes());
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected IVersionedSerializer<ReadCommand> serializer()
|
|
||||||
{
|
|
||||||
return PartitionRangeReadCommand.serializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ConsistencyLevel cassandraConsistencyLevel()
|
|
||||||
{
|
|
||||||
return cassandraConsistencyLevel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -19,75 +19,321 @@
|
||||||
package org.apache.cassandra.service.accord.txn;
|
package org.apache.cassandra.service.accord.txn;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
import accord.api.Data;
|
||||||
|
import accord.api.DataStore;
|
||||||
|
import accord.api.Key;
|
||||||
import accord.api.Read;
|
import accord.api.Read;
|
||||||
|
import accord.local.SafeCommandStore;
|
||||||
|
import accord.primitives.Keys;
|
||||||
|
import accord.primitives.Participants;
|
||||||
|
import accord.primitives.Range;
|
||||||
|
import accord.primitives.Ranges;
|
||||||
|
import accord.primitives.Routable.Domain;
|
||||||
|
import accord.primitives.Seekable;
|
||||||
|
import accord.primitives.Seekables;
|
||||||
|
import accord.primitives.Timestamp;
|
||||||
|
import accord.utils.async.AsyncChain;
|
||||||
|
import accord.utils.async.AsyncChains;
|
||||||
import org.apache.cassandra.db.ConsistencyLevel;
|
import org.apache.cassandra.db.ConsistencyLevel;
|
||||||
|
import org.apache.cassandra.db.PartitionPosition;
|
||||||
|
import org.apache.cassandra.db.PartitionRangeReadCommand;
|
||||||
|
import org.apache.cassandra.db.SinglePartitionReadCommand;
|
||||||
|
import org.apache.cassandra.dht.AbstractBounds;
|
||||||
import org.apache.cassandra.io.IVersionedSerializer;
|
import org.apache.cassandra.io.IVersionedSerializer;
|
||||||
import org.apache.cassandra.io.util.DataInputPlus;
|
import org.apache.cassandra.io.util.DataInputPlus;
|
||||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||||
|
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||||
|
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||||
|
import org.apache.cassandra.utils.ObjectSizes;
|
||||||
|
|
||||||
import static org.apache.cassandra.db.TypeSizes.sizeof;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
import static org.apache.cassandra.service.accord.AccordSerializers.consistencyLevelSerializer;
|
||||||
|
import static org.apache.cassandra.service.accord.IAccordService.SUPPORTED_READ_CONSISTENCY_LEVELS;
|
||||||
|
import static org.apache.cassandra.service.accord.txn.TxnData.TxnDataNameKind.CAS_READ;
|
||||||
|
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.utils.ArraySerializers.deserializeArray;
|
||||||
|
import static org.apache.cassandra.utils.ArraySerializers.serializeArray;
|
||||||
|
import static org.apache.cassandra.utils.ArraySerializers.serializedArraySize;
|
||||||
|
import static org.apache.cassandra.utils.NullableSerializer.deserializeNullable;
|
||||||
|
import static org.apache.cassandra.utils.NullableSerializer.serializeNullable;
|
||||||
|
import static org.apache.cassandra.utils.NullableSerializer.serializedNullableSize;
|
||||||
|
|
||||||
public interface TxnRead extends Read
|
public class TxnRead extends AbstractKeySorted<TxnNamedRead> implements Read
|
||||||
{
|
{
|
||||||
ConsistencyLevel cassandraConsistencyLevel();
|
private static final TxnRead EMPTY_KEY = new TxnRead(Domain.Key);
|
||||||
|
private static final TxnRead EMPTY_RANGE = new TxnRead(Domain.Range);
|
||||||
|
private static final long EMPTY_SIZE = ObjectSizes.measure(EMPTY_KEY);
|
||||||
|
private static final Comparator<TxnNamedRead> TXN_NAMED_READ_KEY_COMPARATOR = Comparator.comparing(a -> ((PartitionKey) a.key()));
|
||||||
|
private static final byte TYPE_EMPTY_KEY = 0;
|
||||||
|
private static final byte TYPE_EMPTY_RANGE = 1;
|
||||||
|
private static final byte TYPE_NOT_EMPTY = 2;
|
||||||
|
|
||||||
public interface TxnReadSerializer<T extends TxnRead> extends IVersionedSerializer<T> {}
|
public static TxnRead empty(Domain domain)
|
||||||
|
|
||||||
enum Kind
|
|
||||||
{
|
{
|
||||||
key(0),
|
switch (domain)
|
||||||
range(1);
|
|
||||||
|
|
||||||
int id;
|
|
||||||
|
|
||||||
Kind(int id)
|
|
||||||
{
|
{
|
||||||
this.id = id;
|
default:
|
||||||
}
|
throw new IllegalStateException("Unhandled domain " + domain);
|
||||||
|
case Key:
|
||||||
public TxnReadSerializer serializer()
|
return EMPTY_KEY;
|
||||||
{
|
case Range:
|
||||||
switch (this)
|
return EMPTY_RANGE;
|
||||||
{
|
|
||||||
case key:
|
|
||||||
return TxnKeyRead.serializer;
|
|
||||||
case range:
|
|
||||||
return TxnRangeRead.serializer;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unrecognized kind " + this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
long estimatedSizeOnHeap();
|
// Cassandra's consistency level used by Accord to safely read data written outside of Accord
|
||||||
|
@Nullable
|
||||||
|
private final ConsistencyLevel cassandraConsistencyLevel;
|
||||||
|
|
||||||
Kind kind();
|
// Specifies the domain in case the TxnRead is empty and it can't be inferred
|
||||||
|
private final Domain domain;
|
||||||
|
|
||||||
void unmemoize();
|
private TxnRead(Domain domain)
|
||||||
|
|
||||||
IVersionedSerializer<TxnRead> serializer = new IVersionedSerializer<TxnRead>()
|
|
||||||
{
|
{
|
||||||
@SuppressWarnings("unchecked")
|
super(new TxnNamedRead[0], domain);
|
||||||
@Override
|
this.domain = domain;
|
||||||
public void serialize(TxnRead txnRead, DataOutputPlus out, int version) throws IOException
|
this.cassandraConsistencyLevel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TxnRead(@Nonnull TxnNamedRead[] items, @Nullable ConsistencyLevel cassandraConsistencyLevel)
|
||||||
|
{
|
||||||
|
super(items, items[0].key().domain());
|
||||||
|
checkArgument(cassandraConsistencyLevel == null || SUPPORTED_READ_CONSISTENCY_LEVELS.contains(cassandraConsistencyLevel), "Unsupported consistency level for read: %s", cassandraConsistencyLevel);
|
||||||
|
this.cassandraConsistencyLevel = cassandraConsistencyLevel;
|
||||||
|
this.domain = items[0].key().domain();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TxnRead(@Nonnull List<TxnNamedRead> items, @Nullable ConsistencyLevel cassandraConsistencyLevel)
|
||||||
|
{
|
||||||
|
super(items, items.get(0).key().domain());
|
||||||
|
checkArgument(cassandraConsistencyLevel == null || SUPPORTED_READ_CONSISTENCY_LEVELS.contains(cassandraConsistencyLevel), "Unsupported consistency level for read: %s", cassandraConsistencyLevel);
|
||||||
|
this.cassandraConsistencyLevel = cassandraConsistencyLevel;
|
||||||
|
this.domain = items.get(0).key().domain();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sortReads(List<TxnNamedRead> reads)
|
||||||
|
{
|
||||||
|
if (reads.size() == 0)
|
||||||
|
return;
|
||||||
|
reads.sort(TXN_NAMED_READ_KEY_COMPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TxnRead createTxnRead(@Nonnull List<TxnNamedRead> items, @Nullable ConsistencyLevel consistencyLevel, Domain domain)
|
||||||
|
{
|
||||||
|
if (items.isEmpty())
|
||||||
|
return empty(domain);
|
||||||
|
sortReads(items);
|
||||||
|
return new TxnRead(items, consistencyLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TxnRead createSerialRead(List<SinglePartitionReadCommand> readCommands, ConsistencyLevel consistencyLevel)
|
||||||
|
{
|
||||||
|
List<TxnNamedRead> reads = new ArrayList<>(readCommands.size());
|
||||||
|
for (int i = 0; i < readCommands.size(); i++)
|
||||||
|
reads.add(new TxnNamedRead(txnDataName(USER, i), readCommands.get(i)));
|
||||||
|
sortReads(reads);
|
||||||
|
return new TxnRead(reads, consistencyLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TxnRead createCasRead(SinglePartitionReadCommand readCommand, ConsistencyLevel consistencyLevel)
|
||||||
|
{
|
||||||
|
TxnNamedRead read = new TxnNamedRead(txnDataName(CAS_READ), readCommand);
|
||||||
|
return new TxnRead(ImmutableList.of(read), consistencyLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A read that declares it will read from keys but doesn't actually read any data so dependent transactions will
|
||||||
|
// still be applied first
|
||||||
|
public static TxnRead createNoOpRead(Keys keys)
|
||||||
|
{
|
||||||
|
List<TxnNamedRead> reads = new ArrayList<>(keys.size());
|
||||||
|
for (int i = 0; i < keys.size(); i++)
|
||||||
|
reads.add(new TxnNamedRead(txnDataName(USER, i), keys.get(i), null));
|
||||||
|
return new TxnRead(reads, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TxnRead createRangeRead(PartitionRangeReadCommand command, AbstractBounds<PartitionPosition> range, ConsistencyLevel consistencyLevel)
|
||||||
|
{
|
||||||
|
return new TxnRead(ImmutableList.of(new TxnNamedRead(txnDataName(USER), range, command)), consistencyLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long estimatedSizeOnHeap()
|
||||||
|
{
|
||||||
|
long size = EMPTY_SIZE;
|
||||||
|
for (TxnNamedRead read : items)
|
||||||
|
size += read.estimatedSizeOnHeap();
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int compareNonKeyFields(TxnNamedRead left, TxnNamedRead right)
|
||||||
|
{
|
||||||
|
return Integer.compare(left.txnDataName(), right.txnDataName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Seekable getKey(TxnNamedRead read)
|
||||||
|
{
|
||||||
|
return read.key();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
TxnNamedRead[] newArray(int size)
|
||||||
|
{
|
||||||
|
return new TxnNamedRead[size];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Seekables<?, ?> keys()
|
||||||
|
{
|
||||||
|
return itemKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Domain domain()
|
||||||
|
{
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsistencyLevel cassandraConsistencyLevel()
|
||||||
|
{
|
||||||
|
return cassandraConsistencyLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Read slice(Ranges ranges)
|
||||||
|
{
|
||||||
|
return intersecting(itemKeys.slice(ranges));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Read intersecting(Participants<?> participants)
|
||||||
|
{
|
||||||
|
return intersecting(itemKeys.intersecting(participants));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Read intersecting(Seekables<?, ?> select)
|
||||||
|
{
|
||||||
|
// TODO (review): Why construct this keys at all and not just check against select?
|
||||||
|
Seekables<?, ?> keys = (Seekables<?, ?>)itemKeys.intersecting(select);
|
||||||
|
List<TxnNamedRead> reads = new ArrayList<>(keys.size());
|
||||||
|
|
||||||
|
switch (keys.domain())
|
||||||
{
|
{
|
||||||
out.writeByte(txnRead.kind().ordinal());
|
case Key:
|
||||||
txnRead.kind().serializer().serialize(txnRead, out, version);
|
for (TxnNamedRead read : items)
|
||||||
|
if (keys.contains((Key)read.key()))
|
||||||
|
reads.add(read);
|
||||||
|
break;
|
||||||
|
case Range:
|
||||||
|
for (TxnNamedRead read : items)
|
||||||
|
if (keys.intersects((Range)read.key()))
|
||||||
|
reads.add(read);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unhandled domain " + keys.domain());
|
||||||
|
}
|
||||||
|
|
||||||
|
return createTxnRead(reads, cassandraConsistencyLevel, keys.domain());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Read merge(Read read)
|
||||||
|
{
|
||||||
|
TxnRead txnRead = (TxnRead)read;
|
||||||
|
List<TxnNamedRead> reads = new ArrayList<>(items.length);
|
||||||
|
Collections.addAll(reads, items);
|
||||||
|
|
||||||
|
for (TxnNamedRead namedRead : txnRead)
|
||||||
|
if (!reads.contains(namedRead))
|
||||||
|
reads.add(namedRead);
|
||||||
|
|
||||||
|
return createTxnRead(reads, cassandraConsistencyLevel, txnRead.domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unmemoize()
|
||||||
|
{
|
||||||
|
for (TxnNamedRead read : items)
|
||||||
|
read.unmemoize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AsyncChain<Data> read(Seekable key, SafeCommandStore safeStore, Timestamp executeAt, DataStore store)
|
||||||
|
{
|
||||||
|
// Set to null since we don't need it and interop can pass in null
|
||||||
|
safeStore = null;
|
||||||
|
ClusterMetadata cm = ClusterMetadata.current();
|
||||||
|
checkState(cm.epoch.getEpoch() >= executeAt.epoch(), "TCM epoch %d is < executeAt epoch %d", cm.epoch.getEpoch(), executeAt.epoch());
|
||||||
|
|
||||||
|
List<AsyncChain<Data>> results = new ArrayList<>();
|
||||||
|
forEachWithKey(key, read -> results.add(read.read(cassandraConsistencyLevel, key, executeAt)));
|
||||||
|
|
||||||
|
if (results.isEmpty())
|
||||||
|
// Result type must match everywhere
|
||||||
|
return AsyncChains.success(new TxnData());
|
||||||
|
|
||||||
|
if (results.size() == 1)
|
||||||
|
return results.get(0);
|
||||||
|
|
||||||
|
return AsyncChains.reduce(results, Data::merge);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final IVersionedSerializer<TxnRead> serializer = new IVersionedSerializer<TxnRead>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void serialize(TxnRead read, DataOutputPlus out, int version) throws IOException
|
||||||
|
{
|
||||||
|
if (read.items.length > 0)
|
||||||
|
{
|
||||||
|
out.write(TYPE_NOT_EMPTY);
|
||||||
|
serializeArray(read.items, out, version, TxnNamedRead.serializer);
|
||||||
|
serializeNullable(read.cassandraConsistencyLevel, out, version, consistencyLevelSerializer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out.write(read.domain == Domain.Key ? TYPE_EMPTY_KEY : TYPE_EMPTY_RANGE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TxnRead deserialize(DataInputPlus in, int version) throws IOException
|
public TxnRead deserialize(DataInputPlus in, int version) throws IOException
|
||||||
{
|
{
|
||||||
TxnRead.Kind kind = TxnRead.Kind.values()[in.readByte()];
|
byte type = in.readByte();
|
||||||
return (TxnRead)kind.serializer().deserialize(in, version);
|
switch (type)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unhandled type " + type);
|
||||||
|
case TYPE_EMPTY_KEY:
|
||||||
|
return EMPTY_KEY;
|
||||||
|
case TYPE_EMPTY_RANGE:
|
||||||
|
return EMPTY_RANGE;
|
||||||
|
case TYPE_NOT_EMPTY:
|
||||||
|
TxnNamedRead[] items = deserializeArray(in, version, TxnNamedRead.serializer, TxnNamedRead[]::new);
|
||||||
|
ConsistencyLevel consistencyLevel = deserializeNullable(in, version, consistencyLevelSerializer);
|
||||||
|
return new TxnRead(items, consistencyLevel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
@Override
|
||||||
public long serializedSize(TxnRead txnRead, int version)
|
public long serializedSize(TxnRead read, int version)
|
||||||
{
|
{
|
||||||
return sizeof((byte)txnRead.kind().ordinal()) + txnRead.kind().serializer().serializedSize(txnRead, version);
|
long size = 1; // type
|
||||||
|
if (read.items.length > 0)
|
||||||
|
{
|
||||||
|
size += serializedArraySize(read.items, version, TxnNamedRead.serializer);
|
||||||
|
size += serializedNullableSize(read.cassandraConsistencyLevel, version, consistencyLevelSerializer);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,12 +49,14 @@ import org.apache.cassandra.service.accord.AccordObjectSizes;
|
||||||
import org.apache.cassandra.service.accord.AccordSerializers;
|
import org.apache.cassandra.service.accord.AccordSerializers;
|
||||||
import org.apache.cassandra.service.accord.IAccordService;
|
import org.apache.cassandra.service.accord.IAccordService;
|
||||||
import org.apache.cassandra.service.accord.serializers.KeySerializers;
|
import org.apache.cassandra.service.accord.serializers.KeySerializers;
|
||||||
|
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||||
import org.apache.cassandra.transport.ProtocolVersion;
|
import org.apache.cassandra.transport.ProtocolVersion;
|
||||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||||
import org.apache.cassandra.utils.ObjectSizes;
|
import org.apache.cassandra.utils.ObjectSizes;
|
||||||
|
|
||||||
import static accord.utils.Invariants.requireArgument;
|
import static accord.utils.Invariants.requireArgument;
|
||||||
import static accord.utils.SortedArrays.Search.CEIL;
|
import static accord.utils.SortedArrays.Search.CEIL;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static org.apache.cassandra.service.accord.AccordSerializers.consistencyLevelSerializer;
|
import static org.apache.cassandra.service.accord.AccordSerializers.consistencyLevelSerializer;
|
||||||
import static org.apache.cassandra.service.accord.AccordSerializers.serialize;
|
import static org.apache.cassandra.service.accord.AccordSerializers.serialize;
|
||||||
import static org.apache.cassandra.utils.ArraySerializers.deserializeArray;
|
import static org.apache.cassandra.utils.ArraySerializers.deserializeArray;
|
||||||
|
|
@ -214,6 +216,8 @@ public class TxnUpdate extends AccordUpdate
|
||||||
@Override
|
@Override
|
||||||
public TxnWrite apply(Timestamp executeAt, Data data)
|
public TxnWrite apply(Timestamp executeAt, Data data)
|
||||||
{
|
{
|
||||||
|
ClusterMetadata cm = ClusterMetadata.current();
|
||||||
|
checkState(cm.epoch.getEpoch() >= executeAt.epoch(), "TCM epoch %d is < executeAt epoch %d", cm.epoch.getEpoch(), executeAt.epoch());
|
||||||
if (!checkCondition(data))
|
if (!checkCondition(data))
|
||||||
return TxnWrite.EMPTY_CONDITION_FAILED;
|
return TxnWrite.EMPTY_CONDITION_FAILED;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,7 @@ import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
@ -37,6 +35,7 @@ import accord.api.DataStore;
|
||||||
import accord.api.Write;
|
import accord.api.Write;
|
||||||
import accord.local.SafeCommandStore;
|
import accord.local.SafeCommandStore;
|
||||||
import accord.primitives.PartialTxn;
|
import accord.primitives.PartialTxn;
|
||||||
|
import accord.primitives.Routable.Domain;
|
||||||
import accord.primitives.RoutableKey;
|
import accord.primitives.RoutableKey;
|
||||||
import accord.primitives.Seekable;
|
import accord.primitives.Seekable;
|
||||||
import accord.primitives.Timestamp;
|
import accord.primitives.Timestamp;
|
||||||
|
|
@ -50,6 +49,7 @@ import org.apache.cassandra.db.Clustering;
|
||||||
import org.apache.cassandra.db.Columns;
|
import org.apache.cassandra.db.Columns;
|
||||||
import org.apache.cassandra.db.DecoratedKey;
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
import org.apache.cassandra.db.Mutation;
|
import org.apache.cassandra.db.Mutation;
|
||||||
|
import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
|
||||||
import org.apache.cassandra.db.RegularAndStaticColumns;
|
import org.apache.cassandra.db.RegularAndStaticColumns;
|
||||||
import org.apache.cassandra.db.TypeSizes;
|
import org.apache.cassandra.db.TypeSizes;
|
||||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||||
|
|
@ -58,11 +58,14 @@ import org.apache.cassandra.io.IVersionedSerializer;
|
||||||
import org.apache.cassandra.io.util.DataInputPlus;
|
import org.apache.cassandra.io.util.DataInputPlus;
|
||||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||||
import org.apache.cassandra.schema.ColumnMetadata;
|
import org.apache.cassandra.schema.ColumnMetadata;
|
||||||
|
import org.apache.cassandra.service.accord.AccordObjectSizes;
|
||||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||||
|
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||||
import org.apache.cassandra.utils.BooleanSerializer;
|
import org.apache.cassandra.utils.BooleanSerializer;
|
||||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||||
import org.apache.cassandra.utils.ObjectSizes;
|
import org.apache.cassandra.utils.ObjectSizes;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static org.apache.cassandra.service.accord.AccordSerializers.partitionUpdateSerializer;
|
import static org.apache.cassandra.service.accord.AccordSerializers.partitionUpdateSerializer;
|
||||||
import static org.apache.cassandra.utils.ArraySerializers.deserializeArray;
|
import static org.apache.cassandra.utils.ArraySerializers.deserializeArray;
|
||||||
import static org.apache.cassandra.utils.ArraySerializers.serializeArray;
|
import static org.apache.cassandra.utils.ArraySerializers.serializeArray;
|
||||||
|
|
@ -100,7 +103,7 @@ public class TxnWrite extends AbstractKeySorted<TxnWrite.Update> implements Writ
|
||||||
long estimatedSizeOnHeap()
|
long estimatedSizeOnHeap()
|
||||||
{
|
{
|
||||||
return EMPTY_SIZE
|
return EMPTY_SIZE
|
||||||
+ key.estimatedSizeOnHeap()
|
+ AccordObjectSizes.key(key)
|
||||||
+ ByteBufferUtil.estimatedSizeOnHeap(bytes());
|
+ ByteBufferUtil.estimatedSizeOnHeap(bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,7 +138,7 @@ public class TxnWrite extends AbstractKeySorted<TxnWrite.Update> implements Writ
|
||||||
PartitionUpdate update = get();
|
PartitionUpdate update = get();
|
||||||
if (!preserveTimestamps)
|
if (!preserveTimestamps)
|
||||||
update = new PartitionUpdate.Builder(get(), 0).updateAllTimestamp(timestamp).build();
|
update = new PartitionUpdate.Builder(get(), 0).updateAllTimestamp(timestamp).build();
|
||||||
Mutation mutation = new Mutation(update, true);
|
Mutation mutation = new Mutation(update, PotentialTxnConflicts.ALLOW);
|
||||||
return AsyncChains.ofRunnable(Stage.MUTATION.executor(), mutation::applyUnsafe);
|
return AsyncChains.ofRunnable(Stage.MUTATION.executor(), mutation::applyUnsafe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -274,7 +277,7 @@ public class TxnWrite extends AbstractKeySorted<TxnWrite.Update> implements Writ
|
||||||
|
|
||||||
private static RegularAndStaticColumns columns(PartitionUpdate update, TxnReferenceOperations referenceOps)
|
private static RegularAndStaticColumns columns(PartitionUpdate update, TxnReferenceOperations referenceOps)
|
||||||
{
|
{
|
||||||
Preconditions.checkState(!referenceOps.isEmpty());
|
checkState(!referenceOps.isEmpty());
|
||||||
RegularAndStaticColumns current = update.columns();
|
RegularAndStaticColumns current = update.columns();
|
||||||
return new RegularAndStaticColumns(columns(current.statics, referenceOps.statics),
|
return new RegularAndStaticColumns(columns(current.statics, referenceOps.statics),
|
||||||
columns(current.regulars, referenceOps.regulars));
|
columns(current.regulars, referenceOps.regulars));
|
||||||
|
|
@ -287,7 +290,7 @@ public class TxnWrite extends AbstractKeySorted<TxnWrite.Update> implements Writ
|
||||||
|
|
||||||
if (existing != null && !existing.isEmpty())
|
if (existing != null && !existing.isEmpty())
|
||||||
{
|
{
|
||||||
Preconditions.checkState(existing.clustering().equals(clustering));
|
checkState(existing.clustering().equals(clustering));
|
||||||
up.addRow(existing);
|
up.addRow(existing);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -335,13 +338,13 @@ public class TxnWrite extends AbstractKeySorted<TxnWrite.Update> implements Writ
|
||||||
|
|
||||||
private TxnWrite(Update[] items, boolean isConditionMet)
|
private TxnWrite(Update[] items, boolean isConditionMet)
|
||||||
{
|
{
|
||||||
super(items);
|
super(items, Domain.Key);
|
||||||
this.isConditionMet = isConditionMet;
|
this.isConditionMet = isConditionMet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TxnWrite(List<Update> items, boolean isConditionMet)
|
public TxnWrite(List<Update> items, boolean isConditionMet)
|
||||||
{
|
{
|
||||||
super(items);
|
super(items, Domain.Key);
|
||||||
this.isConditionMet = isConditionMet;
|
this.isConditionMet = isConditionMet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -352,11 +355,17 @@ public class TxnWrite extends AbstractKeySorted<TxnWrite.Update> implements Writ
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
PartitionKey getKey(Update item)
|
Seekable getKey(Update item)
|
||||||
{
|
{
|
||||||
return item.key;
|
return item.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Domain domain()
|
||||||
|
{
|
||||||
|
return Domain.Key;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Update[] newArray(int size)
|
Update[] newArray(int size)
|
||||||
{
|
{
|
||||||
|
|
@ -372,6 +381,8 @@ public class TxnWrite extends AbstractKeySorted<TxnWrite.Update> implements Writ
|
||||||
@Override
|
@Override
|
||||||
public AsyncChain<Void> apply(Seekable key, SafeCommandStore safeStore, TxnId txnId, Timestamp executeAt, DataStore store, PartialTxn txn)
|
public AsyncChain<Void> apply(Seekable key, SafeCommandStore safeStore, TxnId txnId, Timestamp executeAt, DataStore store, PartialTxn txn)
|
||||||
{
|
{
|
||||||
|
ClusterMetadata cm = ClusterMetadata.current();
|
||||||
|
checkState(cm.epoch.getEpoch() >= executeAt.epoch(), "TCM epoch %d is < executeAt epoch %d", cm.epoch.getEpoch(), executeAt.epoch());
|
||||||
// UnrecoverableRepairUpdate will deserialize as null at other nodes
|
// UnrecoverableRepairUpdate will deserialize as null at other nodes
|
||||||
// Accord should skip the Update for a read transaction, but handle it here anyways
|
// Accord should skip the Update for a read transaction, but handle it here anyways
|
||||||
TxnUpdate txnUpdate = ((TxnUpdate)txn.update());
|
TxnUpdate txnUpdate = ((TxnUpdate)txn.update());
|
||||||
|
|
@ -379,7 +390,6 @@ public class TxnWrite extends AbstractKeySorted<TxnWrite.Update> implements Writ
|
||||||
return Writes.SUCCESS;
|
return Writes.SUCCESS;
|
||||||
|
|
||||||
long timestamp = executeAt.uniqueHlc();
|
long timestamp = executeAt.uniqueHlc();
|
||||||
int nowInSeconds = (int) TimeUnit.MICROSECONDS.toSeconds(timestamp);
|
|
||||||
|
|
||||||
// TODO (expected): optimise for the common single update case; lots of lists allocated
|
// TODO (expected): optimise for the common single update case; lots of lists allocated
|
||||||
List<AsyncChain<Void>> results = new ArrayList<>();
|
List<AsyncChain<Void>> results = new ArrayList<>();
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,9 @@
|
||||||
|
|
||||||
package org.apache.cassandra.service.consensus;
|
package org.apache.cassandra.service.consensus;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
|
|
||||||
import org.apache.cassandra.db.ConsistencyLevel;
|
import org.apache.cassandra.db.ConsistencyLevel;
|
||||||
import org.apache.cassandra.dht.NormalizedRanges;
|
import org.apache.cassandra.db.PartitionPosition;
|
||||||
import org.apache.cassandra.dht.Range;
|
import org.apache.cassandra.dht.AbstractBounds;
|
||||||
import org.apache.cassandra.dht.Token;
|
import org.apache.cassandra.dht.Token;
|
||||||
import org.apache.cassandra.schema.TableId;
|
import org.apache.cassandra.schema.TableId;
|
||||||
import org.apache.cassandra.service.accord.IAccordService;
|
import org.apache.cassandra.service.accord.IAccordService;
|
||||||
|
|
@ -32,7 +30,6 @@ import org.apache.cassandra.tcm.ClusterMetadata;
|
||||||
import org.apache.cassandra.utils.LocalizeString;
|
import org.apache.cassandra.utils.LocalizeString;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static org.apache.cassandra.dht.NormalizedRanges.normalizedRanges;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Configure the transactional behavior of a table. Enables accord on a table and defines how it mixes with non-serial writes
|
* Configure the transactional behavior of a table. Enables accord on a table and defines how it mixes with non-serial writes
|
||||||
|
|
@ -70,33 +67,7 @@ import static org.apache.cassandra.dht.NormalizedRanges.normalizedRanges;
|
||||||
public enum TransactionalMode
|
public enum TransactionalMode
|
||||||
{
|
{
|
||||||
// Running on Paxos V1 or V2 with Accord disabled
|
// Running on Paxos V1 or V2 with Accord disabled
|
||||||
off(false, false, false, false, false),
|
off(false, false, false, false),
|
||||||
|
|
||||||
// TODO (maybe): These unsafe modes don't have Accord do async commit and single replica reads so how useful are they besides preserving non-SERIAL performance?
|
|
||||||
// These modes are unsafe when Accord and non-SERIAL reads and writes interact with the same data
|
|
||||||
// They don't guarantee that non-SERIAL reads or writes will see the latest Accord writes or that
|
|
||||||
// Accord transactions will recover correctly
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Enables Accord but does not allow non-SERIAL reads and writes to occur safely to data read/written by Accord
|
|
||||||
*
|
|
||||||
* Execute non-SERIAL writes through Cassandra via StorageProxy's normal write path. This can lead Accord to compute
|
|
||||||
* multiple outcomes for a transaction that depend on data written by non-SERIAL writes.
|
|
||||||
*
|
|
||||||
* SERIAL reads and CAS will run on Accord. Accord will honor provided consistency levels and do synchronous commit
|
|
||||||
* so the results can be read correctly with non-SERIAL CLs, but read repair could interfere with Accord.
|
|
||||||
*/
|
|
||||||
unsafe(true, false, false, false, false),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Enables Accord and makes it safe to perform non-SERIAL reads of Accord data without guaranteeing that they will
|
|
||||||
* see the latest Accord writes. non-SERIAL writes to data read by Accord will make Accord txn recovery non-deterministic
|
|
||||||
*
|
|
||||||
* Allow mixing of non-SERIAL writes and Accord, but still force BRR through Accord.
|
|
||||||
* This mode makes it safe to perform non-SERIAL or SERIAL reads of Accord data, but unsafe
|
|
||||||
* to write data that Accord may attempt to read.
|
|
||||||
*/
|
|
||||||
unsafe_writes(true, false, false, false, true),
|
|
||||||
|
|
||||||
// These modes always provide correct execution with mixed_reads allow non-transaction non-SERIAL operations
|
// These modes always provide correct execution with mixed_reads allow non-transaction non-SERIAL operations
|
||||||
// at the expense of slower Accord transactions, and full allowing faster transaction execution, but forcing
|
// at the expense of slower Accord transactions, and full allowing faster transaction execution, but forcing
|
||||||
|
|
@ -107,37 +78,68 @@ public enum TransactionalMode
|
||||||
* writes at the provided consistency level so they can be read via non-SERIAL consistency levels.
|
* writes at the provided consistency level so they can be read via non-SERIAL consistency levels.
|
||||||
* This mode makes it safe to read/write data that Accord will read/write.
|
* This mode makes it safe to read/write data that Accord will read/write.
|
||||||
*/
|
*/
|
||||||
mixed_reads(true, false, true, false, true),
|
mixed_reads(true, true, false, true),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Execute writes through Accord skipping StorageProxy's normal write path. Ignores the provided consistency level
|
* Execute writes through Accord skipping StorageProxy's normal write path. Ignores the provided consistency level
|
||||||
* which makes Accord commit writes at ANY similar to Paxos with commit consistency level ANY.
|
* which makes Accord commit writes at ANY similar to Paxos with commit consistency level ANY.
|
||||||
*/
|
*/
|
||||||
full(true, true, true, true, true),
|
full(true, true, true, true),
|
||||||
|
|
||||||
|
// TODO (maybe): These unsafe modes don't have Accord do async commit and single replica reads so how useful are they besides preserving non-SERIAL performance?
|
||||||
|
// These modes are unsafe when Accord and non-SERIAL reads and writes interact with the same data
|
||||||
|
// They don't guarantee that non-SERIAL reads or writes will see the latest Accord writes or that
|
||||||
|
// Accord transactions will recover correctly
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enables Accord and makes it safe to perform non-SERIAL reads of Accord data without guaranteeing that they will
|
||||||
|
* see the latest Accord writes. non-SERIAL writes to data read by Accord will make Accord txn recovery non-deterministic
|
||||||
|
*
|
||||||
|
* Allow mixing of non-SERIAL writes and Accord, but still force BRR through Accord.
|
||||||
|
* This mode makes it safe to perform non-SERIAL or SERIAL reads of Accord data, but unsafe
|
||||||
|
* to write data that Accord may attempt to read.
|
||||||
|
*/
|
||||||
|
test_unsafe_writes(true, false, false, true),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enables Accord but does not allow non-SERIAL reads and writes to occur safely to data read/written by Accord
|
||||||
|
*
|
||||||
|
* Execute non-SERIAL writes through Cassandra via StorageProxy's normal write path. This can lead Accord to compute
|
||||||
|
* multiple outcomes for a transaction that depend on data written by non-SERIAL writes.
|
||||||
|
*
|
||||||
|
* SERIAL reads and CAS will run on Accord. Accord will honor provided consistency levels and do synchronous commit
|
||||||
|
* so the results can be read correctly with non-SERIAL CLs, but read repair could interfere with Accord.
|
||||||
|
**/
|
||||||
|
test_unsafe(true, false, false, false),
|
||||||
|
|
||||||
// For tests, Accord will read and be forced to do interop reads
|
// For tests, Accord will read and be forced to do interop reads
|
||||||
test_interop_read(true, false, false, true, true);
|
test_interop_read(true, false, true, true);
|
||||||
|
|
||||||
public final boolean accordIsEnabled;
|
public final boolean accordIsEnabled;
|
||||||
public final boolean ignoresSuppliedCommitCL;
|
|
||||||
public final boolean nonSerialWritesThroughAccord;
|
public final boolean nonSerialWritesThroughAccord;
|
||||||
public final boolean nonSerialReadsThroughAccord;
|
public final boolean nonSerialReadsThroughAccord;
|
||||||
public final boolean blockingReadRepairThroughAccord;
|
public final boolean blockingReadRepairThroughAccord;
|
||||||
private final String cqlParam;
|
private final String cqlParam;
|
||||||
|
|
||||||
TransactionalMode(boolean accordIsEnabled, boolean ignoreSuppliedCommitCL, boolean nonSerialWritesThroughAccord, boolean nonSerialReadsThroughAccord, boolean blockingReadRepairThroughAccord)
|
TransactionalMode(boolean accordIsEnabled, boolean nonSerialWritesThroughAccord, boolean nonSerialReadsThroughAccord, boolean blockingReadRepairThroughAccord)
|
||||||
{
|
{
|
||||||
this.accordIsEnabled = accordIsEnabled;
|
this.accordIsEnabled = accordIsEnabled;
|
||||||
this.ignoresSuppliedCommitCL = ignoreSuppliedCommitCL;
|
|
||||||
this.nonSerialWritesThroughAccord = nonSerialWritesThroughAccord;
|
this.nonSerialWritesThroughAccord = nonSerialWritesThroughAccord;
|
||||||
this.nonSerialReadsThroughAccord = nonSerialReadsThroughAccord;
|
this.nonSerialReadsThroughAccord = nonSerialReadsThroughAccord;
|
||||||
this.blockingReadRepairThroughAccord = blockingReadRepairThroughAccord;
|
this.blockingReadRepairThroughAccord = blockingReadRepairThroughAccord;
|
||||||
this.cqlParam = String.format("transactional_mode = '%s'", LocalizeString.toLowerCaseLocalized(this.name()));
|
this.cqlParam = String.format("transactional_mode = '%s'", LocalizeString.toLowerCaseLocalized(this.name()));
|
||||||
|
checkState(this.name().startsWith("test_") || (nonSerialReadsThroughAccord && nonSerialWritesThroughAccord) || !nonSerialReadsThroughAccord, "Doesn't make sense to do non-SERIAL reads through Accord without also doing non-SERIAL writes through Accord");
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConsistencyLevel commitCLForStrategy(TransactionalMigrationFromMode fromMode, ConsistencyLevel consistencyLevel, ClusterMetadata cm, TableId tableId, Token token)
|
// This can be inferred from whether non-SERIAL reads are done through Accord
|
||||||
|
public boolean ignoresSuppliedCommitCL()
|
||||||
{
|
{
|
||||||
if (ignoresSuppliedCommitCL)
|
return nonSerialReadsThroughAccord;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsistencyLevel commitCLForMode(TransactionalMigrationFromMode fromMode, ConsistencyLevel consistencyLevel, ClusterMetadata cm, TableId tableId, Token token)
|
||||||
|
{
|
||||||
|
if (ignoresSuppliedCommitCL())
|
||||||
{
|
{
|
||||||
TableMigrationState tms = cm.consensusMigrationState.tableStates.get(tableId);
|
TableMigrationState tms = cm.consensusMigrationState.tableStates.get(tableId);
|
||||||
checkState(tms != null || fromMode == TransactionalMigrationFromMode.none);
|
checkState(tms != null || fromMode == TransactionalMigrationFromMode.none);
|
||||||
|
|
@ -160,13 +162,12 @@ public enum TransactionalMode
|
||||||
* Infer whether Accord can ignore the read CL and bias towards correctness by reading from a quorum
|
* Infer whether Accord can ignore the read CL and bias towards correctness by reading from a quorum
|
||||||
* if it's needed due to how non-SERIAL writes are done
|
* if it's needed due to how non-SERIAL writes are done
|
||||||
*/
|
*/
|
||||||
private boolean ignoresSuppliedReadCL()
|
public boolean ignoresSuppliedReadCL()
|
||||||
{
|
{
|
||||||
return nonSerialWritesThroughAccord && blockingReadRepairThroughAccord;
|
return nonSerialWritesThroughAccord && blockingReadRepairThroughAccord;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ConsistencyLevel readCLForMode(TransactionalMigrationFromMode fromMode, ConsistencyLevel consistencyLevel, ClusterMetadata cm, TableId tableId, Token token)
|
||||||
public ConsistencyLevel readCLForStrategy(TransactionalMigrationFromMode fromMode, ConsistencyLevel consistencyLevel, ClusterMetadata cm, TableId tableId, Token token)
|
|
||||||
{
|
{
|
||||||
if (ignoresSuppliedReadCL())
|
if (ignoresSuppliedReadCL())
|
||||||
{
|
{
|
||||||
|
|
@ -187,19 +188,18 @@ public enum TransactionalMode
|
||||||
return consistencyLevel;
|
return consistencyLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConsistencyLevel readCLForStrategy(TransactionalMigrationFromMode fromMode, ConsistencyLevel consistencyLevel, ClusterMetadata cm, TableId tableId, Range<Token> range)
|
public ConsistencyLevel readCLForMode(TransactionalMigrationFromMode fromMode, ConsistencyLevel consistencyLevel, ClusterMetadata cm, TableId tableId, AbstractBounds<PartitionPosition> range)
|
||||||
{
|
{
|
||||||
if (ignoresSuppliedReadCL())
|
if (ignoresSuppliedReadCL())
|
||||||
{
|
{
|
||||||
TableMigrationState tms = cm.consensusMigrationState.tableStates.get(tableId);
|
TableMigrationState tms = cm.consensusMigrationState.tableStates.get(tableId);
|
||||||
checkState(tms != null || fromMode == TransactionalMigrationFromMode.none);
|
checkState(tms != null || fromMode == TransactionalMigrationFromMode.none);
|
||||||
|
|
||||||
NormalizedRanges<Token> ranges = normalizedRanges(ImmutableList.of(range));
|
|
||||||
// Only ignore the supplied consistency level if none of the range is migrating
|
// Only ignore the supplied consistency level if none of the range is migrating
|
||||||
// otherwise honor it because we might read through Accord for non-SERIAL reads before repair is run
|
// otherwise honor it because we might read through Accord for non-SERIAL reads before repair is run
|
||||||
// this is OK to do because BRR still works and Accord isn't computing a write so recovery
|
// this is OK to do because BRR still works and Accord isn't computing a write so recovery
|
||||||
// determinism isn't an issue
|
// determinism isn't an issue
|
||||||
if (tms == null || tms.migratedRanges.intersection(ranges).equals(ranges))
|
if (tms == null || range.intersects(tms.migratedRangesAsPartitionPosition()))
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,4 +232,9 @@ public enum TransactionalMode
|
||||||
{
|
{
|
||||||
return valueOf(LocalizeString.toLowerCaseLocalized(name));
|
return valueOf(LocalizeString.toLowerCaseLocalized(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTestMode()
|
||||||
|
{
|
||||||
|
return name().startsWith("test_");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,17 +20,21 @@ package org.apache.cassandra.service.consensus.migration;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.SortedSet;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import accord.api.BarrierType;
|
import accord.api.BarrierType;
|
||||||
|
import accord.primitives.Keys;
|
||||||
import accord.primitives.Seekables;
|
import accord.primitives.Seekables;
|
||||||
import com.github.benmanes.caffeine.cache.CacheLoader;
|
import com.github.benmanes.caffeine.cache.CacheLoader;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
|
|
@ -66,6 +70,7 @@ import org.apache.cassandra.tcm.ClusterMetadata;
|
||||||
import org.apache.cassandra.tcm.Epoch;
|
import org.apache.cassandra.tcm.Epoch;
|
||||||
import org.apache.cassandra.transport.Dispatcher;
|
import org.apache.cassandra.transport.Dispatcher;
|
||||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||||
|
import org.apache.cassandra.utils.Collectors3;
|
||||||
import org.apache.cassandra.utils.ObjectSizes;
|
import org.apache.cassandra.utils.ObjectSizes;
|
||||||
import org.apache.cassandra.utils.Pair;
|
import org.apache.cassandra.utils.Pair;
|
||||||
import org.apache.cassandra.utils.UUIDSerializer;
|
import org.apache.cassandra.utils.UUIDSerializer;
|
||||||
|
|
@ -176,7 +181,7 @@ public abstract class ConsensusKeyMigrationState
|
||||||
repairKeyAccord(key, tms.tableId, tms.minMigrationEpoch(key.getToken()).getEpoch(), Dispatcher.RequestTime.forImmediateExecution(), false, isForWrite);
|
repairKeyAccord(key, tms.tableId, tms.minMigrationEpoch(key.getToken()).getEpoch(), Dispatcher.RequestTime.forImmediateExecution(), false, isForWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean paxosReadSatisfiedByKeyMigration()
|
boolean paxosReadSatisfiedByKeyMigration()
|
||||||
{
|
{
|
||||||
// No migration in progress, it's safe
|
// No migration in progress, it's safe
|
||||||
if (tableMigrationState == null)
|
if (tableMigrationState == null)
|
||||||
|
|
@ -230,12 +235,6 @@ public abstract class ConsensusKeyMigrationState
|
||||||
saveConsensusKeyMigrationLocally(key, tableUUID, migratedAt);
|
saveConsensusKeyMigrationLocally(key, tableUUID, migratedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Should be called where we know we replicate the key so that the system table contains useful information
|
|
||||||
* about whether the migration already occurred.
|
|
||||||
*
|
|
||||||
* This is a more expensive check that might read from the system table to determine if migration occurred.
|
|
||||||
*/
|
|
||||||
public static KeyMigrationState getKeyMigrationState(TableId tableId, DecoratedKey key)
|
public static KeyMigrationState getKeyMigrationState(TableId tableId, DecoratedKey key)
|
||||||
{
|
{
|
||||||
ClusterMetadata cm = ClusterMetadata.current();
|
ClusterMetadata cm = ClusterMetadata.current();
|
||||||
|
|
@ -243,10 +242,20 @@ public abstract class ConsensusKeyMigrationState
|
||||||
// No state means no migration for this table
|
// No state means no migration for this table
|
||||||
if (tms == null)
|
if (tms == null)
|
||||||
return KeyMigrationState.MIGRATION_NOT_NEEDED;
|
return KeyMigrationState.MIGRATION_NOT_NEEDED;
|
||||||
|
return getKeyMigrationState(cm, tms, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Should be called where we know we replicate the key so that the system table contains useful information
|
||||||
|
* about whether the migration already occurred.
|
||||||
|
*
|
||||||
|
* This is a more expensive check that might read from the system table to determine if migration occurred.
|
||||||
|
*/
|
||||||
|
static KeyMigrationState getKeyMigrationState(ClusterMetadata cm, TableMigrationState tms, DecoratedKey key)
|
||||||
|
{
|
||||||
if (tms.migratingRanges.intersects(key.getToken()))
|
if (tms.migratingRanges.intersects(key.getToken()))
|
||||||
{
|
{
|
||||||
ConsensusMigratedAt consensusMigratedAt = getConsensusMigratedAt(tableId, key);
|
ConsensusMigratedAt consensusMigratedAt = getConsensusMigratedAt(tms.tableId, key);
|
||||||
if (consensusMigratedAt == null)
|
if (consensusMigratedAt == null)
|
||||||
return new KeyMigrationState(null, cm.epoch, tms, key);
|
return new KeyMigrationState(null, cm.epoch, tms, key);
|
||||||
return new KeyMigrationState(consensusMigratedAt, cm.epoch, tms, key);
|
return new KeyMigrationState(consensusMigratedAt, cm.epoch, tms, key);
|
||||||
|
|
@ -269,6 +278,16 @@ public abstract class ConsensusKeyMigrationState
|
||||||
Dispatcher.RequestTime requestTime,
|
Dispatcher.RequestTime requestTime,
|
||||||
boolean global,
|
boolean global,
|
||||||
boolean isForWrite)
|
boolean isForWrite)
|
||||||
|
{
|
||||||
|
repairKeysAccord(ImmutableList.of(key), tableId, minEpoch, requestTime, global, isForWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void repairKeysAccord(List<DecoratedKey> keys,
|
||||||
|
TableId tableId,
|
||||||
|
long minEpoch,
|
||||||
|
Dispatcher.RequestTime requestTime,
|
||||||
|
boolean global,
|
||||||
|
boolean isForWrite)
|
||||||
{
|
{
|
||||||
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(tableId);
|
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(tableId);
|
||||||
if (isForWrite)
|
if (isForWrite)
|
||||||
|
|
@ -282,7 +301,8 @@ public abstract class ConsensusKeyMigrationState
|
||||||
// will soon be ready to execute, but only waits for the local replica to be ready
|
// will soon be ready to execute, but only waits for the local replica to be ready
|
||||||
// Local will only create a transaction if it can't find an existing one to wait on
|
// Local will only create a transaction if it can't find an existing one to wait on
|
||||||
BarrierType barrierType = global ? BarrierType.global_async : BarrierType.local;
|
BarrierType barrierType = global ? BarrierType.global_async : BarrierType.local;
|
||||||
Seekables keysOrRanges = AccordService.instance().barrier(Seekables.of(new PartitionKey(tableId, key)), minEpoch, requestTime, DatabaseDescriptor.getTransactionTimeout(TimeUnit.NANOSECONDS), barrierType, isForWrite);
|
SortedSet<PartitionKey> partitionKeys = keys.stream().map(key -> new PartitionKey(tableId, key)).collect(Collectors3.toSortedSet());
|
||||||
|
Seekables keysOrRanges = AccordService.instance().barrier(new Keys(partitionKeys), minEpoch, requestTime, DatabaseDescriptor.getTransactionTimeout(TimeUnit.NANOSECONDS), barrierType, isForWrite);
|
||||||
if (keysOrRanges.isEmpty())
|
if (keysOrRanges.isEmpty())
|
||||||
throw new RetryOnDifferentSystemException();
|
throw new RetryOnDifferentSystemException();
|
||||||
// We don't save the state to the cache here. Accord will notify the agent every time a barrier happens.
|
// We don't save the state to the cache here. Accord will notify the agent every time a barrier happens.
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import accord.coordinate.CoordinationFailed;
|
import accord.coordinate.CoordinationFailed;
|
||||||
import accord.primitives.Keys;
|
import accord.primitives.Keys;
|
||||||
|
import accord.primitives.Routable.Domain;
|
||||||
import accord.primitives.Txn;
|
import accord.primitives.Txn;
|
||||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
import org.apache.cassandra.db.ConsistencyLevel;
|
import org.apache.cassandra.db.ConsistencyLevel;
|
||||||
|
|
@ -54,8 +55,8 @@ import org.apache.cassandra.service.accord.IAccordService;
|
||||||
import org.apache.cassandra.service.accord.IAccordService.AsyncTxnResult;
|
import org.apache.cassandra.service.accord.IAccordService.AsyncTxnResult;
|
||||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnCondition;
|
import org.apache.cassandra.service.accord.txn.TxnCondition;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnKeyRead;
|
|
||||||
import org.apache.cassandra.service.accord.txn.TxnQuery;
|
import org.apache.cassandra.service.accord.txn.TxnQuery;
|
||||||
|
import org.apache.cassandra.service.accord.txn.TxnRead;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnReferenceOperations;
|
import org.apache.cassandra.service.accord.txn.TxnReferenceOperations;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnUpdate;
|
import org.apache.cassandra.service.accord.txn.TxnUpdate;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnWrite;
|
import org.apache.cassandra.service.accord.txn.TxnWrite;
|
||||||
|
|
@ -115,10 +116,10 @@ public class ConsensusMigrationMutationHelper
|
||||||
TableParams tableParams = getTableMetadata(cm, tableId).params;
|
TableParams tableParams = getTableMetadata(cm, tableId).params;
|
||||||
TransactionalMode mode = tableParams.transactionalMode;
|
TransactionalMode mode = tableParams.transactionalMode;
|
||||||
TransactionalMigrationFromMode migrationFromMode = tableParams.transactionalMigrationFrom;
|
TransactionalMigrationFromMode migrationFromMode = tableParams.transactionalMigrationFrom;
|
||||||
// commitCLForStrategy should return either null or the supplied consistency level
|
// 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
|
// in which case we will commit everything at that CL since Accord doesn't support per table
|
||||||
// commit consistency
|
// commit consistency
|
||||||
ConsistencyLevel commitCL = mode.commitCLForStrategy(migrationFromMode, consistencyLevel, cm, tableId, mutation.key().getToken());
|
ConsistencyLevel commitCL = mode.commitCLForMode(migrationFromMode, consistencyLevel, cm, tableId, mutation.key().getToken());
|
||||||
if (commitCL != null)
|
if (commitCL != null)
|
||||||
return commitCL;
|
return commitCL;
|
||||||
}
|
}
|
||||||
|
|
@ -217,11 +218,11 @@ public class ConsensusMigrationMutationHelper
|
||||||
|
|
||||||
public <T extends IMutation> SplitMutation<T> splitMutationIntoAccordAndNormal(T mutation, ClusterMetadata cm)
|
public <T extends IMutation> SplitMutation<T> splitMutationIntoAccordAndNormal(T mutation, ClusterMetadata cm)
|
||||||
{
|
{
|
||||||
if (mutation.allowsPotentialTransactionConflicts())
|
if (mutation.potentialTxnConflicts().allowed)
|
||||||
return new SplitMutation<>(null, mutation);
|
return new SplitMutation<>(null, mutation);
|
||||||
|
|
||||||
Token token = mutation.key().getToken();
|
Token token = mutation.key().getToken();
|
||||||
Predicate<TableId> isAccordUpdate = tableId -> tokenShouldBeWrittenThroughAccord(cm, tableId, token, TransactionalMode::nonSerialWritesThroughAccord, TransactionalMigrationFromMode::nonSerialReadsThroughAccord);
|
Predicate<TableId> isAccordUpdate = tableId -> tokenShouldBeWrittenThroughAccord(cm, tableId, token, TransactionalMode::nonSerialWritesThroughAccord, TransactionalMigrationFromMode::nonSerialWritesThroughAccord);
|
||||||
|
|
||||||
T accordMutation = (T)mutation.filter(isAccordUpdate);
|
T accordMutation = (T)mutation.filter(isAccordUpdate);
|
||||||
T normalMutation = (T)mutation.filter(not(isAccordUpdate));
|
T normalMutation = (T)mutation.filter(not(isAccordUpdate));
|
||||||
|
|
@ -258,15 +259,15 @@ public class ConsensusMigrationMutationHelper
|
||||||
// Potentially ignore commit consistency level if the TransactionalMode specifies full
|
// Potentially ignore commit consistency level if the TransactionalMode specifies full
|
||||||
ConsistencyLevel clForCommit = consistencyLevelForCommit(cm, mutations, consistencyLevel);
|
ConsistencyLevel clForCommit = consistencyLevelForCommit(cm, mutations, consistencyLevel);
|
||||||
TxnUpdate update = new TxnUpdate(fragments, TxnCondition.none(), clForCommit, true);
|
TxnUpdate update = new TxnUpdate(fragments, TxnCondition.none(), clForCommit, true);
|
||||||
Txn.InMemory txn = new Txn.InMemory(Keys.of(partitionKeys), TxnKeyRead.EMPTY, TxnQuery.NONE, update);
|
Txn.InMemory txn = new Txn.InMemory(Keys.of(partitionKeys), TxnRead.empty(Domain.Key), TxnQuery.NONE, update);
|
||||||
IAccordService accordService = AccordService.instance();
|
IAccordService accordService = AccordService.instance();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return accordService.coordinateAsync(minEpoch, txn, consistencyLevel, requestTime);
|
return accordService.coordinateAsync(minEpoch, txn, clForCommit, requestTime);
|
||||||
}
|
}
|
||||||
catch (CoordinationFailed coordinationFailed)
|
catch (CoordinationFailed coordinationFailed)
|
||||||
{
|
{
|
||||||
AsyncTxnResult failure = new AsyncTxnResult(coordinationFailed.txnId());
|
AsyncTxnResult failure = new AsyncTxnResult(coordinationFailed.txnId(), minEpoch, clForCommit, true, requestTime);
|
||||||
failure.setFailure(coordinationFailed.wrap());
|
failure.setFailure(coordinationFailed.wrap());
|
||||||
return failure;
|
return failure;
|
||||||
}
|
}
|
||||||
|
|
@ -274,15 +275,16 @@ public class ConsensusMigrationMutationHelper
|
||||||
|
|
||||||
public static void validateSafeToExecuteNonTransactionally(IMutation mutation) throws RetryOnDifferentSystemException
|
public static void validateSafeToExecuteNonTransactionally(IMutation mutation) throws RetryOnDifferentSystemException
|
||||||
{
|
{
|
||||||
if (mutation.allowsPotentialTransactionConflicts())
|
if (mutation.potentialTxnConflicts().allowed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
String keyspace = mutation.getKeyspaceName();
|
||||||
// System keyspaces are never managed by Accord
|
// System keyspaces are never managed by Accord
|
||||||
if (SchemaConstants.isSystemKeyspace(mutation.getKeyspaceName()))
|
if (SchemaConstants.isSystemKeyspace(keyspace))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Local keyspaces are never managed by Accord
|
// Local keyspaces are never managed by Accord
|
||||||
if (Schema.instance.localKeyspaces().containsKeyspace(mutation.getKeyspaceName()))
|
if (Schema.instance.localKeyspaces().containsKeyspace(keyspace))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ClusterMetadata cm = ClusterMetadata.current();
|
ClusterMetadata cm = ClusterMetadata.current();
|
||||||
|
|
@ -297,7 +299,7 @@ public class ConsensusMigrationMutationHelper
|
||||||
{
|
{
|
||||||
TableId tableId = pu.metadata().id;
|
TableId tableId = pu.metadata().id;
|
||||||
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(tableId);
|
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(tableId);
|
||||||
if (tokenShouldBeWrittenThroughAccord(cm, tableId, dk.getToken(), TransactionalMode::nonSerialWritesThroughAccord, TransactionalMigrationFromMode::nonSerialReadsThroughAccord))
|
if (tokenShouldBeWrittenThroughAccord(cm, tableId, dk.getToken(), TransactionalMode::nonSerialWritesThroughAccord, TransactionalMigrationFromMode::nonSerialWritesThroughAccord))
|
||||||
{
|
{
|
||||||
throwRetryOnDifferentSystem = true;
|
throwRetryOnDifferentSystem = true;
|
||||||
if (markedColumnFamilies == null)
|
if (markedColumnFamilies == null)
|
||||||
|
|
@ -350,21 +352,18 @@ public class ConsensusMigrationMutationHelper
|
||||||
// with different results if Accord reads non-transactionally written data that could be seen differently by different coordinators
|
// with different results if Accord reads non-transactionally written data that could be seen differently by different coordinators
|
||||||
|
|
||||||
// If the current mode writes through Accord then we should always write though Accord for ranges managed by Accord.
|
// If the current mode writes through Accord then we should always write though Accord for ranges managed by Accord.
|
||||||
// Accord needs to do synchronous commit and respect the consistency level so that Accord will later be able to
|
// Accord needs to do synchronous commit and respect the consistency level so non-SERIAL reads can read Accord's
|
||||||
// read its own writes
|
// writes.
|
||||||
if (transactionalModeWritesThroughAccord)
|
if (transactionalModeWritesThroughAccord)
|
||||||
|
{
|
||||||
return tms.migratingAndMigratedRanges.intersects(token);
|
return tms.migratingAndMigratedRanges.intersects(token);
|
||||||
|
}
|
||||||
|
|
||||||
// If we are migrating from a mode that used to write to Accord then any range that isn't migrating/migrated
|
// If we are migrating from a mode that used to write to Accord then any range that isn't migrated
|
||||||
// should continue to write through Accord.
|
// should continue to write through Accord. Accord might still be executing txns pre-migration so continue
|
||||||
// It's not completely symmetrical because Paxos is able to read Accord's writes by performing a single key barrier
|
// to route writes through Accord until migration is completed.
|
||||||
// and regular mutations will be able to do the same thing (needs to be added along with non-transactional reads)
|
|
||||||
// This means that migrating ranges don't need to be written through Accord because we are running Paxos now
|
|
||||||
// and not Accord. When migrating to Accord we need to do all the writes through Accord even if we aren't
|
|
||||||
// reading through Accord so that repair + Accord metadata is sufficient for Accord to be able to read
|
|
||||||
// safely and deterministically from any coordinator
|
|
||||||
if (migrationFromWritesThroughAccord)
|
if (migrationFromWritesThroughAccord)
|
||||||
return !tms.migratingAndMigratedRanges.intersects(token);
|
return !tms.migratedRanges.intersects(token);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ import javax.annotation.Nullable;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
|
|
@ -147,10 +146,10 @@ public class ConsensusMigrationState implements MetadataValue<ConsensusMigration
|
||||||
|
|
||||||
Map<Epoch, List<Range<Token>>> migratingRangesByEpoch = ImmutableMap.of();
|
Map<Epoch, List<Range<Token>>> migratingRangesByEpoch = ImmutableMap.of();
|
||||||
if (!ranges.isEmpty())
|
if (!ranges.isEmpty())
|
||||||
ImmutableMap.of(Epoch.EMPTY, ranges);
|
migratingRangesByEpoch = ImmutableMap.of(Epoch.EMPTY, ranges);
|
||||||
|
|
||||||
if (overwrite)
|
if (overwrite)
|
||||||
tableState = new TableMigrationState(metadata.keyspace, metadata.name, metadata.id, target, ImmutableSet.of(), initialRepairPendingRanges(target, ranges), migratingRangesByEpoch);
|
tableState = new TableMigrationState(metadata.keyspace, metadata.name, metadata.id, target, ImmutableList.of(), initialRepairPendingRanges(target, ranges), migratingRangesByEpoch);
|
||||||
else
|
else
|
||||||
tableState = tableState.withRangesMigrating(ranges, target);
|
tableState = tableState.withRangesMigrating(ranges, target);
|
||||||
|
|
||||||
|
|
@ -168,6 +167,7 @@ public class ConsensusMigrationState implements MetadataValue<ConsensusMigration
|
||||||
private static void putUnchanged(Map<TableId, TableMigrationState> current, ImmutableMap.Builder<TableId, TableMigrationState> next, Collection<TableMetadata> changed)
|
private static void putUnchanged(Map<TableId, TableMigrationState> current, ImmutableMap.Builder<TableId, TableMigrationState> next, Collection<TableMetadata> changed)
|
||||||
{
|
{
|
||||||
Set<TableId> changedIds = changed.stream().map(TableMetadata::id).collect(Collectors.toSet());
|
Set<TableId> changedIds = changed.stream().map(TableMetadata::id).collect(Collectors.toSet());
|
||||||
|
|
||||||
putUnchanged(current, next, changedIds);
|
putUnchanged(current, next, changedIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,12 +183,6 @@ public class ConsensusMigrationState implements MetadataValue<ConsensusMigration
|
||||||
{
|
{
|
||||||
ImmutableMap.Builder<TableId, TableMigrationState> updated = ImmutableMap.builder();
|
ImmutableMap.Builder<TableId, TableMigrationState> updated = ImmutableMap.builder();
|
||||||
putUnchanged(tableStates, updated, new HashSet<>(completed));
|
putUnchanged(tableStates, updated, new HashSet<>(completed));
|
||||||
for (Map.Entry<TableId, TableMigrationState> entry : tableStates.entrySet())
|
|
||||||
{
|
|
||||||
if (completed.contains(entry.getKey()))
|
|
||||||
continue;
|
|
||||||
updated.put(entry);
|
|
||||||
}
|
|
||||||
return new ConsensusMigrationState(lastModified, updated.build());
|
return new ConsensusMigrationState(lastModified, updated.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,7 +202,6 @@ public class ConsensusMigrationState implements MetadataValue<ConsensusMigration
|
||||||
updated.put(metadata.id, state);
|
updated.put(metadata.id, state);
|
||||||
return new ConsensusMigrationState(lastModified, updated.build());
|
return new ConsensusMigrationState(lastModified, updated.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConsensusMigrationState withMigrationsRemovedFor(Set<TableId> removed)
|
public ConsensusMigrationState withMigrationsRemovedFor(Set<TableId> removed)
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,15 @@
|
||||||
|
|
||||||
package org.apache.cassandra.service.consensus.migration;
|
package org.apache.cassandra.service.consensus.migration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.BiPredicate;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
import accord.primitives.Routable.Domain;
|
import accord.primitives.Routable.Domain;
|
||||||
import accord.primitives.Seekables;
|
import accord.primitives.Seekables;
|
||||||
|
|
@ -31,25 +36,42 @@ import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
import org.apache.cassandra.db.ConsistencyLevel;
|
import org.apache.cassandra.db.ConsistencyLevel;
|
||||||
import org.apache.cassandra.db.DecoratedKey;
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
|
import org.apache.cassandra.db.PartitionPosition;
|
||||||
|
import org.apache.cassandra.db.PartitionRangeReadCommand;
|
||||||
|
import org.apache.cassandra.db.ReadCommand;
|
||||||
|
import org.apache.cassandra.db.SinglePartitionReadCommand;
|
||||||
|
import org.apache.cassandra.dht.AbstractBounds;
|
||||||
|
import org.apache.cassandra.dht.Bounds;
|
||||||
|
import org.apache.cassandra.dht.NormalizedRanges;
|
||||||
|
import org.apache.cassandra.dht.Range;
|
||||||
import org.apache.cassandra.dht.Token;
|
import org.apache.cassandra.dht.Token;
|
||||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||||
|
import org.apache.cassandra.exceptions.RetryOnDifferentSystemException;
|
||||||
import org.apache.cassandra.locator.EndpointsForToken;
|
import org.apache.cassandra.locator.EndpointsForToken;
|
||||||
import org.apache.cassandra.locator.ReplicaLayout;
|
import org.apache.cassandra.locator.ReplicaLayout;
|
||||||
import org.apache.cassandra.schema.KeyspaceMetadata;
|
import org.apache.cassandra.schema.KeyspaceMetadata;
|
||||||
import org.apache.cassandra.schema.Keyspaces;
|
import org.apache.cassandra.schema.Keyspaces;
|
||||||
import org.apache.cassandra.schema.Schema;
|
import org.apache.cassandra.schema.Schema;
|
||||||
|
import org.apache.cassandra.schema.SchemaConstants;
|
||||||
import org.apache.cassandra.schema.TableId;
|
import org.apache.cassandra.schema.TableId;
|
||||||
import org.apache.cassandra.schema.TableMetadata;
|
import org.apache.cassandra.schema.TableMetadata;
|
||||||
import org.apache.cassandra.schema.TableParams;
|
import org.apache.cassandra.schema.TableParams;
|
||||||
|
import org.apache.cassandra.service.accord.TokenRange;
|
||||||
|
import org.apache.cassandra.service.accord.api.AccordRoutingKey.MinTokenKey;
|
||||||
|
import org.apache.cassandra.service.accord.api.AccordRoutingKey.SentinelKey;
|
||||||
|
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
||||||
import org.apache.cassandra.service.consensus.TransactionalMode;
|
import org.apache.cassandra.service.consensus.TransactionalMode;
|
||||||
|
import org.apache.cassandra.service.consensus.migration.ConsensusKeyMigrationState.KeyMigrationState;
|
||||||
import org.apache.cassandra.service.paxos.Paxos;
|
import org.apache.cassandra.service.paxos.Paxos;
|
||||||
|
import org.apache.cassandra.service.reads.ReadCoordinator;
|
||||||
import org.apache.cassandra.tcm.ClusterMetadata;
|
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||||
import org.apache.cassandra.tcm.ClusterMetadataService;
|
|
||||||
import org.apache.cassandra.tcm.Epoch;
|
|
||||||
import org.apache.cassandra.transport.Dispatcher;
|
import org.apache.cassandra.transport.Dispatcher;
|
||||||
import org.apache.cassandra.utils.FBUtilities;
|
import org.apache.cassandra.utils.FBUtilities;
|
||||||
|
import org.apache.cassandra.utils.Pair;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
import static org.apache.cassandra.dht.Range.compareRightToken;
|
||||||
import static org.apache.cassandra.service.consensus.migration.ConsensusKeyMigrationState.getConsensusMigratedAt;
|
import static org.apache.cassandra.service.consensus.migration.ConsensusKeyMigrationState.getConsensusMigratedAt;
|
||||||
import static org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget.paxos;
|
import static org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget.paxos;
|
||||||
import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.ConsensusRoutingDecision.accord;
|
import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.ConsensusRoutingDecision.accord;
|
||||||
|
|
@ -59,6 +81,10 @@ import static org.apache.cassandra.service.consensus.migration.ConsensusRequestR
|
||||||
/**
|
/**
|
||||||
* Helper class to decide where to route a request that requires consensus, migrating a key if necessary
|
* Helper class to decide where to route a request that requires consensus, migrating a key if necessary
|
||||||
* before rerouting.
|
* before rerouting.
|
||||||
|
*
|
||||||
|
* This router has to be used for all SERIAL reads and writes to ensure the correct operation of Paxos/Acocrd during migration
|
||||||
|
* and for all non-SERIAL reads because non-SERIAL reads may end up being routed to Accord and Accord needs CRR to manage
|
||||||
|
* any key migrations that need to be performed
|
||||||
*/
|
*/
|
||||||
public class ConsensusRequestRouter
|
public class ConsensusRequestRouter
|
||||||
{
|
{
|
||||||
|
|
@ -226,10 +252,18 @@ public class ConsensusRequestRouter
|
||||||
// Accord -> Paxos - Paxos will ask Accord to migrate in the read at each replica if necessary
|
// Accord -> Paxos - Paxos will ask Accord to migrate in the read at each replica if necessary
|
||||||
// Paxos -> Accord - Paxos needs to be repaired before Accord runs so do it here
|
// Paxos -> Accord - Paxos needs to be repaired before Accord runs so do it here
|
||||||
if (tms.targetProtocol == paxos)
|
if (tms.targetProtocol == paxos)
|
||||||
|
// TODO (important): Why are these two cases paxosV2 instead of `pickPaxos`?
|
||||||
|
// Because we only supported PaxosV2 for migration?
|
||||||
|
// Eventually we want to support both so just use pickPaxos and error out on migration from paxosV1 elsewhere?
|
||||||
return paxosV2;
|
return paxosV2;
|
||||||
else
|
else
|
||||||
// Should exit exceptionally if the repair is not done
|
{
|
||||||
ConsensusKeyMigrationState.repairKeyPaxos(naturalReplicas, cm.epoch, key, cfs, consistencyLevel, requestTime, timeoutNanos, isLocallyReplicated, isForWrite);
|
if (tms.accordSafeToReadRanges.intersects(key.getToken()))
|
||||||
|
// Should exit exceptionally if the repair is not done
|
||||||
|
ConsensusKeyMigrationState.repairKeyPaxos(naturalReplicas, cm.epoch, key, cfs, consistencyLevel, requestTime, timeoutNanos, isLocallyReplicated, isForWrite);
|
||||||
|
else
|
||||||
|
return pickPaxos();
|
||||||
|
}
|
||||||
|
|
||||||
return pickMigrated(tms.targetProtocol);
|
return pickMigrated(tms.targetProtocol);
|
||||||
}
|
}
|
||||||
|
|
@ -274,31 +308,169 @@ public class ConsensusRequestRouter
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isKeyInMigratingOrMigratedRangeFromAccord(Epoch epoch, TableId tableId, DecoratedKey key)
|
public boolean isRangeManagedByAccordForReadAndWrite(ClusterMetadata cm, TableId tableId, TokenRange range)
|
||||||
{
|
{
|
||||||
ClusterMetadata cm = ClusterMetadataService.instance().fetchLogFromCMS(epoch);
|
TableMetadata metadata = getTableMetadata(cm, tableId);
|
||||||
|
TransactionalMode transactionalMode = metadata.params.transactionalMode;
|
||||||
|
TransactionalMigrationFromMode transactionalMigrationFromMode = metadata.params.transactionalMigrationFrom;
|
||||||
TableMigrationState tms = cm.consensusMigrationState.tableStates.get(tableId);
|
TableMigrationState tms = cm.consensusMigrationState.tableStates.get(tableId);
|
||||||
return isKeyInMigratingOrMigratedRangeFromAccord(getTableMetadata(cm, tableId), tms, key);
|
if (tms == null)
|
||||||
|
{
|
||||||
|
checkState(transactionalMigrationFromMode == TransactionalMigrationFromMode.none, "TableMigrationState shouldn't be null during migration");
|
||||||
|
return transactionalMode.nonSerialReadsThroughAccord;
|
||||||
|
}
|
||||||
|
|
||||||
|
// = token ends up as a min and max key bound in C* parlance and min and max token key in Accord parlance
|
||||||
|
// and the conversion to a C* range results in the unintentional creation of a wrap around range.
|
||||||
|
// Instead treat it like a key and do that check.
|
||||||
|
if (range.start().getClass() == MinTokenKey.class
|
||||||
|
&& range.end() instanceof TokenKey
|
||||||
|
&& range.start().token().equals(range.end().token()))
|
||||||
|
{
|
||||||
|
checkState(range.end().getClass() != MinTokenKey.class, "Unexpected empty range");
|
||||||
|
return isTokenManagedByAccordForReadAndWrite(metadata, tms, range.start().token());
|
||||||
|
}
|
||||||
|
else if (range.start().getClass() == MinTokenKey.class)
|
||||||
|
{
|
||||||
|
// Start is particularly problematic because we use min MinTokenKey to make start inclusive and this is something
|
||||||
|
// that isn't possible to mimic at all with Range<Token>, for end it's less problematic because just the token
|
||||||
|
// is sufficient for Accord to route the query and select the correct shards even if it might accidentally run
|
||||||
|
// on an extra shard, the filtering will take care of it. There is nothing to do here but convert to a bounds
|
||||||
|
// and use the bounds check
|
||||||
|
PartitionPosition startPP = range.start().token().minKeyBound();
|
||||||
|
PartitionPosition endPP;
|
||||||
|
if (range.end().getClass() == SentinelKey.class)
|
||||||
|
endPP = DatabaseDescriptor.getPartitioner().getMinimumToken().maxKeyBound();
|
||||||
|
else if (range.end().getClass() == MinTokenKey.class)
|
||||||
|
endPP = range.end().token().minKeyBound();
|
||||||
|
else
|
||||||
|
endPP = range.end().token().maxKeyBound();
|
||||||
|
Bounds<PartitionPosition> bounds = new Bounds<>(startPP, endPP);
|
||||||
|
return isBoundsExclusivelyManagedByAccordForRead(transactionalMode, transactionalMigrationFromMode, tms, bounds);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return isRangeManagedByAccordForReadAndWrite(metadata,
|
||||||
|
cm.consensusMigrationState.tableStates.get(tableId),
|
||||||
|
range.toKeyspaceRange());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A lightweight check against cluster metadata that doesn't check if the range has already been migrated
|
||||||
|
* using local system table state. It just assumes that the key migration has already been done.
|
||||||
|
*
|
||||||
|
* This version is for is full read write transactions
|
||||||
|
*/
|
||||||
|
public boolean isRangeManagedByAccordForReadAndWrite(TableMetadata metadata, TableMigrationState tms, Range<Token> range)
|
||||||
|
{
|
||||||
|
checkState(!range.isTrulyWrapAround(), "Accidentally created a wrap around range");
|
||||||
|
TransactionalMode transactionalMode = metadata.params.transactionalMode;
|
||||||
|
TransactionalMigrationFromMode migrationFrom = metadata.params.transactionalMigrationFrom;
|
||||||
|
|
||||||
|
if (migrationFrom.isMigrating())
|
||||||
|
checkState(tms != null, "Can't have migration in progress without tms");
|
||||||
|
|
||||||
|
if (transactionalMode.accordIsEnabled)
|
||||||
|
{
|
||||||
|
if (!migrationFrom.isMigrating())
|
||||||
|
return true;
|
||||||
|
if (migrationFrom.migratingFromAccord())
|
||||||
|
return true;
|
||||||
|
// Accord can only read/write the key if it is in a safe to read (repaired) range
|
||||||
|
if (Range.intersects(tms.accordSafeToReadRanges, ImmutableList.of(range)))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Once the migration starts only barriers are allowed to run for the key in Accord
|
||||||
|
if (migrationFrom.migratingFromAccord() && !Range.intersects(tms.migratingAndMigratedRanges, ImmutableList.of(range)))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isKeyManagedByAccordForReadAndWrite(ClusterMetadata cm, TableId tableId, DecoratedKey key)
|
||||||
|
{
|
||||||
|
return isTokenManagedByAccordForReadAndWrite(getTableMetadata(cm, tableId),
|
||||||
|
cm.consensusMigrationState.tableStates.get(tableId),
|
||||||
|
key.getToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A lightweight check against cluster metadata that doesn't check if the key has already been migrated
|
* A lightweight check against cluster metadata that doesn't check if the key has already been migrated
|
||||||
* using local system table state.
|
* using local system table state. It just assumes that the key migration has already been done.
|
||||||
|
*
|
||||||
|
* This version is for is full read write transactions
|
||||||
*/
|
*/
|
||||||
public boolean isKeyInMigratingOrMigratedRangeFromAccord(TableMetadata metadata, TableMigrationState tms, DecoratedKey key)
|
public boolean isTokenManagedByAccordForReadAndWrite(TableMetadata metadata, TableMigrationState tms, Token token)
|
||||||
{
|
{
|
||||||
if (!metadata.params.transactionalMigrationFrom.isMigrating())
|
TransactionalMode transactionalMode = metadata.params.transactionalMode;
|
||||||
return false;
|
TransactionalMigrationFromMode migrationFrom = metadata.params.transactionalMigrationFrom;
|
||||||
|
|
||||||
// No state means no migration for this table
|
if (migrationFrom.isMigrating())
|
||||||
if (tms == null)
|
checkState(tms != null, "Can't have migration in progress without tms");
|
||||||
return false;
|
|
||||||
|
|
||||||
if (tms.targetProtocol == ConsensusMigrationTarget.accord)
|
if (transactionalMode.accordIsEnabled)
|
||||||
return false;
|
{
|
||||||
|
if (!migrationFrom.isMigrating())
|
||||||
|
return true;
|
||||||
|
if (migrationFrom.migratingFromAccord())
|
||||||
|
return true;
|
||||||
|
// Accord can only read/write the key if it is in a safe to read (repaired) range
|
||||||
|
if (tms.accordSafeToReadRanges.intersects(token))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Once the migration starts only barriers are allowed to run for the key in Accord
|
||||||
|
if (migrationFrom.migratingFromAccord() && !tms.migratingAndMigratedRanges.intersects(token))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (tms.migratingAndMigratedRanges.intersects(key.getToken()))
|
return false;
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
public boolean isKeyManagedByAccordForWrite(ClusterMetadata cm, TableId tableId, DecoratedKey key)
|
||||||
|
{
|
||||||
|
return isKeyManagedByAccordForWrite(getTableMetadata(cm, tableId),
|
||||||
|
cm.consensusMigrationState.tableStates.get(tableId),
|
||||||
|
key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A lightweight check against cluster metadata that doesn't check if the key has already been migrated
|
||||||
|
* using local system table state. It just assumes that the key migration has already been done.
|
||||||
|
*
|
||||||
|
* This version is for writes through Accord before Accord is able to safely read.
|
||||||
|
*/
|
||||||
|
public boolean isKeyManagedByAccordForWrite(TableMetadata metadata, TableMigrationState tms, DecoratedKey key)
|
||||||
|
{
|
||||||
|
TransactionalMode transactionalMode = metadata.params.transactionalMode;
|
||||||
|
TransactionalMigrationFromMode migrationFrom = metadata.params.transactionalMigrationFrom;
|
||||||
|
Token token = key.getToken();
|
||||||
|
|
||||||
|
if (migrationFrom.isMigrating())
|
||||||
|
checkState(tms != null, "Can't have migration in progress without tms");
|
||||||
|
|
||||||
|
if (transactionalMode.accordIsEnabled)
|
||||||
|
{
|
||||||
|
if (!migrationFrom.isMigrating())
|
||||||
|
return true;
|
||||||
|
if (migrationFrom.migratingFromAccord())
|
||||||
|
return true;
|
||||||
|
// Accord can blind write to the key even if it isn't safe to read from it so use migratingAndMigratedRanges
|
||||||
|
if (tms.migratingAndMigratedRanges.intersects(token))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We can always allow writes through Accord and it's necessary to do that so that
|
||||||
|
// andy premigration txns aren't exposed to non-transactional writes
|
||||||
|
if (migrationFrom.nonSerialWritesThroughAccord() && !tms.migratedRanges.intersects(token))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -340,4 +512,409 @@ public class ConsensusRequestRouter
|
||||||
{
|
{
|
||||||
return Paxos.useV2() ? paxosV2 : paxosV1;
|
return Paxos.useV2() ? paxosV2 : paxosV1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void validateSafeToReadNonTransactionally(ReadCommand command)
|
||||||
|
{
|
||||||
|
if (command.potentialTxnConflicts().allowed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
String keyspace = command.metadata().keyspace;
|
||||||
|
// System keyspaces are never managed by Accord
|
||||||
|
if (SchemaConstants.isSystemKeyspace(keyspace))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Local keyspaces are never managed by Accord
|
||||||
|
if (Schema.instance.localKeyspaces().containsKeyspace(keyspace))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ClusterMetadata cm = ClusterMetadata.current();
|
||||||
|
TableId tableId = command.metadata().id;
|
||||||
|
TableMetadata tableMetadata = getTableMetadata(cm, tableId);
|
||||||
|
// Null for local or dropped tables
|
||||||
|
if (tableMetadata == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TransactionalMode transactionalMode = tableMetadata.params.transactionalMode;
|
||||||
|
TransactionalMigrationFromMode transactionalMigrationFromMode = tableMetadata.params.transactionalMigrationFrom;
|
||||||
|
if (!transactionalMode.nonSerialReadsThroughAccord && !transactionalMigrationFromMode.nonSerialReadsThroughAccord())
|
||||||
|
return;
|
||||||
|
|
||||||
|
TableMigrationState tms = cm.consensusMigrationState.tableStates.get(tableId);
|
||||||
|
|
||||||
|
// Null with a transaction mode that reads through Accord indicates a completed migration or table created
|
||||||
|
// to use Accord initially
|
||||||
|
if (tms == null)
|
||||||
|
{
|
||||||
|
checkState(transactionalMigrationFromMode == TransactionalMigrationFromMode.none);
|
||||||
|
if (transactionalMode.nonSerialReadsThroughAccord)
|
||||||
|
{
|
||||||
|
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(tableId);
|
||||||
|
if (cfs != null)
|
||||||
|
cfs.metric.readsRejectedOnWrongSystem.mark();
|
||||||
|
throw new RetryOnDifferentSystemException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isExclusivelyReadableFromAccord;
|
||||||
|
if (command.isRangeRequest())
|
||||||
|
isExclusivelyReadableFromAccord = isBoundsExclusivelyManagedByAccordForRead(transactionalMode, transactionalMigrationFromMode, tms, command.dataRange().keyRange());
|
||||||
|
else
|
||||||
|
isExclusivelyReadableFromAccord = isTokenExclusivelyManagedByAccordForRead(transactionalMode, transactionalMigrationFromMode, tms, ((SinglePartitionReadCommand)command).partitionKey().getToken());
|
||||||
|
|
||||||
|
if (isExclusivelyReadableFromAccord)
|
||||||
|
{
|
||||||
|
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(tableId);
|
||||||
|
if (cfs != null)
|
||||||
|
cfs.metric.readsRejectedOnWrongSystem.mark();
|
||||||
|
throw new RetryOnDifferentSystemException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isTokenExclusivelyManagedByAccordForRead(@Nonnull TransactionalMode transactionalMode,
|
||||||
|
@Nonnull TransactionalMigrationFromMode migrationFrom,
|
||||||
|
@Nonnull TableMigrationState tms,
|
||||||
|
@Nonnull Token token)
|
||||||
|
{
|
||||||
|
checkNotNull(transactionalMode, "transactionalMode is null");
|
||||||
|
checkNotNull(migrationFrom, "migrationFrom is null");
|
||||||
|
checkNotNull(tms, "tms (TableMigrationState) is null");
|
||||||
|
checkNotNull(token, "bounds is null");
|
||||||
|
|
||||||
|
if (transactionalMode.accordIsEnabled)
|
||||||
|
{
|
||||||
|
if (!migrationFrom.isMigrating())
|
||||||
|
return true;
|
||||||
|
if (migrationFrom.migratingFromAccord())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Accord is exclusive once the range is fully migrated to Accord, but possible to read from safely
|
||||||
|
// when accordSafeToReadRanges covers the entire bound
|
||||||
|
if (tms.migratedRanges.intersects(token))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Once the migration starts only barriers are allowed to run for the key in Accord
|
||||||
|
if (migrationFrom.migratingFromAccord() && !tms.migratingAndMigratedRanges.intersects(token))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if any part of the bound
|
||||||
|
private static boolean isBoundsExclusivelyManagedByAccordForRead(@Nonnull TransactionalMode transactionalMode,
|
||||||
|
@Nonnull TransactionalMigrationFromMode migrationFrom,
|
||||||
|
@Nonnull TableMigrationState tms,
|
||||||
|
@Nonnull AbstractBounds<PartitionPosition> bounds)
|
||||||
|
{
|
||||||
|
checkNotNull(transactionalMode, "transactionalMode is null");
|
||||||
|
checkNotNull(migrationFrom, "migrationFrom is null");
|
||||||
|
checkNotNull(tms, "tms (TableMigrationState) is null");
|
||||||
|
checkNotNull(bounds, "bounds is null");
|
||||||
|
|
||||||
|
BiPredicate<AbstractBounds<PartitionPosition>, NormalizedRanges<Token>> intersects = (testBounds, testRanges) -> {
|
||||||
|
// TODO (nicetohave): Efficiency of this intersection
|
||||||
|
for (org.apache.cassandra.dht.Range<Token> range : testRanges)
|
||||||
|
{
|
||||||
|
Pair<AbstractBounds<PartitionPosition>, AbstractBounds<PartitionPosition>> intersectionAndRemainder = Range.intersectionAndRemainder(testBounds, range);
|
||||||
|
return intersectionAndRemainder.left != null;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (bounds.left.getToken().equals(bounds.right.getToken()) && !bounds.inclusiveLeft() && bounds.inclusiveRight())
|
||||||
|
{
|
||||||
|
return isTokenExclusivelyManagedByAccordForRead(transactionalMode, migrationFrom, tms, bounds.left.getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transactionalMode.accordIsEnabled)
|
||||||
|
{
|
||||||
|
if (!migrationFrom.isMigrating())
|
||||||
|
return true;
|
||||||
|
if (migrationFrom.migratingFromAccord())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Accord is exclusive once the range is fully migrated to Accord, but possible to read from safely
|
||||||
|
// when accordSafeToReadRanges covers the entire bound
|
||||||
|
if (intersects.test(bounds, tms.migratedRanges))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Once the migration starts only barriers are allowed to run for the key in Accord
|
||||||
|
if (migrationFrom.migratingFromAccord() && !intersects.test(bounds, tms.migratingAndMigratedRanges))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RangeReadTarget
|
||||||
|
{
|
||||||
|
accord,
|
||||||
|
normal
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RangeReadWithTarget
|
||||||
|
{
|
||||||
|
public final PartitionRangeReadCommand read;
|
||||||
|
public final RangeReadTarget target;
|
||||||
|
|
||||||
|
private RangeReadWithTarget(PartitionRangeReadCommand read, RangeReadTarget target)
|
||||||
|
{
|
||||||
|
this.read = read;
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "RangeReadWithTarget{" +
|
||||||
|
"read=" + read +
|
||||||
|
", target=" + target +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* While it's possible to map the Accord read to a single txn it doesn't seem worth it since it's a pretty unusual
|
||||||
|
* scenario where we do this during migration and have a lot of different read commands.
|
||||||
|
*/
|
||||||
|
public static List<RangeReadWithTarget> splitReadIntoAccordAndNormal(ClusterMetadata cm, PartitionRangeReadCommand read, ReadCoordinator readCoordinator, Dispatcher.RequestTime requestTime)
|
||||||
|
{
|
||||||
|
if (!readCoordinator.isEventuallyConsistent())
|
||||||
|
return ImmutableList.of(new RangeReadWithTarget(read, RangeReadTarget.normal));
|
||||||
|
TableMetadata tm = getTableMetadata(cm, read.metadata().id);
|
||||||
|
if (tm == null || (!tm.params.transactionalMode.nonSerialReadsThroughAccord && !tm.params.transactionalMigrationFrom.nonSerialReadsThroughAccord()))
|
||||||
|
return ImmutableList.of(new RangeReadWithTarget(read, RangeReadTarget.normal));
|
||||||
|
|
||||||
|
List<RangeReadWithTarget> result = null;
|
||||||
|
TransactionalMode transactionalMode = tm.params.transactionalMode;
|
||||||
|
TransactionalMigrationFromMode transactionalMigrationFromMode = tm.params.transactionalMigrationFrom;
|
||||||
|
boolean transactionalModeReadsThroughAccord = transactionalMode.nonSerialReadsThroughAccord;
|
||||||
|
RangeReadTarget migrationToTarget = transactionalModeReadsThroughAccord ? RangeReadTarget.accord : RangeReadTarget.normal;
|
||||||
|
boolean migrationFromReadsThroughAccord = transactionalMigrationFromMode.nonSerialReadsThroughAccord();
|
||||||
|
RangeReadTarget migrationFromTarget = migrationFromReadsThroughAccord ? RangeReadTarget.accord : RangeReadTarget.normal;
|
||||||
|
TableMigrationState tms = cm.consensusMigrationState.tableStates.get(tm.id);
|
||||||
|
if (tms == null)
|
||||||
|
{
|
||||||
|
if (transactionalMigrationFromMode == TransactionalMigrationFromMode.none)
|
||||||
|
// There is no migration and no TMS so do what the schema says since no migration should be required
|
||||||
|
return ImmutableList.of(new RangeReadWithTarget(read, transactionalModeReadsThroughAccord ? RangeReadTarget.accord : RangeReadTarget.normal));
|
||||||
|
else
|
||||||
|
// If we are migrating from something and there is no migration state the migration hasn't begun
|
||||||
|
// so continue to do what we are migrating from does until the range is marked as migrating
|
||||||
|
return ImmutableList.of(new RangeReadWithTarget(read, migrationFromReadsThroughAccord ? RangeReadTarget.accord : RangeReadTarget.normal));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// AbstractBounds can potentially be left/right inclusive while Range used to track migration is only right inclusive
|
||||||
|
// The right way to tackle this seems to be to find the tokens that intersect the key range and then split until
|
||||||
|
// until nothing intersects
|
||||||
|
AbstractBounds<PartitionPosition> keyRange = read.dataRange().keyRange();
|
||||||
|
AbstractBounds<PartitionPosition> remainder = keyRange;
|
||||||
|
|
||||||
|
// Migrating to Accord we only read through Accord when the range is fully migrated, but migrating back
|
||||||
|
// we stop reading from Accord as soon as the range is marked migrating and do key migration on read
|
||||||
|
NormalizedRanges<Token> migratedRanges = transactionalModeReadsThroughAccord ? tms.migratedRanges : tms.migratingAndMigratedRanges;
|
||||||
|
|
||||||
|
// Add the preceding range if any
|
||||||
|
if (!migratedRanges.isEmpty())
|
||||||
|
{
|
||||||
|
Token firstMigratingToken = migratedRanges.get(0).left.getToken();
|
||||||
|
int leftCmp = keyRange.left.getToken().compareTo(firstMigratingToken);
|
||||||
|
int rightCmp = compareRightToken(keyRange.right.getToken(), firstMigratingToken);
|
||||||
|
if (leftCmp <= 0)
|
||||||
|
{
|
||||||
|
if (rightCmp <= 0)
|
||||||
|
return ImmutableList.of(new RangeReadWithTarget(read, migrationFromTarget));
|
||||||
|
result = new ArrayList<>();
|
||||||
|
AbstractBounds<PartitionPosition> precedingRange = keyRange.withNewRight(rightCmp <= 0 ? keyRange.right : firstMigratingToken.maxKeyBound());
|
||||||
|
// Could be an empty bound, it's fine to let a min KeyBound and max KeyBound through as that isn't empty
|
||||||
|
if (!precedingRange.left.equals(precedingRange.right))
|
||||||
|
result.add(new RangeReadWithTarget(read.forSubRange(precedingRange, false), migrationFromTarget));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hadAccordReads = false;
|
||||||
|
for (Range<Token> r : migratedRanges)
|
||||||
|
{
|
||||||
|
Pair<AbstractBounds<PartitionPosition>, AbstractBounds<PartitionPosition>> intersectionAndRemainder = Range.intersectionAndRemainder(remainder, r);
|
||||||
|
if (intersectionAndRemainder.left != null)
|
||||||
|
{
|
||||||
|
if (result == null)
|
||||||
|
result = new ArrayList<>();
|
||||||
|
PartitionRangeReadCommand subRead = read.forSubRange(intersectionAndRemainder.left, result.isEmpty() ? true : false);
|
||||||
|
result.add(new RangeReadWithTarget(subRead, migrationToTarget));
|
||||||
|
hadAccordReads = true;
|
||||||
|
}
|
||||||
|
remainder = intersectionAndRemainder.right;
|
||||||
|
if (remainder == null)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainder != null)
|
||||||
|
{
|
||||||
|
if (result != null)
|
||||||
|
result.add(new RangeReadWithTarget(read.forSubRange(remainder, true), migrationFromTarget));
|
||||||
|
else
|
||||||
|
return ImmutableList.of(new RangeReadWithTarget(read.forSubRange(remainder, false), migrationFromTarget));
|
||||||
|
}
|
||||||
|
|
||||||
|
checkState(result != null && !result.isEmpty(), "Shouldn't have null or empty result");
|
||||||
|
checkState(result.get(0).read.dataRange().startKey().equals(read.dataRange().startKey()), "Split reads should encompass entire range");
|
||||||
|
checkState(result.get(result.size() - 1).read.dataRange().stopKey().equals(read.dataRange().stopKey()), "Split reads should encompass entire range");
|
||||||
|
if (result.size() > 1)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < result.size() - 1; i++)
|
||||||
|
{
|
||||||
|
checkState(result.get(i).read.dataRange().stopKey().equals(result.get(i + 1).read.dataRange().startKey()), "Split reads should all be adjacent");
|
||||||
|
checkState(result.get(i).target != result.get(i + 1).target, "Split reads should be for different targets");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO (later): https://issues.apache.org/jira/browse/CASSANDRA-20211 Range reads could use a barrier
|
||||||
|
if (hadAccordReads)
|
||||||
|
{
|
||||||
|
// do barrier
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of splitting mutations across Accord and non-transactional boundaries
|
||||||
|
*/
|
||||||
|
public static class SplitReads
|
||||||
|
{
|
||||||
|
@Nullable
|
||||||
|
public final SinglePartitionReadCommand.Group accordReads;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public final SinglePartitionReadCommand.Group normalReads;
|
||||||
|
|
||||||
|
private SplitReads(SinglePartitionReadCommand.Group accordReads, SinglePartitionReadCommand.Group normalReads)
|
||||||
|
{
|
||||||
|
this.accordReads = accordReads;
|
||||||
|
this.normalReads = normalReads;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SplitReads splitReadsIntoAccordAndNormal(ClusterMetadata cm, SinglePartitionReadCommand.Group reads, ReadCoordinator coordinator, Dispatcher.RequestTime requestTime)
|
||||||
|
{
|
||||||
|
if (!coordinator.isEventuallyConsistent())
|
||||||
|
return new SplitReads(null, reads);
|
||||||
|
List<SinglePartitionReadCommand> accordReads = null;
|
||||||
|
List<SinglePartitionReadCommand> normalReads = null;
|
||||||
|
|
||||||
|
TableMetadata tm = getTableMetadata(cm, reads.queries.get(0).metadata().id);
|
||||||
|
if (tm == null || (!tm.params.transactionalMode.nonSerialReadsThroughAccord && !tm.params.transactionalMigrationFrom.nonSerialReadsThroughAccord()))
|
||||||
|
return new SplitReads(null, reads);
|
||||||
|
|
||||||
|
TransactionalMode transactionalMode = tm.params.transactionalMode;
|
||||||
|
TransactionalMigrationFromMode transactionalMigrationFromMode = tm.params.transactionalMigrationFrom;
|
||||||
|
TableMigrationState tms = cm.consensusMigrationState.tableStates.get(tm.id);
|
||||||
|
|
||||||
|
for (SinglePartitionReadCommand command : reads.queries)
|
||||||
|
{
|
||||||
|
if (tokenShouldBeReadThroughAccord(tms, command.partitionKey().getToken(), transactionalMode, transactionalMigrationFromMode))
|
||||||
|
{
|
||||||
|
if (accordReads == null)
|
||||||
|
accordReads = new ArrayList<>(reads.queries.size());
|
||||||
|
accordReads.add(command);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (normalReads == null)
|
||||||
|
normalReads = new ArrayList<>(reads.queries.size());
|
||||||
|
normalReads.add(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When migrating from Accord -> Paxos we need to do the Accord barrier to have acknowledged Accord writes
|
||||||
|
// be visible to non-SERIAL reads, but from Paxos -> Accord we don't need to because read only transactions
|
||||||
|
// don't have recovery determinism issues and Accord will honor read consistency levels and match the behavior
|
||||||
|
// of non-serially reading Paxos transactions. Since it's a non-SERIAL read there is no guarantee of seeing
|
||||||
|
// in-flight Paxos operations, for that you would need to read at SERIAL.
|
||||||
|
// If the migration direction is from a mode that used to read through Accord then Accord would be
|
||||||
|
// doing async commit so we need barriers if this mode is no longer reading through Accord.
|
||||||
|
if (transactionalMigrationFromMode.isMigrating() && transactionalMigrationFromMode.nonSerialReadsThroughAccord() && !transactionalMode.nonSerialReadsThroughAccord && normalReads != null)
|
||||||
|
{
|
||||||
|
checkState(!normalReads.isEmpty());
|
||||||
|
List<DecoratedKey> keysNeedingBarrier = null;
|
||||||
|
long maxRequiredEpoch = Long.MIN_VALUE;
|
||||||
|
for (SinglePartitionReadCommand readCommand : normalReads)
|
||||||
|
{
|
||||||
|
DecoratedKey key = readCommand.partitionKey();
|
||||||
|
KeyMigrationState kms = ConsensusKeyMigrationState.getKeyMigrationState(cm, tms, key);
|
||||||
|
if (!kms.paxosReadSatisfiedByKeyMigration())
|
||||||
|
{
|
||||||
|
if (keysNeedingBarrier == null)
|
||||||
|
keysNeedingBarrier = new ArrayList<>(normalReads.size());
|
||||||
|
keysNeedingBarrier.add(key);
|
||||||
|
maxRequiredEpoch = Math.max(tms.minMigrationEpoch(key.getToken()).getEpoch(), maxRequiredEpoch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keysNeedingBarrier != null)
|
||||||
|
{
|
||||||
|
checkState(!keysNeedingBarrier.isEmpty());
|
||||||
|
checkState(maxRequiredEpoch != Long.MIN_VALUE);
|
||||||
|
// Local barriers don't support multiple keys so create a global one unless there is a single key
|
||||||
|
// See BarrierType enum for explanation of global vs local
|
||||||
|
boolean global = keysNeedingBarrier.size() > 1 ? true : false;
|
||||||
|
ConsensusKeyMigrationState.repairKeysAccord(keysNeedingBarrier, tm.id, maxRequiredEpoch, requestTime, global, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SinglePartitionReadCommand.Group accordGroup = accordReads != null ? SinglePartitionReadCommand.Group.create(accordReads, reads.limits()) : null;
|
||||||
|
SinglePartitionReadCommand.Group normalGroup = normalReads != null ? SinglePartitionReadCommand.Group.create(normalReads, reads.limits()) : null;
|
||||||
|
return new SplitReads(accordGroup, normalGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean tokenShouldBeReadThroughAccord(TableMigrationState tms,
|
||||||
|
@Nonnull Token token,
|
||||||
|
@Nonnull TransactionalMode transactionalMode,
|
||||||
|
TransactionalMigrationFromMode transactionalMigrationFromMode)
|
||||||
|
{
|
||||||
|
boolean transactionalModeReadsThroughAccord = transactionalMode.nonSerialReadsThroughAccord;
|
||||||
|
boolean migrationFromReadsThroughAccord = transactionalMigrationFromMode.nonSerialReadsThroughAccord();
|
||||||
|
|
||||||
|
if (transactionalModeReadsThroughAccord && migrationFromReadsThroughAccord)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Could be migrating or could be completely migrated, if it's migrating check if the key for this mutation
|
||||||
|
if (transactionalModeReadsThroughAccord || migrationFromReadsThroughAccord)
|
||||||
|
{
|
||||||
|
if (tms == null)
|
||||||
|
{
|
||||||
|
if (transactionalMigrationFromMode == TransactionalMigrationFromMode.none)
|
||||||
|
// There is no migration and no TMS so do what the schema says since no migration should be required
|
||||||
|
return transactionalModeReadsThroughAccord;
|
||||||
|
else
|
||||||
|
// If we are migrating from something and there is no migration state the migration hasn't begun
|
||||||
|
// so continue to do what we are migrating from does until the range is marked as migrating
|
||||||
|
return migrationFromReadsThroughAccord;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In theory we can start reading from Accord immediately because we know these transactions are 100%
|
||||||
|
// read only but then that impacts performance more so wait for the range to be completely migrated
|
||||||
|
// when it can potentially do single replica reads
|
||||||
|
if (transactionalModeReadsThroughAccord)
|
||||||
|
return tms.migratedRanges.intersects(token);
|
||||||
|
|
||||||
|
// If we are migrating from a mode that used to write to Accord then any range that isn't migrating/migrated
|
||||||
|
// should continue to write through Accord.
|
||||||
|
// It's not completely symmetrical because Paxos is able to read Accord's writes by performing a single key barrier
|
||||||
|
// and regular mutations will be able to do the same thing (needs to be added along with non-transactional reads)
|
||||||
|
// This means that migrating ranges don't need to be written through Accord because we are running Paxos now
|
||||||
|
// and not Accord. When migrating to Accord we need to do all the writes through Accord even if we aren't
|
||||||
|
// reading through Accord so that repair + Accord metadata is sufficient for Accord to be able to read
|
||||||
|
// safely and deterministically from any coordinator
|
||||||
|
if (migrationFromReadsThroughAccord)
|
||||||
|
return !tms.migratingAndMigratedRanges.intersects(token);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ import org.apache.cassandra.schema.TableId;
|
||||||
import org.apache.cassandra.schema.TableMetadata;
|
import org.apache.cassandra.schema.TableMetadata;
|
||||||
import org.apache.cassandra.service.StorageService;
|
import org.apache.cassandra.service.StorageService;
|
||||||
import org.apache.cassandra.service.accord.TokenRange;
|
import org.apache.cassandra.service.accord.TokenRange;
|
||||||
import org.apache.cassandra.service.consensus.TransactionalMode;
|
|
||||||
import org.apache.cassandra.service.paxos.Paxos;
|
import org.apache.cassandra.service.paxos.Paxos;
|
||||||
import org.apache.cassandra.streaming.PreviewKind;
|
import org.apache.cassandra.streaming.PreviewKind;
|
||||||
import org.apache.cassandra.tcm.ClusterMetadata;
|
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||||
|
|
@ -171,31 +170,26 @@ public abstract class ConsensusTableMigration
|
||||||
return cm.consensusMigrationState.tableStates.get(tableId);
|
return cm.consensusMigrationState.tableStates.get(tableId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void startMigrationToConsensusProtocol(@Nonnull String targetProtocolName,
|
public static void startMigrationToConsensusProtocol(@Nullable List<String> keyspaceNames,
|
||||||
@Nullable List<String> keyspaceNames,
|
|
||||||
@Nonnull Optional<List<String>> maybeTables,
|
@Nonnull Optional<List<String>> maybeTables,
|
||||||
@Nonnull Optional<String> maybeRangesStr)
|
@Nonnull Optional<String> maybeRangesStr)
|
||||||
{
|
{
|
||||||
checkArgument(!maybeTables.isPresent() || !maybeTables.get().isEmpty(), "Must provide at least 1 table if Optional is not empty");
|
checkArgument(!maybeTables.isPresent() || !maybeTables.get().isEmpty(), "Must provide at least 1 table if Optional is not empty");
|
||||||
ConsensusMigrationTarget targetProtocol = ConsensusMigrationTarget.fromString(targetProtocolName);
|
ClusterMetadata cm = ClusterMetadata.current();
|
||||||
|
|
||||||
if (keyspaceNames == null || keyspaceNames.isEmpty())
|
if (keyspaceNames == null || keyspaceNames.isEmpty())
|
||||||
{
|
{
|
||||||
keyspaceNames = ImmutableList.copyOf(StorageService.instance.getNonLocalStrategyKeyspaces());
|
keyspaceNames = ImmutableList.copyOf(StorageService.instance.getNonLocalStrategyKeyspaces());
|
||||||
}
|
}
|
||||||
checkState(keyspaceNames.size() == 1 || !maybeTables.isPresent(), "Can't specify tables with multiple keyspaces");
|
checkState(keyspaceNames.size() == 1 || !maybeTables.isPresent(), "Can't specify tables with multiple keyspaces");
|
||||||
List<TableId> ids = keyspacesAndTablesToTableIds(keyspaceNames, maybeTables);
|
List<TableId> ids = keyspacesAndTablesToTableIds(cm, keyspaceNames, maybeTables);
|
||||||
|
|
||||||
// TODO (review): should this perform the schema change to make these tables accord tables?
|
|
||||||
List<TableId> tableIds = new ArrayList<>();
|
List<TableId> tableIds = new ArrayList<>();
|
||||||
for (TableId tableId : ids)
|
for (TableId tableId : ids)
|
||||||
{
|
{
|
||||||
TableMetadata metadata = Schema.instance.getTableMetadata(tableId);
|
TableMetadata metadata = cm.schema.getTableMetadata(tableId);
|
||||||
if (metadata == null || !metadata.params.transactionalMigrationFrom.isMigrating())
|
if (metadata == null || !metadata.params.transactionalMigrationFrom.isMigrating())
|
||||||
continue;
|
continue;
|
||||||
TransactionalMode transactionalMode = metadata.params.transactionalMode;
|
|
||||||
if (!transactionalMode.nonSerialWritesThroughAccord && transactionalMode != TransactionalMode.unsafe_writes)
|
|
||||||
throw new IllegalStateException("non-SERIAL writes need to be routed through Accord before attempting migration, or enable mixed mode");
|
|
||||||
tableIds.add(tableId);
|
tableIds.add(tableId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,41 +201,41 @@ public abstract class ConsensusTableMigration
|
||||||
Token minToken = partitioner.getMinimumToken();
|
Token minToken = partitioner.getMinimumToken();
|
||||||
NormalizedRanges<Token> ranges = normalizedRanges(maybeParsedRanges.orElse(ImmutableList.of(new Range(minToken, minToken))));
|
NormalizedRanges<Token> ranges = normalizedRanges(maybeParsedRanges.orElse(ImmutableList.of(new Range(minToken, minToken))));
|
||||||
|
|
||||||
|
ClusterMetadataService.instance().commit(new BeginConsensusMigrationForTableAndRange(ranges, tableIds));
|
||||||
ClusterMetadataService.instance().commit(new BeginConsensusMigrationForTableAndRange(targetProtocol, ranges, tableIds));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Integer finishMigrationToConsensusProtocol(@Nonnull String keyspace,
|
public static Integer finishMigrationToConsensusProtocol(@Nonnull String keyspace,
|
||||||
@Nonnull Optional<List<String>> maybeTables,
|
@Nonnull Optional<List<String>> maybeTables,
|
||||||
@Nonnull Optional<String> maybeRangesStr,
|
@Nonnull Optional<String> maybeRangesStr,
|
||||||
ConsensusMigrationTarget target)
|
@Nonnull ConsensusMigrationTarget target)
|
||||||
{
|
{
|
||||||
checkArgument(!maybeTables.isPresent() || !maybeTables.get().isEmpty(), "Must provide at least 1 table if Optional is not empty");
|
checkArgument(!maybeTables.isPresent() || !maybeTables.get().isEmpty(), "Must provide at least 1 table if Optional is not empty");
|
||||||
checkNotNull(target);
|
checkNotNull(target);
|
||||||
|
ClusterMetadata cm = ClusterMetadata.current();
|
||||||
|
|
||||||
Optional<List<Range<Token>>> localKeyspaceRanges = Optional.of(ImmutableList.copyOf(StorageService.instance.getLocalReplicas(keyspace).onlyFull().ranges()));
|
Optional<List<Range<Token>>> localKeyspaceRanges = Optional.of(ImmutableList.copyOf(StorageService.instance.getLocalReplicas(keyspace).onlyFull().ranges()));
|
||||||
List<Range<Token>> ranges = maybeRangesToRanges(maybeRangesStr, localKeyspaceRanges);
|
List<Range<Token>> ranges = maybeRangesToRanges(maybeRangesStr, localKeyspaceRanges);
|
||||||
Map<TableId, TableMigrationState> allTableMigrationStates = ClusterMetadata.current().consensusMigrationState.tableStates;
|
Map<TableId, TableMigrationState> allTableMigrationStates = ClusterMetadata.current().consensusMigrationState.tableStates;
|
||||||
List<TableId> tableIds = keyspacesAndTablesToTableIds(ImmutableList.of(keyspace), maybeTables, Optional.of(allTableMigrationStates::containsKey));
|
List<TableId> tableIds = keyspacesAndTablesToTableIds(cm, ImmutableList.of(keyspace), maybeTables, Optional.of(allTableMigrationStates::containsKey));
|
||||||
|
|
||||||
checkState(tableIds.stream().allMatch(allTableMigrationStates::containsKey), "All tables need to be migrating");
|
checkState(tableIds.stream().allMatch(allTableMigrationStates::containsKey), "All tables need to be migrating");
|
||||||
List<TableMigrationState> tableMigrationStates = new ArrayList<>();
|
List<TableMigrationState> tableMigrationStates = new ArrayList<>();
|
||||||
tableIds.forEach(table -> {
|
tableIds.forEach(table -> {
|
||||||
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(table);
|
TableMetadata tm = cm.schema.getTableMetadata(table);
|
||||||
if (cfs == null)
|
if (tm == null)
|
||||||
{
|
{
|
||||||
logger.warn("Table {} does not exist or was dropped", cfs);
|
logger.warn("Table {} does not exist or was dropped", table);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TableMigrationState tms = allTableMigrationStates.get(table);
|
TableMigrationState tms = allTableMigrationStates.get(table);
|
||||||
if (tms == null)
|
if (tms == null)
|
||||||
{
|
{
|
||||||
logger.warn("Table {} does not have any migration state", cfs.name);
|
logger.warn("Table {} does not have any migration state", tm.name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!Range.intersects(ranges, tms.migratingRanges))
|
if (!Range.intersects(ranges, tms.migratingRanges))
|
||||||
{
|
{
|
||||||
logger.warn("Table {} with migrating ranges {} does not intersect with any requested ranges {}", cfs.name, tms.migratingRanges, ranges);
|
logger.warn("Table {} with migrating ranges {} does not intersect with any requested ranges {}", tm.name, tms.migratingRanges, ranges);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tableMigrationStates.add(tms);
|
tableMigrationStates.add(tms);
|
||||||
|
|
@ -341,12 +335,12 @@ public abstract class ConsensusTableMigration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static List<TableId> keyspacesAndTablesToTableIds(@Nonnull List<String> keyspaceNames, @Nonnull Optional<List<String>> maybeTables)
|
private static List<TableId> keyspacesAndTablesToTableIds(@Nonnull ClusterMetadata cm, @Nonnull List<String> keyspaceNames, @Nonnull Optional<List<String>> maybeTables)
|
||||||
{
|
{
|
||||||
return keyspacesAndTablesToTableIds(keyspaceNames, maybeTables, Optional.empty());
|
return keyspacesAndTablesToTableIds(cm, keyspaceNames, maybeTables, Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<TableId> keyspacesAndTablesToTableIds(@Nonnull List<String> keyspaceNames, @Nonnull Optional<List<String>> maybeTables, @Nonnull Optional<Predicate<TableId>> includeTable)
|
private static List<TableId> keyspacesAndTablesToTableIds(@Nonnull ClusterMetadata cm, @Nonnull List<String> keyspaceNames, @Nonnull Optional<List<String>> maybeTables, @Nonnull Optional<Predicate<TableId>> includeTable)
|
||||||
{
|
{
|
||||||
List<TableId> tableIds = new ArrayList<>();
|
List<TableId> tableIds = new ArrayList<>();
|
||||||
for (String keyspaceName : keyspaceNames)
|
for (String keyspaceName : keyspaceNames)
|
||||||
|
|
@ -355,7 +349,7 @@ public abstract class ConsensusTableMigration
|
||||||
tableNames
|
tableNames
|
||||||
.stream()
|
.stream()
|
||||||
.map(tableName -> {
|
.map(tableName -> {
|
||||||
TableMetadata tm = Schema.instance.getTableMetadata(keyspaceName, tableName);
|
TableMetadata tm = cm.schema.getTableMetadata(keyspaceName, tableName);
|
||||||
if (tm == null)
|
if (tm == null)
|
||||||
throw new IllegalArgumentException(format("Unknown table %s.%s", keyspaceName, tableName));
|
throw new IllegalArgumentException(format("Unknown table %s.%s", keyspaceName, tableName));
|
||||||
return tm.id;
|
return tm.id;
|
||||||
|
|
@ -363,7 +357,7 @@ public abstract class ConsensusTableMigration
|
||||||
.collect(toImmutableList()));
|
.collect(toImmutableList()));
|
||||||
tableIds.addAll(
|
tableIds.addAll(
|
||||||
maybeTableIds.orElseGet(() ->
|
maybeTableIds.orElseGet(() ->
|
||||||
Schema.instance.getKeyspaceInstance(keyspaceName).getColumnFamilyStores()
|
cm.schema.getKeyspace(keyspaceName).getColumnFamilyStores()
|
||||||
.stream()
|
.stream()
|
||||||
.map(ColumnFamilyStore::getTableId)
|
.map(ColumnFamilyStore::getTableId)
|
||||||
.filter(includeTable.orElse(Predicates.alwaysTrue())) // Filter out non-migrating so they don't generate an error
|
.filter(includeTable.orElse(Predicates.alwaysTrue())) // Filter out non-migrating so they don't generate an error
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,14 @@ import javax.annotation.Nullable;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSortedMap;
|
import com.google.common.collect.ImmutableSortedMap;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
import org.apache.cassandra.db.DecoratedKey;
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
|
import org.apache.cassandra.db.PartitionPosition;
|
||||||
import org.apache.cassandra.dht.IPartitioner;
|
import org.apache.cassandra.dht.IPartitioner;
|
||||||
import org.apache.cassandra.dht.NormalizedRanges;
|
import org.apache.cassandra.dht.NormalizedRanges;
|
||||||
import org.apache.cassandra.dht.Range;
|
import org.apache.cassandra.dht.Range;
|
||||||
|
|
@ -58,8 +60,8 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||||
import static org.apache.cassandra.db.TypeSizes.sizeof;
|
import static org.apache.cassandra.db.TypeSizes.sizeof;
|
||||||
import static org.apache.cassandra.dht.Range.normalize;
|
|
||||||
import static org.apache.cassandra.dht.NormalizedRanges.normalizedRanges;
|
import static org.apache.cassandra.dht.NormalizedRanges.normalizedRanges;
|
||||||
|
import static org.apache.cassandra.dht.Range.normalize;
|
||||||
import static org.apache.cassandra.dht.Range.subtract;
|
import static org.apache.cassandra.dht.Range.subtract;
|
||||||
import static org.apache.cassandra.utils.CollectionSerializers.deserializeMap;
|
import static org.apache.cassandra.utils.CollectionSerializers.deserializeMap;
|
||||||
import static org.apache.cassandra.utils.CollectionSerializers.deserializeSet;
|
import static org.apache.cassandra.utils.CollectionSerializers.deserializeSet;
|
||||||
|
|
@ -119,11 +121,19 @@ public class TableMigrationState
|
||||||
public final NormalizedRanges<Token> repairPendingRanges;
|
public final NormalizedRanges<Token> repairPendingRanges;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ranges that are migrating could be in either phase when migrating to Accord. PAxos only has one phase.
|
* Ranges that are migrating could be in either phase when migrating to Accord. Paxos only has one phase.
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public final NormalizedRanges<Token> migratingAndMigratedRanges;
|
public final NormalizedRanges<Token> migratingAndMigratedRanges;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as migratingAndMigratedRanges if migrating to Paxos, otherwise migratingAndMigratedRanges.subtract(repairPendingRanges)
|
||||||
|
*
|
||||||
|
* Not included in equals or hashCode because it is inferred from other fields
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public final NormalizedRanges<Token> accordSafeToReadRanges;
|
||||||
|
|
||||||
public TableMigrationState(@Nonnull String keyspaceName,
|
public TableMigrationState(@Nonnull String keyspaceName,
|
||||||
@Nonnull String tableName,
|
@Nonnull String tableName,
|
||||||
@Nonnull TableId tableId,
|
@Nonnull TableId tableId,
|
||||||
|
|
@ -145,6 +155,7 @@ public class TableMigrationState
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
this.migratingRanges = normalizedRanges(migratingRangesByEpoch.values().stream().flatMap(Collection::stream).collect(Collectors.toList()));
|
this.migratingRanges = normalizedRanges(migratingRangesByEpoch.values().stream().flatMap(Collection::stream).collect(Collectors.toList()));
|
||||||
this.migratingAndMigratedRanges = normalizedRanges(ImmutableList.<Range<Token>>builder().addAll(migratedRanges).addAll(migratingRanges).build());
|
this.migratingAndMigratedRanges = normalizedRanges(ImmutableList.<Range<Token>>builder().addAll(migratedRanges).addAll(migratingRanges).build());
|
||||||
|
this.accordSafeToReadRanges = !repairPendingRanges.isEmpty() ? migratingAndMigratedRanges.subtract(this.repairPendingRanges) : migratingAndMigratedRanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<Range<Token>> initialRepairPendingRanges(ConsensusMigrationTarget target, List<Range<Token>> initialMigratingRanges)
|
static List<Range<Token>> initialRepairPendingRanges(ConsensusMigrationTarget target, List<Range<Token>> initialMigratingRanges)
|
||||||
|
|
@ -415,6 +426,11 @@ public class TableMigrationState
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public Iterable<Range<PartitionPosition>> migratedRangesAsPartitionPosition()
|
||||||
|
{
|
||||||
|
return Iterables.transform(migratedRanges, range -> new Range<>(range.left.maxKeyBound(), range.right.maxKeyBound()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,10 @@ public enum TransactionalMigrationFromMode
|
||||||
{
|
{
|
||||||
none(null), // No migration is in progress. The currently active transaction system could be either Accord or Paxos.
|
none(null), // No migration is in progress. The currently active transaction system could be either Accord or Paxos.
|
||||||
off(TransactionalMode.off),
|
off(TransactionalMode.off),
|
||||||
unsafe(TransactionalMode.unsafe),
|
|
||||||
unsafe_writes(TransactionalMode.unsafe_writes),
|
|
||||||
mixed_reads(TransactionalMode.mixed_reads),
|
mixed_reads(TransactionalMode.mixed_reads),
|
||||||
full(TransactionalMode.full),
|
full(TransactionalMode.full),
|
||||||
|
test_unsafe(TransactionalMode.test_unsafe),
|
||||||
|
test_unsafe_writes(TransactionalMode.test_unsafe_writes),
|
||||||
test_interop_read(TransactionalMode.test_interop_read);
|
test_interop_read(TransactionalMode.test_interop_read);
|
||||||
|
|
||||||
public final TransactionalMode from;
|
public final TransactionalMode from;
|
||||||
|
|
@ -52,11 +52,11 @@ public enum TransactionalMigrationFromMode
|
||||||
{
|
{
|
||||||
default: throw new IllegalArgumentException();
|
default: throw new IllegalArgumentException();
|
||||||
case off: return off;
|
case off: return off;
|
||||||
case unsafe: return unsafe;
|
|
||||||
case unsafe_writes: return unsafe_writes;
|
|
||||||
case mixed_reads: return mixed_reads;
|
case mixed_reads: return mixed_reads;
|
||||||
case full: return full;
|
case full: return full;
|
||||||
case test_interop_read: return test_interop_read;
|
case test_interop_read: return test_interop_read;
|
||||||
|
case test_unsafe: return test_unsafe;
|
||||||
|
case test_unsafe_writes: return test_unsafe_writes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import com.google.common.base.Objects;
|
||||||
|
|
||||||
import org.apache.cassandra.db.DecoratedKey;
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
import org.apache.cassandra.db.Mutation;
|
import org.apache.cassandra.db.Mutation;
|
||||||
|
import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
|
||||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||||
import org.apache.cassandra.db.rows.DeserializationHelper;
|
import org.apache.cassandra.db.rows.DeserializationHelper;
|
||||||
import org.apache.cassandra.io.IVersionedSerializer;
|
import org.apache.cassandra.io.IVersionedSerializer;
|
||||||
|
|
@ -314,7 +315,7 @@ public class Commit
|
||||||
|
|
||||||
public Mutation makeMutation()
|
public Mutation makeMutation()
|
||||||
{
|
{
|
||||||
return new Mutation(update, true);
|
return new Mutation(update, PotentialTxnConflicts.ALLOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ public abstract class AbstractReadExecutor
|
||||||
{
|
{
|
||||||
boolean hasLocalEndpoint = false;
|
boolean hasLocalEndpoint = false;
|
||||||
Message<ReadCommand> message = null;
|
Message<ReadCommand> message = null;
|
||||||
readCommand = coordinator.maybeAllowOutOfRangeReads(readCommand);
|
readCommand = coordinator.maybeAllowOutOfRangeReads(readCommand, replicaPlan().consistencyLevel());
|
||||||
|
|
||||||
for (Replica replica: replicas)
|
for (Replica replica: replicas)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|
@ -32,10 +33,12 @@ import org.apache.cassandra.db.MessageParams;
|
||||||
import org.apache.cassandra.db.PartitionRangeReadCommand;
|
import org.apache.cassandra.db.PartitionRangeReadCommand;
|
||||||
import org.apache.cassandra.db.ReadCommand;
|
import org.apache.cassandra.db.ReadCommand;
|
||||||
import org.apache.cassandra.db.ReadResponse;
|
import org.apache.cassandra.db.ReadResponse;
|
||||||
|
import org.apache.cassandra.exceptions.CoordinatorBehindException;
|
||||||
import org.apache.cassandra.exceptions.ReadFailureException;
|
import org.apache.cassandra.exceptions.ReadFailureException;
|
||||||
import org.apache.cassandra.exceptions.ReadTimeoutException;
|
import org.apache.cassandra.exceptions.ReadTimeoutException;
|
||||||
import org.apache.cassandra.exceptions.RequestFailure;
|
import org.apache.cassandra.exceptions.RequestFailure;
|
||||||
import org.apache.cassandra.exceptions.RequestFailureReason;
|
import org.apache.cassandra.exceptions.RequestFailureReason;
|
||||||
|
import org.apache.cassandra.exceptions.RetryOnDifferentSystemException;
|
||||||
import org.apache.cassandra.locator.Endpoints;
|
import org.apache.cassandra.locator.Endpoints;
|
||||||
import org.apache.cassandra.locator.InetAddressAndPort;
|
import org.apache.cassandra.locator.InetAddressAndPort;
|
||||||
import org.apache.cassandra.locator.ReplicaPlan;
|
import org.apache.cassandra.locator.ReplicaPlan;
|
||||||
|
|
@ -54,6 +57,8 @@ import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater;
|
import static java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater;
|
||||||
|
import static org.apache.cassandra.exceptions.RequestFailureReason.COORDINATOR_BEHIND;
|
||||||
|
import static org.apache.cassandra.exceptions.RequestFailureReason.RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM;
|
||||||
import static org.apache.cassandra.tracing.Tracing.isTracing;
|
import static org.apache.cassandra.tracing.Tracing.isTracing;
|
||||||
import static org.apache.cassandra.utils.concurrent.Condition.newOneTimeCondition;
|
import static org.apache.cassandra.utils.concurrent.Condition.newOneTimeCondition;
|
||||||
|
|
||||||
|
|
@ -173,6 +178,34 @@ public class ReadCallback<E extends Endpoints<E>, P extends ReplicaPlan.ForRead<
|
||||||
if (snapshot != null)
|
if (snapshot != null)
|
||||||
snapshot.maybeAbort(command, replicaPlan().consistencyLevel(), received, replicaPlan().readQuorum(), resolver.isDataPresent(), failureReasonByEndpoint);
|
snapshot.maybeAbort(command, replicaPlan().consistencyLevel(), received, replicaPlan().readQuorum(), resolver.isDataPresent(), failureReasonByEndpoint);
|
||||||
|
|
||||||
|
// failures keeps incrementing, and this.failureReasonByEndpoint keeps getting new entries after signaling.
|
||||||
|
// Simpler to reason about what happened by copying this.failureReasonByEndpoint and then inferring
|
||||||
|
// failures from it
|
||||||
|
final Map<InetAddressAndPort, RequestFailureReason> failureReasonByEndpoint = ImmutableMap.copyOf(this.failureReasonByEndpoint);
|
||||||
|
int transactionRetryErrors = 0;
|
||||||
|
int coordinatorBehindErrors = 0;
|
||||||
|
for (RequestFailureReason reason : failureReasonByEndpoint.values())
|
||||||
|
{
|
||||||
|
if (reason == RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM)
|
||||||
|
transactionRetryErrors++;
|
||||||
|
if (reason == COORDINATOR_BEHIND)
|
||||||
|
coordinatorBehindErrors++;
|
||||||
|
}
|
||||||
|
int totalRetriableFailures = transactionRetryErrors + coordinatorBehindErrors;
|
||||||
|
|
||||||
|
// TODO (nicetohave): This could be smarter and check if retrying would succeed instead of pessimistically
|
||||||
|
// failing unless all errors are retriable
|
||||||
|
if (!timedout && totalRetriableFailures > 0 && totalRetriableFailures == failureReasonByEndpoint.size())
|
||||||
|
{
|
||||||
|
// Doesn't matter which we throw really but for clarity/metrics be specific
|
||||||
|
// Retrying on the correct system might make this write succeed
|
||||||
|
if (transactionRetryErrors > 0)
|
||||||
|
throw new RetryOnDifferentSystemException();
|
||||||
|
if (coordinatorBehindErrors > 0)
|
||||||
|
throw new CoordinatorBehindException("Read request failed due to coordinator behind");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Same as for writes, see AbstractWriteResponseHandler
|
// Same as for writes, see AbstractWriteResponseHandler
|
||||||
throw !timedout
|
throw !timedout
|
||||||
? new ReadFailureException(replicaPlan().consistencyLevel(), received, replicaPlan().readQuorum(), resolver.isDataPresent(), failureReasonByEndpoint)
|
? new ReadFailureException(replicaPlan().consistencyLevel(), received, replicaPlan().readQuorum(), resolver.isDataPresent(), failureReasonByEndpoint)
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,10 @@
|
||||||
|
|
||||||
package org.apache.cassandra.service.reads;
|
package org.apache.cassandra.service.reads;
|
||||||
|
|
||||||
|
import org.apache.cassandra.db.ConsistencyLevel;
|
||||||
import org.apache.cassandra.db.Mutation;
|
import org.apache.cassandra.db.Mutation;
|
||||||
import org.apache.cassandra.db.ReadCommand;
|
import org.apache.cassandra.db.ReadCommand;
|
||||||
|
import org.apache.cassandra.db.ReadCommand.PotentialTxnConflicts;
|
||||||
import org.apache.cassandra.db.ReadResponse;
|
import org.apache.cassandra.db.ReadResponse;
|
||||||
import org.apache.cassandra.dht.Token;
|
import org.apache.cassandra.dht.Token;
|
||||||
import org.apache.cassandra.locator.EndpointsForToken;
|
import org.apache.cassandra.locator.EndpointsForToken;
|
||||||
|
|
@ -64,16 +66,16 @@ public interface ReadCoordinator
|
||||||
|
|
||||||
boolean localReadSupported();
|
boolean localReadSupported();
|
||||||
EndpointsForToken forNonLocalStrategyTokenRead(ClusterMetadata metadata, KeyspaceMetadata keyspace, TableId tableId, Token token);
|
EndpointsForToken forNonLocalStrategyTokenRead(ClusterMetadata metadata, KeyspaceMetadata keyspace, TableId tableId, Token token);
|
||||||
default ReadCommand maybeAllowOutOfRangeReads(ReadCommand command)
|
default ReadCommand maybeAllowOutOfRangeReads(ReadCommand command, ConsistencyLevel cl)
|
||||||
{
|
{
|
||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
void sendReadCommand(Message<ReadCommand> message, InetAddressAndPort to, RequestCallback<ReadResponse> callback);
|
void sendReadCommand(Message<ReadCommand> message, InetAddressAndPort to, RequestCallback<ReadResponse> callback);
|
||||||
default void notifyOfInitialContacts(EndpointsForToken fullDataRequests, EndpointsForToken transientRequests, EndpointsForToken digestRequests) {}
|
default void notifyOfInitialContacts(EndpointsForToken fullDataRequests, EndpointsForToken transientRequests, EndpointsForToken digestRequests) {}
|
||||||
void sendReadRepairMutation(Message<Mutation> message, InetAddressAndPort to, RequestCallback<Object> callback);
|
void sendReadRepairMutation(Message<Mutation> message, InetAddressAndPort to, RequestCallback<Object> callback);
|
||||||
default boolean allowsPotentialTransactionConflicts()
|
default PotentialTxnConflicts potentialTxnConflicts()
|
||||||
{
|
{
|
||||||
return !isEventuallyConsistent();
|
return isEventuallyConsistent() ? PotentialTxnConflicts.DISALLOW : PotentialTxnConflicts.ALLOW;
|
||||||
}
|
}
|
||||||
boolean isEventuallyConsistent();
|
boolean isEventuallyConsistent();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ public class ReplicaFilteringProtection<E extends Endpoints<E>>
|
||||||
|
|
||||||
ReadCallback<EndpointsForToken, ReplicaPlan.ForTokenRead> handler = new ReadCallback<>(resolver, cmd, replicaPlan, requestTime);
|
ReadCallback<EndpointsForToken, ReplicaPlan.ForTokenRead> handler = new ReadCallback<>(resolver, cmd, replicaPlan, requestTime);
|
||||||
|
|
||||||
if (source.isSelf())
|
if (source.isSelf() && coordinator.localReadSupported())
|
||||||
{
|
{
|
||||||
Stage.READ.maybeExecuteImmediately(new StorageProxy.LocalReadRunnable(cmd, handler, requestTime));
|
Stage.READ.maybeExecuteImmediately(new StorageProxy.LocalReadRunnable(cmd, handler, requestTime));
|
||||||
}
|
}
|
||||||
|
|
@ -164,6 +164,7 @@ public class ReplicaFilteringProtection<E extends Endpoints<E>>
|
||||||
{
|
{
|
||||||
if (source.isTransient())
|
if (source.isTransient())
|
||||||
cmd = cmd.copyAsTransientQuery(source);
|
cmd = cmd.copyAsTransientQuery(source);
|
||||||
|
cmd = coordinator.maybeAllowOutOfRangeReads(cmd, consistency);
|
||||||
MessagingService.instance().sendWithCallback(cmd.createMessage(false, requestTime), source.endpoint(), handler);
|
MessagingService.instance().sendWithCallback(cmd.createMessage(false, requestTime), source.endpoint(), handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ public class ShortReadPartitionsProtection extends Transformation<UnfilteredRowI
|
||||||
private <E extends Endpoints<E>, P extends ReplicaPlan.ForRead<E, P>>
|
private <E extends Endpoints<E>, P extends ReplicaPlan.ForRead<E, P>>
|
||||||
UnfilteredPartitionIterator executeReadCommand(ReadCommand cmd, ReplicaPlan.Shared<E, P> replicaPlan)
|
UnfilteredPartitionIterator executeReadCommand(ReadCommand cmd, ReplicaPlan.Shared<E, P> replicaPlan)
|
||||||
{
|
{
|
||||||
cmd = coordinator.maybeAllowOutOfRangeReads(cmd);
|
cmd = coordinator.maybeAllowOutOfRangeReads(cmd, replicaPlan.get().consistencyLevel());
|
||||||
DataResolver<E, P> resolver = new DataResolver<>(coordinator, cmd, replicaPlan, (NoopReadRepair<E, P>)NoopReadRepair.instance, requestTime);
|
DataResolver<E, P> resolver = new DataResolver<>(coordinator, cmd, replicaPlan, (NoopReadRepair<E, P>)NoopReadRepair.instance, requestTime);
|
||||||
ReadCallback<E, P> handler = new ReadCallback<>(resolver, cmd, replicaPlan, requestTime);
|
ReadCallback<E, P> handler = new ReadCallback<>(resolver, cmd, replicaPlan, requestTime);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,15 +23,14 @@ import java.util.function.IntPredicate;
|
||||||
import org.apache.cassandra.db.ConsistencyLevel;
|
import org.apache.cassandra.db.ConsistencyLevel;
|
||||||
import org.apache.cassandra.db.partitions.PartitionIterator;
|
import org.apache.cassandra.db.partitions.PartitionIterator;
|
||||||
import org.apache.cassandra.db.rows.RowIterator;
|
import org.apache.cassandra.db.rows.RowIterator;
|
||||||
|
import org.apache.cassandra.exceptions.RetryOnDifferentSystemException;
|
||||||
import org.apache.cassandra.service.StorageProxy;
|
import org.apache.cassandra.service.StorageProxy;
|
||||||
import org.apache.cassandra.service.StorageProxy.ConsensusAttemptResult;
|
import org.apache.cassandra.service.StorageProxy.ConsensusAttemptResult;
|
||||||
import org.apache.cassandra.service.accord.IAccordService.AsyncTxnResult;
|
import org.apache.cassandra.service.accord.IAccordService.AsyncTxnResult;
|
||||||
import org.apache.cassandra.transport.Dispatcher;
|
import org.apache.cassandra.transport.Dispatcher;
|
||||||
import org.apache.cassandra.utils.AbstractIterator;
|
import org.apache.cassandra.utils.AbstractIterator;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
public class AccordRangeResponse extends AbstractIterator<RowIterator> implements PartitionIterator
|
||||||
|
|
||||||
public class AccordRangeResponse extends AbstractIterator<RowIterator> implements IRangeResponse
|
|
||||||
{
|
{
|
||||||
private final AsyncTxnResult asyncTxnResult;
|
private final AsyncTxnResult asyncTxnResult;
|
||||||
// Range queries don't support reverse, but dutifully threading it through anyways
|
// Range queries don't support reverse, but dutifully threading it through anyways
|
||||||
|
|
@ -55,8 +54,9 @@ public class AccordRangeResponse extends AbstractIterator<RowIterator> implement
|
||||||
IntPredicate alwaysTrue = ignored -> true;
|
IntPredicate alwaysTrue = ignored -> true;
|
||||||
IntPredicate alwaysFalse = ignored -> false;
|
IntPredicate alwaysFalse = ignored -> false;
|
||||||
// TODO (required): Handle retry on different system
|
// TODO (required): Handle retry on different system
|
||||||
ConsensusAttemptResult consensusAttemptResult = StorageProxy.getConsensusAttemptResultFromAsyncTxnResult(asyncTxnResult, 1, reversed ? alwaysTrue : alwaysFalse, cl, requestTime);
|
ConsensusAttemptResult consensusAttemptResult = StorageProxy.getConsensusAttemptResultFromAsyncTxnResult(asyncTxnResult, 1, reversed ? alwaysTrue : alwaysFalse);
|
||||||
checkState(!consensusAttemptResult.shouldRetryOnNewConsensusProtocol, "Live migration is not supported yet");
|
if (consensusAttemptResult.shouldRetryOnNewConsensusProtocol)
|
||||||
|
throw new RetryOnDifferentSystemException();
|
||||||
result = consensusAttemptResult.serialReadResult;
|
result = consensusAttemptResult.serialReadResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,37 +21,40 @@ package org.apache.cassandra.service.reads.range;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import org.apache.cassandra.concurrent.Stage;
|
import org.apache.cassandra.concurrent.Stage;
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
|
import org.apache.cassandra.db.ConsistencyLevel;
|
||||||
import org.apache.cassandra.db.Keyspace;
|
import org.apache.cassandra.db.Keyspace;
|
||||||
import org.apache.cassandra.db.PartitionRangeReadCommand;
|
import org.apache.cassandra.db.PartitionRangeReadCommand;
|
||||||
import org.apache.cassandra.db.ReadCommand;
|
import org.apache.cassandra.db.ReadCommand;
|
||||||
import org.apache.cassandra.db.filter.DataLimits;
|
import org.apache.cassandra.db.filter.DataLimits;
|
||||||
import org.apache.cassandra.db.partitions.PartitionIterator;
|
import org.apache.cassandra.db.partitions.PartitionIterator;
|
||||||
|
import org.apache.cassandra.db.partitions.PartitionIterators;
|
||||||
import org.apache.cassandra.db.rows.RowIterator;
|
import org.apache.cassandra.db.rows.RowIterator;
|
||||||
|
import org.apache.cassandra.exceptions.CoordinatorBehindException;
|
||||||
import org.apache.cassandra.exceptions.ReadAbortException;
|
import org.apache.cassandra.exceptions.ReadAbortException;
|
||||||
import org.apache.cassandra.exceptions.ReadFailureException;
|
import org.apache.cassandra.exceptions.ReadFailureException;
|
||||||
import org.apache.cassandra.exceptions.ReadTimeoutException;
|
import org.apache.cassandra.exceptions.ReadTimeoutException;
|
||||||
|
import org.apache.cassandra.exceptions.RetryOnDifferentSystemException;
|
||||||
import org.apache.cassandra.exceptions.UnavailableException;
|
import org.apache.cassandra.exceptions.UnavailableException;
|
||||||
import org.apache.cassandra.locator.EndpointsForRange;
|
import org.apache.cassandra.locator.EndpointsForRange;
|
||||||
import org.apache.cassandra.locator.Replica;
|
import org.apache.cassandra.locator.Replica;
|
||||||
import org.apache.cassandra.locator.ReplicaPlan;
|
import org.apache.cassandra.locator.ReplicaPlan;
|
||||||
import org.apache.cassandra.metrics.ClientRangeRequestMetrics;
|
import org.apache.cassandra.metrics.ClientRangeRequestMetrics;
|
||||||
import org.apache.cassandra.net.Message;
|
import org.apache.cassandra.net.Message;
|
||||||
import org.apache.cassandra.net.MessagingService;
|
|
||||||
import org.apache.cassandra.schema.TableMetadata;
|
|
||||||
import org.apache.cassandra.schema.TableParams;
|
|
||||||
import org.apache.cassandra.service.StorageProxy;
|
import org.apache.cassandra.service.StorageProxy;
|
||||||
import org.apache.cassandra.service.accord.IAccordService.AsyncTxnResult;
|
import org.apache.cassandra.service.accord.IAccordService.AsyncTxnResult;
|
||||||
import org.apache.cassandra.service.consensus.TransactionalMode;
|
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter;
|
||||||
import org.apache.cassandra.service.consensus.migration.TransactionalMigrationFromMode;
|
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.RangeReadTarget;
|
||||||
|
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.RangeReadWithTarget;
|
||||||
import org.apache.cassandra.service.reads.DataResolver;
|
import org.apache.cassandra.service.reads.DataResolver;
|
||||||
import org.apache.cassandra.service.reads.ReadCallback;
|
import org.apache.cassandra.service.reads.ReadCallback;
|
||||||
import org.apache.cassandra.service.reads.ReadCoordinator;
|
import org.apache.cassandra.service.reads.ReadCoordinator;
|
||||||
|
|
@ -62,6 +65,9 @@ import org.apache.cassandra.transport.Dispatcher;
|
||||||
import org.apache.cassandra.utils.AbstractIterator;
|
import org.apache.cassandra.utils.AbstractIterator;
|
||||||
import org.apache.cassandra.utils.CloseableIterator;
|
import org.apache.cassandra.utils.CloseableIterator;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.readMetrics;
|
||||||
|
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.readMetricsForLevel;
|
||||||
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
|
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|
@ -187,6 +193,47 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
|
||||||
return concurrencyFactor;
|
return concurrencyFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PartitionIterator executeAccord(ClusterMetadata cm, PartitionRangeReadCommand rangeCommand, ConsistencyLevel cl)
|
||||||
|
{
|
||||||
|
//TODO (nicetohave): https://issues.apache.org/jira/browse/CASSANDRA-20210 More efficient reads across command stores
|
||||||
|
AsyncTxnResult result = StorageProxy.readWithAccord(cm, rangeCommand, rangeCommand.dataRange().keyRange(), cl, requestTime);
|
||||||
|
return new AccordRangeResponse(result, rangeCommand.isReversed(), cl, requestTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SingleRangeResponse executeNormal(ReplicaPlan.ForRangeRead replicaPlan, PartitionRangeReadCommand rangeCommand, ReadCoordinator readCoordinator)
|
||||||
|
{
|
||||||
|
rangeCommand = (PartitionRangeReadCommand) readCoordinator.maybeAllowOutOfRangeReads(rangeCommand, replicaPlan.consistencyLevel());
|
||||||
|
// If enabled, request repaired data tracking info from full replicas, but
|
||||||
|
// only if there are multiple full replicas to compare results from.
|
||||||
|
boolean trackRepairedStatus = DatabaseDescriptor.getRepairedDataTrackingForRangeReadsEnabled()
|
||||||
|
&& replicaPlan.contacts().filter(Replica::isFull).size() > 1;
|
||||||
|
|
||||||
|
ReplicaPlan.SharedForRangeRead sharedReplicaPlan = ReplicaPlan.shared(replicaPlan);
|
||||||
|
ReadRepair<EndpointsForRange, ReplicaPlan.ForRangeRead> readRepair =
|
||||||
|
ReadRepair.create(readCoordinator, command, sharedReplicaPlan, requestTime);
|
||||||
|
DataResolver<EndpointsForRange, ReplicaPlan.ForRangeRead> resolver =
|
||||||
|
new DataResolver<>(readCoordinator, rangeCommand, sharedReplicaPlan, readRepair, requestTime, trackRepairedStatus);
|
||||||
|
ReadCallback<EndpointsForRange, ReplicaPlan.ForRangeRead> handler =
|
||||||
|
new ReadCallback<>(resolver, rangeCommand, sharedReplicaPlan, requestTime);
|
||||||
|
|
||||||
|
if (replicaPlan.contacts().size() == 1 && replicaPlan.contacts().get(0).isSelf() && readCoordinator.localReadSupported())
|
||||||
|
{
|
||||||
|
Stage.READ.execute(new StorageProxy.LocalReadRunnable(rangeCommand, handler, requestTime, trackRepairedStatus));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (Replica replica : replicaPlan.contacts())
|
||||||
|
{
|
||||||
|
Tracing.trace("Enqueuing request to {}", replica);
|
||||||
|
ReadCommand command = replica.isFull() ? rangeCommand : rangeCommand.copyAsTransientQuery(replica);
|
||||||
|
Message<ReadCommand> message = command.createMessage(trackRepairedStatus && replica.isFull(), requestTime);
|
||||||
|
readCoordinator.sendReadCommand(message, replica.endpoint(), handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new SingleRangeResponse(resolver, handler, readRepair);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queries the provided sub-range.
|
* Queries the provided sub-range.
|
||||||
*
|
*
|
||||||
|
|
@ -196,56 +243,88 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
|
||||||
* {@code DataLimits}) may have "state" information and that state may only be valid for the first query (in
|
* {@code DataLimits}) may have "state" information and that state may only be valid for the first query (in
|
||||||
* that it's the query that "continues" whatever we're previously queried).
|
* that it's the query that "continues" whatever we're previously queried).
|
||||||
*/
|
*/
|
||||||
private IRangeResponse query(ReplicaPlan.ForRangeRead replicaPlan, ReadCoordinator readCoordinator, boolean isFirst)
|
private PartitionIterator query(ClusterMetadata cm, ReplicaPlan.ForRangeRead replicaPlan, ReadCoordinator readCoordinator, List<ReadRepair<?, ?>> readRepairs, boolean isFirst)
|
||||||
{
|
{
|
||||||
PartitionRangeReadCommand rangeCommand = command.forSubRange(replicaPlan.range(), isFirst);
|
PartitionRangeReadCommand rangeCommand = command.forSubRange(replicaPlan.range(), isFirst);
|
||||||
|
|
||||||
// If enabled, request repaired data tracking info from full replicas, but
|
|
||||||
// only if there are multiple full replicas to compare results from.
|
|
||||||
boolean trackRepairedStatus = DatabaseDescriptor.getRepairedDataTrackingForRangeReadsEnabled()
|
|
||||||
&& replicaPlan.contacts().filter(Replica::isFull).size() > 1;
|
|
||||||
|
|
||||||
ClusterMetadata cm = ClusterMetadata.current();
|
// Accord interop execution should always be coordinated through the C* plumbing
|
||||||
TableMetadata metadata = command.metadata();
|
if (!readCoordinator.isEventuallyConsistent())
|
||||||
TableParams tableParams = metadata.params;
|
|
||||||
TransactionalMode transactionalMode = tableParams.transactionalMode;
|
|
||||||
TransactionalMigrationFromMode transactionalMigrationFromMode = tableParams.transactionalMigrationFrom;
|
|
||||||
if (transactionalMigrationFromMode != TransactionalMigrationFromMode.none && transactionalMode.nonSerialReadsThroughAccord && transactionalMigrationFromMode.nonSerialWritesThroughAccord() && transactionalMigrationFromMode.nonSerialReadsThroughAccord())
|
|
||||||
throw new UnsupportedOperationException("Live migration is not supported, can't safely read when migrating from " + transactionalMigrationFromMode + " to " + transactionalMode);
|
|
||||||
if (transactionalMode.nonSerialReadsThroughAccord && readCoordinator.isEventuallyConsistent())
|
|
||||||
{
|
{
|
||||||
//TODO (nicetohave): This is very inefficient because it will not map the the command store owned ranges
|
SingleRangeResponse response = executeNormal(replicaPlan, rangeCommand, readCoordinator);
|
||||||
// so every command store will return results and most will be discarded due to the limit
|
readRepairs.add(response.getReadRepair());
|
||||||
// Really we want to split the ranges by command stores owned ranges and then query one at a time
|
return response;
|
||||||
AsyncTxnResult result = StorageProxy.readWithAccord(cm, rangeCommand, ImmutableList.of(rangeCommand.dataRange().keyRange()), replicaPlan.consistencyLevel(), requestTime);
|
|
||||||
return new AccordRangeResponse(result, rangeCommand.isReversed(), replicaPlan.consistencyLevel(), requestTime);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
ReplicaPlan.SharedForRangeRead sharedReplicaPlan = ReplicaPlan.shared(replicaPlan);
|
|
||||||
ReadRepair<EndpointsForRange, ReplicaPlan.ForRangeRead> readRepair =
|
|
||||||
ReadRepair.create(ReadCoordinator.DEFAULT, command, sharedReplicaPlan, requestTime);
|
|
||||||
DataResolver<EndpointsForRange, ReplicaPlan.ForRangeRead> resolver =
|
|
||||||
new DataResolver<>(ReadCoordinator.DEFAULT, rangeCommand, sharedReplicaPlan, readRepair, requestTime, trackRepairedStatus);
|
|
||||||
ReadCallback<EndpointsForRange, ReplicaPlan.ForRangeRead> handler =
|
|
||||||
new ReadCallback<>(resolver, rangeCommand, sharedReplicaPlan, requestTime);
|
|
||||||
|
|
||||||
if (replicaPlan.contacts().size() == 1 && replicaPlan.contacts().get(0).isSelf())
|
List<RangeReadWithTarget> reads = ConsensusRequestRouter.splitReadIntoAccordAndNormal(cm, rangeCommand, readCoordinator, requestTime);
|
||||||
|
// Special case returning directly to avoid wrapping the iterator and applying the limits an extra time
|
||||||
|
if (reads.size() == 1)
|
||||||
|
{
|
||||||
|
RangeReadWithTarget rangeReadWithTarget = reads.get(0);
|
||||||
|
checkState(rangeReadWithTarget.read.dataRange().keyRange().equals(rangeCommand.dataRange().keyRange()));
|
||||||
|
if (rangeReadWithTarget.target == RangeReadTarget.accord && readCoordinator.isEventuallyConsistent())
|
||||||
{
|
{
|
||||||
Stage.READ.execute(new StorageProxy.LocalReadRunnable(rangeCommand, handler, requestTime, trackRepairedStatus));
|
return executeAccord(cm,
|
||||||
|
rangeReadWithTarget.read,
|
||||||
|
replicaPlan.consistencyLevel());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (Replica replica : replicaPlan.contacts())
|
SingleRangeResponse response = executeNormal(replicaPlan, rangeReadWithTarget.read, readCoordinator);
|
||||||
{
|
readRepairs.add(response.getReadRepair());
|
||||||
Tracing.trace("Enqueuing request to {}", replica);
|
return response;
|
||||||
ReadCommand command = replica.isFull() ? rangeCommand : rangeCommand.copyAsTransientQuery(replica);
|
|
||||||
Message<ReadCommand> message = command.createMessage(trackRepairedStatus && replica.isFull(), requestTime);
|
|
||||||
MessagingService.instance().sendWithCallback(message, replica.endpoint(), handler);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return new CassandraRangeResponse(resolver, handler, readRepair);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO (review): Should this be reworked to execute the queries serially from the iterator? It would respect
|
||||||
|
// any provided limits better but the number of queries created will generally be low (2-3)
|
||||||
|
List<PartitionIterator> responses = new ArrayList<>(reads.size() + 1);
|
||||||
|
// Dummy iterator that checks all the responses for retry on different system hasNext so we don't read
|
||||||
|
// from the first iterator when the second needs to be retried because the split was wrong
|
||||||
|
responses.add(new PartitionIterator()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext()
|
||||||
|
{
|
||||||
|
for (int i = 1; i < responses.size(); i++)
|
||||||
|
responses.get(i).hasNext();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RowIterator next()
|
||||||
|
{
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (RangeReadWithTarget rangeReadWithTarget : reads)
|
||||||
|
{
|
||||||
|
if (rangeReadWithTarget.target == RangeReadTarget.accord && readCoordinator.isEventuallyConsistent())
|
||||||
|
responses.add(executeAccord(cm, rangeReadWithTarget.read, replicaPlan.consistencyLevel()));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SingleRangeResponse response = executeNormal(replicaPlan, rangeReadWithTarget.read, readCoordinator);
|
||||||
|
responses.add(response);
|
||||||
|
readRepairs.add(response.getReadRepair());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We have to apply limits here if the query spans different systems because each subquery we created
|
||||||
|
* could have gaps in the results since the limit is pushed down independently to each subquery.
|
||||||
|
* So if we don't meet the limit in the first subquery, it's not safe to go to the next one unless
|
||||||
|
* we fully exhausted the data the first subquery might have reached
|
||||||
|
*/
|
||||||
|
return command.limits().filter(PartitionIterators.concat(responses),
|
||||||
|
0,
|
||||||
|
command.selectsFullPartition(),
|
||||||
|
command.metadata().enforceStrictLiveness());
|
||||||
}
|
}
|
||||||
|
|
||||||
PartitionIterator sendNextRequests()
|
PartitionIterator sendNextRequests()
|
||||||
|
|
@ -253,15 +332,26 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
|
||||||
List<PartitionIterator> concurrentQueries = new ArrayList<>(concurrencyFactor);
|
List<PartitionIterator> concurrentQueries = new ArrayList<>(concurrencyFactor);
|
||||||
List<ReadRepair<?, ?>> readRepairs = new ArrayList<>(concurrencyFactor);
|
List<ReadRepair<?, ?>> readRepairs = new ArrayList<>(concurrencyFactor);
|
||||||
|
|
||||||
|
ClusterMetadata cm = ClusterMetadata.current();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
for (int i = 0; i < concurrencyFactor && replicaPlans.hasNext(); )
|
for (int i = 0; i < concurrencyFactor && replicaPlans.hasNext(); )
|
||||||
{
|
{
|
||||||
ReplicaPlan.ForRangeRead replicaPlan = replicaPlans.next();
|
ReplicaPlan.ForRangeRead replicaPlan = replicaPlans.next();
|
||||||
|
boolean isFirst = i == 0;
|
||||||
IRangeResponse response = query(replicaPlan, readCoordinator, i == 0);
|
PartitionIterator response;
|
||||||
|
// Only add the retry wrapper to reroute for the top level coordinator execution
|
||||||
|
// not Accord's interop execution
|
||||||
|
if (readCoordinator.isEventuallyConsistent())
|
||||||
|
{
|
||||||
|
Function<ClusterMetadata, PartitionIterator> querySupplier = clusterMetadata -> query(clusterMetadata, replicaPlan, readCoordinator, readRepairs, isFirst);
|
||||||
|
response = retryingPartitionIterator(querySupplier, replicaPlan.consistencyLevel());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response = query(cm, replicaPlan, readCoordinator, readRepairs, isFirst);
|
||||||
|
}
|
||||||
concurrentQueries.add(response);
|
concurrentQueries.add(response);
|
||||||
readRepairs.add(response.getReadRepair());
|
|
||||||
// due to RangeMerger, coordinator may fetch more ranges than required by concurrency factor.
|
// due to RangeMerger, coordinator may fetch more ranges than required by concurrency factor.
|
||||||
rangesQueried += replicaPlan.vnodeCount();
|
rangesQueried += replicaPlan.vnodeCount();
|
||||||
i += replicaPlan.vnodeCount();
|
i += replicaPlan.vnodeCount();
|
||||||
|
|
@ -283,6 +373,57 @@ public class RangeCommandIterator extends AbstractIterator<RowIterator> implemen
|
||||||
return counter.applyTo(StorageProxy.concatAndBlockOnRepair(concurrentQueries, readRepairs));
|
return counter.applyTo(StorageProxy.concatAndBlockOnRepair(concurrentQueries, readRepairs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wrap the iterator to retry if request routing is incorrect
|
||||||
|
private PartitionIterator retryingPartitionIterator(Function<ClusterMetadata, PartitionIterator> attempt, ConsistencyLevel cl)
|
||||||
|
{
|
||||||
|
return new PartitionIterator()
|
||||||
|
{
|
||||||
|
private ClusterMetadata lastClusterMetadata = ClusterMetadata.current();
|
||||||
|
private PartitionIterator delegate = attempt.apply(lastClusterMetadata);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
delegate.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return delegate.hasNext();
|
||||||
|
}
|
||||||
|
catch (RetryOnDifferentSystemException e)
|
||||||
|
{
|
||||||
|
readMetrics.retryDifferentSystem.mark();
|
||||||
|
readMetricsForLevel(cl).retryDifferentSystem.mark();
|
||||||
|
logger.debug("Retrying range read on different system because some reads were misrouted according to Accord");
|
||||||
|
Tracing.trace("Got {} from range reads, will retry", e);
|
||||||
|
}
|
||||||
|
catch (CoordinatorBehindException e)
|
||||||
|
{
|
||||||
|
readMetrics.retryCoordinatorBehind.mark();
|
||||||
|
readMetricsForLevel(cl).retryCoordinatorBehind.mark();
|
||||||
|
logger.debug("Retrying range read now that coordinator has caught up to cluster metadata");
|
||||||
|
Tracing.trace("Got {} from range reads, will retry", e);
|
||||||
|
}
|
||||||
|
// Fetch the next epoch to retry
|
||||||
|
lastClusterMetadata = ClusterMetadata.current();
|
||||||
|
delegate = attempt.apply(lastClusterMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RowIterator next()
|
||||||
|
{
|
||||||
|
return delegate.next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,6 @@ public class ScanAllRangesCommandIterator extends RangeCommandIterator
|
||||||
Tracing.trace("Submitted scanning all ranges requests to {} nodes", nodes);
|
Tracing.trace("Submitted scanning all ranges requests to {} nodes", nodes);
|
||||||
|
|
||||||
// skip read-repair for top-k query because data mismatch may be caused by top-k algorithm instead of actual inconsistency.
|
// skip read-repair for top-k query because data mismatch may be caused by top-k algorithm instead of actual inconsistency.
|
||||||
return new CassandraRangeResponse(resolver, handler, NoopReadRepair.instance);
|
return new SingleRangeResponse(resolver, handler, NoopReadRepair.instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,13 @@ import org.apache.cassandra.db.rows.RowIterator;
|
||||||
import org.apache.cassandra.exceptions.ReadTimeoutException;
|
import org.apache.cassandra.exceptions.ReadTimeoutException;
|
||||||
import org.apache.cassandra.locator.EndpointsForRange;
|
import org.apache.cassandra.locator.EndpointsForRange;
|
||||||
import org.apache.cassandra.locator.ReplicaPlan;
|
import org.apache.cassandra.locator.ReplicaPlan;
|
||||||
|
import org.apache.cassandra.locator.ReplicaPlan.ForRangeRead;
|
||||||
import org.apache.cassandra.service.reads.DataResolver;
|
import org.apache.cassandra.service.reads.DataResolver;
|
||||||
import org.apache.cassandra.service.reads.ReadCallback;
|
import org.apache.cassandra.service.reads.ReadCallback;
|
||||||
import org.apache.cassandra.service.reads.repair.ReadRepair;
|
import org.apache.cassandra.service.reads.repair.ReadRepair;
|
||||||
import org.apache.cassandra.utils.AbstractIterator;
|
import org.apache.cassandra.utils.AbstractIterator;
|
||||||
|
|
||||||
class CassandraRangeResponse extends AbstractIterator<RowIterator> implements IRangeResponse
|
class SingleRangeResponse extends AbstractIterator<RowIterator> implements PartitionIterator
|
||||||
{
|
{
|
||||||
private final DataResolver<EndpointsForRange, ReplicaPlan.ForRangeRead> resolver;
|
private final DataResolver<EndpointsForRange, ReplicaPlan.ForRangeRead> resolver;
|
||||||
private final ReadCallback<EndpointsForRange, ReplicaPlan.ForRangeRead> handler;
|
private final ReadCallback<EndpointsForRange, ReplicaPlan.ForRangeRead> handler;
|
||||||
|
|
@ -36,17 +37,16 @@ class CassandraRangeResponse extends AbstractIterator<RowIterator> implements IR
|
||||||
|
|
||||||
private PartitionIterator result;
|
private PartitionIterator result;
|
||||||
|
|
||||||
CassandraRangeResponse(DataResolver<EndpointsForRange, ReplicaPlan.ForRangeRead> resolver,
|
SingleRangeResponse(DataResolver<EndpointsForRange, ReplicaPlan.ForRangeRead> resolver,
|
||||||
ReadCallback<EndpointsForRange, ReplicaPlan.ForRangeRead> handler,
|
ReadCallback<EndpointsForRange, ReplicaPlan.ForRangeRead> handler,
|
||||||
ReadRepair<EndpointsForRange, ReplicaPlan.ForRangeRead> readRepair)
|
ReadRepair<EndpointsForRange, ReplicaPlan.ForRangeRead> readRepair)
|
||||||
{
|
{
|
||||||
this.resolver = resolver;
|
this.resolver = resolver;
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.readRepair = readRepair;
|
this.readRepair = readRepair;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public ReadRepair<EndpointsForRange, ForRangeRead> getReadRepair()
|
||||||
public ReadRepair<EndpointsForRange, ReplicaPlan.ForRangeRead> getReadRepair()
|
|
||||||
{
|
{
|
||||||
return readRepair;
|
return readRepair;
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +94,7 @@ public abstract class AbstractReadRepair<E extends Endpoints<E>, P extends Repli
|
||||||
|
|
||||||
void sendReadCommand(Replica to, ReadCallback<E, P> readCallback, boolean speculative, boolean trackRepairedStatus)
|
void sendReadCommand(Replica to, ReadCallback<E, P> readCallback, boolean speculative, boolean trackRepairedStatus)
|
||||||
{
|
{
|
||||||
ReadCommand command = coordinator.maybeAllowOutOfRangeReads(this.command);
|
ReadCommand command = coordinator.maybeAllowOutOfRangeReads(this.command, replicaPlan().consistencyLevel());
|
||||||
|
|
||||||
if (to.isSelf() && coordinator.localReadSupported())
|
if (to.isSelf() && coordinator.localReadSupported())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,10 @@ import org.apache.cassandra.utils.concurrent.CountDownLatch;
|
||||||
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
|
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.collect.Iterables.all;
|
||||||
import static org.apache.cassandra.net.Verb.READ_REPAIR_REQ;
|
import static org.apache.cassandra.net.Verb.READ_REPAIR_REQ;
|
||||||
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
|
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
|
||||||
import static org.apache.cassandra.utils.concurrent.CountDownLatch.newCountDownLatch;
|
import static org.apache.cassandra.utils.concurrent.CountDownLatch.newCountDownLatch;
|
||||||
import static com.google.common.collect.Iterables.all;
|
|
||||||
|
|
||||||
public class BlockingPartitionRepair
|
public class BlockingPartitionRepair
|
||||||
extends AsyncFuture<Object> implements RequestCallback<Object>, PendingPartitionRepair
|
extends AsyncFuture<Object> implements RequestCallback<Object>, PendingPartitionRepair
|
||||||
|
|
@ -155,7 +155,7 @@ public class BlockingPartitionRepair
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
protected void sendRR(Message<Mutation> message, InetAddressAndPort endpoint)
|
protected void sendRR(Message<Mutation> message, InetAddressAndPort endpoint)
|
||||||
{
|
{
|
||||||
checkArgument(message.payload.allowsPotentialTransactionConflicts() == coordinator.allowsPotentialTransactionConflicts(), "Mutation allowing transaction conflicts should match coordinator");
|
checkArgument(message.payload.potentialTxnConflicts() == coordinator.potentialTxnConflicts(), "Mutation allowing transaction conflicts should match coordinator");
|
||||||
coordinator.sendReadRepairMutation(message, endpoint, this);
|
coordinator.sendReadRepairMutation(message, endpoint, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,7 +241,7 @@ public class BlockingPartitionRepair
|
||||||
|
|
||||||
if (mutation == null)
|
if (mutation == null)
|
||||||
{
|
{
|
||||||
mutation = BlockingReadRepairs.createRepairMutation(update, repairPlan.consistencyLevel(), replica.endpoint(), true, coordinator.allowsPotentialTransactionConflicts());
|
mutation = BlockingReadRepairs.createRepairMutation(update, repairPlan.consistencyLevel(), replica.endpoint(), true, coordinator.potentialTxnConflicts());
|
||||||
versionedMutations[versionIdx] = mutation;
|
versionedMutations[versionIdx] = mutation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue