From 8ab50031187e520e4e23164447c0d703f8342091 Mon Sep 17 00:00:00 2001 From: Ariel Weisberg Date: Mon, 18 Mar 2024 15:38:31 -0400 Subject: [PATCH] Accord migration and interop correctness Patch by Ariel Weisberg; Reviewed by Blake Eggleston for CASSANDRA-19744 --- .gitmodules | 2 +- modules/accord | 2 +- .../cassandra/batchlog/BatchlogManager.java | 229 ++++- .../config/CassandraRelevantProperties.java | 3 + .../cassandra/config/DatabaseDescriptor.java | 6 + .../cql3/statements/BatchStatement.java | 4 +- .../statements/BatchUpdatesCollector.java | 16 +- .../cql3/statements/CQL3CasRequest.java | 5 +- .../statements/ModificationStatement.java | 10 +- ...eTableSinglePartitionUpdatesCollector.java | 8 +- .../SingleTableUpdatesCollector.java | 10 +- .../cql3/statements/TransactionStatement.java | 2 +- .../cql3/statements/UpdatesCollector.java | 2 +- .../schema/AlterTableStatement.java | 10 +- .../db/AbstractMutationVerbHandler.java | 23 +- .../db/CassandraKeyspaceWriteHandler.java | 4 +- .../apache/cassandra/db/CounterMutation.java | 52 +- .../org/apache/cassandra/db/IMutation.java | 20 +- .../org/apache/cassandra/db/Keyspace.java | 6 +- .../org/apache/cassandra/db/Mutation.java | 139 ++- .../apache/cassandra/db/SimpleBuilders.java | 33 +- .../cassandra/db/virtual/VirtualMutation.java | 23 + .../cassandra/exceptions/RequestFailure.java | 13 +- .../exceptions/RequestFailureReason.java | 26 +- .../RetryOnDifferentSystemException.java | 30 + .../cassandra/hints/HintDiagnostics.java | 16 +- .../org/apache/cassandra/hints/HintEvent.java | 7 +- .../cassandra/hints/HintVerbHandler.java | 14 +- .../cassandra/hints/HintsBufferPool.java | 9 + .../apache/cassandra/hints/HintsCatalog.java | 13 +- .../hints/HintsDispatchExecutor.java | 15 +- .../cassandra/hints/HintsDispatcher.java | 347 ++++++- .../apache/cassandra/hints/HintsService.java | 47 +- .../apache/cassandra/hints/HintsStore.java | 17 + .../metrics/AccordClientRequestMetrics.java | 7 + .../metrics/ClientRequestMetrics.java | 6 + .../metrics/HintsServiceMetrics.java | 25 + .../cassandra/metrics/KeyspaceMetrics.java | 2 + .../cassandra/metrics/TableMetrics.java | 2 + .../cassandra/net/ResponseVerbHandler.java | 9 +- .../apache/cassandra/repair/RepairJob.java | 14 +- .../cassandra/schema/DistributedSchema.java | 29 +- .../service/AbstractWriteResponseHandler.java | 67 +- .../service/BatchlogResponseHandler.java | 15 +- .../cassandra/service/StorageProxy.java | 523 ++++++---- .../service/accord/AccordJournal.java | 22 +- .../service/accord/AccordService.java | 273 +++-- .../service/accord/AccordVerbHandler.java | 6 +- .../service/accord/IAccordService.java | 49 +- .../service/accord/api/AccordAgent.java | 8 +- .../interop/AccordInteropExecution.java | 22 +- .../interop/AccordInteropReadCallback.java | 11 +- .../service/accord/repair/AccordRepair.java | 30 +- .../service/accord/txn/TxnQuery.java | 26 +- .../service/accord/txn/TxnUpdate.java | 36 +- .../service/accord/txn/TxnWrite.java | 15 +- .../accord/txn/UnrecoverableRepairUpdate.java | 6 +- .../service/consensus/TransactionalMode.java | 4 + .../migration/ConsensusKeyMigrationState.java | 8 +- .../ConsensusMigrationMutationHelper.java | 359 +++++++ .../ConsensusMigrationRepairResult.java | 24 +- .../ConsensusMigrationRepairType.java | 16 +- .../migration/ConsensusRequestRouter.java | 61 +- .../migration/ConsensusTableMigration.java | 21 +- .../cassandra/service/paxos/Commit.java | 12 +- .../service/reads/ReadCoordinator.java | 4 +- .../reads/repair/BlockingPartitionRepair.java | 9 +- .../reads/repair/BlockingReadRepair.java | 133 +-- .../reads/repair/BlockingReadRepairs.java | 4 +- .../service/reads/repair/ReadRepair.java | 5 + .../repair/RowIteratorMergeListener.java | 2 +- ...ishConsensusMigrationForTableAndRange.java | 44 +- .../cassandra/transport/Dispatcher.java | 7 +- .../cassandra/triggers/TriggerExecutor.java | 16 +- .../apache/cassandra/utils/NoSpamLogger.java | 5 +- .../apache/cassandra/utils/Throwables.java | 31 + .../distributed/api/ICoordinator.java | 5 +- .../distributed/impl/Coordinator.java | 6 + .../distributed/impl/TestChangeListener.java | 101 +- .../distributed/shared/ClusterUtils.java | 5 + .../distributed/test/CASAddTest.java | 4 +- .../distributed/test/CASMultiDCTest.java | 12 +- .../distributed/test/MessageFiltersTest.java | 3 +- .../distributed/test/QueriesTableTest.java | 3 +- .../test/RepairDigestTrackingTest.java | 33 +- .../test/ShortReadProtectionTest.java | 3 +- .../distributed/test/TestBaseImpl.java | 44 +- .../test/accord/AccordCQLTestBase.java | 942 +++++++++--------- .../accord/AccordIncrementalRepairTest.java | 31 +- .../test/accord/AccordIntegrationTest.java | 20 +- .../accord/AccordInteroperabilityTest.java | 12 +- .../test/accord/AccordLoadTest.java | 10 +- .../test/accord/AccordMetricsTest.java | 8 +- .../accord/AccordMigrationRaceTestBase.java | 771 ++++++++++++++ .../test/accord/AccordMigrationTest.java | 70 +- .../test/accord/AccordTestBase.java | 148 ++- .../AccordTimestampPreservationTest.java | 209 ++++ .../accord/MigrationFromAccordRaceTest.java | 27 + .../accord/MigrationToAccordRaceTest.java | 27 + .../test/metrics/HintsServiceMetricsTest.java | 7 + .../test/ShortPaxosSimulationTest.java | 4 +- test/unit/org/apache/cassandra/Util.java | 27 +- .../cassandra/hints/HintsServiceTest.java | 78 +- .../apache/cassandra/hints/HintsTestUtil.java | 20 +- .../schema/TransactionalConfigSchemaTest.java | 11 + .../service/accord/AccordReadRepairTest.java | 16 +- .../service/accord/AccordServiceTest.java | 25 +- .../service/accord/AccordTestUtils.java | 14 +- .../service/accord/EpochSyncTest.java | 8 +- .../accord/SimulatedAccordCommandStore.java | 6 +- 110 files changed, 4461 insertions(+), 1380 deletions(-) create mode 100644 src/java/org/apache/cassandra/exceptions/RetryOnDifferentSystemException.java create mode 100644 src/java/org/apache/cassandra/service/consensus/migration/ConsensusMigrationMutationHelper.java create mode 100644 test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationRaceTestBase.java create mode 100644 test/distributed/org/apache/cassandra/distributed/test/accord/AccordTimestampPreservationTest.java create mode 100644 test/distributed/org/apache/cassandra/distributed/test/accord/MigrationFromAccordRaceTest.java create mode 100644 test/distributed/org/apache/cassandra/distributed/test/accord/MigrationToAccordRaceTest.java diff --git a/.gitmodules b/.gitmodules index 616dacf610..60a9510e7a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "modules/accord"] path = modules/accord - url = https://github.com/apache/cassandra-accord.git + url = ../cassandra-accord.git branch = trunk diff --git a/modules/accord b/modules/accord index 4aa0a8aeb6..129a4862df 160000 --- a/modules/accord +++ b/modules/accord @@ -1 +1 @@ -Subproject commit 4aa0a8aeb6b12036660695e3fb89c69b5d40f345 +Subproject commit 129a4862df43fdc5893687922a77bb0288f8cb83 diff --git a/src/java/org/apache/cassandra/batchlog/BatchlogManager.java b/src/java/org/apache/cassandra/batchlog/BatchlogManager.java index b5b33d5e0b..1844f84249 100644 --- a/src/java/org/apache/cassandra/batchlog/BatchlogManager.java +++ b/src/java/org/apache/cassandra/batchlog/BatchlogManager.java @@ -27,25 +27,25 @@ import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; +import javax.annotation.Nullable; + import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.RateLimiter; -import org.apache.cassandra.concurrent.ScheduledExecutorPlus; -import org.apache.cassandra.schema.KeyspaceMetadata; -import org.apache.cassandra.tcm.ClusterMetadata; -import org.apache.cassandra.transport.Dispatcher; -import org.apache.cassandra.utils.TimeUUID; -import org.apache.cassandra.utils.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.cassandra.concurrent.ScheduledExecutorPlus; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.UntypedResultSet; +import org.apache.cassandra.cql3.UntypedResultSet.Row; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.Keyspace; @@ -55,6 +55,7 @@ import org.apache.cassandra.db.WriteType; import org.apache.cassandra.db.marshal.BytesType; import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.dht.Token; +import org.apache.cassandra.exceptions.RetryOnDifferentSystemException; import org.apache.cassandra.exceptions.WriteFailureException; import org.apache.cassandra.exceptions.WriteTimeoutException; import org.apache.cassandra.gms.FailureDetector; @@ -70,26 +71,41 @@ import org.apache.cassandra.locator.Replicas; import org.apache.cassandra.net.Message; import org.apache.cassandra.net.MessageFlag; import org.apache.cassandra.net.MessagingService; +import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.WriteResponseHandler; +import org.apache.cassandra.service.accord.AccordService; +import org.apache.cassandra.service.accord.IAccordService; +import org.apache.cassandra.service.accord.IAccordService.AsyncTxnResult; +import org.apache.cassandra.service.accord.txn.TxnResult; +import org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper; +import org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper.SplitMutations; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.transport.Dispatcher; +import org.apache.cassandra.utils.Clock; import org.apache.cassandra.utils.ExecutorUtils; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.MBeanWrapper; +import org.apache.cassandra.utils.Throwables; +import org.apache.cassandra.utils.TimeUUID; +import org.apache.cassandra.utils.concurrent.Future; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.config.CassandraRelevantProperties.BATCHLOG_REPLAY_TIMEOUT_IN_MS; import static org.apache.cassandra.cql3.QueryProcessor.executeInternal; import static org.apache.cassandra.cql3.QueryProcessor.executeInternalWithPaging; +import static org.apache.cassandra.hints.HintsService.RETRY_ON_DIFFERENT_SYSTEM_UUID; import static org.apache.cassandra.net.Verb.MUTATION_REQ; +import static org.apache.cassandra.service.accord.txn.TxnResult.Kind.retry_new_protocol; +import static org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper.mutateWithAccordAsync; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; public class BatchlogManager implements BatchlogManagerMBean { public static final String MBEAN_NAME = "org.apache.cassandra.db:type=BatchlogManager"; - private static final long REPLAY_INTERVAL = 10 * 1000; // milliseconds static final int DEFAULT_PAGE_SIZE = 128; private static final Logger logger = LoggerFactory.getLogger(BatchlogManager.class); @@ -104,6 +120,8 @@ public class BatchlogManager implements BatchlogManagerMBean private final RateLimiter rateLimiter = RateLimiter.create(Double.MAX_VALUE); + private final AtomicBoolean isBatchlogReplayPaused = new AtomicBoolean(false); + public BatchlogManager() { batchlogTasks = executorFactory().scheduled(false, "BatchlogTasks"); @@ -115,7 +133,7 @@ public class BatchlogManager implements BatchlogManagerMBean batchlogTasks.scheduleWithFixedDelay(this::replayFailedBatches, StorageService.RING_DELAY_MILLIS, - REPLAY_INTERVAL, + CassandraRelevantProperties.BATCHLOG_REPLAY_INTERVAL_MS.getLong(), MILLISECONDS); } @@ -184,7 +202,9 @@ public class BatchlogManager implements BatchlogManagerMBean public void forceBatchlogReplay() throws Exception { + logger.debug("Forcing batchlog replay"); startBatchlogReplay().get(); + logger.debug("Finished forcing batchlog replay"); } public Future startBatchlogReplay() @@ -193,14 +213,25 @@ public class BatchlogManager implements BatchlogManagerMBean return batchlogTasks.submit(this::replayFailedBatches); } - void performInitialReplay() throws InterruptedException, ExecutionException + public void pauseReplay() { - // Invokes initial replay. Used for testing only. - batchlogTasks.submit(this::replayFailedBatches).get(); + logger.debug("Paused batchlog replay"); + isBatchlogReplayPaused.set(true); + } + + public void resumeReplay() + { + logger.debug("Resumed batchlog replay"); + isBatchlogReplayPaused.set(false); } private void replayFailedBatches() { + if (isBatchlogReplayPaused.get()) + { + logger.debug("Batch log replay is paused, skipping replay"); + return; + } logger.trace("Started replayFailedBatches"); // rate limit is in bytes per second. Uses Double.MAX_VALUE if disabled (set to 0 in cassandra.yaml). @@ -223,6 +254,7 @@ public class BatchlogManager implements BatchlogManagerMBean SchemaConstants.SYSTEM_KEYSPACE_NAME, SystemKeyspace.BATCHES); UntypedResultSet batches = executeInternalWithPaging(query, pageSize, lastReplayedUuid, limitUuid); + processBatchlogEntries(batches, pageSize, rateLimiter); lastReplayedUuid = limitUuid; logger.trace("Finished replayFailedBatches"); @@ -276,16 +308,7 @@ public class BatchlogManager implements BatchlogManagerMBean int version = row.getInt("version"); try { - ReplayingBatch batch = new ReplayingBatch(id, version, row.getList("mutations", BytesType.instance)); - if (batch.replay(rateLimiter, hintedNodes) > 0) - { - unfinishedBatches.add(batch); - } - else - { - remove(id); // no write mutations were sent (either expired or all CFs involved truncated). - ++totalBatchesReplayed; - } + dispatchBatch(rateLimiter, row, id, version, hintedNodes, unfinishedBatches); } catch (IOException e) { @@ -307,6 +330,8 @@ public class BatchlogManager implements BatchlogManagerMBean // finalize the incomplete last page of batches if (positionInPage > 0) finishAndClearBatches(unfinishedBatches, hintedNodes, replayedBatches); + else + logger.trace("Had no batches to replay"); if (caughtException != null) logger.warn(String.format("Encountered %d unexpected exceptions while sending out batches", skipped), caughtException); @@ -318,6 +343,35 @@ public class BatchlogManager implements BatchlogManagerMBean replayedBatches.forEach(BatchlogManager::remove); } + private void dispatchBatch(RateLimiter rateLimiter, Row row, TimeUUID id, int version, Set hintedNodes, ArrayList unfinishedBatches) throws IOException + { + while (true) + { + ClusterMetadata cm = ClusterMetadata.current(); + try + { + ReplayingBatch batch = new ReplayingBatch(id, version, row.getList("mutations", BytesType.instance), cm); + if (batch.replay(rateLimiter, hintedNodes)) + { + unfinishedBatches.add(batch); + } + else + { + remove(id); // no write mutations were sent (either expired or all CFs involved truncated). + ++totalBatchesReplayed; + } + } + catch (RetryOnDifferentSystemException e) + { + // Self apply can throw retry on different system + // Barring bugs we should already have the latest cluster metadata needed to correctly + // split the batch and retry since that is what was used to generate the exception + continue; + } + break; + } + } + private void finishAndClearBatches(ArrayList batches, Set hintedNodes, Set replayedBatches) { // schedule hints for timed out deliveries @@ -340,61 +394,112 @@ public class BatchlogManager implements BatchlogManagerMBean { private final TimeUUID id; private final long writtenAt; - private final List mutations; + private final int unsplitGcGs; + private final List normalMutations; + private final List accordMutations; private final int replayedBytes; + private final ClusterMetadata cm; - private List> replayHandlers; + private List> replayHandlers = ImmutableList.of(); + private AsyncTxnResult accordResult; + @Nullable + private Dispatcher.RequestTime accordTxnStart; - ReplayingBatch(TimeUUID id, int version, List serializedMutations) throws IOException + ReplayingBatch(TimeUUID id, int version, List serializedMutations, ClusterMetadata cm) throws IOException { this.id = id; this.writtenAt = id.unix(MILLISECONDS); - this.mutations = new ArrayList<>(serializedMutations.size()); - this.replayedBytes = addMutations(version, serializedMutations); + List unsplitMutations = new ArrayList<>(serializedMutations.size()); + this.replayedBytes = addMutations(unsplitMutations, writtenAt, version, serializedMutations); + unsplitGcGs = gcgs(unsplitMutations); + SplitMutations splitMutations = ConsensusMigrationMutationHelper.splitMutationsIntoAccordAndNormal(cm, unsplitMutations); + logger.trace("Replaying batch with Accord {} and normal {}", splitMutations.accordMutations(), splitMutations.normalMutations()); + normalMutations = splitMutations.normalMutations(); + accordMutations = splitMutations.accordMutations(); + if (accordMutations != null) + accordTxnStart = new Dispatcher.RequestTime(Clock.Global.nanoTime()); + this.cm = cm; } - public int replay(RateLimiter rateLimiter, Set hintedNodes) throws IOException + public boolean replay(RateLimiter rateLimiter, Set hintedNodes) throws IOException { logger.trace("Replaying batch {}", id); - if (mutations.isEmpty()) - return 0; + if ((normalMutations == null || normalMutations.isEmpty()) && (accordMutations == null || accordMutations.isEmpty())) + return false; - int gcgs = gcgs(mutations); - if (MILLISECONDS.toSeconds(writtenAt) + gcgs <= FBUtilities.nowInSeconds()) - return 0; + if (MILLISECONDS.toSeconds(writtenAt) + unsplitGcGs <= FBUtilities.nowInSeconds()) + return false; - replayHandlers = sendReplays(mutations, writtenAt, hintedNodes); + if (accordMutations != null) + { + accordTxnStart = accordTxnStart.withStartedAt(Clock.Global.nanoTime()); + accordResult = accordMutations != null ? mutateWithAccordAsync(cm, accordMutations, null, accordTxnStart) : null; + } + + if (normalMutations != null) + replayHandlers = sendReplays(normalMutations, writtenAt, hintedNodes); rateLimiter.acquire(replayedBytes); // acquire afterwards, to not mess up ttl calculation. - return replayHandlers.size(); + return replayHandlers.size() > 0 || accordMutations != null; } public void finish(Set hintedNodes) { - for (int i = 0; i < replayHandlers.size(); i++) + Throwable failure = null; + // Check if the Accord mutations succeeded asynchronously + try { - ReplayWriteResponseHandler handler = replayHandlers.get(i); - try + if (accordResult != null) { - handler.get(); + IAccordService accord = AccordService.instance(); + TxnResult.Kind kind = accord.getTxnResult(accordResult, true, ConsistencyLevel.QUORUM, accordTxnStart).kind(); + if (kind == retry_new_protocol) + throw new RetryOnDifferentSystemException(); } - catch (WriteTimeoutException|WriteFailureException e) + } + catch (WriteTimeoutException|WriteFailureException|RetryOnDifferentSystemException e) + { + logger.trace("Failed replaying a batched mutation on Accord, will write a hint"); + logger.trace("Failure was : {}", e.getMessage()); + writeHintsForUndeliveredAccordTxns(hintedNodes); + } + catch (Exception e) + { + failure = Throwables.merge(failure, e); + } + + try + { + for (int i = 0; i < replayHandlers.size(); i++) { - if (logger.isTraceEnabled()) + ReplayWriteResponseHandler handler = replayHandlers.get(i); + try + { + handler.get(); + } + catch (WriteTimeoutException|WriteFailureException|RetryOnDifferentSystemException e) { logger.trace("Failed replaying a batched mutation to a node, will write a hint"); logger.trace("Failure was : {}", e.getMessage()); + // writing hints for the rest to hints, starting from i + writeHintsForUndeliveredEndpoints(i, hintedNodes); + break; } - // writing hints for the rest to hints, starting from i - writeHintsForUndeliveredEndpoints(i, hintedNodes); - return; } } + catch (Exception e) + { + logger.debug("Unexpected batchlog replay exception", e); + failure = Throwables.merge(failure, e); + } + + if (failure != null) + throw Throwables.unchecked(failure); } - private int addMutations(int version, List serializedMutations) throws IOException + private static int addMutations(List unsplitMutations, long writtenAt, int version, List serializedMutations) throws IOException { int ret = 0; for (ByteBuffer serializedMutation : serializedMutations) @@ -402,7 +507,7 @@ public class BatchlogManager implements BatchlogManagerMBean ret += serializedMutation.remaining(); try (DataInputBuffer in = new DataInputBuffer(serializedMutation, true)) { - addMutation(Mutation.serializer.deserialize(in, version)); + addMutation(unsplitMutations, writtenAt, Mutation.serializer.deserialize(in, version)); } } @@ -412,19 +517,41 @@ public class BatchlogManager implements BatchlogManagerMBean // Remove CFs that have been truncated since. writtenAt and SystemTable#getTruncatedAt() both return millis. // We don't abort the replay entirely b/c this can be considered a success (truncated is same as delivered then // truncated. - private void addMutation(Mutation mutation) + private static void addMutation(List unsplitMutations, long writtenAt, Mutation mutation) { for (TableId tableId : mutation.getTableIds()) if (writtenAt <= SystemKeyspace.getTruncatedAt(tableId)) mutation = mutation.without(tableId); - if (!mutation.isEmpty()) - mutations.add(mutation); + if (mutation != null) + unsplitMutations.add(mutation); + } + + // Write the hint assuming that when it is replayed it will probably be replayed + // as an Accord transaction so no reason to record per endpoint hints for all the endpoints + // Hints will still have to split and re-route on replay + private void writeHintsForUndeliveredAccordTxns(Set hintedNodes) + { + if (accordMutations == null) + return; + + int gcgs = gcgs(accordMutations); + + // expired + if (MILLISECONDS.toSeconds(writtenAt) + gcgs <= FBUtilities.nowInSeconds()) + return; + + for (Mutation m : accordMutations) + HintsService.instance.write(ImmutableList.of(RETRY_ON_DIFFERENT_SYSTEM_UUID), Hint.create(m, writtenAt)); + hintedNodes.add(RETRY_ON_DIFFERENT_SYSTEM_UUID); } private void writeHintsForUndeliveredEndpoints(int startFrom, Set hintedNodes) { - int gcgs = gcgs(mutations); + if (normalMutations == null) + return; + + int gcgs = gcgs(normalMutations); // expired if (MILLISECONDS.toSeconds(writtenAt) + gcgs <= FBUtilities.nowInSeconds()) @@ -434,7 +561,7 @@ public class BatchlogManager implements BatchlogManagerMBean for (int i = startFrom; i < replayHandlers.size(); i++) { ReplayWriteResponseHandler handler = replayHandlers.get(i); - Mutation undeliveredMutation = mutations.get(i); + Mutation undeliveredMutation = normalMutations.get(i); if (handler != null) { diff --git a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java index b8b4f3e33a..10cf8d6b10 100644 --- a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java +++ b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java @@ -61,6 +61,7 @@ public enum CassandraRelevantProperties AUTOCOMPACTION_ON_STARTUP_ENABLED("cassandra.autocompaction_on_startup_enabled", "true"), AUTO_BOOTSTRAP("cassandra.auto_bootstrap"), AUTO_REPAIR_FREQUENCY_SECONDS("cassandra.auto_repair_frequency_seconds", convertToString(TimeUnit.MINUTES.toSeconds(5))), + BATCHLOG_REPLAY_INTERVAL_MS("cassandra.batchlog.replay_interval_ms", "10000"), BATCHLOG_REPLAY_TIMEOUT_IN_MS("cassandra.batchlog.replay_timeout_in_ms"), BATCH_COMMIT_LOG_SYNC_INTERVAL("cassandra.batch_commitlog_sync_interval_millis", "1000"), /** @@ -271,6 +272,8 @@ public enum CassandraRelevantProperties */ GOSSIP_SETTLE_POLL_SUCCESSES_REQUIRED("cassandra.gossip_settle_poll_success_required", "3"), + HINT_DISPATCH_INTERVAL_MS("cassandra.hint_dispatch_interval_ms", "10000"), + IGNORED_SCHEMA_CHECK_ENDPOINTS("cassandra.skip_schema_check_for_endpoints"), IGNORED_SCHEMA_CHECK_VERSIONS("cassandra.skip_schema_check_for_versions"), IGNORE_CORRUPTED_SCHEMA_TABLES("cassandra.ignore_corrupted_schema_tables"), diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java index 07227a40e9..9a2c680b47 100644 --- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java +++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java @@ -80,6 +80,7 @@ import org.apache.cassandra.config.Config.CommitLogSync; import org.apache.cassandra.config.Config.DiskAccessMode; import org.apache.cassandra.config.Config.PaxosOnLinearizabilityViolation; import org.apache.cassandra.config.Config.PaxosStatePurging; +import org.apache.cassandra.config.DurationSpec.IntMillisecondsBound; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.commitlog.AbstractCommitLogSegmentManager; import org.apache.cassandra.db.commitlog.CommitLog; @@ -4047,6 +4048,11 @@ public class DatabaseDescriptor return conf.hints_flush_period.toMilliseconds(); } + public static void setHintsFlushPeriodInMS(int milliseconds) + { + conf.hints_flush_period = new IntMillisecondsBound(milliseconds); + } + public static long getMaxHintsFileSize() { return conf.max_hints_file_size.toBytesInLong(); diff --git a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java index 65c7f56662..45c433a530 100644 --- a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java @@ -354,7 +354,9 @@ public class BatchStatement implements CQLStatement.CompositeCQLStatement ClientWarn.instance.warn(MessageFormatter.arrayFormat(LOGGED_BATCH_LOW_GCGS_WARNING, new Object[] { suffix, tablesWithZeroGcGs }) .getMessage()); } - return collector.toMutations(state); + // local is either executeWithoutConditions modifying a virtual table (doesn't support txns) or executeLocal + // which is called by test or internal things that are bypassing distributed system modification/checks + return collector.toMutations(state, local); } /** diff --git a/src/java/org/apache/cassandra/cql3/statements/BatchUpdatesCollector.java b/src/java/org/apache/cassandra/cql3/statements/BatchUpdatesCollector.java index aabcecec72..49b5a404dd 100644 --- a/src/java/org/apache/cassandra/cql3/statements/BatchUpdatesCollector.java +++ b/src/java/org/apache/cassandra/cql3/statements/BatchUpdatesCollector.java @@ -137,14 +137,14 @@ final class BatchUpdatesCollector implements UpdatesCollector * @return a collection containing all the mutations. */ @Override - public List toMutations(ClientState state) + public List toMutations(ClientState state, boolean allowPotentialTxnConflicts) { List ms = new ArrayList<>(); for (Map ksMap : mutationBuilders.values()) { for (IMutationBuilder builder : ksMap.values()) { - IMutation mutation = builder.build(); + IMutation mutation = builder.build(allowPotentialTxnConflicts); mutation.validateIndexedColumns(state); mutation.validateSize(MessagingService.current_version, CommitLogSegment.ENTRY_OVERHEAD_SIZE); ms.add(mutation); @@ -182,7 +182,7 @@ final class BatchUpdatesCollector implements UpdatesCollector /** * Build the immutable mutation */ - IMutation build(); + IMutation build(boolean allowPotentialTxnConflicts); /** * Get the builder for the given tableId @@ -215,7 +215,7 @@ final class BatchUpdatesCollector implements UpdatesCollector return this; } - public Mutation build() + public Mutation build(boolean allowPotentialTxnConflicts) { ImmutableMap.Builder updates = new ImmutableMap.Builder<>(); for (Map.Entry updateEntry : modifications.entrySet()) @@ -223,7 +223,7 @@ final class BatchUpdatesCollector implements UpdatesCollector PartitionUpdate update = updateEntry.getValue().build(); updates.put(updateEntry.getKey(), update); } - return new Mutation(keyspaceName, key, updates.build(), createdAt, false); + return new Mutation(keyspaceName, key, updates.build(), createdAt, allowPotentialTxnConflicts); } public PartitionUpdate.Builder get(TableId tableId) @@ -263,9 +263,9 @@ final class BatchUpdatesCollector implements UpdatesCollector return mutationBuilder.add(builder); } - public IMutation build() + public IMutation build(boolean allowPotentialTxnConflicts) { - return new CounterMutation(mutationBuilder.build(), cl); + return new CounterMutation(mutationBuilder.build(allowPotentialTxnConflicts), cl); } public PartitionUpdate.Builder get(TableId id) @@ -297,7 +297,7 @@ final class BatchUpdatesCollector implements UpdatesCollector } @Override - public VirtualMutation build() + public VirtualMutation build(boolean allowPotentialTxnConflicts) { ImmutableMap.Builder updates = new ImmutableMap.Builder<>(); modifications.forEach((tableId, updateBuilder) -> updates.put(tableId, updateBuilder.build())); diff --git a/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java b/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java index bbfc333ca6..41967b619b 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java +++ b/src/java/org/apache/cassandra/cql3/statements/CQL3CasRequest.java @@ -511,10 +511,11 @@ public class CQL3CasRequest implements CASRequest private Update createUpdate(ClientState clientState, ConsistencyLevel commitConsistencyLevel) { - // Potentially ignore commit consistency level if non-SERIAL write strategy is Accord + // Potentially ignore commit consistency level if TransactionalMode is full // since it is safe to match what non-SERIAL writes do commitConsistencyLevel = metadata.params.transactionalMode.commitCLForStrategy(commitConsistencyLevel); - return new TxnUpdate(createWriteFragments(clientState), createCondition(), commitConsistencyLevel); + // CAS requires using the new txn timestamp to correctly linearize some kinds of updates + return new TxnUpdate(createWriteFragments(clientState), createCondition(), commitConsistencyLevel, false); } private TxnCondition createCondition() diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java index 19556a766b..74a7bb9823 100644 --- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java @@ -44,10 +44,9 @@ import org.apache.cassandra.cql3.Attributes; import org.apache.cassandra.cql3.CQLStatement; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.ColumnSpecification; -import org.apache.cassandra.cql3.Ordering; -import org.apache.cassandra.cql3.terms.Constants; import org.apache.cassandra.cql3.Operation; import org.apache.cassandra.cql3.Operations; +import org.apache.cassandra.cql3.Ordering; import org.apache.cassandra.cql3.QualifiedName; import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.cql3.QueryProcessor; @@ -66,6 +65,7 @@ import org.apache.cassandra.cql3.restrictions.StatementRestrictions; import org.apache.cassandra.cql3.selection.ResultSetBuilder; import org.apache.cassandra.cql3.selection.Selection; import org.apache.cassandra.cql3.selection.Selection.Selectors; +import org.apache.cassandra.cql3.terms.Constants; import org.apache.cassandra.cql3.transactions.ReferenceOperation; import org.apache.cassandra.db.CBuilder; import org.apache.cassandra.db.Clustering; @@ -869,14 +869,16 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa { SingleTableSinglePartitionUpdatesCollector collector = new SingleTableSinglePartitionUpdatesCollector(metadata, updatedColumns); addUpdates(collector, keys, state, options, local, timestamp, nowInSeconds, requestTime, constructingAccordBaseUpdate); - return collector.toMutations(state); + // local means this is test or internal things that are bypassing distributed system modification/checks + return collector.toMutations(state, local); } else { HashMultiset perPartitionKeyCounts = HashMultiset.create(keys); SingleTableUpdatesCollector collector = new SingleTableUpdatesCollector(metadata, updatedColumns, perPartitionKeyCounts); addUpdates(collector, keys, state, options, local, timestamp, nowInSeconds, requestTime, constructingAccordBaseUpdate); - return collector.toMutations(state); + // local means this is test or internal things that are bypassing distributed system modification/checks + return collector.toMutations(state, local); } } diff --git a/src/java/org/apache/cassandra/cql3/statements/SingleTableSinglePartitionUpdatesCollector.java b/src/java/org/apache/cassandra/cql3/statements/SingleTableSinglePartitionUpdatesCollector.java index c650ef0370..6664c09e2c 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SingleTableSinglePartitionUpdatesCollector.java +++ b/src/java/org/apache/cassandra/cql3/statements/SingleTableSinglePartitionUpdatesCollector.java @@ -78,16 +78,16 @@ final class SingleTableSinglePartitionUpdatesCollector implements UpdatesCollect * Returns a collection containing all the mutations. */ @Override - public List toMutations(ClientState state) + public List toMutations(ClientState state, boolean allowPotentialTxnConflicts) { // it is possible that a modification statement does not create any mutations // for example: DELETE FROM some_table WHERE part_key = 1 AND clust_key < 3 AND clust_key > 5 if (builder == null) return Collections.emptyList(); - return Collections.singletonList(createMutation(state, builder)); + return Collections.singletonList(createMutation(state, builder, allowPotentialTxnConflicts)); } - private IMutation createMutation(ClientState state, PartitionUpdate.Builder builder) + private IMutation createMutation(ClientState state, PartitionUpdate.Builder builder, boolean allowPotentialTxnConflicts) { IMutation mutation; @@ -96,7 +96,7 @@ final class SingleTableSinglePartitionUpdatesCollector implements UpdatesCollect else if (metadata.isCounter()) mutation = new CounterMutation(new Mutation(builder.build()), counterConsistencyLevel); else - mutation = new Mutation(builder.build()); + mutation = new Mutation(builder.build(), allowPotentialTxnConflicts); mutation.validateIndexedColumns(state); mutation.validateSize(MessagingService.current_version, CommitLogSegment.ENTRY_OVERHEAD_SIZE); diff --git a/src/java/org/apache/cassandra/cql3/statements/SingleTableUpdatesCollector.java b/src/java/org/apache/cassandra/cql3/statements/SingleTableUpdatesCollector.java index 2da6b89180..c2497360a7 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SingleTableUpdatesCollector.java +++ b/src/java/org/apache/cassandra/cql3/statements/SingleTableUpdatesCollector.java @@ -95,24 +95,24 @@ final class SingleTableUpdatesCollector implements UpdatesCollector * @return a collection containing all the mutations. */ @Override - public List toMutations(ClientState state) + public List toMutations(ClientState state, boolean allowPotentialTxnConflicts) { if (puBuilders.size() == 1) { PartitionUpdate.Builder builder = puBuilders.values().iterator().next(); - return Collections.singletonList(createMutation(state, builder)); + return Collections.singletonList(createMutation(state, builder, allowPotentialTxnConflicts)); } List ms = new ArrayList<>(puBuilders.size()); for (PartitionUpdate.Builder builder : puBuilders.values()) { - IMutation mutation = createMutation(state, builder); + IMutation mutation = createMutation(state, builder, allowPotentialTxnConflicts); ms.add(mutation); } return ms; } - private IMutation createMutation(ClientState state, PartitionUpdate.Builder builder) + private IMutation createMutation(ClientState state, PartitionUpdate.Builder builder, boolean allowPotentialTxnConflicts) { IMutation mutation; @@ -121,7 +121,7 @@ final class SingleTableUpdatesCollector implements UpdatesCollector else if (metadata.isCounter()) mutation = new CounterMutation(new Mutation(builder.build()), counterConsistencyLevel); else - mutation = new Mutation(builder.build()); + mutation = new Mutation(builder.build(), allowPotentialTxnConflicts); mutation.validateIndexedColumns(state); mutation.validateSize(MessagingService.current_version, CommitLogSegment.ENTRY_OVERHEAD_SIZE); diff --git a/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java b/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java index 2adb5572e8..950eb8a6da 100644 --- a/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/TransactionStatement.java @@ -310,7 +310,7 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement, AccordUpdate createUpdate(ClientState state, QueryOptions options, Map autoReads, Consumer keyConsumer) { - return new TxnUpdate(createWriteFragments(state, options, autoReads, keyConsumer), createCondition(options), null); + return new TxnUpdate(createWriteFragments(state, options, autoReads, keyConsumer), createCondition(options), null, false); } Keys toKeys(SortedSet keySet) diff --git a/src/java/org/apache/cassandra/cql3/statements/UpdatesCollector.java b/src/java/org/apache/cassandra/cql3/statements/UpdatesCollector.java index 40b75ab5fa..de19a8e567 100644 --- a/src/java/org/apache/cassandra/cql3/statements/UpdatesCollector.java +++ b/src/java/org/apache/cassandra/cql3/statements/UpdatesCollector.java @@ -30,5 +30,5 @@ import org.apache.cassandra.service.ClientState; public interface UpdatesCollector { PartitionUpdate.Builder getPartitionUpdateBuilder(TableMetadata metadata, DecoratedKey dk, ConsistencyLevel consistency); - List toMutations(ClientState state); + List toMutations(ClientState state, boolean allowPotentialTxnConflicts); } diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java index 2a8cca87fc..a7abcbada0 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java @@ -62,6 +62,7 @@ import org.apache.cassandra.schema.MemtableParams; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableParams; +import org.apache.cassandra.schema.TableParams.Option; import org.apache.cassandra.schema.UserFunctions; import org.apache.cassandra.schema.ViewMetadata; import org.apache.cassandra.schema.Views; @@ -594,7 +595,10 @@ public abstract class AlterTableStatement extends AlterSchemaStatement boolean modeChange = prev.transactionalMode != next.transactionalMode; boolean wasMigrating = prev.transactionalMigrationFrom.isMigrating(); - boolean forceMigrationChange = prev.transactionalMigrationFrom != next.transactionalMigrationFrom; + boolean explicitlySetMigrationFrom = attrs.hasOption(Option.TRANSACTIONAL_MIGRATION_FROM); + // set table to migrating + TransactionalMigrationFromMode newMigrateFrom = TransactionalMigrationFromMode.fromMode(prev.transactionalMode, next.transactionalMode); + boolean forceMigrationChange = modeChange && explicitlySetMigrationFrom && next.transactionalMigrationFrom != newMigrateFrom; if (modeChange && next.transactionalMode.accordIsEnabled && !DatabaseDescriptor.getAccordTransactionsEnabled()) throw ire(format("Cannot change transactional mode to %s for %s.%s with accord_transactions_enabled set to false", @@ -617,9 +621,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement prev.transactionalMode, next.transactionalMode, keyspaceName, tableName)); - // set table to migrating - TransactionalMigrationFromMode migrateFrom = TransactionalMigrationFromMode.fromMode(prev.transactionalMode, next.transactionalMode); - return next.unbuild().transactionalMigrationFrom(migrateFrom).build(); + return next.unbuild().transactionalMigrationFrom(newMigrateFrom).build(); } diff --git a/src/java/org/apache/cassandra/db/AbstractMutationVerbHandler.java b/src/java/org/apache/cassandra/db/AbstractMutationVerbHandler.java index 76b765ae70..05512e5a8e 100644 --- a/src/java/org/apache/cassandra/db/AbstractMutationVerbHandler.java +++ b/src/java/org/apache/cassandra/db/AbstractMutationVerbHandler.java @@ -27,18 +27,23 @@ import org.slf4j.LoggerFactory; import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.exceptions.CoordinatorBehindException; import org.apache.cassandra.exceptions.InvalidRoutingException; +import org.apache.cassandra.exceptions.RetryOnDifferentSystemException; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.metrics.TCMMetrics; import org.apache.cassandra.net.IVerbHandler; import org.apache.cassandra.net.Message; +import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadataService; import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.ownership.VersionedEndpoints; +import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.utils.NoSpamLogger; +import static org.apache.cassandra.exceptions.RequestFailureReason.RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM; + public abstract class AbstractMutationVerbHandler implements IVerbHandler { private static final Logger logger = LoggerFactory.getLogger(AbstractMutationVerbHandler.class); @@ -57,7 +62,17 @@ public abstract class AbstractMutationVerbHandler implement metadata = checkTokenOwnership(metadata, message, respondTo); metadata = checkSchemaVersion(metadata, message, respondTo); } - applyMutation(message, respondTo); + + try + { + applyMutation(message, respondTo); + } + 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"); + } } abstract void applyMutation(Message message, InetAddressAndPort respondToAddress); @@ -85,9 +100,7 @@ public abstract class AbstractMutationVerbHandler implement } } - // Mutations may intentionally be sent against an older Epoch so out of range checking doesn't work - // and could cause data to not end up where it needs to be for future operations - if (!message.payload.allowsOutOfRangeMutations() && !forToken.get().containsSelf()) + if (!forToken.get().containsSelf()) { StorageService.instance.incOutOfRangeOperationCount(); Keyspace.open(message.payload.getKeyspaceName()).metric.outOfRangeTokenWrites.inc(); @@ -95,7 +108,7 @@ public abstract class AbstractMutationVerbHandler implement throw InvalidRoutingException.forWrite(respondTo, key.getToken(), metadata.epoch, message.payload); } - if (!message.payload.allowsOutOfRangeMutations() && forToken.lastModified().isAfter(message.epoch())) + if (forToken.lastModified().isAfter(message.epoch())) { TCMMetrics.instance.coordinatorBehindPlacements.mark(); throw new CoordinatorBehindException(String.format("Routing is correct, but coordinator needs to catch-up at least to epoch %s to maintain consistency. Current coordinator epoch is %s", diff --git a/src/java/org/apache/cassandra/db/CassandraKeyspaceWriteHandler.java b/src/java/org/apache/cassandra/db/CassandraKeyspaceWriteHandler.java index ef9d0d1377..825ef12292 100644 --- a/src/java/org/apache/cassandra/db/CassandraKeyspaceWriteHandler.java +++ b/src/java/org/apache/cassandra/db/CassandraKeyspaceWriteHandler.java @@ -86,10 +86,10 @@ public class CassandraKeyspaceWriteHandler implements KeyspaceWriteHandler Set ids = new HashSet<>(); for (PartitionUpdate update : mutation.getPartitionUpdates()) { - if (update.metadata().params.memtable.factory().writesShouldSkipCommitLog()) + if (!update.metadata().params.memtable.factory().writesShouldSkipCommitLog()) ids.add(update.metadata().id); } - mutation = mutation.without(ids); + mutation = mutation.filter(ids::contains); } } // Note: It may be a good idea to precalculate none/all for the set of all tables in the keyspace, diff --git a/src/java/org/apache/cassandra/db/CounterMutation.java b/src/java/org/apache/cassandra/db/CounterMutation.java index 14d7a010ff..6935b09452 100644 --- a/src/java/org/apache/cassandra/db/CounterMutation.java +++ b/src/java/org/apache/cassandra/db/CounterMutation.java @@ -18,10 +18,15 @@ package org.apache.cassandra.db; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; +import java.util.function.Predicate; import java.util.function.Supplier; +import javax.annotation.Nullable; import com.google.common.base.Function; import com.google.common.base.Objects; @@ -31,11 +36,16 @@ import com.google.common.collect.PeekingIterator; import com.google.common.util.concurrent.Striped; import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.db.marshal.ByteBufferAccessor; -import org.apache.cassandra.db.rows.*; -import org.apache.cassandra.db.filter.*; -import org.apache.cassandra.db.partitions.*; import org.apache.cassandra.db.context.CounterContext; +import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter; +import org.apache.cassandra.db.filter.ColumnFilter; +import org.apache.cassandra.db.marshal.ByteBufferAccessor; +import org.apache.cassandra.db.partitions.PartitionUpdate; +import org.apache.cassandra.db.rows.Cell; +import org.apache.cassandra.db.rows.ColumnData; +import org.apache.cassandra.db.rows.Row; +import org.apache.cassandra.db.rows.RowIterator; +import org.apache.cassandra.db.rows.UnfilteredRowIterators; import org.apache.cassandra.exceptions.WriteTimeoutException; import org.apache.cassandra.io.IVersionedSerializer; import org.apache.cassandra.io.util.DataInputPlus; @@ -44,10 +54,11 @@ import org.apache.cassandra.locator.AbstractReplicationStrategy; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.service.CacheService; import org.apache.cassandra.tracing.Tracing; -import org.apache.cassandra.utils.*; +import org.apache.cassandra.utils.CounterId; +import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.btree.BTreeSet; -import static java.util.concurrent.TimeUnit.*; +import static java.util.concurrent.TimeUnit.NANOSECONDS; import static org.apache.cassandra.net.MessagingService.VERSION_40; import static org.apache.cassandra.net.MessagingService.VERSION_50; import static org.apache.cassandra.net.MessagingService.VERSION_51; @@ -83,6 +94,12 @@ public class CounterMutation implements IMutation return mutation.getPartitionUpdates(); } + @Override + public boolean hasUpdateForTable(TableId tableId) + { + return mutation.hasUpdateForTable(tableId); + } + @Override public Supplier hintOnFailure() { @@ -156,6 +173,27 @@ public class CounterMutation implements IMutation applyCounterMutation(); } + @Override + public @Nullable CounterMutation filter(Predicate test) + { + Mutation m = mutation.filter(test); + if (m == null) + return null; + if (m == mutation) + return this; + return new CounterMutation(m, consistency); + } + + /* + * Accord currently doesn't support interoperability with counters so no Accord transactions should read them + * anyways and it's safe to continue non-transactionally updating them + */ + @Override + public boolean allowsPotentialTransactionConflicts() + { + return true; + } + private void grabCounterLocks(Keyspace keyspace, List locks) throws WriteTimeoutException { long startTime = nanoTime(); diff --git a/src/java/org/apache/cassandra/db/IMutation.java b/src/java/org/apache/cassandra/db/IMutation.java index ba8d586dea..1e77ee7fc8 100644 --- a/src/java/org/apache/cassandra/db/IMutation.java +++ b/src/java/org/apache/cassandra/db/IMutation.java @@ -19,7 +19,9 @@ package org.apache.cassandra.db; import java.util.Collection; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; import java.util.function.Supplier; +import javax.annotation.Nullable; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.partitions.PartitionUpdate; @@ -37,6 +39,7 @@ public interface IMutation long getTimeout(TimeUnit unit); String toString(boolean shallow); Collection getPartitionUpdates(); + boolean hasUpdateForTable(TableId tableId); Supplier hintOnFailure(); default void validateIndexedColumns(ClientState state) @@ -71,8 +74,23 @@ public interface IMutation return size; } - default boolean allowsOutOfRangeMutations() + /** + * True if this mutation is being applied by a transaction system or doesn't need to be + * and conflicts between this mutation and transactions systems that are managing all or part of this table + * should be assumed to be handled already (by either Paxos or Accord) and the mutation should be applied. + * + * This causes mutations against tables to fail if they are from a non-transaction sub-system such as mutations, + * logged and unlogged batches, hints, and read repair against tables that are being managed by a transaction system + * like Accord that can't safely read data that is written non-transactionally. + * + */ + default boolean allowsPotentialTransactionConflicts() { return false; } + + // Construct replacement mutation that is identical except it only includes updates for the specified tables + @Nullable IMutation filter(Predicate predicate); + + default void clearCachedSerializationsForRetry() {} } diff --git a/src/java/org/apache/cassandra/db/Keyspace.java b/src/java/org/apache/cassandra/db/Keyspace.java index 8b8cc9f2c9..f31662660b 100644 --- a/src/java/org/apache/cassandra/db/Keyspace.java +++ b/src/java/org/apache/cassandra/db/Keyspace.java @@ -47,8 +47,8 @@ import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry; import org.apache.cassandra.exceptions.WriteTimeoutException; import org.apache.cassandra.index.Index; import org.apache.cassandra.index.SecondaryIndexManager; -import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.sstable.format.SSTableReader; +import org.apache.cassandra.io.util.File; import org.apache.cassandra.locator.AbstractReplicationStrategy; import org.apache.cassandra.metrics.KeyspaceMetrics; import org.apache.cassandra.repair.KeyspaceRepairManager; @@ -58,14 +58,15 @@ import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.SchemaProvider; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper; import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.concurrent.AsyncPromise; import org.apache.cassandra.utils.concurrent.Future; import org.apache.cassandra.utils.concurrent.OpOrder; -import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; import org.apache.cassandra.utils.concurrent.Promise; +import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; @@ -545,6 +546,7 @@ public class Keyspace } try (WriteContext ctx = getWriteHandler().beginWrite(mutation, makeDurable)) { + ConsensusMigrationMutationHelper.validateSafeToExecuteNonTransactionally(mutation); for (PartitionUpdate upd : mutation.getPartitionUpdates()) { ColumnFamilyStore cfs = columnFamilyStores.get(upd.metadata().id); diff --git a/src/java/org/apache/cassandra/db/Mutation.java b/src/java/org/apache/cassandra/db/Mutation.java index d8ded1eeb1..f7afb8d568 100644 --- a/src/java/org/apache/cassandra/db/Mutation.java +++ b/src/java/org/apache/cassandra/db/Mutation.java @@ -19,15 +19,17 @@ package org.apache.cassandra.db; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.Predicate; import java.util.function.Supplier; +import javax.annotation.Nullable; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableCollection; @@ -54,6 +56,7 @@ import org.apache.cassandra.service.AbstractWriteResponseHandler; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.concurrent.Future; +import static com.google.common.base.Preconditions.checkState; import static org.apache.cassandra.net.MessagingService.VERSION_40; import static org.apache.cassandra.net.MessagingService.VERSION_50; import static org.apache.cassandra.net.MessagingService.VERSION_51; @@ -62,7 +65,8 @@ import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime; public class Mutation implements IMutation, Supplier { public static final MutationSerializer serializer = new MutationSerializer(); - public static final int ALLOW_OUT_OF_RANGE_MUTATIONS_FLAG = 0x01; + public static final int ALLOW_POTENTIAL_TRANSACTION_CONFLICTS = 0x01; + // todo this is redundant // when we remove it, also restore SerializationsTest.testMutationRead to not regenerate new Mutations each test @@ -84,33 +88,41 @@ public class Mutation implements IMutation, Supplier private static final int SERIALIZATION_VERSION_COUNT = MessagingService.Version.values().length; // Contains serialized representations of this mutation. - // Note: there is no functionality to clear/remove serialized instances, because a mutation must never - // be modified (e.g. calling add(PartitionUpdate)) when it's being serialized. + // Note: The cached serializations can be cleared when CoordinatorBehindException is being retried private final Serialization[] cachedSerializations = new Serialization[SERIALIZATION_VERSION_COUNT]; /** @see CassandraRelevantProperties#CACHEABLE_MUTATION_SIZE_LIMIT */ private static final long CACHEABLE_MUTATION_SIZE_LIMIT = CassandraRelevantProperties.CACHEABLE_MUTATION_SIZE_LIMIT.getLong(); - private boolean allowOutOfRangeMutations; + // Paxos & Accord manage conflicts directly and needs to apply mutations to tables/ranges + // that are only safe to write to from a transaction system. + // Don't refuse to apply this mutation because it should go through a transaction system + // because it is being applied by one or in a context where transaction conflicts don't occur + private boolean allowPotentialTransactionConflicts; public Mutation(PartitionUpdate update) { - this(update.metadata().keyspace, update.partitionKey(), ImmutableMap.of(update.metadata().id, update), approxTime.now(), update.metadata().params.cdc); + this(update, false); } - public Mutation(String keyspaceName, DecoratedKey key, ImmutableMap modifications, long approxCreatedAtNanos, boolean allowOutOfRangeMutations) + public Mutation(PartitionUpdate update, boolean allowPotentialTransactionConflicts) { - this(keyspaceName, key, modifications, approxCreatedAtNanos, cdcEnabled(modifications.values()), allowOutOfRangeMutations); + this(update.metadata().keyspace, update.partitionKey(), ImmutableMap.of(update.metadata().id, update), approxTime.now(), update.metadata().params.cdc, allowPotentialTransactionConflicts); } - public Mutation(String keyspaceName, DecoratedKey key, ImmutableMap modifications, long approxCreatedAtNanos, boolean cdcEnabled, boolean allowOutOfRangeMutations) + public Mutation(String keyspaceName, DecoratedKey key, ImmutableMap modifications, long approxCreatedAtNanos, boolean allowPotentialTransactionConflicts) + { + this(keyspaceName, key, modifications, approxCreatedAtNanos, cdcEnabled(modifications.values()), allowPotentialTransactionConflicts); + } + + public Mutation(String keyspaceName, DecoratedKey key, ImmutableMap modifications, long approxCreatedAtNanos, boolean cdcEnabled, boolean allowPotentialTransactionConflicts) { this.keyspaceName = keyspaceName; this.key = key; this.modifications = modifications; this.cdcEnabled = cdcEnabled; this.approxCreatedAtNanos = approxCreatedAtNanos; - this.allowOutOfRangeMutations = allowOutOfRangeMutations; + this.allowPotentialTransactionConflicts = allowPotentialTransactionConflicts; } private static boolean cdcEnabled(Iterable modifications) @@ -121,26 +133,35 @@ public class Mutation implements IMutation, Supplier return cdc; } - public Mutation without(Set tableIds) + @Override + public @Nullable Mutation filter(Predicate predicate) { - if (tableIds.isEmpty()) + boolean allMatch = true; + boolean noneMatch = true; + for (TableId tableId : modifications.keySet()) + { + boolean test = predicate.test(tableId); + allMatch &= test; + noneMatch &= !test; + } + if (allMatch) return this; + if (noneMatch) + return null; ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); for (Map.Entry update : modifications.entrySet()) - { - if (!tableIds.contains(update.getKey())) - { + if (predicate.test(update.getKey())) builder.put(update); - } - } - return new Mutation(keyspaceName, key, builder.build(), approxCreatedAtNanos, allowOutOfRangeMutations); + Map updates = builder.build(); + checkState(!updates.isEmpty(), "Updates should not be empty"); + return new Mutation(keyspaceName, key, builder.build(), approxCreatedAtNanos, allowPotentialTransactionConflicts); } - public Mutation without(TableId tableId) + public @Nullable Mutation without(TableId tableId) { - return without(Collections.singleton(tableId)); + return filter(otherTableId -> !tableId.equals(otherTableId)); } public String getKeyspaceName() @@ -168,6 +189,12 @@ public class Mutation implements IMutation, Supplier return approxCreatedAtNanos; } + @Override + public boolean hasUpdateForTable(TableId tableId) + { + return modifications.containsKey(tableId); + } + @Override public Supplier hintOnFailure() { @@ -215,18 +242,18 @@ public class Mutation implements IMutation, Supplier { assert !mutations.isEmpty(); - if (mutations.size() == ALLOW_OUT_OF_RANGE_MUTATIONS_FLAG) + if (mutations.size() == 1) return mutations.iterator().next(); Set updatedTables = new HashSet<>(); String ks = null; DecoratedKey key = null; - Boolean allowOutOfRangeMutations = null; + Boolean allowPotentialTransactionConflicts = null; for (Mutation mutation : mutations) { - if (allowOutOfRangeMutations != null && allowOutOfRangeMutations != mutation.allowOutOfRangeMutations) - throw new IllegalArgumentException("Can't merge mutations with differing policies on allowing out of range mutations"); - allowOutOfRangeMutations = mutation.allowOutOfRangeMutations; + if (allowPotentialTransactionConflicts != null && allowPotentialTransactionConflicts != mutation.allowPotentialTransactionConflicts) + throw new IllegalArgumentException("Can't merge mutations with differing policies on allowing potential transaction conflicts"); + allowPotentialTransactionConflicts = mutation.allowPotentialTransactionConflicts; updatedTables.addAll(mutation.modifications.keySet()); if (ks != null && !ks.equals(mutation.keyspaceName)) throw new IllegalArgumentException(); @@ -250,10 +277,10 @@ public class Mutation implements IMutation, Supplier if (updates.isEmpty()) continue; - modifications.put(table, updates.size() == ALLOW_OUT_OF_RANGE_MUTATIONS_FLAG ? updates.get(0) : PartitionUpdate.merge(updates)); + modifications.put(table, updates.size() == 1 ? updates.get(0) : PartitionUpdate.merge(updates)); updates.clear(); } - return new Mutation(ks, key, modifications.build(), approxTime.now(), allowOutOfRangeMutations); + return new Mutation(ks, key, modifications.build(), approxTime.now(), allowPotentialTransactionConflicts); } public Future applyFuture() @@ -310,25 +337,26 @@ public class Mutation implements IMutation, Supplier return cdcEnabled; } - public Mutation allowOutOfRangeMutations() + public void allowPotentialTransactionConflicts() { - allowOutOfRangeMutations = true; - return this; + allowPotentialTransactionConflicts = true; + Arrays.fill(cachedSerializations, null); } - public boolean allowsOutOfRangeMutations() + @Override + public boolean allowsPotentialTransactionConflicts() { - return allowOutOfRangeMutations; + return allowPotentialTransactionConflicts; } - private static int allowsOutOfRangeMutationsFlag(boolean allowOutOfRangeMutations) + private static int allowPotentialTransactionConflictsFlag(boolean allowPotentialTransactionConflicts) { - return allowOutOfRangeMutations ? ALLOW_OUT_OF_RANGE_MUTATIONS_FLAG : 0; + return allowPotentialTransactionConflicts ? ALLOW_POTENTIAL_TRANSACTION_CONFLICTS : 0; } - private static boolean allowsOutOfRangeMutations(int flags) + public static boolean allowPotentialTransactionConflicts(int flags) { - return (flags & ALLOW_OUT_OF_RANGE_MUTATIONS_FLAG) != 0; + return (flags & ALLOW_POTENTIAL_TRANSACTION_CONFLICTS) != 0; } public String toString() @@ -336,6 +364,12 @@ public class Mutation implements IMutation, Supplier return toString(false); } + @Override + public void clearCachedSerializationsForRetry() + { + Arrays.fill(cachedSerializations, null); + } + public String toString(boolean shallow) { StringBuilder buff = new StringBuilder("Mutation("); @@ -404,6 +438,13 @@ public class Mutation implements IMutation, Supplier */ public interface SimpleBuilder { + /** + * Assume any potential transaction conflicts that might occur by applying this mutation are already + * being handled by the caller + * @return this builder + */ + public SimpleBuilder allowPotentialTransactionConflicts(); + /** * Sets the timestamp to use for the following additions to this builder or any derived (update or row) builder. * @@ -517,7 +558,11 @@ public class Mutation implements IMutation, Supplier Map modifications = mutation.modifications; if (version >= VERSION_51) - out.write(allowsOutOfRangeMutationsFlag(mutation.allowsOutOfRangeMutations())); + { + int flags = 0; + flags |= allowPotentialTransactionConflictsFlag(mutation.allowPotentialTransactionConflicts); + out.write(flags); + } /* serialize the modifications in the mutation */ int size = modifications.size(); @@ -538,11 +583,11 @@ public class Mutation implements IMutation, Supplier { teeIn = new TeeDataInputPlus(in, dob, CACHEABLE_MUTATION_SIZE_LIMIT); - boolean allowsOutOfRangeMutations = false; + boolean allowPotentialTransactionConflicts = false; if (version >= VERSION_51) { int flags = teeIn.readByte(); - allowsOutOfRangeMutations = allowsOutOfRangeMutations(flags); + allowPotentialTransactionConflicts = allowPotentialTransactionConflicts(flags); } int size = teeIn.readUnsignedVInt32(); assert size > 0; @@ -550,7 +595,7 @@ public class Mutation implements IMutation, Supplier PartitionUpdate update = PartitionUpdate.serializer.deserialize(teeIn, version, flag); if (size == 1) { - m = new Mutation(update); + m = new Mutation(update, allowPotentialTransactionConflicts); } else { @@ -563,7 +608,7 @@ public class Mutation implements IMutation, Supplier update = PartitionUpdate.serializer.deserialize(teeIn, version, flag); modifications.put(update.metadata().id, update); } - m = new Mutation(update.metadata().keyspace, dk, modifications.build(), approxTime.now(), allowsOutOfRangeMutations); + m = new Mutation(update.metadata().keyspace, dk, modifications.build(), approxTime.now(), allowPotentialTransactionConflicts); } //Only cache serializations that don't hit the limit @@ -642,7 +687,7 @@ public class Mutation implements IMutation, Supplier if (size == 0L) { if (version >= VERSION_51) - size += TypeSizes.sizeof((byte)ALLOW_OUT_OF_RANGE_MUTATIONS_FLAG); // flags + size += TypeSizes.sizeof((byte)ALLOW_POTENTIAL_TRANSACTION_CONFLICTS); // flags size += TypeSizes.sizeofUnsignedVInt(mutation.modifications.size()); for (PartitionUpdate partitionUpdate : mutation.modifications.values()) size += serializer.serializedSize(partitionUpdate, version); @@ -663,10 +708,18 @@ public class Mutation implements IMutation, Supplier private final long approxCreatedAtNanos = approxTime.now(); private boolean empty = true; + private boolean allowPotentialTransactionConflicts; + public PartitionUpdateCollector(String keyspaceName, DecoratedKey key) + { + this(keyspaceName, key, false); + } + + public PartitionUpdateCollector(String keyspaceName, DecoratedKey key, boolean allowPotentialTransactionConflicts) { this.keyspaceName = keyspaceName; this.key = key; + this.allowPotentialTransactionConflicts = allowPotentialTransactionConflicts; } public PartitionUpdateCollector add(PartitionUpdate partitionUpdate) @@ -700,7 +753,7 @@ public class Mutation implements IMutation, Supplier public Mutation build() { - return new Mutation(keyspaceName, key, modifications.build(), approxCreatedAtNanos, false); + return new Mutation(keyspaceName, key, modifications.build(), approxCreatedAtNanos, allowPotentialTransactionConflicts); } } } diff --git a/src/java/org/apache/cassandra/db/SimpleBuilders.java b/src/java/org/apache/cassandra/db/SimpleBuilders.java index 3564eb1f10..af13cb9126 100644 --- a/src/java/org/apache/cassandra/db/SimpleBuilders.java +++ b/src/java/org/apache/cassandra/db/SimpleBuilders.java @@ -18,21 +18,30 @@ package org.apache.cassandra.db; import java.nio.ByteBuffer; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; -import org.apache.cassandra.schema.ColumnMetadata; -import org.apache.cassandra.schema.TableId; -import org.apache.cassandra.schema.TableMetadata; -import org.apache.cassandra.schema.Schema; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.db.context.CounterContext; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.CollectionType; +import org.apache.cassandra.db.marshal.ListType; +import org.apache.cassandra.db.marshal.MapType; +import org.apache.cassandra.db.marshal.SetType; import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.db.rows.BTreeRow; import org.apache.cassandra.db.rows.BufferCell; import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.db.rows.CellPath; import org.apache.cassandra.db.rows.Row; -import org.apache.cassandra.db.marshal.*; +import org.apache.cassandra.schema.ColumnMetadata; +import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.TableId; +import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.CounterId; import org.apache.cassandra.utils.FBUtilities; @@ -111,12 +120,20 @@ public abstract class SimpleBuilders private final Map updateBuilders = new HashMap<>(); + private boolean allowPotentialTransactionConflicts = false; + public MutationBuilder(String keyspaceName, DecoratedKey key) { this.keyspaceName = keyspaceName; this.key = key; } + public MutationBuilder allowPotentialTransactionConflicts() + { + allowPotentialTransactionConflicts = true; + return this; + } + public PartitionUpdate.SimpleBuilder update(TableMetadata metadata) { assert metadata.keyspace.equals(keyspaceName); @@ -145,9 +162,9 @@ public abstract class SimpleBuilders assert !updateBuilders.isEmpty() : "Cannot create empty mutation"; if (updateBuilders.size() == 1) - return new Mutation(updateBuilders.values().iterator().next().build()); + return new Mutation(updateBuilders.values().iterator().next().build(), allowPotentialTransactionConflicts); - Mutation.PartitionUpdateCollector mutationBuilder = new Mutation.PartitionUpdateCollector(keyspaceName, key); + Mutation.PartitionUpdateCollector mutationBuilder = new Mutation.PartitionUpdateCollector(keyspaceName, key, allowPotentialTransactionConflicts); for (PartitionUpdateBuilder builder : updateBuilders.values()) mutationBuilder.add(builder.build()); return mutationBuilder.build(); diff --git a/src/java/org/apache/cassandra/db/virtual/VirtualMutation.java b/src/java/org/apache/cassandra/db/virtual/VirtualMutation.java index 8c3b5b4afd..f07e0e3282 100644 --- a/src/java/org/apache/cassandra/db/virtual/VirtualMutation.java +++ b/src/java/org/apache/cassandra/db/virtual/VirtualMutation.java @@ -19,7 +19,9 @@ package org.apache.cassandra.db.virtual; import java.util.Collection; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; import java.util.function.Supplier; +import javax.annotation.Nullable; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableMap; @@ -107,6 +109,12 @@ public final class VirtualMutation implements IMutation return modifications.values(); } + @Override + public boolean hasUpdateForTable(TableId tableId) + { + return modifications.containsKey(tableId); + } + @Override public Supplier hintOnFailure() { @@ -123,4 +131,19 @@ public final class VirtualMutation implements IMutation { // no-op } + + @Override + public @Nullable VirtualMutation filter(Predicate test) + { + throw new UnsupportedOperationException(); + } + + /* + * Accord doesn't support reading/writing virtual tables yet so updating them non-transactionally is always safe + */ + @Override + public boolean allowsPotentialTransactionConflicts() + { + return true; + } } diff --git a/src/java/org/apache/cassandra/exceptions/RequestFailure.java b/src/java/org/apache/cassandra/exceptions/RequestFailure.java index b1dbbd8e74..2b2282731a 100644 --- a/src/java/org/apache/cassandra/exceptions/RequestFailure.java +++ b/src/java/org/apache/cassandra/exceptions/RequestFailure.java @@ -35,7 +35,10 @@ import static org.apache.cassandra.exceptions.ExceptionSerializer.nullableRemote /** * Allow inclusion of a serialized exception in failure response messages * This continues to use the same verb as the old failure response (whether a message payload or parameter) - * and has a nullable failure field that may contain a serialized in later versions. + * and has a nullable failure field that may contain a serialized exception in later versions. + * + * It's important to note RequestFailure is not a singleton for each type, unlike RequestFailureReason, + * since it might include a stack trace so don't compare using identity. */ public class RequestFailure { @@ -50,6 +53,7 @@ public class RequestFailure public static final RequestFailure INDEX_NOT_AVAILABLE = new RequestFailure(RequestFailureReason.INDEX_NOT_AVAILABLE); public static final RequestFailure COORDINATOR_BEHIND = new RequestFailure(RequestFailureReason.COORDINATOR_BEHIND); public static final RequestFailure READ_TOO_MANY_INDEXES = new RequestFailure(RequestFailureReason.READ_TOO_MANY_INDEXES); + public static final RequestFailure RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM = new RequestFailure(RequestFailureReason.RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM); static { @@ -114,6 +118,12 @@ public class RequestFailure if (t instanceof InvalidRoutingException) return INVALID_ROUTING; + if (t instanceof RetryOnDifferentSystemException) + return RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM; + + if (t instanceof CoordinatorBehindException) + return COORDINATOR_BEHIND; + return UNKNOWN; } @@ -133,6 +143,7 @@ public class RequestFailure case INDEX_NOT_AVAILABLE: return INDEX_NOT_AVAILABLE; case COORDINATOR_BEHIND: return COORDINATOR_BEHIND; case READ_TOO_MANY_INDEXES: return READ_TOO_MANY_INDEXES; + case RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM: return RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM; } } diff --git a/src/java/org/apache/cassandra/exceptions/RequestFailureReason.java b/src/java/org/apache/cassandra/exceptions/RequestFailureReason.java index 560b8d68e0..600cdebf86 100644 --- a/src/java/org/apache/cassandra/exceptions/RequestFailureReason.java +++ b/src/java/org/apache/cassandra/exceptions/RequestFailureReason.java @@ -34,21 +34,23 @@ import static org.apache.cassandra.net.MessagingService.VERSION_40; public enum RequestFailureReason { - UNKNOWN (0), - READ_TOO_MANY_TOMBSTONES (1), - TIMEOUT (2), - INCOMPATIBLE_SCHEMA (3), - READ_SIZE (4), + UNKNOWN (0), + READ_TOO_MANY_TOMBSTONES (1), + TIMEOUT (2), + INCOMPATIBLE_SCHEMA (3), + READ_SIZE (4), // below reason is only logged, but it does not have associated exception - NODE_DOWN (5), - INDEX_NOT_AVAILABLE (6), + NODE_DOWN (5), + INDEX_NOT_AVAILABLE (6), // below reason does not have an associated exception - READ_TOO_MANY_INDEXES (7), - NOT_CMS (8), - INVALID_ROUTING (9), - COORDINATOR_BEHIND (10), + READ_TOO_MANY_INDEXES (7), + NOT_CMS (8), + INVALID_ROUTING (9), + COORDINATOR_BEHIND (10), // The following codes have been ported from an external fork, where they were offset explicitly to avoid conflicts. - INDEX_BUILD_IN_PROGRESS (503); + INDEX_BUILD_IN_PROGRESS (503), + RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM (504), + ; static { diff --git a/src/java/org/apache/cassandra/exceptions/RetryOnDifferentSystemException.java b/src/java/org/apache/cassandra/exceptions/RetryOnDifferentSystemException.java new file mode 100644 index 0000000000..e0ca033a74 --- /dev/null +++ b/src/java/org/apache/cassandra/exceptions/RetryOnDifferentSystemException.java @@ -0,0 +1,30 @@ +/* + * 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.exceptions; + +/** + * Thrown when a non-transactional operation is attempted when the operation needs to be done transactionally (or vice versa) + * and it could interfere with operations performed transactionally or can't be applied by the chosen transaction system. + * + * The correct way to handle this is to forward the error the originator of the operation who can then retry it on + * the correct system. + */ +public class RetryOnDifferentSystemException extends RuntimeException +{ +} diff --git a/src/java/org/apache/cassandra/hints/HintDiagnostics.java b/src/java/org/apache/cassandra/hints/HintDiagnostics.java index 285193b8a8..67e1f73809 100644 --- a/src/java/org/apache/cassandra/hints/HintDiagnostics.java +++ b/src/java/org/apache/cassandra/hints/HintDiagnostics.java @@ -37,44 +37,44 @@ final class HintDiagnostics { if (isEnabled(HintEventType.DISPATCHER_CREATED)) service.publish(new HintEvent(HintEventType.DISPATCHER_CREATED, dispatcher, - dispatcher.hostId, dispatcher.address, null, null, null, null)); + dispatcher.hostId, dispatcher.address, null, null, null, null, null)); } static void dispatcherClosed(HintsDispatcher dispatcher) { if (isEnabled(HintEventType.DISPATCHER_CLOSED)) service.publish(new HintEvent(HintEventType.DISPATCHER_CLOSED, dispatcher, - dispatcher.hostId, dispatcher.address, null, null, null, null)); + dispatcher.hostId, dispatcher.address, null, null, null, null, null)); } static void dispatchPage(HintsDispatcher dispatcher) { if (isEnabled(HintEventType.DISPATCHER_PAGE)) service.publish(new HintEvent(HintEventType.DISPATCHER_PAGE, dispatcher, - dispatcher.hostId, dispatcher.address, null, null, null, null)); + dispatcher.hostId, dispatcher.address, null, null, null, null, null)); } static void abortRequested(HintsDispatcher dispatcher) { if (isEnabled(HintEventType.ABORT_REQUESTED)) service.publish(new HintEvent(HintEventType.ABORT_REQUESTED, dispatcher, - dispatcher.hostId, dispatcher.address, null, null, null, null)); + dispatcher.hostId, dispatcher.address, null, null, null, null, null)); } - static void pageSuccessResult(HintsDispatcher dispatcher, long success, long failures, long timeouts) + static void pageSuccessResult(HintsDispatcher dispatcher, long success, long failures, long timeouts, long retryDifferentSystem) { if (isEnabled(HintEventType.DISPATCHER_HINT_RESULT)) service.publish(new HintEvent(HintEventType.DISPATCHER_HINT_RESULT, dispatcher, dispatcher.hostId, dispatcher.address, HintResult.PAGE_SUCCESS, - success, failures, timeouts)); + success, failures, timeouts, retryDifferentSystem)); } - static void pageFailureResult(HintsDispatcher dispatcher, long success, long failures, long timeouts) + static void pageFailureResult(HintsDispatcher dispatcher, long success, long failures, long timeouts, long retryDifferentSystem) { if (isEnabled(HintEventType.DISPATCHER_HINT_RESULT)) service.publish(new HintEvent(HintEventType.DISPATCHER_HINT_RESULT, dispatcher, dispatcher.hostId, dispatcher.address, HintResult.PAGE_FAILURE, - success, failures, timeouts)); + success, failures, timeouts, retryDifferentSystem)); } private static boolean isEnabled(HintEventType type) diff --git a/src/java/org/apache/cassandra/hints/HintEvent.java b/src/java/org/apache/cassandra/hints/HintEvent.java index 695357e9b6..d709fbcd82 100644 --- a/src/java/org/apache/cassandra/hints/HintEvent.java +++ b/src/java/org/apache/cassandra/hints/HintEvent.java @@ -64,10 +64,13 @@ final class HintEvent extends DiagnosticEvent private final Long pageHintsFailed; @Nullable private final Long pageHintsTimeout; + @Nullable + private final Long pageHintsRetryDifferentSystem; HintEvent(HintEventType type, HintsDispatcher dispatcher, UUID targetHostId, InetAddressAndPort targetAddress, @Nullable HintResult dispatchResult, @Nullable Long pageHintsSuccessful, - @Nullable Long pageHintsFailed, @Nullable Long pageHintsTimeout) + @Nullable Long pageHintsFailed, @Nullable Long pageHintsTimeout, + @Nullable Long pageHintsRetryDifferentSystem) { this.type = type; this.dispatcher = dispatcher; @@ -77,6 +80,7 @@ final class HintEvent extends DiagnosticEvent this.pageHintsSuccessful = pageHintsSuccessful; this.pageHintsFailed = pageHintsFailed; this.pageHintsTimeout = pageHintsTimeout; + this.pageHintsRetryDifferentSystem = pageHintsRetryDifferentSystem; } public Enum getType() @@ -96,6 +100,7 @@ final class HintEvent extends DiagnosticEvent ret.put("hint.page.hints_succeeded", pageHintsSuccessful); ret.put("hint.page.hints_failed", pageHintsFailed); ret.put("hint.page.hints_timed_out", pageHintsTimeout); + ret.put("hint.page.hints_retry_different_system", pageHintsRetryDifferentSystem); } return ret; } diff --git a/src/java/org/apache/cassandra/hints/HintVerbHandler.java b/src/java/org/apache/cassandra/hints/HintVerbHandler.java index 73e6967e39..c91219d0d5 100644 --- a/src/java/org/apache/cassandra/hints/HintVerbHandler.java +++ b/src/java/org/apache/cassandra/hints/HintVerbHandler.java @@ -24,6 +24,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.db.partitions.PartitionUpdate; +import org.apache.cassandra.exceptions.RetryOnDifferentSystemException; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.net.IVerbHandler; import org.apache.cassandra.net.Message; @@ -34,6 +35,8 @@ import org.apache.cassandra.service.StorageService; import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.membership.NodeId; +import static org.apache.cassandra.exceptions.RequestFailureReason.RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM; + /** * Verb handler used both for hint dispatch and streaming. * @@ -100,8 +103,15 @@ public final class HintVerbHandler implements IVerbHandler } else { - // the common path - the node is both the destination and a valid replica for the hint. - hint.applyFuture().addCallback(o -> respond(message), e -> logger.debug("Failed to apply hint", e)); + try + { + // the common path - the node is both the destination and a valid replica for the hint. + hint.applyFuture().addCallback(o -> respond(message), e -> logger.debug("Failed to apply hint", e)); + } + catch (RetryOnDifferentSystemException e) + { + MessagingService.instance().respondWithFailure(RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM, message); + } } } diff --git a/src/java/org/apache/cassandra/hints/HintsBufferPool.java b/src/java/org/apache/cassandra/hints/HintsBufferPool.java index 275dbc37e6..f6a0c2b460 100644 --- a/src/java/org/apache/cassandra/hints/HintsBufferPool.java +++ b/src/java/org/apache/cassandra/hints/HintsBufferPool.java @@ -21,6 +21,8 @@ import java.io.Closeable; import java.util.UUID; import java.util.concurrent.BlockingQueue; +import com.google.common.annotations.VisibleForTesting; + import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; @@ -133,6 +135,13 @@ final class HintsBufferPool implements Closeable return HintsBuffer.create(bufferSize); } + @VisibleForTesting + public void clearUnsafe() + { + if (currentBuffer != null) + currentBuffer = currentBuffer.recycle(); + } + public void close() { currentBuffer.free(); diff --git a/src/java/org/apache/cassandra/hints/HintsCatalog.java b/src/java/org/apache/cassandra/hints/HintsCatalog.java index e989850dff..902b239dec 100644 --- a/src/java/org/apache/cassandra/hints/HintsCatalog.java +++ b/src/java/org/apache/cassandra/hints/HintsCatalog.java @@ -20,7 +20,11 @@ package org.apache.cassandra.hints; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; import javax.annotation.Nullable; @@ -30,10 +34,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.FSError; import org.apache.cassandra.io.FSReadError; import org.apache.cassandra.io.FSWriteError; +import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.utils.NativeLibrary; import org.apache.cassandra.utils.SyncUtil; @@ -110,6 +114,11 @@ final class HintsCatalog return stores.get(hostId); } + void deleteAllHintsUnsafe() + { + stores.values().forEach(HintsStore::deleteAllHintsUnsafe); + } + /** * Delete all hints for all host ids. * diff --git a/src/java/org/apache/cassandra/hints/HintsDispatchExecutor.java b/src/java/org/apache/cassandra/hints/HintsDispatchExecutor.java index 5a566e3639..5777062db9 100644 --- a/src/java/org/apache/cassandra/hints/HintsDispatchExecutor.java +++ b/src/java/org/apache/cassandra/hints/HintsDispatchExecutor.java @@ -26,9 +26,9 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BooleanSupplier; import java.util.function.Predicate; import java.util.function.Supplier; +import javax.annotation.Nullable; import com.google.common.util.concurrent.RateLimiter; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,10 +39,11 @@ import org.apache.cassandra.io.util.File; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.tcm.ClusterMetadata; -import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; import org.apache.cassandra.utils.concurrent.Future; +import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; +import static org.apache.cassandra.hints.HintsService.RETRY_ON_DIFFERENT_SYSTEM_UUID; /** * A multi-threaded (by default) executor for dispatching hints. @@ -273,7 +274,7 @@ final class HintsDispatchExecutor logger.trace("Dispatching hints file {}", descriptor.hintsFileName); InetAddressAndPort address = StorageService.instance.getEndpointForHostId(hostId); - if (address != null) + if (address != null || hostId == RETRY_ON_DIFFERENT_SYSTEM_UUID) return deliver(descriptor, address); // address == null means the target no longer exist; find new home for each hint entry. @@ -281,12 +282,12 @@ final class HintsDispatchExecutor return true; } - private boolean deliver(HintsDescriptor descriptor, InetAddressAndPort address) + private boolean deliver(HintsDescriptor descriptor, @Nullable InetAddressAndPort address) { File file = descriptor.file(hintsDirectory); InputPosition offset = store.getDispatchOffset(descriptor); - BooleanSupplier shouldAbort = () -> !isAlive.test(address) || isPaused.get(); + BooleanSupplier shouldAbort = () -> (!hostId.equals(RETRY_ON_DIFFERENT_SYSTEM_UUID) && (address == null || !isAlive.test(address)) || isPaused.get()); try (HintsDispatcher dispatcher = HintsDispatcher.create(file, rateLimiter, address, descriptor.hostId, shouldAbort)) { if (offset != null) @@ -298,7 +299,7 @@ final class HintsDispatchExecutor { store.delete(descriptor); store.cleanUp(descriptor); - logger.info("Finished hinted handoff of file {} to endpoint {}: {}", descriptor.fileName(), address, hostId); + logger.info("Finished hinted handoff of file {} to destination {}: {}", descriptor.fileName(), dispatcher.destination(), hostId); return true; } else @@ -322,7 +323,7 @@ final class HintsDispatchExecutor { store.markDispatchOffset(descriptor, dispatcher.dispatchPosition()); store.offerFirst(descriptor); - logger.info("Finished hinted handoff of file {} to endpoint {}: {}, partially", descriptor.fileName(), address, hostId); + logger.info("Finished hinted handoff of file {} to destination {}: {}, partially", descriptor.fileName(), dispatcher.destination(), hostId); } // for each hint in the hints file for a node that isn't part of the ring anymore, write RF hints for each replica diff --git a/src/java/org/apache/cassandra/hints/HintsDispatcher.java b/src/java/org/apache/cassandra/hints/HintsDispatcher.java index ce1f7282a6..6022f284df 100644 --- a/src/java/org/apache/cassandra/hints/HintsDispatcher.java +++ b/src/java/org/apache/cassandra/hints/HintsDispatcher.java @@ -19,31 +19,64 @@ package org.apache.cassandra.hints; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.BitSet; import java.util.Collection; import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; import java.util.UUID; +import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import com.google.common.util.concurrent.RateLimiter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.cassandra.concurrent.DebuggableTask.RunnableDebuggableTask; +import org.apache.cassandra.concurrent.ImmediateExecutor; +import org.apache.cassandra.concurrent.Stage; +import org.apache.cassandra.db.Mutation; import org.apache.cassandra.exceptions.RequestFailure; +import org.apache.cassandra.exceptions.RequestFailureReason; +import org.apache.cassandra.exceptions.WriteTimeoutException; import org.apache.cassandra.io.util.File; import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.locator.Replica; import org.apache.cassandra.metrics.HintsServiceMetrics; import org.apache.cassandra.net.Message; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.RequestCallback; +import org.apache.cassandra.service.accord.AccordService; +import org.apache.cassandra.service.accord.IAccordService; +import org.apache.cassandra.service.accord.IAccordService.AsyncTxnResult; +import org.apache.cassandra.service.accord.txn.TxnResult; +import org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper; +import org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper.SplitMutation; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.tcm.ownership.DataPlacement; +import org.apache.cassandra.tcm.ownership.VersionedEndpoints; +import org.apache.cassandra.transport.Dispatcher; +import org.apache.cassandra.utils.Clock; +import org.apache.cassandra.utils.MonotonicClock; +import org.apache.cassandra.utils.NoSpamLogger; import org.apache.cassandra.utils.concurrent.Condition; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; import static org.apache.cassandra.hints.HintsDispatcher.Callback.Outcome.FAILURE; import static org.apache.cassandra.hints.HintsDispatcher.Callback.Outcome.INTERRUPTED; +import static org.apache.cassandra.hints.HintsDispatcher.Callback.Outcome.RETRY_DIFFERENT_SYSTEM; import static org.apache.cassandra.hints.HintsDispatcher.Callback.Outcome.SUCCESS; import static org.apache.cassandra.hints.HintsDispatcher.Callback.Outcome.TIMEOUT; +import static org.apache.cassandra.hints.HintsService.RETRY_ON_DIFFERENT_SYSTEM_UUID; +import static org.apache.cassandra.metrics.HintsServiceMetrics.ACCORD_HINT_ENDPOINT; import static org.apache.cassandra.metrics.HintsServiceMetrics.updateDelayMetrics; import static org.apache.cassandra.net.Verb.HINT_REQ; +import static org.apache.cassandra.service.accord.txn.TxnResult.Kind.retry_new_protocol; import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime; import static org.apache.cassandra.utils.concurrent.Condition.newOneTimeCondition; @@ -56,19 +89,28 @@ import static org.apache.cassandra.utils.concurrent.Condition.newOneTimeConditio final class HintsDispatcher implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(HintsDispatcher.class); + private static final NoSpamLogger noSpamLogger = NoSpamLogger.getLogger(logger, 1, TimeUnit.MINUTES); private enum Action { CONTINUE, ABORT } private final HintsReader reader; final UUID hostId; + + @Nullable final InetAddressAndPort address; private final int messagingVersion; private final BooleanSupplier abortRequested; private InputPosition currentPagePosition; - private HintsDispatcher(HintsReader reader, UUID hostId, InetAddressAndPort address, int messagingVersion, BooleanSupplier abortRequested) + // Hints from the batch log that were attempted on Accord don't have a list of hosts that need hinting + // since Accord doesn't expose that on failure. If Accord no longer manages the range for this hint then we need + // to send the hint to all replicas after the page succeeds + private final Queue hintsNeedingRehinting = new LinkedList<>(); + + private HintsDispatcher(HintsReader reader, UUID hostId, @Nullable InetAddressAndPort address, int messagingVersion, BooleanSupplier abortRequested) { + checkArgument(address != null ^ hostId.equals(RETRY_ON_DIFFERENT_SYSTEM_UUID), "address must be nonnull or hostId must be " + RETRY_ON_DIFFERENT_SYSTEM_UUID); currentPagePosition = null; this.reader = reader; @@ -78,9 +120,9 @@ final class HintsDispatcher implements AutoCloseable this.abortRequested = abortRequested; } - static HintsDispatcher create(File file, RateLimiter rateLimiter, InetAddressAndPort address, UUID hostId, BooleanSupplier abortRequested) + static HintsDispatcher create(File file, RateLimiter rateLimiter, @Nullable InetAddressAndPort address, UUID hostId, BooleanSupplier abortRequested) { - int messagingVersion = MessagingService.instance().versions.get(address); + int messagingVersion = address == null ? MessagingService.current_version : MessagingService.instance().versions.get(address); HintsDispatcher dispatcher = new HintsDispatcher(HintsReader.open(file, rateLimiter), hostId, address, messagingVersion, abortRequested); HintDiagnostics.dispatcherCreated(dispatcher); return dispatcher; @@ -92,6 +134,11 @@ final class HintsDispatcher implements AutoCloseable reader.close(); } + String destination() + { + return address == null ? "RETRY_ON_DIFFERENT_SYSTEM" : address.toString(); + } + void seek(InputPosition position) { reader.seek(position); @@ -130,7 +177,19 @@ final class HintsDispatcher implements AutoCloseable private Action sendHintsAndAwait(HintsReader.Page page) { - Collection callbacks = new ArrayList<>(); + try + { + return doSendHintsAndAwait(page, null); + } + finally + { + hintsNeedingRehinting.clear(); + } + } + + private Action doSendHintsAndAwait(HintsReader.Page page, @Nullable BitSet hintsFilter) + { + List callbacks = new ArrayList<>(); /* * If hints file messaging version matches the version of the target host, we'll use the optimised path - @@ -138,50 +197,135 @@ final class HintsDispatcher implements AutoCloseable * * If that is not the case, we'll need to perform conversion to a newer (or an older) format, and decoding the hint * is an unavoidable intermediate step. + * + * If these hints are from the batchlog and were originally attempted on Accord then + * we also need to decode so we can route the Hint contents appropriately. + * + * If filtering of hints is requested, because this is retrying a page that had some retry on different system + * errors, then also don't go down the sendEncodedHints path since it won't re-route the mutation and will trigger + * the same retry on different system error. */ - Action action = reader.descriptor().messagingVersion() == messagingVersion - ? sendHints(page.buffersIterator(), callbacks, this::sendEncodedHint) - : sendHints(page.hintsIterator(), callbacks, this::sendHint); + boolean isBatchLogHints = hostId.equals(RETRY_ON_DIFFERENT_SYSTEM_UUID); + boolean sendEncodedHints = reader.descriptor().messagingVersion() == messagingVersion && !isBatchLogHints && hintsFilter == null; + // If the hints filter is set then splitting the hints is needed and encoded hints can't do that + checkState(!sendEncodedHints || hintsFilter == null, "Should not send encoded hints if hints filter is set"); + Action action = sendEncodedHints + ? sendHints(page.buffersIterator(), null, callbacks, this::sendEncodedHint) + : sendHints(page.hintsIterator(), hintsFilter, callbacks, this::sendHint); if (action == Action.ABORT) return action; - long success = 0, failures = 0, timeouts = 0; - for (Callback cb : callbacks) + BitSet retryDifferentSystemHints = new BitSet(callbacks.size()); + long success = 0, failures = 0, timeouts = 0, retryDifferentSystem = 0; + for (int i = 0; i < callbacks.size(); i++) { + Callback cb = callbacks.get(i); Callback.Outcome outcome = cb.await(); if (outcome == Callback.Outcome.SUCCESS) success++; else if (outcome == Callback.Outcome.FAILURE) failures++; else if (outcome == Callback.Outcome.TIMEOUT) timeouts++; + else if (outcome == RETRY_DIFFERENT_SYSTEM) + { + retryDifferentSystemHints.set(i); + retryDifferentSystem++; + } + else throw new IllegalStateException("Unhandled outcome: " + outcome); } - updateMetrics(success, failures, timeouts); + updateMetrics(success, failures, timeouts, retryDifferentSystem); - if (failures > 0 || timeouts > 0) + // If the only errors were retryDifferentSystem and we aren't already filtering the hints then retry + // immediately otherwise we will repeat the page later including any successful hints we may have already delivered + // Hints for the batch log can hit RETRY_DIFFERENT_SYSTEM but don't need to be retried here and it could result + // in the same hint ending up in hintsNeedingRehinting twice + boolean failedRetryDifferentSystem = false; + if (retryDifferentSystem > 0 && failures < 1 && timeouts < 1 && hintsFilter == null && !isBatchLogHints) { - HintDiagnostics.pageFailureResult(this, success, failures, timeouts); + reader.seek(currentPagePosition); + Action retryResult = doSendHintsAndAwait(page, retryDifferentSystemHints); + if (retryResult != Action.CONTINUE) + failedRetryDifferentSystem = true; + } + + if (failures > 0 || timeouts > 0 || failedRetryDifferentSystem) + { + HintDiagnostics.pageFailureResult(this, success, failures, timeouts, retryDifferentSystem); return Action.ABORT; } else { - HintDiagnostics.pageSuccessResult(this, success, failures, timeouts); + HintDiagnostics.pageSuccessResult(this, success, failures, timeouts, retryDifferentSystem); + rehintHintsNeedingRehinting(); return Action.CONTINUE; } } - private void updateMetrics(long success, long failures, long timeouts) + private void rehintHintsNeedingRehinting() + { + ClusterMetadata cm = ClusterMetadata.current(); + Hint hint; + while ((hint = hintsNeedingRehinting.poll()) != null) + { + HintsService.instance.writeForAllReplicas(hint); + Mutation mutation = hint.mutation; + // Also may need to apply locally because it's possible this is from the batchlog + // and we never applied it locally + // TODO (review): Additional error handling necessary? Hints are lossy + DataPlacement dataPlacement = cm.placements.get(cm.schema.getKeyspace(mutation.getKeyspaceName()).getMetadata().params.replication); + VersionedEndpoints.ForToken forToken = dataPlacement.writes.forToken(mutation.key().getToken()); + Replica self = forToken.get().selfIfPresent(); + if (self != null) + { + Stage.MUTATION.maybeExecuteImmediately(new RunnableDebuggableTask() + { + private final long approxCreationTimeNanos = MonotonicClock.Global.approxTime.now(); + private volatile long approxStartTimeNanos; + + @Override + public void run() + { + approxStartTimeNanos = MonotonicClock.Global.approxTime.now(); + mutation.apply(); + } + + @Override + public long creationTimeNanos() + { + return approxCreationTimeNanos; + } + + @Override + public long startTimeNanos() + { + return approxStartTimeNanos; + } + + @Override + public String description() + { + return "HintsService rehinting Accord txn"; + } + }); + } + } + + } + + private void updateMetrics(long success, long failures, long timeouts, long retryDifferentSystem) { HintsServiceMetrics.hintsSucceeded.mark(success); HintsServiceMetrics.hintsFailed.mark(failures); HintsServiceMetrics.hintsTimedOut.mark(timeouts); + HintsServiceMetrics.hintsRetryDifferentSystem.mark(retryDifferentSystem); } /* * Sending hints in compatibility mode. */ - - private Action sendHints(Iterator hints, Collection callbacks, Function sendFunction) + private Action sendHints(Iterator hints, @Nullable BitSet hintsFilter, Collection callbacks, Function sendFunction) { + int hintIndex = -1; while (hints.hasNext()) { if (abortRequested.getAsBoolean()) @@ -189,19 +333,95 @@ final class HintsDispatcher implements AutoCloseable HintDiagnostics.abortRequested(this); return Action.ABORT; } - callbacks.add(sendFunction.apply(hints.next())); + + T hint = hints.next(); + hintIndex++; + if (hintsFilter != null && !hintsFilter.get(hintIndex)) + continue; + + callbacks.add(sendFunction.apply(hint)); } return Action.CONTINUE; } private Callback sendHint(Hint hint) { - Callback callback = new Callback(hint.creationTime); - Message message = Message.out(HINT_REQ, new HintMessage(hostId, hint)); - MessagingService.instance().sendWithCallback(message, address, callback); + ClusterMetadata cm = ClusterMetadata.current(); + SplitHint splitHint = splitHintIntoAccordAndNormal(cm, hint); + Mutation accordHintMutation = splitHint.accordMutation; + Dispatcher.RequestTime requestTime = null; + AsyncTxnResult accordTxnResult = null; + if (accordHintMutation != null) + { + requestTime = Dispatcher.RequestTime.forImmediateExecution(); + accordTxnResult = accordHintMutation != null ? ConsensusMigrationMutationHelper.instance().mutateWithAccordAsync(cm, accordHintMutation, null, requestTime) : null; + } + + Hint normalHint = splitHint.normalHint; + Callback callback = new Callback(address, hint.creationTime, requestTime, accordTxnResult); + if (normalHint != null) + { + // We had a hint that was supposed to be done on Accord for the batch log (otherwise address would be non-null), + // but Accord no longer manages that table/range and now we don't know which nodes (if any) are missing the Mutation. + // Convert them to per replica hints *after* all the hints in this page have been applied so we can be reasonably sure + // this page isn't going to be played again thus avoiding any futher amplification from the same hint being + // replayed and repeatedly converted to per replica hints + if (address == null) + { + checkState(hostId.equals(RETRY_ON_DIFFERENT_SYSTEM_UUID), "If there is no address to send the hint to then the host ID should be BATCHLOG_ACCORD_HINT_UUID"); + callback.onResponse(null); + hintsNeedingRehinting.add(normalHint); + } + else + { + Message message = Message.out(HINT_REQ, new HintMessage(hostId, normalHint)); + MessagingService.instance().sendWithCallback(message, address, callback); + } + } + else + { + // Don't wait for a normal response that will never come since no hints were sent + callback.onResponse(null); + } + return callback; } + /** + * Result of splitting a hint across Accord and non-transactional boundaries + */ + private class SplitHint + { + private final Mutation accordMutation; + private final Hint normalHint; + + public SplitHint(Mutation accordMutation, Hint normalHint) + { + this.accordMutation = accordMutation; + this.normalHint = normalHint; + } + + @Override + public String toString() + { + return "SplitHint{" + + "accordMutation=" + accordMutation + + ", normalHint=" + normalHint + + '}'; + } + } + + private SplitHint splitHintIntoAccordAndNormal(ClusterMetadata cm, Hint hint) + { + SplitMutation splitMutation = ConsensusMigrationMutationHelper.instance().splitMutationIntoAccordAndNormal(hint.mutation, cm); + if (splitMutation.accordMutation == null) + return new SplitHint(null, hint); + if (splitMutation.normalMutation == null) + return new SplitHint(splitMutation.accordMutation, null); + Hint normalHint = Hint.create(splitMutation.normalMutation, hint.creationTime, splitMutation.normalMutation.smallestGCGS()); + return new SplitHint(splitMutation.accordMutation, normalHint); + } + /* * Sending hints in raw mode. */ @@ -209,23 +429,41 @@ final class HintsDispatcher implements AutoCloseable private Callback sendEncodedHint(ByteBuffer hint) { HintMessage.Encoded message = new HintMessage.Encoded(hostId, hint, messagingVersion); - Callback callback = new Callback(message.getHintCreationTime()); + Callback callback = new Callback(address, message.getHintCreationTime()); MessagingService.instance().sendWithCallback(Message.out(HINT_REQ, message), address, callback); return callback; } - static final class Callback implements RequestCallback + static final class Callback implements RequestCallback, Runnable { - enum Outcome { SUCCESS, TIMEOUT, FAILURE, INTERRUPTED } + enum Outcome { SUCCESS, TIMEOUT, FAILURE, INTERRUPTED, RETRY_DIFFERENT_SYSTEM } private final long start = approxTime.now(); private final Condition condition = newOneTimeCondition(); - private volatile Outcome outcome; + private Outcome normalOutcome; + private Outcome accordOutcome; + @Nullable + private final InetAddressAndPort to; private final long hintCreationNanoTime; + @Nullable + private final Dispatcher.RequestTime requestTime; + private final AsyncTxnResult accordTxnResult; - private Callback(long hintCreationTimeMillisSinceEpoch) + private Callback(@Nonnull InetAddressAndPort to, long hintCreationTimeMillisSinceEpoch) { + this(to, hintCreationTimeMillisSinceEpoch, null, null); + } + + private Callback(@Nullable InetAddressAndPort to, long hintCreationTimeMillisSinceEpoch, Dispatcher.RequestTime requestTime, @Nullable AsyncTxnResult accordTxnResult) + { + this.to = to != null ? to : ACCORD_HINT_ENDPOINT; this.hintCreationNanoTime = approxTime.translate().fromMillisSinceEpoch(hintCreationTimeMillisSinceEpoch); + this.requestTime = requestTime; + this.accordTxnResult = accordTxnResult; + if (accordTxnResult != null) + accordTxnResult.addListener(this, ImmediateExecutor.INSTANCE); + else + accordOutcome = SUCCESS; } Outcome await() @@ -240,8 +478,31 @@ final class HintsDispatcher implements AutoCloseable logger.warn("Hint dispatch was interrupted", e); return INTERRUPTED; } + normalOutcome = timedOut ? TIMEOUT : normalOutcome; - return timedOut ? TIMEOUT : outcome; + return outcome(); + } + + private Outcome outcome() + { + checkState((normalOutcome != null && accordOutcome != null) || (normalOutcome != SUCCESS || accordOutcome != SUCCESS), "Outcome for both normal and accord hint delivery should be known"); + if (normalOutcome == RETRY_DIFFERENT_SYSTEM || accordOutcome == RETRY_DIFFERENT_SYSTEM) + return RETRY_DIFFERENT_SYSTEM; + if (normalOutcome == TIMEOUT || accordOutcome == TIMEOUT) + return TIMEOUT; + if (normalOutcome == FAILURE || accordOutcome == FAILURE) + return FAILURE; + checkState(normalOutcome == SUCCESS && accordOutcome == SUCCESS, "Hint delivery should have been successful"); + return SUCCESS; + } + + private synchronized void maybeSignal() + { + if ((normalOutcome != null && accordOutcome != null) || normalOutcome == FAILURE || accordOutcome == FAILURE) + { + updateDelayMetrics(to, approxTime.now() - this.hintCreationNanoTime); + condition.signalAll(); + } } @Override @@ -253,16 +514,40 @@ final class HintsDispatcher implements AutoCloseable @Override public void onFailure(InetAddressAndPort from, RequestFailure failureMessage) { - outcome = FAILURE; - condition.signalAll(); + if (failureMessage.reason == RequestFailureReason.RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM) + normalOutcome = RETRY_DIFFERENT_SYSTEM; + else + normalOutcome = FAILURE; + maybeSignal(); } @Override public void onResponse(Message msg) { - updateDelayMetrics(msg.from(), approxTime.now() - this.hintCreationNanoTime); - outcome = SUCCESS; - condition.signalAll(); + normalOutcome = SUCCESS; + maybeSignal(); + } + + @Override + public void run() + { + try + { + IAccordService accord = AccordService.instance(); + TxnResult.Kind kind = accord.getTxnResult(accordTxnResult, true, null, requestTime).kind(); + if (kind == retry_new_protocol) + accordOutcome = RETRY_DIFFERENT_SYSTEM; + else + accordOutcome = SUCCESS; + } + catch (Exception e) + { + accordOutcome = e instanceof WriteTimeoutException ? TIMEOUT : FAILURE; + String msg = "Accord hint delivery transaction failed"; + if (noSpamLogger.getStatement(msg).shouldLog(Clock.Global.nanoTime())) + logger.error(msg, e); + } + maybeSignal(); } } } diff --git a/src/java/org/apache/cassandra/hints/HintsService.java b/src/java/org/apache/cassandra/hints/HintsService.java index a1877802d2..bd3de9521e 100644 --- a/src/java/org/apache/cassandra/hints/HintsService.java +++ b/src/java/org/apache/cassandra/hints/HintsService.java @@ -33,30 +33,32 @@ import java.util.stream.Collectors; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; -import org.apache.cassandra.db.Keyspace; -import org.apache.cassandra.io.util.File; -import org.apache.cassandra.locator.ReplicaLayout; -import org.apache.cassandra.utils.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.concurrent.ScheduledExecutors; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.ParameterizedClass; +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.dht.Token; import org.apache.cassandra.gms.FailureDetector; import org.apache.cassandra.gms.IFailureDetector; +import org.apache.cassandra.io.util.File; import org.apache.cassandra.locator.EndpointsForToken; import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.locator.ReplicaLayout; import org.apache.cassandra.metrics.HintedHandoffMetrics; import org.apache.cassandra.metrics.StorageMetrics; -import org.apache.cassandra.dht.Token; import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.MBeanWrapper; +import org.apache.cassandra.utils.TimeUUID; +import org.apache.cassandra.utils.concurrent.Future; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Iterables.transform; +import static org.apache.cassandra.config.CassandraRelevantProperties.HINT_DISPATCH_INTERVAL_MS; /** * A singleton-ish wrapper over various hints components: @@ -70,12 +72,35 @@ import static com.google.common.collect.Iterables.transform; */ public final class HintsService implements HintsServiceMBean { - private static final Logger logger = LoggerFactory.getLogger(HintsService.class); + // Dummy address to use for storing metrics for hints that will be retried on a different transaction system + // and aren't being sent to a specific node + public static final InetAddressAndPort RETRY_ON_DIFFERENT_SYSTEM_ADDRESS; + + static + { + try + { + RETRY_ON_DIFFERENT_SYSTEM_ADDRESS = InetAddressAndPort.getByNameOverrideDefaults("0.0.0.0", 65535); + } + catch (UnknownHostException e) + { + throw new RuntimeException(e); + } + } + + // Batch log replay may need to route some mutations to Accord which may fail and Hints are used for retry by the batch log. + // Write them to this endpoint which indicates that on replay hints will need to calculate the endpoints + // to deliver to since it's not really a per node hint, but part of a batch that needs replaying. + // This can also occur with regular mutations as well when some replicas return a retry error but quorum + // is reached so hinting is used to bring the other replicas up to date + public static final UUID RETRY_ON_DIFFERENT_SYSTEM_UUID = TimeUUID.atUnixMicrosWithLsbAsUUID(-1, -1); public static HintsService instance = new HintsService(); public static final String MBEAN_NAME = "org.apache.cassandra.hints:type=HintsService"; + private static final Logger logger = LoggerFactory.getLogger(HintsService.class); + private static final int MIN_BUFFER_SIZE = 32 << 20; static final ImmutableMap EMPTY_PARAMS = ImmutableMap.of(); @@ -226,7 +251,8 @@ public final class HintsService implements HintsServiceMBean HintsDispatchTrigger trigger = new HintsDispatchTrigger(catalog, writeExecutor, dispatchExecutor, isDispatchPaused); // triggering hint dispatch is now very cheap, so we can do it more often - every 10 seconds vs. every 10 minutes, // previously; this reduces mean time to delivery, and positively affects batchlog delivery latencies, too - triggerDispatchFuture = ScheduledExecutors.scheduledTasks.scheduleWithFixedDelay(trigger, 10, 10, TimeUnit.SECONDS); + long hintDispatchIntervalMs = HINT_DISPATCH_INTERVAL_MS.getLong(); + triggerDispatchFuture = ScheduledExecutors.scheduledTasks.scheduleWithFixedDelay(trigger, hintDispatchIntervalMs, hintDispatchIntervalMs, TimeUnit.MILLISECONDS); } public void pauseDispatch() @@ -322,6 +348,13 @@ public final class HintsService implements HintsServiceMBean catalog.deleteAllHints(); } + @VisibleForTesting + public void deleteAllHintsUnsafe() + { + catalog.deleteAllHintsUnsafe(); + bufferPool.clearUnsafe(); + } + /** * Deletes all hints for the provided destination. Doesn't make snapshots - should be used with care. * diff --git a/src/java/org/apache/cassandra/hints/HintsStore.java b/src/java/org/apache/cassandra/hints/HintsStore.java index cb3d67b8af..795c1479e1 100644 --- a/src/java/org/apache/cassandra/hints/HintsStore.java +++ b/src/java/org/apache/cassandra/hints/HintsStore.java @@ -18,6 +18,7 @@ package org.apache.cassandra.hints; import java.io.IOException; +import java.nio.channels.ClosedChannelException; import java.util.Deque; import java.util.HashSet; import java.util.Iterator; @@ -174,6 +175,8 @@ final class HintsStore boolean isLive() { + if (hostId.equals(HintsService.RETRY_ON_DIFFERENT_SYSTEM_UUID)) + return true; InetAddressAndPort address = address(); return address != null && FailureDetector.instance.isAlive(address); } @@ -193,6 +196,20 @@ final class HintsStore dispatchDequeue.offerLast(descriptor); } + void deleteAllHintsUnsafe() + { + try + { + closeWriter(); + } + catch (FSWriteError e) + { + if (!(e.getCause() instanceof ClosedChannelException)) + throw e; + } + deleteAllHints(); + } + void deleteAllHints() { HintsDescriptor descriptor; diff --git a/src/java/org/apache/cassandra/metrics/AccordClientRequestMetrics.java b/src/java/org/apache/cassandra/metrics/AccordClientRequestMetrics.java index 33f7e8f201..dec6a5d916 100644 --- a/src/java/org/apache/cassandra/metrics/AccordClientRequestMetrics.java +++ b/src/java/org/apache/cassandra/metrics/AccordClientRequestMetrics.java @@ -39,6 +39,8 @@ public class AccordClientRequestMetrics extends ClientRequestMetrics // Number of times a query was rejected by Accord in TxnQuery due to a migration back to Paxos public final Meter accordMigrationRejects; + public final Meter preempted; + public final Meter topologyMismatches; public AccordClientRequestMetrics(String scope) { @@ -48,6 +50,8 @@ public class AccordClientRequestMetrics extends ClientRequestMetrics migrationSkippedReads = Metrics.meter(factory.createMetricName("MigrationSkippedReads")); paxosKeyMigrations = Metrics.meter(factory.createMetricName("PaxosKeyMigrations")); accordMigrationRejects = Metrics.meter(factory.createMetricName("AccordMigrationRejects")); + preempted = Metrics.meter(factory.createMetricName("Preempted")); + topologyMismatches = Metrics.meter(factory.createMetricName("TopologyMismatches")); } @Override @@ -58,5 +62,8 @@ public class AccordClientRequestMetrics extends ClientRequestMetrics Metrics.remove(factory.createMetricName("MigrationSkippedReads")); Metrics.remove(factory.createMetricName("PaxosKeyMigrations")); Metrics.remove(factory.createMetricName("AccordMigrationRejects")); + Metrics.remove(factory.createMetricName("Preempted")); + Metrics.remove(factory.createMetricName("TopologyMismatches")); + } } diff --git a/src/java/org/apache/cassandra/metrics/ClientRequestMetrics.java b/src/java/org/apache/cassandra/metrics/ClientRequestMetrics.java index 61fcc34bf1..e22eec6f51 100644 --- a/src/java/org/apache/cassandra/metrics/ClientRequestMetrics.java +++ b/src/java/org/apache/cassandra/metrics/ClientRequestMetrics.java @@ -40,6 +40,8 @@ public class ClientRequestMetrics extends LatencyMetrics public final Meter readSizeAborts; public final Meter localRequests; public final Meter remoteRequests; + public final Meter retryDifferentSystem; + public final Meter retryCoordinatorBehind; public ClientRequestMetrics(String scope) { @@ -53,6 +55,8 @@ public class ClientRequestMetrics extends LatencyMetrics readSizeAborts = Metrics.meter(factory.createMetricName("ReadSizeAborts")); localRequests = Metrics.meter(factory.createMetricName("LocalRequests")); remoteRequests = Metrics.meter(factory.createMetricName("RemoteRequests")); + retryDifferentSystem = Metrics.meter(factory.createMetricName("RetryDifferentSystem")); + retryCoordinatorBehind = Metrics.meter(factory.createMetricName("RetryCoordinatorBehind")); } public void markAbort(Throwable cause) @@ -81,5 +85,7 @@ public class ClientRequestMetrics extends LatencyMetrics Metrics.remove(factory.createMetricName("ReadSizeAborts")); Metrics.remove(factory.createMetricName("LocalRequests")); Metrics.remove(factory.createMetricName("RemoteRequests")); + Metrics.remove(factory.createMetricName("RetryDifferentSystem")); + Metrics.remove(factory.createMetricName("RetryCoordinatorBehind")); } } diff --git a/src/java/org/apache/cassandra/metrics/HintsServiceMetrics.java b/src/java/org/apache/cassandra/metrics/HintsServiceMetrics.java index 2a8ce92776..c3203e74f4 100644 --- a/src/java/org/apache/cassandra/metrics/HintsServiceMetrics.java +++ b/src/java/org/apache/cassandra/metrics/HintsServiceMetrics.java @@ -17,6 +17,8 @@ */ package org.apache.cassandra.metrics; +import java.net.UnknownHostException; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +37,22 @@ import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics; public final class HintsServiceMetrics { public static final String TYPE_NAME = "HintsService"; + + // Hint metrics are by address and hints that are for Accord need an address + public static final InetAddressAndPort ACCORD_HINT_ENDPOINT; + + static + { + try + { + ACCORD_HINT_ENDPOINT = InetAddressAndPort.getByNameOverrideDefaults("0.0.0.0", 0); + } + catch (UnknownHostException e) + { + throw new RuntimeException(e); + } + } + private static final Logger logger = LoggerFactory.getLogger(HintsServiceMetrics.class); private static final MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME); @@ -42,6 +60,8 @@ public final class HintsServiceMetrics public static final Meter hintsSucceeded = Metrics.meter(factory.createMetricName("HintsSucceeded")); public static final Meter hintsFailed = Metrics.meter(factory.createMetricName("HintsFailed")); public static final Meter hintsTimedOut = Metrics.meter(factory.createMetricName("HintsTimedOut")); + public static final Meter hintsRetryDifferentSystem = Metrics.meter(factory.createMetricName("HintsRetryDifferentSystem")); + /** Histogram of all hint delivery delays */ private static final Histogram globalDelayHistogram = Metrics.histogram(factory.createMetricName("Hint_delays"), false); @@ -62,4 +82,9 @@ public final class HintsServiceMetrics globalDelayHistogram.update(delay); delayByEndpoint.get(endpoint).update(delay); } + + public static long getDelayCount(InetAddressAndPort endpoint) + { + return delayByEndpoint.get(endpoint).getCount(); + } } diff --git a/src/java/org/apache/cassandra/metrics/KeyspaceMetrics.java b/src/java/org/apache/cassandra/metrics/KeyspaceMetrics.java index c290c321f3..17102b6cc0 100644 --- a/src/java/org/apache/cassandra/metrics/KeyspaceMetrics.java +++ b/src/java/org/apache/cassandra/metrics/KeyspaceMetrics.java @@ -107,6 +107,7 @@ public class KeyspaceMetrics public final LatencyMetrics rangeMigration; public final Meter rangeMigrationUnexpectedFailures; public final Meter rangeMigrationDependencyLimitFailures; + public final Meter mutationsRejectedOnWrongSystem; /** Writes failed ideal consistency **/ public final Counter writeFailedIdealCL; /** Ideal CL write latency metrics */ @@ -257,6 +258,7 @@ public class KeyspaceMetrics rangeMigration = createLatencyMetrics("RangeMigration"); rangeMigrationUnexpectedFailures = createKeyspaceMeter("RangeMigrationUnexpectedFailures"); rangeMigrationDependencyLimitFailures = createKeyspaceMeter("RangeMigratingDependencyLimitFailures"); + mutationsRejectedOnWrongSystem = createKeyspaceMeter("MutationsRejectedOnWrongSystem"); writeFailedIdealCL = createKeyspaceCounter("WriteFailedIdealCL"); idealCLWriteLatency = createLatencyMetrics("IdealCLWrite"); diff --git a/src/java/org/apache/cassandra/metrics/TableMetrics.java b/src/java/org/apache/cassandra/metrics/TableMetrics.java index 86693ec8e2..b490a26299 100644 --- a/src/java/org/apache/cassandra/metrics/TableMetrics.java +++ b/src/java/org/apache/cassandra/metrics/TableMetrics.java @@ -196,6 +196,7 @@ public class TableMetrics public final LatencyMetrics accordRepair; public final TableMeter accordRepairUnexpectedFailures; public final TableMeter accordRepairDependencyLimitFailures; + public final TableMeter mutationsRejectedOnWrongSystem; /** percent of the data that is repaired */ public final Gauge percentRepaired; /** Reports the size of sstables in repaired, unrepaired, and any ongoing repair buckets */ @@ -817,6 +818,7 @@ public class TableMetrics accordRepair = createLatencyMetrics("AccordRepair", cfs.keyspace.metric.rangeMigration, GLOBAL_RANGE_MIGRATION_LATENCY); accordRepairUnexpectedFailures = createTableMeter("AccordRepairUnexpectedFailures", cfs.keyspace.metric.rangeMigrationUnexpectedFailures); accordRepairDependencyLimitFailures = createTableMeter("AccordRepairDependencyLimitFaiures", cfs.keyspace.metric.rangeMigrationDependencyLimitFailures); + mutationsRejectedOnWrongSystem = createTableMeter("MutationsRejectedOnWrongSystem", cfs.keyspace.metric.mutationsRejectedOnWrongSystem); repairsStarted = createTableCounter("RepairJobsStarted"); repairsCompleted = createTableCounter("RepairJobsCompleted"); diff --git a/src/java/org/apache/cassandra/net/ResponseVerbHandler.java b/src/java/org/apache/cassandra/net/ResponseVerbHandler.java index f89362715b..517a10fd2a 100644 --- a/src/java/org/apache/cassandra/net/ResponseVerbHandler.java +++ b/src/java/org/apache/cassandra/net/ResponseVerbHandler.java @@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory; import org.apache.cassandra.concurrent.Stage; import org.apache.cassandra.exceptions.RequestFailure; +import org.apache.cassandra.exceptions.RequestFailureReason; import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadataService; import org.apache.cassandra.tracing.Tracing; @@ -33,6 +34,7 @@ import org.apache.cassandra.utils.FBUtilities; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static org.apache.cassandra.exceptions.RequestFailureReason.COORDINATOR_BEHIND; import static org.apache.cassandra.exceptions.RequestFailureReason.INVALID_ROUTING; +import static org.apache.cassandra.exceptions.RequestFailureReason.RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM; import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime; class ResponseVerbHandler implements IVerbHandler @@ -101,8 +103,11 @@ class ResponseVerbHandler implements IVerbHandler // Gossip stage is single-threaded, so we may end up in a deadlock with after-commit hook // that executes something on the gossip stage as well. - if (message.isFailureResponse() && - (message.payload == COORDINATOR_BEHIND || message.payload == INVALID_ROUTING) && + boolean isFailureResponse = message.isFailureResponse(); + // RequestFailure is not a singleton so we need to extract and compare against the reason + RequestFailureReason reason = isFailureResponse ? ((RequestFailure)message.payload).reason : null; + if (isFailureResponse && + (reason == COORDINATOR_BEHIND || reason == INVALID_ROUTING || reason == RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM) && // Gossip stage is single-threaded, so we may end up in a deadlock with after-commit hook // that executes something on the gossip stage as well. !Stage.GOSSIP.executor().inExecutor()) diff --git a/src/java/org/apache/cassandra/repair/RepairJob.java b/src/java/org/apache/cassandra/repair/RepairJob.java index ad0510cd81..3eaf206f80 100644 --- a/src/java/org/apache/cassandra/repair/RepairJob.java +++ b/src/java/org/apache/cassandra/repair/RepairJob.java @@ -39,6 +39,7 @@ import com.google.common.util.concurrent.FutureCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import accord.primitives.Ranges; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; @@ -68,6 +69,7 @@ import org.apache.cassandra.utils.concurrent.Future; import org.apache.cassandra.utils.concurrent.FutureCombiner; import org.apache.cassandra.utils.concurrent.ImmediateFuture; +import static com.google.common.util.concurrent.Futures.getUnchecked; import static org.apache.cassandra.config.DatabaseDescriptor.paxosRepairEnabled; import static org.apache.cassandra.schema.SchemaConstants.METADATA_KEYSPACE_NAME; import static org.apache.cassandra.service.paxos.Paxos.useV2; @@ -193,7 +195,7 @@ public class RepairJob extends AsyncFuture implements Runnable return; } - Future accordRepair; + Future accordRepair; if (doAccordRepair) { accordRepair = paxosRepair.flatMap(unused -> { @@ -213,12 +215,12 @@ public class RepairJob extends AsyncFuture implements Runnable if (session.accordOnly) { - accordRepair.addCallback(new FutureCallback() + accordRepair.addCallback(new FutureCallback<>() { - public void onSuccess(Void ignored) + public void onSuccess(Ranges barrieredRanges) { logger.info("{} {}.{} accord repair completed", session.previewKind.logPrefix(session.getId()), desc.keyspace, desc.columnFamily); - trySuccess(new RepairResult(desc, Collections.emptyList(), ConsensusMigrationRepairResult.fromAccordOnlyRepair(repairStartingEpoch, session.excludedDeadNodes))); + trySuccess(new RepairResult(desc, Collections.emptyList(), ConsensusMigrationRepairResult.fromAccordOnlyRepair(repairStartingEpoch, barrieredRanges, session.excludedDeadNodes))); } public void onFailure(Throwable t) @@ -282,7 +284,7 @@ public class RepairJob extends AsyncFuture implements Runnable } cfs.metric.repairsCompleted.inc(); logger.info("Completing repair with excludedDeadNodes {}", session.excludedDeadNodes); - trySuccess(new RepairResult(desc, stats, ConsensusMigrationRepairResult.fromRepair(repairStartingEpoch, doPaxosRepair, doAccordRepair, session.excludedDeadNodes))); + trySuccess(new RepairResult(desc, stats, ConsensusMigrationRepairResult.fromRepair(repairStartingEpoch, getUnchecked(accordRepair), doPaxosRepair, doAccordRepair, session.excludedDeadNodes))); } /** @@ -307,7 +309,7 @@ public class RepairJob extends AsyncFuture implements Runnable }, taskExecutor); } - private Future> createSyncTasks(Future accordRepair, Future allSnapshotTasks, List allEndpoints) + private Future> createSyncTasks(Future accordRepair, Future allSnapshotTasks, List allEndpoints) { Future> treeResponses; if (allSnapshotTasks != null) diff --git a/src/java/org/apache/cassandra/schema/DistributedSchema.java b/src/java/org/apache/cassandra/schema/DistributedSchema.java index 17f4d33ccb..e0658739c2 100644 --- a/src/java/org/apache/cassandra/schema/DistributedSchema.java +++ b/src/java/org/apache/cassandra/schema/DistributedSchema.java @@ -18,20 +18,8 @@ package org.apache.cassandra.schema; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; - import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; - import org.apache.cassandra.auth.AuthKeyspace; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.functions.UserFunction; @@ -47,6 +35,18 @@ import org.apache.cassandra.tcm.serialization.Version; import org.apache.cassandra.tracing.TraceKeyspace; import org.apache.cassandra.utils.FBUtilities; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + import static org.apache.cassandra.db.TypeSizes.sizeof; /** @@ -130,6 +130,11 @@ public class DistributedSchema implements MetadataValue return keyspaces.get(keyspace).get(); } + public Optional maybeGetKeyspaceMetadata(String keyspace) + { + return keyspaces.get(keyspace); + } + public TableMetadata getTableMetadata(TableId id) { return tables.get(id); diff --git a/src/java/org/apache/cassandra/service/AbstractWriteResponseHandler.java b/src/java/org/apache/cassandra/service/AbstractWriteResponseHandler.java index d085174043..4219cad301 100644 --- a/src/java/org/apache/cassandra/service/AbstractWriteResponseHandler.java +++ b/src/java/org/apache/cassandra/service/AbstractWriteResponseHandler.java @@ -25,7 +25,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Collectors; import javax.annotation.Nullable; import org.slf4j.Logger; @@ -36,8 +35,10 @@ import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.IMutation; import org.apache.cassandra.db.Mutation; import org.apache.cassandra.db.WriteType; +import org.apache.cassandra.exceptions.CoordinatorBehindException; import org.apache.cassandra.exceptions.RequestFailure; import org.apache.cassandra.exceptions.RequestFailureReason; +import org.apache.cassandra.exceptions.RetryOnDifferentSystemException; import org.apache.cassandra.exceptions.WriteFailureException; import org.apache.cassandra.exceptions.WriteTimeoutException; import org.apache.cassandra.locator.EndpointsForToken; @@ -51,6 +52,7 @@ import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.concurrent.Condition; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static java.lang.Long.MAX_VALUE; import static java.lang.Math.min; import static java.util.concurrent.TimeUnit.MICROSECONDS; @@ -59,6 +61,8 @@ import static java.util.stream.Collectors.toList; import static org.apache.cassandra.config.DatabaseDescriptor.getCounterWriteRpcTimeout; import static org.apache.cassandra.config.DatabaseDescriptor.getWriteRpcTimeout; import static org.apache.cassandra.db.WriteType.COUNTER; +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.locator.Replicas.countInOurDc; import static org.apache.cassandra.schema.Schema.instance; import static org.apache.cassandra.service.StorageProxy.WritePerformer; @@ -79,6 +83,10 @@ public abstract class AbstractWriteResponseHandler implements RequestCallback private static final AtomicIntegerFieldUpdater failuresUpdater = AtomicIntegerFieldUpdater.newUpdater(AbstractWriteResponseHandler.class, "failures"); private volatile int failures = 0; + private static final AtomicIntegerFieldUpdater alreadyHintedForRetryOnDifferentSystemUpdater = + AtomicIntegerFieldUpdater.newUpdater(AbstractWriteResponseHandler.class, "alreadyHintedForRetryOnDifferentSystem"); + // Only write a hint to be applied as a transaction once + private volatile int alreadyHintedForRetryOnDifferentSystem = 0; private volatile Map failureReasonByEndpoint; private final Dispatcher.RequestTime requestTime; private @Nullable final Supplier hintOnFailure; @@ -111,7 +119,7 @@ public abstract class AbstractWriteResponseHandler implements RequestCallback this.requestTime = requestTime; } - public void get() throws WriteTimeoutException, WriteFailureException + public void get() throws WriteTimeoutException, WriteFailureException, RetryOnDifferentSystemException { long timeoutNanos = currentTimeoutNanos(); @@ -128,14 +136,41 @@ public abstract class AbstractWriteResponseHandler implements RequestCallback if (!signaled) throwTimeout(); - if (blockFor() + failures > candidateReplicaCount()) + int candidateReplicaCount = candidateReplicaCount(); + if (blockFor() + failures > candidateReplicaCount) { - if (RequestCallback.isTimeout(this.getFailureReasonByEndpointMap().keySet().stream() - .filter(this::waitingFor) // DatacenterWriteResponseHandler filters errors from remote DCs - .collect(Collectors.toMap(Function.identity(), this.getFailureReasonByEndpointMap()::get)))) + // 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 failureReasonByEndpoint = getFailureReasonByEndpointMap().keySet().stream() + .filter(this::waitingFor) // DatacenterWriteResponseHandler filters errors from remote DCs + .collect(toImmutableMap(Function.identity(), getFailureReasonByEndpointMap()::get)); + final int failures = failureReasonByEndpoint.size(); + if (RequestCallback.isTimeout(failureReasonByEndpoint)) throwTimeout(); - throw new WriteFailureException(replicaPlan.consistencyLevel(), ackCount(), blockFor(), writeType, this.getFailureReasonByEndpointMap()); + 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; + + // Retrying might fix this + if (candidateReplicaCount - failures + totalRetriableFailures >= blockFor()) + { + // 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(); + throw new CoordinatorBehindException("Write request failed due to coordinator behind"); + } + + throw new WriteFailureException(replicaPlan.consistencyLevel(), ackCount(), blockFor(), writeType, getFailureReasonByEndpointMap()); } if (replicaPlan.stillAppliesTo(ClusterMetadata.current())) @@ -298,7 +333,7 @@ public abstract class AbstractWriteResponseHandler implements RequestCallback @Override public void onFailure(InetAddressAndPort from, RequestFailure failure) { - logger.trace("Got failure from {}", from); + logger.trace("Got failure {} from {}", failure, from); int n = waitingFor(from) ? failuresUpdater.incrementAndGet(this) @@ -317,8 +352,20 @@ public abstract class AbstractWriteResponseHandler implements RequestCallback if (blockFor() + n > candidateReplicaCount()) signal(); - if (hintOnFailure != null && StorageProxy.shouldHint(replicaPlan.lookup(from)) && requestTime.shouldSendHints()) - StorageProxy.submitHint(hintOnFailure.get(), replicaPlan.lookup(from), null); + // If the failure was RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM then we only want to hint once + // and not for each instance since odds are it will be applied as a transaction at all replicas + if (hintOnFailure != null && StorageProxy.shouldHint(replicaPlan.lookup(from)) ) + { + if (failure.reason == RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM) + { + if (alreadyHintedForRetryOnDifferentSystemUpdater.compareAndSet(this, 0, 1)) + StorageProxy.submitHintForRetryOnDifferentSystem(hintOnFailure.get()); + } + else + { + StorageProxy.submitHint(hintOnFailure.get(), replicaPlan.lookup(from), null); + } + } } @Override diff --git a/src/java/org/apache/cassandra/service/BatchlogResponseHandler.java b/src/java/org/apache/cassandra/service/BatchlogResponseHandler.java index dd2ebae915..41b68de395 100644 --- a/src/java/org/apache/cassandra/service/BatchlogResponseHandler.java +++ b/src/java/org/apache/cassandra/service/BatchlogResponseHandler.java @@ -29,9 +29,10 @@ import org.apache.cassandra.transport.Dispatcher; public class BatchlogResponseHandler extends AbstractWriteResponseHandler { - AbstractWriteResponseHandler wrapped; - BatchlogCleanup cleanup; + final AbstractWriteResponseHandler wrapped; + final BatchlogCleanup cleanup; protected volatile int requiredBeforeFinish; + private static final AtomicIntegerFieldUpdater requiredBeforeFinishUpdater = AtomicIntegerFieldUpdater.newUpdater(BatchlogResponseHandler.class, "requiredBeforeFinish"); @@ -104,6 +105,11 @@ public class BatchlogResponseHandler extends AbstractWriteResponseHandler this.callback = callback; } + public BatchlogCleanup(BatchlogCleanupCallback callback) + { + this.callback = callback; + } + public int decrement() { return mutationsWaitingForUpdater.decrementAndGet(this); @@ -114,6 +120,11 @@ public class BatchlogResponseHandler extends AbstractWriteResponseHandler if (decrement() == 0) callback.invoke(); } + + public void setMutationsWaitingFor(int mutationsWaitingFor) + { + mutationsWaitingForUpdater.lazySet(this, mutationsWaitingFor); + } } public interface BatchlogCleanupCallback diff --git a/src/java/org/apache/cassandra/service/StorageProxy.java b/src/java/org/apache/cassandra/service/StorageProxy.java index 59b39f5ee3..240c58f895 100644 --- a/src/java/org/apache/cassandra/service/StorageProxy.java +++ b/src/java/org/apache/cassandra/service/StorageProxy.java @@ -31,7 +31,6 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Future; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -42,14 +41,13 @@ import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import com.google.common.base.Preconditions; import com.google.common.cache.CacheLoader; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.Uninterruptibles; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import accord.primitives.Keys; import accord.primitives.Txn; import accord.utils.Invariants; import org.apache.cassandra.batchlog.Batch; @@ -58,7 +56,6 @@ import org.apache.cassandra.concurrent.DebuggableTask.RunnableDebuggableTask; import org.apache.cassandra.concurrent.Stage; import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.Config; -import org.apache.cassandra.service.consensus.TransactionalMode; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ConsistencyLevel; @@ -88,6 +85,7 @@ import org.apache.cassandra.db.view.ViewUtils; import org.apache.cassandra.dht.Token; import org.apache.cassandra.exceptions.CasWriteTimeoutException; import org.apache.cassandra.exceptions.CasWriteUnknownResultException; +import org.apache.cassandra.exceptions.CoordinatorBehindException; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.IsBootstrappingException; import org.apache.cassandra.exceptions.OverloadedException; @@ -98,6 +96,7 @@ import org.apache.cassandra.exceptions.ReadTimeoutException; import org.apache.cassandra.exceptions.RequestFailure; import org.apache.cassandra.exceptions.RequestFailureException; import org.apache.cassandra.exceptions.RequestTimeoutException; +import org.apache.cassandra.exceptions.RetryOnDifferentSystemException; import org.apache.cassandra.exceptions.UnavailableException; import org.apache.cassandra.exceptions.WriteFailureException; import org.apache.cassandra.exceptions.WriteTimeoutException; @@ -131,15 +130,14 @@ import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.accord.AccordService; import org.apache.cassandra.service.accord.IAccordService; -import org.apache.cassandra.service.accord.api.PartitionKey; -import org.apache.cassandra.service.accord.txn.TxnCondition; +import org.apache.cassandra.service.accord.IAccordService.AsyncTxnResult; import org.apache.cassandra.service.accord.txn.TxnData; 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.TxnResult; -import org.apache.cassandra.service.accord.txn.TxnUpdate; -import org.apache.cassandra.service.accord.txn.TxnWrite; +import org.apache.cassandra.service.consensus.TransactionalMode; +import org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper.SplitConsumer; +import org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper.SplitMutations; import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter; import org.apache.cassandra.service.paxos.Ballot; import org.apache.cassandra.service.paxos.Commit; @@ -165,8 +163,10 @@ import org.apache.cassandra.utils.MBeanWrapper; import org.apache.cassandra.utils.MonotonicClock; import org.apache.cassandra.utils.NoSpamLogger; import org.apache.cassandra.utils.Pair; +import org.apache.cassandra.utils.Throwables; import org.apache.cassandra.utils.TimeUUID; import org.apache.cassandra.utils.concurrent.CountDownLatch; +import org.apache.cassandra.utils.concurrent.Future; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; import static accord.primitives.Txn.Kind.EphemeralRead; @@ -197,6 +197,8 @@ import static org.apache.cassandra.service.StorageProxy.ConsensusAttemptResult.R import static org.apache.cassandra.service.StorageProxy.ConsensusAttemptResult.casResult; import static org.apache.cassandra.service.StorageProxy.ConsensusAttemptResult.serialReadResult; import static org.apache.cassandra.service.accord.txn.TxnResult.Kind.retry_new_protocol; +import static org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper.mutateWithAccordAsync; +import static org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper.splitMutationsIntoAccordAndNormal; import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.ConsensusRoutingDecision; import static org.apache.cassandra.service.paxos.Ballot.Flag.GLOBAL; import static org.apache.cassandra.service.paxos.Ballot.Flag.LOCAL; @@ -207,6 +209,8 @@ import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.apache.cassandra.utils.LocalizeString.toUpperCaseLocalized; import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime; +import static org.apache.cassandra.utils.Throwables.getStackTraceAsToString; +import static org.apache.cassandra.utils.Throwables.unchecked; import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUID; import static org.apache.cassandra.utils.concurrent.CountDownLatch.newCountDownLatch; import static org.apache.commons.lang3.StringUtils.join; @@ -1184,54 +1188,6 @@ public class StorageProxy implements StorageProxyMBean } } - private static ConsistencyLevel consistencyLevelForCommit(Collection mutations, ConsistencyLevel consistencyLevel) - { - ConsistencyLevel result = null; - for (IMutation mutation : mutations) - { - for (TableId tableId : mutation.getTableIds()) - { - TransactionalMode mode = Schema.instance.getTableMetadata(tableId).params.transactionalMode; - ConsistencyLevel commitCL = mode.commitCLForStrategy(consistencyLevel); - if (result == null || commitCL.compareTo(result) > 0) - result = commitCL; - } - } - return result; - } - - private static boolean writesThroughAccord(List mutations, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) - { - boolean accordWrite = false; - boolean normalWrite = false; - for (int i=0,mi=mutations.size(); i mutations, ConsistencyLevel consistencyLevel, @@ -1258,53 +1214,140 @@ public class StorageProxy implements StorageProxyMBean } } - Collection augmented = TriggerExecutor.instance.execute(mutations); + List augmented = TriggerExecutor.instance.execute(mutations); String keyspaceName = mutations.iterator().next().getKeyspaceName(); - boolean updatesView = Keyspace.open(mutations.iterator().next().getKeyspaceName()) + boolean updatesView = Keyspace.open(keyspaceName) .viewManager .updatesAffectView(mutations, true); - - long size = IMutation.dataSize(mutations); + long size = IMutation.dataSize(augmented != null ? augmented : mutations); writeMetrics.mutationSize.update(size); writeMetricsForLevel(consistencyLevel).mutationSize.update(size); - if (writesThroughAccord(mutations, consistencyLevel, requestTime)) - { - Preconditions.checkState(!SchemaConstants.getSystemKeyspaces().contains(keyspaceName)); - mutateWithAccord(augmented != null ? augmented : mutations, consistencyLevel, requestTime); - } - else if (augmented != null) - mutateAtomically(augmented, consistencyLevel, updatesView, requestTime); + if (augmented != null || mutateAtomically || updatesView) + mutateAtomically(augmented != null ? augmented : (List)mutations, consistencyLevel, updatesView, requestTime); else + dispatchMutationsWithRetryOnDifferentSystem(mutations, consistencyLevel, requestTime); + } + + public static void dispatchMutationsWithRetryOnDifferentSystem(List mutations, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) + { + while (true) { - if (mutateAtomically || updatesView) - mutateAtomically((Collection) mutations, consistencyLevel, updatesView, requestTime); - else - mutate(mutations, consistencyLevel, requestTime); + ClusterMetadata cm = ClusterMetadata.current(); + try + { + SplitMutations splitMutations = splitMutationsIntoAccordAndNormal(cm, (List)mutations); + List accordMutations = splitMutations.accordMutations(); + AsyncTxnResult accordResult = accordMutations != null ? mutateWithAccordAsync(cm, accordMutations, consistencyLevel, requestTime) : null; + List normalMutations = splitMutations.normalMutations(); + Tracing.trace("Split mutations into Accord {} and normal {}", accordMutations, normalMutations); + + Throwable failure = null; + try + { + if (normalMutations != null) + { + mutate(normalMutations, consistencyLevel, requestTime); + Tracing.trace("Successfully wrote normal mutations"); + } + } + catch (RetryOnDifferentSystemException e) + { + writeMetrics.retryDifferentSystem.mark(); + writeMetricsForLevel(consistencyLevel).retryDifferentSystem.mark(); + logger.debug("Retrying mutations on different system because some mutations were misrouted"); + Tracing.trace("Got {} from normal mutations, will retry", e); + continue; + } + catch (CoordinatorBehindException e) + { + writeMetrics.retryCoordinatorBehind.mark(); + writeMetricsForLevel(consistencyLevel).retryCoordinatorBehind.mark(); + mutations.forEach(IMutation::clearCachedSerializationsForRetry); + logger.debug("Retrying mutations now that coordinator has caught up to cluster metadata"); + Tracing.trace("Got {} from normal mutations, will retry", e); + continue; + } + catch (Exception e) + { + failure = Throwables.merge(failure, e); + } + + // Check if the Accord mutations succeeded asynchronously + try + { + if (accordResult != null) + { + IAccordService accord = AccordService.instance(); + TxnResult.Kind kind = accord.getTxnResult(accordResult, true, consistencyLevel, requestTime).kind(); + if (kind == retry_new_protocol) + continue; + Tracing.trace("Successfully wrote Accord mutations"); + } + } + catch (Exception e) + { + failure = Throwables.merge(failure, e); + } + + if (failure != null) + throw unchecked(failure); + } + catch (Exception t) + { + // Unexpected error so it would be helpful to have details + Tracing.trace("{}", getStackTraceAsToString(t)); + throw t; + } + break; } } - private static void mutateWithAccord(Collection mutations, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) + private static ConsistencyLevel consistencyLevelForBatchLog(ConsistencyLevel consistencyLevel, boolean requireQuorumForRemove) { - int fragmentIndex = 0; - List fragments = new ArrayList<>(mutations.size()); - List partitionKeys = new ArrayList<>(mutations.size()); - for (IMutation mutation : mutations) + // If we are requiring quorum nodes for removal, we upgrade consistency level to QUORUM unless we already + // require ALL, or EACH_QUORUM. This is so that *at least* QUORUM nodes see the update. + ConsistencyLevel batchConsistencyLevel = requireQuorumForRemove + ? ConsistencyLevel.QUORUM + : consistencyLevel; + + switch (consistencyLevel) { - for (PartitionUpdate update : mutation.getPartitionUpdates()) - { - PartitionKey pk = PartitionKey.of(update); - partitionKeys.add(pk); - fragments.add(new TxnWrite.Fragment(PartitionKey.of(update), fragmentIndex++, update, TxnReferenceOperations.empty())); - } + case ALL: + case EACH_QUORUM: + batchConsistencyLevel = consistencyLevel; + } + return batchConsistencyLevel; + } + + private static void doFallibleWriteWithMetricTracking(Runnable r, ConsistencyLevel consistencyLevel) + { + try + { + r.run(); + } + catch (UnavailableException e) + { + writeMetrics.unavailables.mark(); + writeMetricsForLevel(consistencyLevel).unavailables.mark(); + Tracing.trace("Unavailable"); + throw e; + } + catch (WriteTimeoutException e) + { + writeMetrics.timeouts.mark(); + writeMetricsForLevel(consistencyLevel).timeouts.mark(); + Tracing.trace("Write timeout; received {} of {} required replies", e.received, e.blockFor); + throw e; + } + catch (WriteFailureException e) + { + writeMetrics.failures.mark(); + writeMetricsForLevel(consistencyLevel).failures.mark(); + Tracing.trace("Write failure; received {} of {} required replies", e.received, e.blockFor); + throw e; } - // Potentially ignore commit consistency level if the strategy specifies accord and not migration - ConsistencyLevel clForCommit = consistencyLevelForCommit(mutations, consistencyLevel); - TxnUpdate update = new TxnUpdate(fragments, TxnCondition.none(), clForCommit); - Txn.InMemory txn = new Txn.InMemory(Keys.of(partitionKeys), TxnRead.EMPTY, TxnQuery.EMPTY, update); - IAccordService accordService = AccordService.instance(); - accordService.coordinate(txn, consistencyLevel, requestTime); } /** @@ -1314,92 +1357,171 @@ public class StorageProxy implements StorageProxyMBean * After: remove the batchlog entry (after writing hints for the batch rows, if necessary). * * @param mutations the Mutations to be applied across the replicas - * @param consistency_level the consistency level for the operation + * @param consistencyLevel the consistency level for the operation * @param requireQuorumForRemove at least a quorum of nodes will see update before deleting batchlog * @param requestTime object holding times when request got enqueued and started execution */ - public static void mutateAtomically(Collection mutations, - ConsistencyLevel consistency_level, + public static void mutateAtomically(List mutations, + ConsistencyLevel consistencyLevel, boolean requireQuorumForRemove, Dispatcher.RequestTime requestTime) throws UnavailableException, OverloadedException, WriteTimeoutException { Tracing.trace("Determining replicas for atomic batch"); long startTime = nanoTime(); - - List wrappers = new ArrayList<>(mutations.size()); + boolean attributeNonAccordLatency = true; + long nonAccordEndTime = -1; if (mutations.stream().anyMatch(mutation -> Keyspace.open(mutation.getKeyspaceName()).getReplicationStrategy().hasTransientReplicas())) throw new AssertionError("Logged batches are unsupported with transient replication"); try { - - // If we are requiring quorum nodes for removal, we upgrade consistency level to QUORUM unless we already - // require ALL, or EACH_QUORUM. This is so that *at least* QUORUM nodes see the update. - ConsistencyLevel batchConsistencyLevel = requireQuorumForRemove - ? ConsistencyLevel.QUORUM - : consistency_level; - - switch (consistency_level) - { - case ALL: - case EACH_QUORUM: - batchConsistencyLevel = consistency_level; - } - - ReplicaPlan.ForWrite replicaPlan = ReplicaPlans.forBatchlogWrite(batchConsistencyLevel == ConsistencyLevel.ANY); - + ConsistencyLevel batchConsistencyLevel = consistencyLevelForBatchLog(consistencyLevel, requireQuorumForRemove); + // This can't be updated for each iteration because cleanup has to go to the correct replicas which is where the batchlog is originally written + ReplicaPlan.ForWrite batchlogReplicaPlan = ReplicaPlans.forBatchlogWrite(ClusterMetadata.current(), batchConsistencyLevel == ConsistencyLevel.ANY); final TimeUUID batchUUID = nextTimeUUID(); - BatchlogCleanup cleanup = new BatchlogCleanup(mutations.size(), - () -> asyncRemoveFromBatchlog(replicaPlan, batchUUID, requestTime)); - - // add a handler for each mutation - includes checking availability, but doesn't initiate any writes, yet - for (Mutation mutation : mutations) + boolean wroteToBatchLog = false; + while (true) { - WriteResponseHandlerWrapper wrapper = wrapBatchResponseHandler(mutation, - consistency_level, - batchConsistencyLevel, - WriteType.BATCH, - cleanup, - requestTime); - // exit early if we can't fulfill the CL at this time. - wrappers.add(wrapper); + // In case we hit an error in before/during splitting + attributeNonAccordLatency = true; + ClusterMetadata cm = ClusterMetadata.current(); + List wrappers = new ArrayList<>(mutations.size()); + List accordMutations = new ArrayList<>(mutations.size()); + BatchlogCleanup cleanup = new BatchlogCleanup(() -> asyncRemoveFromBatchlog(batchlogReplicaPlan, batchUUID, requestTime)); + + // add a handler for each mutation that will not be written on Accord - includes checking availability, but doesn't initiate any writes, yet + SplitConsumer splitConsumer = (accordMutation, normalMutation, originalMutations, mutationIndex) -> { + Mutation eitherMutation = normalMutation != null ? normalMutation : accordMutation; + Keyspace keyspace = Keyspace.open(eitherMutation.getKeyspaceName()); + Token tk = eitherMutation.key().getToken(); + + if (accordMutation != null) + accordMutations.add(accordMutation); + + if (normalMutation == null) + return; + + // Always construct the replica plan to check availability + ReplicaPlan.ForWrite dataReplicaPlan = ReplicaPlans.forWrite(cm, keyspace, consistencyLevel, tk, ReplicaPlans.writeNormal); + + if (dataReplicaPlan.lookup(FBUtilities.getBroadcastAddressAndPort()) != null) + writeMetrics.localRequests.mark(); + else + writeMetrics.remoteRequests.mark(); + + WriteResponseHandlerWrapper wrapper = wrapBatchResponseHandler(normalMutation, + dataReplicaPlan, + batchConsistencyLevel, + WriteType.BATCH, + cleanup, + requestTime); + wrappers.add(wrapper); + }; + splitMutationsIntoAccordAndNormal(cm, mutations, splitConsumer); + attributeNonAccordLatency = !wrappers.isEmpty(); + cleanup.setMutationsWaitingFor(wrappers.size() + (accordMutations.isEmpty() ? 0 : 1)); + Tracing.trace("Split batch into Accord {} and normal {}", accordMutations, wrappers); + + // If the entire batch can execute on Accord then we can skip the batch log entirely + // Write to the batch log first in case it fails so we don't end up with Accord applying + // part of the batch independently + if (!wrappers.isEmpty() && !wroteToBatchLog) + { + // write to the batchlog, including writes that will be routed to Accord to preserve the behavior + // of the batch log where if part of a batch is visible then eventually the entire batch is visible. + // If the Accord routed mutations depend on the Accord txn succeeding then it is no longer consistent + // with the mutations delivered by the batch log since an unacknowledged Accord txn won't be retried + // unless those mutations are also written to the batch log + // Only write to the log once and reuse the batchUUID for every attempt to route the mutations correctly + doFallibleWriteWithMetricTracking(() -> syncWriteToBatchlog(mutations, batchlogReplicaPlan, batchUUID, requestTime), consistencyLevel); + Tracing.trace("Successfully wrote to batchlog"); + wroteToBatchLog = true; + } + + // Start Accord executing so it executes while the mutations are synchronously applied + AsyncTxnResult accordResult = !accordMutations.isEmpty() ? mutateWithAccordAsync(cm, accordMutations, consistencyLevel, requestTime) : null; + + Throwable failure = null; + try + { + // now actually perform the writes and wait for them to complete + if (!wrappers.isEmpty()) + { + doFallibleWriteWithMetricTracking(() -> syncWriteBatchedMutations(wrappers, Stage.MUTATION, requestTime), consistencyLevel); + Tracing.trace("Successfully wrote normal mutations"); + } + } + catch (RetryOnDifferentSystemException e) + { + writeMetrics.retryDifferentSystem.mark(); + writeMetricsForLevel(consistencyLevel).retryDifferentSystem.mark(); + logger.debug("Retrying batch txn on different system because some mutations were misrouted"); + Tracing.trace("Got {} from normal mutations, will retry", e); + continue; + } + catch (CoordinatorBehindException e) + { + writeMetrics.retryCoordinatorBehind.mark(); + writeMetricsForLevel(consistencyLevel).retryCoordinatorBehind.mark(); + mutations.forEach(IMutation::clearCachedSerializationsForRetry); + logger.debug("Retrying batch now that coordinator has caught up to cluster metadata"); + Tracing.trace("Got {} from normal mutations, will retry", e); + continue; + } + catch (Exception e) + { + failure = Throwables.merge(failure, e); + } + finally + { + // Try to exclude most of the Accord time + nonAccordEndTime = nanoTime(); + } + + // Check if the Accord mutations succeeded asynchronously + try + { + // It's notable here that the Accord portion of the batch will not be hinted + // while the regular mutations are hinted on failure and also going to be replayed later from + // the batch log. It wouldn't be difficult to add hinting here, but it does seem redundant with + // the batch log. + if (accordResult != null) + { + IAccordService accord = AccordService.instance(); + TxnResult.Kind kind = accord.getTxnResult(accordResult, true, consistencyLevel, requestTime).kind(); + if (kind == retry_new_protocol && failure == null) + continue; + Tracing.trace("Successfully wrote Accord mutations"); + cleanup.ackMutation(); + } + } + catch (Exception e) + { + failure = Throwables.merge(failure, e); + } + if (failure != null) + throw unchecked(failure); + break; } - - // write to the batchlog - syncWriteToBatchlog(mutations, replicaPlan, batchUUID, requestTime); - - // now actually perform the writes and wait for them to complete - syncWriteBatchedMutations(wrappers, Stage.MUTATION, requestTime); } - catch (UnavailableException e) + catch (Exception t) { - writeMetrics.unavailables.mark(); - writeMetricsForLevel(consistency_level).unavailables.mark(); - Tracing.trace("Unavailable"); - throw e; - } - catch (WriteTimeoutException e) - { - writeMetrics.timeouts.mark(); - writeMetricsForLevel(consistency_level).timeouts.mark(); - Tracing.trace("Write timeout; received {} of {} required replies", e.received, e.blockFor); - throw e; - } - catch (WriteFailureException e) - { - writeMetrics.failures.mark(); - writeMetricsForLevel(consistency_level).failures.mark(); - Tracing.trace("Write failure; received {} of {} required replies", e.received, e.blockFor); - throw e; + // Unexpected error so it would be helpful to have details + Tracing.trace("{}", getStackTraceAsToString(t)); + throw t; } finally { - long latency = nanoTime() - startTime; - writeMetrics.addNano(latency); - writeMetricsForLevel(consistency_level).addNano(latency); - updateCoordinatorWriteLatencyTableMetric(mutations, latency); + if (attributeNonAccordLatency) + { + // On the exception path nonAccordEndTime will be -1 + long latency = nonAccordEndTime != -1 ? nonAccordEndTime : nanoTime() - startTime; + writeMetrics.addNano(latency); + writeMetricsForLevel(consistencyLevel).addNano(latency); + updateCoordinatorWriteLatencyTableMetric(mutations, latency); + } } } @@ -1501,7 +1623,7 @@ public class StorageProxy implements StorageProxyMBean } } - private static void syncWriteBatchedMutations(List wrappers, Stage stage, Dispatcher.RequestTime requestTime) + private static void syncWriteBatchedMutations(Iterable wrappers, Stage stage, Dispatcher.RequestTime requestTime) throws WriteTimeoutException, OverloadedException { String localDataCenter = DatabaseDescriptor.getLocator().local().datacenter; @@ -1559,22 +1681,12 @@ public class StorageProxy implements StorageProxyMBean // same as performWrites except does not initiate writes (but does perform availability checks). private static WriteResponseHandlerWrapper wrapBatchResponseHandler(Mutation mutation, - ConsistencyLevel consistencyLevel, + ReplicaPlan.ForWrite replicaPlan, ConsistencyLevel batchConsistencyLevel, WriteType writeType, BatchlogResponseHandler.BatchlogCleanup cleanup, Dispatcher.RequestTime requestTime) { - Keyspace keyspace = Keyspace.open(mutation.getKeyspaceName()); - Token tk = mutation.key().getToken(); - - ReplicaPlan.ForWrite replicaPlan = ReplicaPlans.forWrite(keyspace, consistencyLevel, tk, ReplicaPlans.writeNormal); - - if (replicaPlan.lookup(FBUtilities.getBroadcastAddressAndPort()) != null) - writeMetrics.localRequests.mark(); - else - writeMetrics.remoteRequests.mark(); - AbstractReplicationStrategy rs = replicaPlan.replicationStrategy(); AbstractWriteResponseHandler writeHandler = rs.getWriteResponseHandler(replicaPlan, null, writeType, mutation, requestTime); BatchlogResponseHandler batchHandler = new BatchlogResponseHandler<>(writeHandler, batchConsistencyLevel.blockFor(rs), cleanup, requestTime); @@ -1603,13 +1715,17 @@ public class StorageProxy implements StorageProxyMBean } // used by atomic_batch_mutate to decouple availability check from the write itself, caches consistency level and endpoints. - private static class WriteResponseHandlerWrapper + public static class WriteResponseHandlerWrapper { - final BatchlogResponseHandler handler; - final Mutation mutation; + @Nonnull + public final BatchlogResponseHandler handler; + @Nonnull + public final Mutation mutation; - WriteResponseHandlerWrapper(BatchlogResponseHandler handler, Mutation mutation) + public WriteResponseHandlerWrapper(@Nonnull BatchlogResponseHandler handler, @Nonnull Mutation mutation) { + checkNotNull(handler); + checkNotNull(mutation); this.handler = handler; this.mutation = mutation; } @@ -1844,7 +1960,7 @@ public class StorageProxy implements StorageProxyMBean } catch (Exception ex) { - if (!(ex instanceof WriteTimeoutException)) + if (!(ex instanceof WriteTimeoutException) && !(ex instanceof RetryOnDifferentSystemException)) logger.error("Failed to apply mutation locally : ", ex); handler.onFailure(FBUtilities.getBroadcastAddressAndPort(), RequestFailure.forException(ex)); } @@ -2807,17 +2923,18 @@ public class StorageProxy implements StorageProxyMBean long timeTakenNanos = now - startTimeNanos(); MessagingService.instance().metrics.recordSelfDroppedMessage(Verb.MUTATION_REQ, timeTakenNanos, NANOSECONDS); - if (requestTime.shouldSendHints()) + // Don't submit a hint if this replica is transient + if (localReplica.isTransient()) + return; + + HintRunnable runnable = new HintRunnable(ImmutableSet.of(localReplica.endpoint())) { - HintRunnable runnable = new HintRunnable(EndpointsForToken.of(localReplica.range().right, localReplica)) + protected void runMayThrow() throws Exception { - protected void runMayThrow() throws Exception - { - LocalMutationRunnable.this.runMayThrow(); - } - }; - submitHint(runnable); - } + LocalMutationRunnable.this.runMayThrow(); + } + }; + submitHint(runnable); return; } @@ -2872,9 +2989,9 @@ public class StorageProxy implements StorageProxyMBean */ private abstract static class HintRunnable implements Runnable { - public final EndpointsForToken targets; + public final Set targets; - protected HintRunnable(EndpointsForToken targets) + protected HintRunnable(Set targets) { this.targets = targets; } @@ -2892,7 +3009,7 @@ public class StorageProxy implements StorageProxyMBean finally { StorageMetrics.totalHintsInProgress.dec(targets.size()); - for (InetAddressAndPort target : targets.endpoints()) + for (InetAddressAndPort target : targets) getHintsInProgressFor(target).decrementAndGet(); } } @@ -2938,25 +3055,43 @@ public class StorageProxy implements StorageProxyMBean } } - public static void submitHint(Mutation mutation, Replica target, AbstractWriteResponseHandler responseHandler) + public static void submitHintForRetryOnDifferentSystem(Mutation mutation) { - submitHint(mutation, EndpointsForToken.of(target.range().right, target), responseHandler); + submitHint(mutation, ImmutableSet.of(HintsService.RETRY_ON_DIFFERENT_SYSTEM_ADDRESS), null); } - private static void submitHint(Mutation mutation, - EndpointsForToken targets, - AbstractWriteResponseHandler responseHandler) + public static Future submitHint(Mutation mutation, Replica target, AbstractWriteResponseHandler responseHandler) + { + return submitHint(mutation, EndpointsForToken.of(target.range().right, target), responseHandler); + } + + private static Future submitHint(Mutation mutation, + EndpointsForToken targets, + AbstractWriteResponseHandler responseHandler) + { + // hints should not be written for transient replicas because there is no point if they didn't contribute + // to quorum, they would eventually be removed anyways after running incremental repair. + // This logic assumes we don't always write to transient replicas to minimize incremental repair mismatches + // so we may want to walk this back when revisiting transient replication + Replicas.assertFull(targets); + return submitHint(mutation, targets.endpoints(), responseHandler); + } + + private static Future submitHint(Mutation mutation, + Set targets, + AbstractWriteResponseHandler responseHandler) { - Replicas.assertFull(targets); // hints should not be written for transient replicas HintRunnable runnable = new HintRunnable(targets) { public void runMayThrow() { Set validTargets = new HashSet<>(targets.size()); Set hostIds = new HashSet<>(targets.size()); - for (InetAddressAndPort target : targets.endpoints()) + for (InetAddressAndPort target : targets) { - UUID hostId = StorageService.instance.getHostIdForEndpoint(target); + UUID hostId = target == HintsService.RETRY_ON_DIFFERENT_SYSTEM_ADDRESS ? + HintsService.RETRY_ON_DIFFERENT_SYSTEM_UUID : + StorageService.instance.getHostIdForEndpoint(target); if (hostId != null) { hostIds.add(hostId); @@ -2981,14 +3116,14 @@ public class StorageProxy implements StorageProxyMBean } }; - submitHint(runnable); + return submitHint(runnable); } private static Future submitHint(HintRunnable runnable) { StorageMetrics.totalHintsInProgress.inc(runnable.targets.size()); - for (Replica target : runnable.targets) - getHintsInProgressFor(target.endpoint()).incrementAndGet(); + for (InetAddressAndPort target : runnable.targets) + getHintsInProgressFor(target).incrementAndGet(); return (Future) Stage.MUTATION.submit(runnable); } @@ -3181,9 +3316,9 @@ public class StorageProxy implements StorageProxyMBean RowIterator casResult; @Nonnull - PartitionIterator serialReadResult; + public final PartitionIterator serialReadResult; - boolean shouldRetryOnNewConsensusProtocol; + public final boolean shouldRetryOnNewConsensusProtocol; private ConsensusAttemptResult(@Nullable RowIterator casResult, @Nullable PartitionIterator serialReadResult, boolean shouldRetryOnNewConsensusProtocol) { diff --git a/src/java/org/apache/cassandra/service/accord/AccordJournal.java b/src/java/org/apache/cassandra/service/accord/AccordJournal.java index 27e838cbcf..1e84338f5f 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordJournal.java +++ b/src/java/org/apache/cassandra/service/accord/AccordJournal.java @@ -36,6 +36,7 @@ import com.google.common.primitives.Ints; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import accord.coordinate.Timeout; import accord.local.Command; import accord.local.Node; import accord.messages.AbstractEpochRequest; @@ -660,7 +661,26 @@ public class AccordJournal implements IJournal, Shutdownable if (l.isEmpty()) waitForEpochs.pushLong(waitForEpoch); l.add(context); - node.withEpoch(waitForEpoch, this::runOnce); + BiConsumer withEpochCallback = new BiConsumer<>() + { + @Override + public void accept(Void unused, Throwable withEpochFailure) + { + if (withEpochFailure != null) + { + // Nothing to do but keep waiting + if (withEpochFailure instanceof Timeout) + { + node.withEpoch(waitForEpoch, this); + return; + } + else + throw new RuntimeException(withEpochFailure); + } + runOnce(); + } + }; + node.withEpoch(waitForEpoch, withEpochCallback); } // Next, process all delayed epochs diff --git a/src/java/org/apache/cassandra/service/accord/AccordService.java b/src/java/org/apache/cassandra/service/accord/AccordService.java index 8574b5db27..b3e03cb8c8 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordService.java +++ b/src/java/org/apache/cassandra/service/accord/AccordService.java @@ -27,49 +27,37 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.BiFunction; -import java.util.function.LongSupplier; +import java.util.function.Supplier; import java.util.stream.Collectors; import javax.annotation.Nonnull; - +import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.common.base.Stopwatch; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; import com.google.common.primitives.Ints; - -import accord.coordinate.Barrier; -import accord.coordinate.CoordinateSyncPoint; -import accord.coordinate.Exhausted; -import accord.coordinate.FailureAccumulator; -import accord.coordinate.TopologyMismatch; -import accord.impl.CoordinateDurabilityScheduling; -import accord.local.CommandStores; -import accord.primitives.SyncPoint; -import org.apache.cassandra.config.CassandraRelevantProperties; -import org.apache.cassandra.cql3.statements.RequestValidations; -import org.apache.cassandra.exceptions.RequestExecutionException; -import org.apache.cassandra.service.StorageService; -import org.apache.cassandra.locator.InetAddressAndPort; -import org.apache.cassandra.service.accord.exceptions.ReadExhaustedException; -import org.apache.cassandra.service.accord.interop.AccordInteropAdapter.AccordInteropFactory; -import org.apache.cassandra.tcm.ClusterMetadata; -import org.apache.cassandra.service.accord.repair.RepairSyncPointAdapter; -import org.apache.cassandra.tcm.ClusterMetadataService; -import org.apache.cassandra.service.accord.api.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import accord.api.BarrierType; import accord.api.Result; import accord.config.LocalConfig; +import accord.coordinate.Barrier; +import accord.coordinate.CoordinateSyncPoint; import accord.coordinate.CoordinationFailed; +import accord.coordinate.Exhausted; +import accord.coordinate.FailureAccumulator; import accord.coordinate.Preempted; import accord.coordinate.Timeout; +import accord.coordinate.TopologyMismatch; import accord.impl.AbstractConfigurationService; +import accord.impl.CoordinateDurabilityScheduling; import accord.impl.SimpleProgressLog; import accord.impl.SizeOfIntersectionSorter; +import accord.local.CommandStores; import accord.local.DurableBefore; import accord.local.Node; import accord.local.Node.Id; @@ -78,7 +66,11 @@ import accord.local.RedundantBefore; import accord.local.ShardDistributor.EvenSplit; import accord.messages.LocalRequest; import accord.messages.Request; +import accord.primitives.Keys; +import accord.primitives.Ranges; +import accord.primitives.Seekable; import accord.primitives.Seekables; +import accord.primitives.SyncPoint; import accord.primitives.Timestamp; import accord.primitives.Txn; import accord.primitives.Txn.Kind; @@ -91,25 +83,46 @@ import accord.utils.async.AsyncChains; import accord.utils.async.AsyncResult; import org.agrona.collections.Int2ObjectHashMap; import org.apache.cassandra.concurrent.Shutdownable; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.cql3.statements.RequestValidations; +import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.WriteType; import org.apache.cassandra.exceptions.ReadTimeoutException; +import org.apache.cassandra.exceptions.RequestExecutionException; import org.apache.cassandra.exceptions.RequestTimeoutException; import org.apache.cassandra.exceptions.WriteTimeoutException; +import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.metrics.AccordClientRequestMetrics; import org.apache.cassandra.net.IVerbHandler; import org.apache.cassandra.net.Message; import org.apache.cassandra.net.MessageDelivery; import org.apache.cassandra.net.MessagingService; +import org.apache.cassandra.schema.TableId; +import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.accord.AccordSyncPropagator.Notification; +import org.apache.cassandra.service.accord.api.AccordAgent; import org.apache.cassandra.service.accord.api.AccordRoutingKey.KeyspaceSplitter; +import org.apache.cassandra.service.accord.api.AccordScheduler; +import org.apache.cassandra.service.accord.api.AccordTopologySorter; +import org.apache.cassandra.service.accord.api.CompositeTopologySorter; +import org.apache.cassandra.service.accord.api.PartitionKey; +import org.apache.cassandra.service.accord.exceptions.ReadExhaustedException; import org.apache.cassandra.service.accord.exceptions.ReadPreemptedException; import org.apache.cassandra.service.accord.exceptions.WritePreemptedException; +import org.apache.cassandra.service.accord.interop.AccordInteropAdapter.AccordInteropFactory; +import org.apache.cassandra.service.accord.repair.RepairSyncPointAdapter; import org.apache.cassandra.service.accord.txn.TxnResult; +import org.apache.cassandra.service.consensus.TransactionalMode; +import org.apache.cassandra.service.consensus.migration.TableMigrationState; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.tcm.ClusterMetadataService; import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.membership.NodeId; import org.apache.cassandra.transport.Dispatcher; +import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.utils.Blocking; import org.apache.cassandra.utils.Clock; import org.apache.cassandra.utils.ExecutorUtils; @@ -120,7 +133,10 @@ import org.apache.cassandra.utils.concurrent.ImmediateFuture; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; import static accord.messages.SimpleReply.Ok; +import static accord.primitives.Routable.Domain.Key; +import static accord.primitives.Routable.Domain.Range; import static accord.utils.Invariants.checkState; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.apache.cassandra.config.DatabaseDescriptor.getPartitioner; @@ -161,15 +177,21 @@ public class AccordService implements IAccordService, Shutdownable } @Override - public long barrier(@Nonnull Seekables keysOrRanges, long minEpoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite) + public Seekables barrierWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException + { + throw new UnsupportedOperationException(); + } + + @Override + public Seekables barrier(@Nonnull Seekables keysOrRanges, long minEpoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite) { throw new UnsupportedOperationException("No accord barriers should be executed when accord.enabled = false in cassandra.yaml"); } @Override - public long barrierWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException + public Seekables repair(@Nonnull Seekables keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List allEndpoints) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("No accord repairs should be executed when accord.enabled = false in cassandra.yaml"); } @Override @@ -179,9 +201,15 @@ public class AccordService implements IAccordService, Shutdownable } @Override - public long repair(@Nonnull Seekables keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List allEndpoints) + public @Nonnull AsyncTxnResult coordinateAsync(@Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) { - throw new UnsupportedOperationException("No accord repairs 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"); + } + + @Override + public TxnResult getTxnResult(AsyncTxnResult asyncTxnResult, boolean isWrite, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) + { + throw new UnsupportedOperationException("No accord transaction should be executed when accord.enabled = false in cassandra.yaml"); } @Override @@ -328,7 +356,7 @@ public class AccordService implements IAccordService, Shutdownable this::handleLocalRequest, configService, AccordService::uniqueNow, - NodeTimeService.unixWrapper(TimeUnit.MICROSECONDS, AccordService::uniqueNow), + NodeTimeService.elapsedWrapperFromMonotonicSource(NANOSECONDS, Clock.Global::nanoTime), () -> dataStore, new KeyspaceSplitter(new EvenSplit<>(DatabaseDescriptor.getAccordShardCount(), getPartitioner().accordSplitter())), agent, @@ -369,8 +397,17 @@ public class AccordService implements IAccordService, Shutdownable return requestHandler; } - private > long barrier(@Nonnull S keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, BiFunction>> syncPoint) + private > Seekables barrier(@Nonnull S keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, BiFunction>> syncPoint) { + Stopwatch sw = Stopwatch.createStarted(); + keysOrRanges = (S)intersectionWithAccordManagedRanges(keysOrRanges); + // It's possible none of them were Accord managed and we aren't going to treat that as an error + if (keysOrRanges.isEmpty()) + { + logger.info("Skipping barrier because there are no ranges managed by Accord"); + return keysOrRanges; + } + AccordClientRequestMetrics metrics = isForWrite ? accordWriteMetrics : accordReadMetrics; TxnId txnId = null; try @@ -382,14 +419,15 @@ public class AccordService implements IAccordService, Shutdownable : Barrier.barrier(node, keysOrRanges, epoch, barrierType, syncPoint); long deadlineNanos = requestTime.startedAtNanos() + timeoutNanos; Timestamp barrierExecuteAt = AsyncChains.getBlocking(asyncResult, deadlineNanos - nanoTime(), NANOSECONDS); - logger.debug("Completed in {}ms barrier key: {} epoch: {} barrierType: {} isForWrite {}", + logger.debug("Completed barrier attempt in {}ms, {}ms since attempts start, barrier key: {} epoch: {} barrierType: {} isForWrite {}", + sw.elapsed(MILLISECONDS), NANOSECONDS.toMillis(nanoTime() - requestTime.startedAtNanos()), keysOrRanges, epoch, barrierType, isForWrite); - return barrierExecuteAt.epoch(); + return keysOrRanges; } catch (ExecutionException e) { - Throwable cause = e.getCause(); + Throwable cause = Throwables.getRootCause(e); if (cause instanceof Timeout) { metrics.timeouts.mark(); @@ -431,7 +469,7 @@ public class AccordService implements IAccordService, Shutdownable } @Override - public long barrier(@Nonnull Seekables keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite) + public Seekables barrier(@Nonnull Seekables keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite) { return barrier(keysOrRanges, epoch, requestTime, timeoutNanos, barrierType, isForWrite, null); } @@ -442,12 +480,59 @@ public class AccordService implements IAccordService, Shutdownable } @Override - public long repair(@Nonnull Seekables keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List allEndpoints) + public Seekables repair(@Nonnull Seekables keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List allEndpoints) { Set allNodes = allEndpoints.stream().map(configService::mappedId).collect(Collectors.toUnmodifiableSet()); return barrier(keysOrRanges, epoch, requestTime, timeoutNanos, barrierType, isForWrite, repairSyncPoint(allNodes)); } + private static > Seekables intersectionWithAccordManagedRanges(Seekables keysOrRanges) + { + TableId tableId = null; + for (Seekable seekable : keysOrRanges) + { + TableId newTableId; + if (keysOrRanges.domain() == Key) + newTableId = ((PartitionKey)seekable).table(); + else if (keysOrRanges.domain() == Range) + newTableId = ((TokenRange)seekable).table(); + else + throw new IllegalStateException("Unexpected domain " + keysOrRanges.domain()); + + if (tableId == null) + tableId = newTableId; + else if (!tableId.equals(newTableId)) + throw new IllegalArgumentException("Currently only one table is handled here."); + } + + ClusterMetadata cm = ClusterMetadata.current(); + ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(tableId); + TableMetadata tm = cfs.metadata(); + + // Barriers can be needed just because it's an Accord managed range, but it could also be a migration back to Paxos + // in which case we do want to barrier the migrating/migrated ranges even though the target for the migration is not Accord + // In either case Accord should be aware of those ranges and not generate a topology mismatch + if (tm.params.transactionalMode != TransactionalMode.off || tm.params.transactionalMigrationFrom.from != TransactionalMode.off) + { + TableMigrationState tms = cm.consensusMigrationState.tableStates.get(tm.id); + // null is fine could be completely migrated or was always an Accord table on creation + if (tms == null) + return keysOrRanges; + Ranges migratingAndMigratedRanges = AccordTopology.toAccordRanges(tms.tableId, tms.migratingAndMigratedRanges); + return keysOrRanges.slice(migratingAndMigratedRanges); + } + + switch (keysOrRanges.domain()) + { + case Key: + return Keys.EMPTY; + case Range: + return Ranges.EMPTY; + default: + throw new IllegalStateException("Only keys and ranges are supported"); + } + } + @VisibleForTesting static ReadTimeoutException newBarrierTimeout(TxnId txnId, boolean global) { @@ -474,21 +559,27 @@ public class AccordService implements IAccordService, Shutdownable } @VisibleForTesting - static long doWithRetries(Blocking blocking, LongSupplier action, int retryAttempts, long initialBackoffMillis, long maxBackoffMillis) throws InterruptedException + static Seekables doWithRetries(Blocking blocking, Supplier action, int retryAttempts, long initialBackoffMillis, long maxBackoffMillis) throws InterruptedException { // Since we could end up having the barrier transaction or the transaction it listens to invalidated Throwable existingFailures = null; - Long success = null; + Seekables success = null; long backoffMillis = initialBackoffMillis; for (int attempt = 0; attempt < retryAttempts; attempt++) { try { - success = action.getAsLong(); + success = action.get(); break; } + catch (TopologyMismatch topologyMismatch) + { + // Retry topology mismatch immediately because we should be able calculate the correct ranges immediately + backoffMillis = 0; + } catch (RequestExecutionException | CoordinationFailed newFailures) { + logger.error("Had failure on barrier", newFailures); existingFailures = FailureAccumulator.append(existingFailures, newFailures, AccordService::isTimeout); try @@ -514,6 +605,7 @@ public class AccordService implements IAccordService, Shutdownable } if (success == null) { + logger.error("Ran out of retries for barrier"); checkState(existingFailures != null, "Didn't have success, but also didn't have failures"); Throwables.throwIfUnchecked(existingFailures); throw new RuntimeException(existingFailures); @@ -522,7 +614,7 @@ public class AccordService implements IAccordService, Shutdownable } @Override - public long barrierWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException + public Seekables barrierWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException { return doWithRetries(Blocking.Default.instance, () -> AccordService.instance().barrier(keysOrRanges, minEpoch, Dispatcher.RequestTime.forImmediateExecution(), DatabaseDescriptor.getAccordRangeBarrierTimeoutNanos(), barrierType, isForWrite), DatabaseDescriptor.getAccordBarrierRetryAttempts(), @@ -531,7 +623,7 @@ public class AccordService implements IAccordService, Shutdownable } @Override - public long repairWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite, List allEndpoints) throws InterruptedException + public Seekables repairWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite, List allEndpoints) throws InterruptedException { return doWithRetries(Blocking.Default.instance, () -> AccordService.instance().repair(keysOrRanges, minEpoch, Dispatcher.RequestTime.forImmediateExecution(), DatabaseDescriptor.getAccordRangeBarrierTimeoutNanos(), barrierType, isForWrite, allEndpoints), DatabaseDescriptor.getAccordBarrierRetryAttempts(), @@ -558,38 +650,86 @@ public class AccordService implements IAccordService, Shutdownable @Override public @Nonnull TxnResult coordinate(@Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) { + AsyncTxnResult asyncTxnResult = coordinateAsync(txn, consistencyLevel, requestTime); + return getTxnResult(asyncTxnResult, txn.isWrite(), consistencyLevel, requestTime); + } + + @Override + public @Nonnull AsyncTxnResult coordinateAsync(Txn txn, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) + { + TxnId txnId = node.nextTxnId(txn.kind(), txn.keys().domain()); AccordClientRequestMetrics metrics = txn.isWrite() ? accordWriteMetrics : accordReadMetrics; - TxnId txnId = null; - try - { - metrics.keySize.update(txn.keys().size()); - txnId = node.nextTxnId(txn.kind(), txn.keys().domain()); - long deadlineNanos = requestTime.startedAtNanos() + DatabaseDescriptor.getTransactionTimeout(NANOSECONDS); - AsyncResult asyncResult = node.coordinate(txnId, txn); - Result result = AsyncChains.getBlocking(asyncResult, deadlineNanos - nanoTime(), NANOSECONDS); - return (TxnResult) result; - } - catch (ExecutionException e) - { - Throwable cause = e.getCause(); + metrics.keySize.update(txn.keys().size()); + AsyncResult asyncResult = node.coordinate(txnId, txn); + AsyncTxnResult asyncTxnResult = new AsyncTxnResult(txnId); + asyncResult.addCallback((success, failure) -> { + long durationNanos = nanoTime() - requestTime.startedAtNanos(); + metrics.addNano(durationNanos); + Throwable cause = failure != null ? Throwables.getRootCause(failure) : null; + if (success != null) + { + if (((TxnResult)success).kind() == TxnResult.Kind.retry_new_protocol) + { + metrics.retryDifferentSystem.mark(); + Tracing.trace("Got retry different system error from Accord, will retry"); + } + asyncTxnResult.trySuccess((TxnResult)success); + return; + } + if (cause instanceof Timeout) { - metrics.timeouts.mark(); - throw newTimeout(txnId, txn, consistencyLevel); + // Don't mark the metric here, should be done in getTxnResult to ensure it only happens once + // since both Accord and the thread blocked on the result can trigger a timeout + asyncTxnResult.tryFailure(newTimeout(txnId, txn.isWrite(), consistencyLevel)); + return; } if (cause instanceof Preempted) { + metrics.preempted.mark(); //TODO need to improve // Coordinator "could" query the accord state to see whats going on but that doesn't exist yet. // Protocol also doesn't have a way to denote "unknown" outcome, so using a timeout as the closest match - throw newPreempted(txnId, txn, consistencyLevel); + asyncTxnResult.tryFailure(newPreempted(txnId, txn.isWrite(), consistencyLevel)); + return; } if (cause instanceof TopologyMismatch) { - throw RequestValidations.invalidRequest(cause.getMessage()); + metrics.topologyMismatches.mark(); + asyncTxnResult.tryFailure(RequestValidations.invalidRequest(cause.getMessage())); + return; } metrics.failures.mark(); - throw new RuntimeException(cause); + asyncTxnResult.tryFailure(new RuntimeException(cause)); + }); + return asyncTxnResult; + } + + @Override + public TxnResult getTxnResult(AsyncTxnResult asyncTxnResult, boolean isWrite, @Nullable ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) + { + AccordClientRequestMetrics metrics = isWrite ? accordWriteMetrics : accordReadMetrics; + try + { + long deadlineNanos = requestTime.startedAtNanos() + DatabaseDescriptor.getTransactionTimeout(NANOSECONDS); + TxnResult result = asyncTxnResult.get(deadlineNanos - nanoTime(), NANOSECONDS); + return result; + } + catch (ExecutionException e) + { + // Metrics except timeout have already been handled + Throwable cause = e.getCause(); + if (cause instanceof RequestTimeoutException) + { + // Mark here instead of in coordinate async since this is where the request timeout actually occurs + metrics.timeouts.mark(); + cause.addSuppressed(e); + throw (RequestTimeoutException) cause; + } + else if (cause instanceof RuntimeException) + throw (RuntimeException)cause; + else + throw new RuntimeException(cause); } catch (InterruptedException e) { @@ -599,11 +739,7 @@ public class AccordService implements IAccordService, Shutdownable catch (TimeoutException e) { metrics.timeouts.mark(); - throw newTimeout(txnId, txn, consistencyLevel); - } - finally - { - metrics.addNano(nanoTime() - requestTime.startedAtNanos()); + throw newTimeout(asyncTxnResult.txnId, isWrite, consistencyLevel); } } @@ -614,15 +750,20 @@ public class AccordService implements IAccordService, Shutdownable journal.processLocalRequest(request, callback); } - private static RequestTimeoutException newTimeout(TxnId txnId, Txn txn, ConsistencyLevel consistencyLevel) + private static RequestTimeoutException newTimeout(TxnId txnId, boolean isWrite, ConsistencyLevel consistencyLevel) { - throw txn.isWrite() ? new WriteTimeoutException(WriteType.CAS, consistencyLevel, 0, 0, txnId.toString()) + // Client protocol doesn't handle null consistency level so use ANY + if (consistencyLevel == null) + consistencyLevel = ConsistencyLevel.ANY; + return isWrite ? new WriteTimeoutException(WriteType.CAS, consistencyLevel, 0, 0, txnId.toString()) : new ReadTimeoutException(consistencyLevel, 0, 0, false, txnId.toString()); } - private static RuntimeException newPreempted(TxnId txnId, Txn txn, ConsistencyLevel consistencyLevel) + private static RuntimeException newPreempted(TxnId txnId, boolean isWrite, ConsistencyLevel consistencyLevel) { - throw txn.isWrite() ? new WritePreemptedException(WriteType.CAS, consistencyLevel, 0, 0, txnId.toString()) + if (consistencyLevel == null) + consistencyLevel = ConsistencyLevel.ANY; + return isWrite ? new WritePreemptedException(WriteType.CAS, consistencyLevel, 0, 0, txnId.toString()) : new ReadPreemptedException(consistencyLevel, 0, 0, false, txnId.toString()); } diff --git a/src/java/org/apache/cassandra/service/accord/AccordVerbHandler.java b/src/java/org/apache/cassandra/service/accord/AccordVerbHandler.java index 92b54e28c7..5bf50308ea 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordVerbHandler.java +++ b/src/java/org/apache/cassandra/service/accord/AccordVerbHandler.java @@ -67,6 +67,10 @@ public class AccordVerbHandler implements IVerbHandler if (node.topology().hasEpoch(waitForEpoch)) request.process(node, fromNodeId, message); else - node.withEpoch(waitForEpoch, () -> request.process(node, fromNodeId, message)); + 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); + }); } } diff --git a/src/java/org/apache/cassandra/service/accord/IAccordService.java b/src/java/org/apache/cassandra/service/accord/IAccordService.java index b804e80fec..4a8c0be6bb 100644 --- a/src/java/org/apache/cassandra/service/accord/IAccordService.java +++ b/src/java/org/apache/cassandra/service/accord/IAccordService.java @@ -18,6 +18,15 @@ package org.apache.cassandra.service.accord; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.common.collect.ImmutableSet; + import accord.api.BarrierType; import accord.local.CommandStores; import accord.local.DurableBefore; @@ -27,8 +36,8 @@ import accord.messages.Request; import accord.primitives.Ranges; import accord.primitives.Seekables; import accord.primitives.Txn; +import accord.primitives.TxnId; import accord.topology.TopologyManager; -import com.google.common.collect.ImmutableSet; import org.agrona.collections.Int2ObjectHashMap; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ConsistencyLevel; @@ -37,19 +46,14 @@ import org.apache.cassandra.dht.Token; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.net.IVerbHandler; import org.apache.cassandra.net.Message; -import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; import org.apache.cassandra.service.accord.api.AccordScheduler; import org.apache.cassandra.service.accord.txn.TxnResult; import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.transport.Dispatcher; +import org.apache.cassandra.utils.concurrent.AsyncPromise; import org.apache.cassandra.utils.concurrent.Future; -import javax.annotation.Nonnull; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; +import static com.google.common.base.Preconditions.checkNotNull; public interface IAccordService { @@ -58,25 +62,21 @@ public interface IAccordService IVerbHandler verbHandler(); - long barrierWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException; + Seekables barrierWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException; - long barrier(@Nonnull Seekables keysOrRanges, long minEpoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite); + Seekables barrier(@Nonnull Seekables keysOrRanges, long minEpoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite); - default long repairWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite, List allEndpoints) throws InterruptedException + default Seekables repairWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite, List allEndpoints) throws InterruptedException { throw new UnsupportedOperationException(); } - long repair(@Nonnull Seekables keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List allEndpoints); + Seekables repair(@Nonnull Seekables keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List allEndpoints); default void postStreamReceivingBarrier(ColumnFamilyStore cfs, List> ranges) { String ks = cfs.keyspace.getName(); - Ranges accordRanges = Ranges.of(ranges - .stream() - .map(r -> new TokenRange(new TokenKey(cfs.getTableId(), r.left), new TokenKey(cfs.getTableId(), r.right))) - .collect(Collectors.toList()) - .toArray(new accord.primitives.Range[0])); + Ranges accordRanges = AccordTopology.toAccordRanges(ks, ranges); try { barrierWithRetries(accordRanges, Epoch.FIRST.getEpoch(), BarrierType.global_async, true); @@ -89,6 +89,21 @@ public interface IAccordService @Nonnull TxnResult coordinate(@Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime); + class AsyncTxnResult extends AsyncPromise + { + public final @Nonnull TxnId txnId; + + public AsyncTxnResult(@Nonnull TxnId txnId) + { + checkNotNull(txnId); + this.txnId = txnId; + } + } + + @Nonnull + AsyncTxnResult coordinateAsync(@Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime); + TxnResult getTxnResult(AsyncTxnResult asyncTxnResult, boolean isWrite, @Nullable ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime); + long currentEpoch(); void setCacheSize(long kb); diff --git a/src/java/org/apache/cassandra/service/accord/api/AccordAgent.java b/src/java/org/apache/cassandra/service/accord/api/AccordAgent.java index 899a00c4bf..7caeead9b1 100644 --- a/src/java/org/apache/cassandra/service/accord/api/AccordAgent.java +++ b/src/java/org/apache/cassandra/service/accord/api/AccordAgent.java @@ -132,10 +132,14 @@ public class AccordAgent implements Agent return 32; } + /** + * Create an empty transaction that Accord can use for its internal transactions. This is not suitable + * for tests since it skips validation done by regular transactions. + */ @Override - public Txn emptyTxn(Kind kind, Seekables seekables) + public Txn emptySystemTxn(Kind kind, Seekables seekables) { - return new Txn.InMemory(kind, seekables, TxnRead.EMPTY, TxnQuery.EMPTY, null); + return new Txn.InMemory(kind, seekables, TxnRead.EMPTY, TxnQuery.UNSAFE_EMPTY, null); } @Override diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java index bafb96b4db..5cff4f8184 100644 --- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java +++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java @@ -28,9 +28,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; -import accord.messages.ReadTxnData; -import accord.primitives.Ballot; -import org.apache.cassandra.schema.TableId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,6 +40,8 @@ import accord.local.Node; import accord.local.Node.Id; import accord.messages.Commit; import accord.messages.Commit.Kind; +import accord.messages.ReadTxnData; +import accord.primitives.Ballot; import accord.primitives.Deps; import accord.primitives.FullRoute; import accord.primitives.Participants; @@ -74,6 +73,7 @@ import org.apache.cassandra.metrics.AccordClientRequestMetrics; import org.apache.cassandra.net.Message; import org.apache.cassandra.net.RequestCallback; import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.TableId; import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.service.accord.AccordEndpointMapper; import org.apache.cassandra.service.accord.TokenRange; @@ -239,9 +239,9 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen @Override public void sendReadRepairMutation(Message message, InetAddressAndPort to, RequestCallback callback) { + checkArgument(message.payload.allowsPotentialTransactionConflicts()); Node.Id id = endpointMapper.mappedId(to); - Mutation mutation = message.payload; - AccordInteropReadRepair readRepair = new AccordInteropReadRepair(id, executes, txnId, readScope, executeAt.epoch(), mutation); + 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)); } @@ -260,8 +260,8 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen // 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, but we can respond faster skipping it - // and getting the transaction on the correct protocol + // It would be harmless to do the read, because it will be rejected in `TxnQuery` anyways, + // but it's faster to skip 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())) @@ -376,13 +376,7 @@ public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSen return readCommand.allowOutOfRangeReads(); } - @Override - public Mutation maybeAllowOutOfRangeMutations(Mutation m) - { - return m.allowOutOfRangeMutations(); - } - - // Prrovide 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 @Override public void sendMaximalCommit(Id to) { diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropReadCallback.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropReadCallback.java index 36dcedd09e..89b6577b83 100644 --- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropReadCallback.java +++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropReadCallback.java @@ -20,12 +20,14 @@ package org.apache.cassandra.service.accord.interop; import javax.annotation.Nonnull; +import accord.coordinate.Timeout; import accord.local.Node; import accord.messages.Callback; import accord.messages.ReadData.ReadOk; import accord.messages.ReadData.ReadReply; import accord.utils.Invariants; 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; @@ -78,7 +80,14 @@ public abstract class AccordInteropReadCallback implements Callback repairedRanges = new ArrayList<>(); for (accord.primitives.Range range : ranges) - repairRange((TokenRange)range); + repairedRanges.addAll(repairRange((TokenRange)range)); + return Ranges.of(repairedRanges.toArray(new accord.primitives.Range[0])); } - public Future repair(Executor executor) + public Future repair(Executor executor) { - AsyncPromise future = new AsyncPromise<>(); + AsyncPromise future = new AsyncPromise<>(); executor.execute(() -> { try { - repair(); - future.trySuccess(null); + future.trySuccess(repair()); } catch (Throwable e) { @@ -117,8 +119,9 @@ public class AccordRepair shouldAbort = reason == null ? new RuntimeException("Abort") : reason; } - private void repairRange(TokenRange range) throws Throwable + private List repairRange(TokenRange range) throws Throwable { + List repairedRanges = new ArrayList<>(); int rangeStepUpdateInterval = ACCORD_REPAIR_RANGE_STEP_UPDATE_INTERVAL.getInt(); RoutingKey remainingStart = range.start(); BigInteger rangeSize = splitter.sizeOf(range); @@ -154,7 +157,7 @@ public class AccordRepair if (remainingStart.equals(range.end())) { logger.info("Completed barriers for {} in {} iterations", range, iteration - 1); - return; + return repairedRanges; } // Final repair is whatever remains @@ -169,14 +172,13 @@ public class AccordRepair checkState(lastRepaired == null || toRepair.start().equals(lastRepaired.end()), "Next range should directly follow previous range"); lastRepaired = toRepair; + Ranges barrieredRanges; if (requireAllEndpoints) - { - AccordService.instance().repairWithRetries(Seekables.of(toRepair), minEpoch.getEpoch(), BarrierType.global_sync, false, endpoints); - } + barrieredRanges = (Ranges)AccordService.instance().repairWithRetries(Seekables.of(toRepair), minEpoch.getEpoch(), BarrierType.global_sync, false, endpoints); else - { - AccordService.instance().barrierWithRetries(Seekables.of(toRepair), minEpoch.getEpoch(), BarrierType.global_sync, false); - } + barrieredRanges = (Ranges)AccordService.instance().barrierWithRetries(Seekables.of(toRepair), minEpoch.getEpoch(), BarrierType.global_sync, false); + for (accord.primitives.Range barrieredRange : barrieredRanges) + repairedRanges.add(barrieredRange); remainingStart = toRepair.end(); } diff --git a/src/java/org/apache/cassandra/service/accord/txn/TxnQuery.java b/src/java/org/apache/cassandra/service/accord/txn/TxnQuery.java index defa96b554..7051e328c3 100644 --- a/src/java/org/apache/cassandra/service/accord/txn/TxnQuery.java +++ b/src/java/org/apache/cassandra/service/accord/txn/TxnQuery.java @@ -50,6 +50,11 @@ import static org.apache.cassandra.service.accord.txn.TxnRead.CAS_READ; 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 + * 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. + */ public static final TxnQuery ALL = new TxnQuery() { @Override @@ -65,6 +70,10 @@ public abstract class TxnQuery implements Query } }; + /** + * For transactions that return no results but do still care that they don't apply if the tokens/ranges + * are not owned/managed by Accord + */ public static final TxnQuery NONE = new TxnQuery() { @Override @@ -80,6 +89,9 @@ public abstract class TxnQuery implements Query } }; + /** + * For supporting CQL CAS compatible transactions + */ public static final TxnQuery CONDITION = new TxnQuery() { @Override @@ -114,7 +126,13 @@ public abstract class TxnQuery implements Query } }; - public static final TxnQuery EMPTY = new TxnQuery() + /** + * 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 + * for things like blind writes. Empty is used by Accord for things like sync points which may need to exeucte + * for ranges Accord used to manage, but no longer does. + */ + public static final TxnQuery UNSAFE_EMPTY = new TxnQuery() { @Override @@ -171,7 +189,7 @@ public abstract class TxnQuery implements Query @Override public void serialize(TxnQuery query, DataOutputPlus out, int version) throws IOException { - Preconditions.checkArgument(query == null | query == ALL | query == NONE | query == CONDITION | query == EMPTY); + Preconditions.checkArgument(query == null | query == ALL | query == NONE | query == CONDITION | query == UNSAFE_EMPTY); out.writeByte(query == null ? 0 : query.type()); } @@ -185,14 +203,14 @@ public abstract class TxnQuery implements Query case 1: return ALL; case 2: return NONE; case 3: return CONDITION; - case 4: return EMPTY; + case 4: return UNSAFE_EMPTY; } } @Override public long serializedSize(TxnQuery query, int version) { - Preconditions.checkArgument(query == null | query == ALL | query == NONE | query == CONDITION | query == EMPTY); + Preconditions.checkArgument(query == null | query == ALL | query == NONE | query == CONDITION | query == UNSAFE_EMPTY); return TypeSizes.sizeof((byte)2); } }; diff --git a/src/java/org/apache/cassandra/service/accord/txn/TxnUpdate.java b/src/java/org/apache/cassandra/service/accord/txn/TxnUpdate.java index 563af81675..674cf5cb23 100644 --- a/src/java/org/apache/cassandra/service/accord/txn/TxnUpdate.java +++ b/src/java/org/apache/cassandra/service/accord/txn/TxnUpdate.java @@ -69,7 +69,8 @@ import static org.apache.cassandra.utils.NullableSerializer.serializedNullableSi public class TxnUpdate extends AccordUpdate { - private static final long EMPTY_SIZE = ObjectSizes.measure(new TxnUpdate(null, new ByteBuffer[0], null, null)); + private static final long EMPTY_SIZE = ObjectSizes.measure(new TxnUpdate(null, new ByteBuffer[0], null, null, false)); + private static final int FLAG_PRESERVE_TIMESTAMPS = 0x1; private final Keys keys; private final ByteBuffer[] fragments; @@ -78,10 +79,15 @@ public class TxnUpdate extends AccordUpdate @Nullable private final ConsistencyLevel cassandraCommitCL; + // Hints and batchlog want to write with the lower timestamp they generated when applying their writes via Accord + // so they don't resurrect data if they are applied at a later time. Accord should be fine with this because + // the writes are still deterministic from the perspective of coordinators/recovery coordinators. + private final boolean preserveTimestamps; + // Memoize computation of condition private Boolean conditionResult; - public TxnUpdate(List fragments, TxnCondition condition, @Nullable ConsistencyLevel cassandraCommitCL) + public TxnUpdate(List fragments, TxnCondition condition, @Nullable ConsistencyLevel cassandraCommitCL, boolean preserveTimestamps) { checkArgument(cassandraCommitCL == null || IAccordService.SUPPORTED_COMMIT_CONSISTENCY_LEVELS.contains(cassandraCommitCL)); // TODO: Figure out a way to shove keys into TxnCondition, and have it implement slice/merge. @@ -90,14 +96,16 @@ public class TxnUpdate extends AccordUpdate this.fragments = toSerializedValuesArray(keys, fragments, fragment -> fragment.key, TxnWrite.Fragment.serializer); this.condition = serialize(condition, TxnCondition.serializer); this.cassandraCommitCL = cassandraCommitCL; + this.preserveTimestamps = preserveTimestamps; } - private TxnUpdate(Keys keys, ByteBuffer[] fragments, ByteBuffer condition, ConsistencyLevel cassandraCommitCL) + private TxnUpdate(Keys keys, ByteBuffer[] fragments, ByteBuffer condition, ConsistencyLevel cassandraCommitCL, boolean preserveTimestamps) { this.keys = keys; this.fragments = fragments; this.condition = condition; this.cassandraCommitCL = cassandraCommitCL; + this.preserveTimestamps = preserveTimestamps; } @Override @@ -141,12 +149,19 @@ public class TxnUpdate extends AccordUpdate return keys; } + // Batch log and hints want to keep their lower timestamp for the applied writes to avoid resurrecting old data + // when they are applied later, possibly after further updates have already been acknowledged. + public boolean preserveTimestamps() + { + return preserveTimestamps; + } + @Override public Update slice(Ranges ranges) { Keys keys = this.keys.slice(ranges); // TODO: Slice the condition. - return new TxnUpdate(keys, select(this.keys, keys, fragments), condition, cassandraCommitCL); + return new TxnUpdate(keys, select(this.keys, keys, fragments), condition, cassandraCommitCL, preserveTimestamps); } @Override @@ -154,7 +169,7 @@ public class TxnUpdate extends AccordUpdate { Keys keys = this.keys.intersecting(participants); // TODO: Slice the condition. - return new TxnUpdate(keys, select(this.keys, keys, fragments), condition, cassandraCommitCL); + return new TxnUpdate(keys, select(this.keys, keys, fragments), condition, cassandraCommitCL, preserveTimestamps); } private static ByteBuffer[] select(Keys in, Keys out, ByteBuffer[] from) @@ -176,7 +191,7 @@ public class TxnUpdate extends AccordUpdate TxnUpdate that = (TxnUpdate) update; Keys mergedKeys = this.keys.with(that.keys); ByteBuffer[] mergedFragments = merge(this.keys, that.keys, this.fragments, that.fragments, mergedKeys.size()); - return new TxnUpdate(mergedKeys, mergedFragments, condition, cassandraCommitCL); + return new TxnUpdate(mergedKeys, mergedFragments, condition, cassandraCommitCL, preserveTimestamps); } private static ByteBuffer[] merge(Keys leftKeys, Keys rightKeys, ByteBuffer[] left, ByteBuffer[] right, int outputSize) @@ -207,7 +222,6 @@ public class TxnUpdate extends AccordUpdate QueryOptions options = QueryOptions.forProtocolVersion(ProtocolVersion.CURRENT); AccordUpdateParameters parameters = new AccordUpdateParameters((TxnData) data, options); - // First completes all fragments and join them with the repairs pending for those partitions for (TxnWrite.Fragment fragment : fragments) // Filter out fragments that already constitute complete updates to avoid persisting them via TxnWrite: if (!fragment.isComplete()) @@ -233,6 +247,7 @@ public class TxnUpdate extends AccordUpdate @Override public void serialize(TxnUpdate update, DataOutputPlus out, int version) throws IOException { + out.writeByte(update.preserveTimestamps ? FLAG_PRESERVE_TIMESTAMPS : 0); KeySerializers.keys.serialize(update.keys, out, version); writeWithVIntLength(update.condition, out); serializeArray(update.fragments, out, version, ByteBufferUtil.byteBufferSerializer); @@ -242,17 +257,20 @@ public class TxnUpdate extends AccordUpdate @Override public TxnUpdate deserialize(DataInputPlus in, int version) throws IOException { + int flags = in.readByte(); + boolean preserveTimestamps = (FLAG_PRESERVE_TIMESTAMPS & flags) == 1; Keys keys = KeySerializers.keys.deserialize(in, version); ByteBuffer condition = readWithVIntLength(in); ByteBuffer[] fragments = deserializeArray(in, version, ByteBufferUtil.byteBufferSerializer, ByteBuffer[]::new); ConsistencyLevel consistencyLevel = deserializeNullable(in, version, consistencyLevelSerializer); - return new TxnUpdate(keys, fragments, condition, consistencyLevel); + return new TxnUpdate(keys, fragments, condition, consistencyLevel, preserveTimestamps); } @Override public long serializedSize(TxnUpdate update, int version) { - long size = KeySerializers.keys.serializedSize(update.keys, version); + long size = 1; // flags + size += KeySerializers.keys.serializedSize(update.keys, version); size += serializedSizeWithVIntLength(update.condition); size += serializedArraySize(update.fragments, version, ByteBufferUtil.byteBufferSerializer); size += serializedNullableSize(update.cassandraCommitCL, version, consistencyLevelSerializer); diff --git a/src/java/org/apache/cassandra/service/accord/txn/TxnWrite.java b/src/java/org/apache/cassandra/service/accord/txn/TxnWrite.java index 5e3e9ff831..7d6e67ad84 100644 --- a/src/java/org/apache/cassandra/service/accord/txn/TxnWrite.java +++ b/src/java/org/apache/cassandra/service/accord/txn/TxnWrite.java @@ -38,8 +38,8 @@ import accord.api.DataStore; import accord.api.Key; import accord.api.Write; import accord.impl.AbstractSafeCommandStore; -import accord.impl.TimestampsForKeys; import accord.impl.TimestampsForKey; +import accord.impl.TimestampsForKeys; import accord.local.SafeCommandStore; import accord.primitives.PartialTxn; import accord.primitives.RoutableKey; @@ -138,10 +138,12 @@ public class TxnWrite extends AbstractKeySorted implements Writ '}'; } - public AsyncChain write(@Nonnull Function cellToMaybeNewListPath, long timestamp, int nowInSeconds) + public AsyncChain write(boolean preserveTimestamps, @Nonnull Function cellToMaybeNewListPath, long timestamp, int nowInSeconds) { - PartitionUpdate update = new PartitionUpdate.Builder(get(), 0).updateTimesAndPathsForAccord(cellToMaybeNewListPath, timestamp, nowInSeconds).build(); - Mutation mutation = new Mutation(update); + PartitionUpdate update = get(); + if (!preserveTimestamps) + update = new PartitionUpdate.Builder(get(), 0).updateTimesAndPathsForAccord(cellToMaybeNewListPath, timestamp, nowInSeconds).build(); + Mutation mutation = new Mutation(update, true); return AsyncChains.ofRunnable(Stage.MUTATION.executor(), mutation::apply); } @@ -383,10 +385,11 @@ public class TxnWrite extends AbstractKeySorted implements Writ List> results = new ArrayList<>(); + boolean preserveTimestamps = ((TxnUpdate)txn.update()).preserveTimestamps(); // Apply updates not specified fully by the client but built from fragments completed by data from reads. // This occurs, for example, when an UPDATE statement uses a value assigned by a LET statement. Function accordListPathSuppler = accordListPathSupplier(timestamp); - forEachWithKey((PartitionKey) key, write -> results.add(write.write(accordListPathSuppler, timestamp, nowInSeconds))); + forEachWithKey((PartitionKey) key, write -> results.add(write.write(preserveTimestamps, accordListPathSuppler, timestamp, nowInSeconds))); if (isConditionMet) { @@ -396,7 +399,7 @@ public class TxnWrite extends AbstractKeySorted implements Writ TxnUpdate txnUpdate = (TxnUpdate) txn.update(); assert txnUpdate != null : "PartialTxn should contain an update if we're applying a write!"; List updates = txnUpdate.completeUpdatesForKey((RoutableKey) key); - updates.forEach(update -> results.add(update.write(accordListPathSuppler, timestamp, nowInSeconds))); + updates.forEach(update -> results.add(update.write(preserveTimestamps, accordListPathSuppler, timestamp, nowInSeconds))); } if (results.isEmpty()) diff --git a/src/java/org/apache/cassandra/service/accord/txn/UnrecoverableRepairUpdate.java b/src/java/org/apache/cassandra/service/accord/txn/UnrecoverableRepairUpdate.java index 0efcbf1ffd..25bbc0e6f9 100644 --- a/src/java/org/apache/cassandra/service/accord/txn/UnrecoverableRepairUpdate.java +++ b/src/java/org/apache/cassandra/service/accord/txn/UnrecoverableRepairUpdate.java @@ -47,7 +47,7 @@ import org.apache.cassandra.service.reads.repair.BlockingReadRepair; /** * This update is used to support blocking read repair from non-transactional Cassandra reads. Cassandra creates - * a read repair mutation per node and this enables some partitiosn to be readable that would otherwise run into messages + * a read repair mutation per node and this enables some partitions to be readable that would otherwise run into messages * size limits. * * This update is used during the `Execute` phase to apply the repair mutations directly in AccordInteropExecution similar @@ -58,7 +58,8 @@ import org.apache.cassandra.service.reads.repair.BlockingReadRepair; * The state for this update is always kept in memory and is never serialized. Only the Id is propagated so the cache * can evict the update and then load it back. We don't need to persist it or have it be recoverable because if the original * coordinator fails to complete the transaction then the dependent Cassandra read that triggered the read repair will - * also fail and it doesn't matter if the read repair is partially applied or not applied at all. + * also fail and it doesn't matter if the read repair is partially applied or not applied at all since it doesn't propose + * new values. */ public class UnrecoverableRepairUpdate, P extends ReplicaPlan.ForRead> extends AccordUpdate { @@ -117,6 +118,7 @@ public class UnrecoverableRepairUpdate, P extends Replica this.keys = keys; this.dk = dk; this.mutations = mutations; + mutations.values().forEach(Mutation::allowPotentialTransactionConflicts); this.writePlan = writePlan; this.updateKey = new Key(nodeId.id, nextCounter.getAndIncrement()); } diff --git a/src/java/org/apache/cassandra/service/consensus/TransactionalMode.java b/src/java/org/apache/cassandra/service/consensus/TransactionalMode.java index 2bcaee3ce2..25524404ec 100644 --- a/src/java/org/apache/cassandra/service/consensus/TransactionalMode.java +++ b/src/java/org/apache/cassandra/service/consensus/TransactionalMode.java @@ -113,6 +113,10 @@ public enum TransactionalMode return consistencyLevel; } + // TODO (required): This won't work for migration directly from none to full because there is no safe system to read from + // during the first phase (repair). Accord won't read correctly beacuse it won't honor the CL and miss non-transactional writes that haven't been repaired and non-transactional + // reads will miss all the writes being routed through Accord since they occur asynchronously. Something has to give here where either writes routed through are Accord are synchronous at CL + // or reads are routed through Accord and read at quorum as long as the range has not completed the first phase (repair). public ConsistencyLevel readCLForStrategy(ConsistencyLevel consistencyLevel) { if (ignoresSuppliedConsistencyLevel) diff --git a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusKeyMigrationState.java b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusKeyMigrationState.java index 9e051ccc5e..816e242575 100644 --- a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusKeyMigrationState.java +++ b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusKeyMigrationState.java @@ -46,6 +46,7 @@ import org.apache.cassandra.db.SystemKeyspace; import org.apache.cassandra.db.WriteType; import org.apache.cassandra.dht.Range; import org.apache.cassandra.exceptions.CasWriteTimeoutException; +import org.apache.cassandra.exceptions.RetryOnDifferentSystemException; import org.apache.cassandra.io.IVersionedSerializer; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; @@ -173,7 +174,7 @@ public abstract class ConsensusKeyMigrationState // TODO (desired): Better query start time TableMigrationState tms = tableMigrationState; - repairKeyAccord(key, tms.keyspaceName, 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() @@ -264,7 +265,6 @@ public abstract class ConsensusKeyMigrationState * Trigger a distributed repair of Accord state for this key. */ static void repairKeyAccord(DecoratedKey key, - String keyspace, TableId tableId, long minEpoch, Dispatcher.RequestTime requestTime, @@ -283,7 +283,9 @@ public abstract class ConsensusKeyMigrationState // 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 BarrierType barrierType = global ? BarrierType.global_async : BarrierType.local; - AccordService.instance().barrier(Seekables.of(new PartitionKey(tableId, key)), minEpoch, requestTime, DatabaseDescriptor.getTransactionTimeout(TimeUnit.NANOSECONDS), barrierType, isForWrite); + Seekables keysOrRanges = AccordService.instance().barrier(Seekables.of(new PartitionKey(tableId, key)), minEpoch, requestTime, DatabaseDescriptor.getTransactionTimeout(TimeUnit.NANOSECONDS), barrierType, isForWrite); + if (keysOrRanges.isEmpty()) + throw new RetryOnDifferentSystemException(); // We don't save the state to the cache here. Accord will notify the agent every time a barrier happens. } finally diff --git a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusMigrationMutationHelper.java b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusMigrationMutationHelper.java new file mode 100644 index 0000000000..32997bfb0c --- /dev/null +++ b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusMigrationMutationHelper.java @@ -0,0 +1,359 @@ +/* + * 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.consensus.migration; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import accord.coordinate.CoordinationFailed; +import accord.primitives.Keys; +import accord.primitives.Txn; +import org.apache.cassandra.db.ColumnFamilyStore; +import org.apache.cassandra.db.ConsistencyLevel; +import org.apache.cassandra.db.DecoratedKey; +import org.apache.cassandra.db.IMutation; +import org.apache.cassandra.db.Mutation; +import org.apache.cassandra.db.partitions.PartitionUpdate; +import org.apache.cassandra.dht.Token; +import org.apache.cassandra.exceptions.RetryOnDifferentSystemException; +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.service.accord.AccordService; +import org.apache.cassandra.service.accord.IAccordService; +import org.apache.cassandra.service.accord.IAccordService.AsyncTxnResult; +import org.apache.cassandra.service.accord.api.PartitionKey; +import org.apache.cassandra.service.accord.txn.TxnCondition; +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.TxnUpdate; +import org.apache.cassandra.service.accord.txn.TxnWrite; +import org.apache.cassandra.service.consensus.TransactionalMode; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; + +import static com.google.common.base.Preconditions.checkState; +import static java.util.function.Predicate.not; +import static org.apache.cassandra.dht.Range.isInNormalizedRanges; +import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.getTableMetadata; + +/** + * Applying mutations can fail with RetryOnDifferentSystemException if a + * mutation conflicts with a table and range that needs to be managed + * transactionally. This impacts mutations, logged/unlogged batches, hints,and blocking read repair. + * + * This class contains the logic needed for managing these retry loops and splitting the mutations up + */ +public class ConsensusMigrationMutationHelper +{ + private static final Logger logger = LoggerFactory.getLogger(ConsensusMigrationMutationHelper.class); + + private static ConsensusMigrationMutationHelper instance = new ConsensusMigrationMutationHelper(); + + public static ConsensusMigrationMutationHelper instance() + { + return instance; + } + + @VisibleForTesting + public static void replaceInstanceForTest(ConsensusMigrationMutationHelper testInstance) + { + instance = testInstance; + } + + @VisibleForTesting + public static void resetInstanceForTest() + { + instance = new ConsensusMigrationMutationHelper(); + } + + public ConsensusMigrationMutationHelper() {} + + private static ConsistencyLevel consistencyLevelForCommit(ClusterMetadata cm, Collection mutations, @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; + + for (IMutation mutation : mutations) + { + for (TableId tableId : mutation.getTableIds()) + { + TransactionalMode mode = getTableMetadata(cm, tableId).params.transactionalMode; + // commitCLForStrategy 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 = mode.commitCLForStrategy(consistencyLevel); + if (commitCL != null) + return commitCL; + } + } + return null; + } + + /** + * Result of splitting mutations across Accord and non-transactional boundaries + */ + public static class SplitMutations implements SplitConsumer + { + @Nullable + private List accordMutations; + + @Nullable + private List normalMutations; + + private SplitMutations() {} + + public List accordMutations() + { + return accordMutations; + } + + public List normalMutations() + { + return normalMutations; + } + + @Override + public void consume(@Nullable T accordMutation, @Nullable T normalMutation, List mutations, int mutationIndex) + { + // Avoid allocating an ArrayList in common single mutation single system case + if (mutations.size() == 1 && (accordMutation != null ^ normalMutation != null)) + { + if (accordMutation != null) + accordMutations = mutations; + else + normalMutations = mutations; + return; + } + + if (accordMutation != null) + { + if (accordMutations == null) + accordMutations = new ArrayList<>(Math.min(mutations.size(), 10)); + accordMutations.add(accordMutation); + } + if (normalMutation != null) + { + if (normalMutations == null) + normalMutations = new ArrayList<>(Math.min(mutations.size(), 10)); + normalMutations.add(normalMutation); + } + } + } + + public interface SplitConsumer + { + void consume(@Nullable T accordMutation, @Nullable T normalMutation, List mutations, int mutationIndex); + } + + public static SplitMutations splitMutationsIntoAccordAndNormal(ClusterMetadata cm, List mutations) + { + SplitMutations splitMutations = new SplitMutations<>(); + splitMutationsIntoAccordAndNormal(cm, mutations, splitMutations); + return splitMutations; + } + + public static void splitMutationsIntoAccordAndNormal(ClusterMetadata cm, List mutations, SplitConsumer splitConsumer) + { + for (int i=0,mi=mutations.size(); i splitMutation = instance.splitMutationIntoAccordAndNormal(mutations.get(i), cm); + splitConsumer.consume(splitMutation.accordMutation, splitMutation.normalMutation, mutations, i); + } + } + + /** + * Result of splitting a mutation across Accord and non-transactional boundaries + */ + public static class SplitMutation + { + @Nullable + public final T accordMutation; + @Nullable + public final T normalMutation; + + public SplitMutation(@Nullable T accordMutation, @Nullable T normalMutation) + { + this.accordMutation = accordMutation; + this.normalMutation = normalMutation; + } + } + + public SplitMutation splitMutationIntoAccordAndNormal(T mutation, ClusterMetadata cm) + { + if (mutation.allowsPotentialTransactionConflicts()) + return new SplitMutation<>(null, mutation); + + Token token = mutation.key().getToken(); + Predicate isAccordUpdate = tableId -> tokenShouldBeWrittenThroughAccord(cm, tableId, token); + + T accordMutation = (T)mutation.filter(isAccordUpdate); + T normalMutation = (T)mutation.filter(not(isAccordUpdate)); + for (PartitionUpdate pu : mutation.getPartitionUpdates()) + checkState((accordMutation == null ? false : accordMutation.hasUpdateForTable(pu.metadata().id)) + || (normalMutation == null ? false : normalMutation.hasUpdateForTable(pu.metadata().id)), + "All partition updates should still be present after splitting"); + return new SplitMutation(accordMutation, normalMutation); + } + + public AsyncTxnResult mutateWithAccordAsync(ClusterMetadata cm, Mutation mutation, @Nullable ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) + { + return mutateWithAccordAsync(cm, ImmutableList.of(mutation), consistencyLevel, requestTime); + } + + public static AsyncTxnResult mutateWithAccordAsync(ClusterMetadata cm, Collection mutations, @Nullable ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) + { + int fragmentIndex = 0; + List fragments = new ArrayList<>(mutations.size()); + List partitionKeys = new ArrayList<>(mutations.size()); + for (IMutation mutation : mutations) + { + for (PartitionUpdate update : mutation.getPartitionUpdates()) + { + PartitionKey pk = PartitionKey.of(update); + partitionKeys.add(pk); + fragments.add(new TxnWrite.Fragment(PartitionKey.of(update), fragmentIndex++, update, TxnReferenceOperations.empty())); + } + } + // Potentially ignore commit consistency level if the TransactionalMode specifies full + ConsistencyLevel clForCommit = consistencyLevelForCommit(cm, mutations, consistencyLevel); + TxnUpdate update = new TxnUpdate(fragments, TxnCondition.none(), clForCommit, true); + Txn.InMemory txn = new Txn.InMemory(Keys.of(partitionKeys), TxnRead.EMPTY, TxnQuery.NONE, update); + IAccordService accordService = AccordService.instance(); + try + { + return accordService.coordinateAsync(txn, consistencyLevel, requestTime); + } + catch (CoordinationFailed coordinationFailed) + { + AsyncTxnResult failure = new AsyncTxnResult(coordinationFailed.txnId()); + failure.setFailure(coordinationFailed.wrap()); + return failure; + } + } + + public static void validateSafeToExecuteNonTransactionally(IMutation mutation) throws RetryOnDifferentSystemException + { + if (mutation.allowsPotentialTransactionConflicts()) + return; + + // System keyspaces are never managed by Accord + if (SchemaConstants.isSystemKeyspace(mutation.getKeyspaceName())) + return; + + // Local keyspaces are never managed by Accord + if (Schema.instance.localKeyspaces().containsKeyspace(mutation.getKeyspaceName())) + return; + + ClusterMetadata cm = ClusterMetadata.current(); + + DecoratedKey dk = mutation.key(); + // Check all the partition updates and if any can't be done return an error response + // and the coordinator can retry with things correctly routed + boolean throwRetryOnDifferentSystem = false; + // Track CFS so we only mark each one once + Set markedColumnFamilies = null; + for (PartitionUpdate pu : mutation.getPartitionUpdates()) + { + TableId tableId = pu.metadata().id; + ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(tableId); + if (tokenShouldBeWrittenThroughAccord(cm, tableId, dk.getToken())) + { + throwRetryOnDifferentSystem = true; + if (markedColumnFamilies == null) + markedColumnFamilies = new HashSet<>(); + if (markedColumnFamilies.add(tableId)) + cfs.metric.mutationsRejectedOnWrongSystem.mark(); + logger.debug("Rejecting mutation on wrong system to table {}.{}", cfs.keyspace.getName(), cfs.name); + Tracing.trace("Rejecting mutation on wrong system to table {}.{} token {}", cfs.keyspace.getName(), cfs.name, dk.getToken()); + } + } + if (throwRetryOnDifferentSystem) + throw new RetryOnDifferentSystemException(); + } + + public static boolean tokenShouldBeWrittenThroughAccord(@Nonnull ClusterMetadata cm, @Nonnull TableId tableId, @Nonnull Token token) + { + TableMetadata tm = getTableMetadata(cm, tableId); + if (tm == null) + return false; + + boolean transactionalModeWritesThroughAccord = tm.params.transactionalMode.writesThroughAccord; + TransactionalMigrationFromMode transactionalMigrationFromMode = tm.params.transactionalMigrationFrom; + boolean migrationFromWritesThroughAccord = transactionalMigrationFromMode.writesThroughAccord(); + if (transactionalModeWritesThroughAccord && migrationFromWritesThroughAccord) + return true; + + // Could be migrating or could be completely migrated, if it's migrating check if the key for this mutation + if (transactionalModeWritesThroughAccord || migrationFromWritesThroughAccord) + { + 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 transactionalModeWritesThroughAccord; + 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 migrationFromWritesThroughAccord; + } + + // This logic is driven by the fact that Paxos is not picky about how data is written since it's txn recovery + // is deterministic in the face of non-deterministic reads because consensus is agreeing on the writes that will be done to the database + // Accord agrees on what computation will produce those writes and then asynchronously executes those computations, potentially multiple times + // 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. + // Accord needs to do synchronous commit and respect the consistency level so that Accord will later be able to + // read its own writes + if (transactionalModeWritesThroughAccord) + return isInNormalizedRanges(token, tms.migratingAndMigratedRanges); + + // 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 (migrationFromWritesThroughAccord) + return !isInNormalizedRanges(token, tms.migratingAndMigratedRanges); + } + return false; + } +} diff --git a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusMigrationRepairResult.java b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusMigrationRepairResult.java index 2215c734c8..b7eb69978f 100644 --- a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusMigrationRepairResult.java +++ b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusMigrationRepairResult.java @@ -18,40 +18,46 @@ package org.apache.cassandra.service.consensus.migration; +import javax.annotation.Nullable; + +import accord.primitives.Ranges; import org.apache.cassandra.tcm.Epoch; import static com.google.common.base.Preconditions.checkArgument; public class ConsensusMigrationRepairResult { - private static final ConsensusMigrationRepairResult INELIGIBLE = new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.ineligible, Epoch.EMPTY); + private static final ConsensusMigrationRepairResult INELIGIBLE = new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.ineligible, Epoch.EMPTY, null); public final ConsensusMigrationRepairType type; public final Epoch minEpoch; + @Nullable + public final Ranges barrieredRanges; - private ConsensusMigrationRepairResult(ConsensusMigrationRepairType type, Epoch minEpoch) + private ConsensusMigrationRepairResult(ConsensusMigrationRepairType type, Epoch minEpoch, @Nullable Ranges barrieredRanges) { this.type = type; this.minEpoch = minEpoch; + this.barrieredRanges = barrieredRanges; } - public static ConsensusMigrationRepairResult fromRepair(Epoch minEpoch, boolean paxosAndDataRepaired, boolean accordRepaired, boolean deadNodesExcluded) + public static ConsensusMigrationRepairResult fromRepair(Epoch minEpoch, Ranges barrieredRanges, boolean paxosAndDataRepaired, boolean accordRepaired, boolean deadNodesExcluded) { checkArgument((!paxosAndDataRepaired && !accordRepaired) || minEpoch.isAfter(Epoch.EMPTY), "Epoch should not be empty if Paxos and regular repairs were performed"); if (deadNodesExcluded) return INELIGIBLE; - if (paxosAndDataRepaired && accordRepaired) return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.either, minEpoch); - if (paxosAndDataRepaired) return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.paxos, minEpoch); - if (accordRepaired) return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.accord, minEpoch); + if (paxosAndDataRepaired && accordRepaired) return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.either, minEpoch, barrieredRanges); + if (paxosAndDataRepaired) return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.paxos, minEpoch, barrieredRanges); + if (accordRepaired) return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.accord, minEpoch, barrieredRanges); return INELIGIBLE; } public static ConsensusMigrationRepairResult fromPaxosOnlyRepair(Epoch minEpoch, boolean deadNodesExcluded) { - return fromRepair(minEpoch, false, false, deadNodesExcluded); + return fromRepair(minEpoch, null, false, false, deadNodesExcluded); } - public static ConsensusMigrationRepairResult fromAccordOnlyRepair(Epoch minEpoch, boolean deadNodesExcluded) + public static ConsensusMigrationRepairResult fromAccordOnlyRepair(Epoch minEpoch, Ranges barrieredRanges, boolean deadNodesExcluded) { - return fromRepair(minEpoch, false, true, deadNodesExcluded); + return fromRepair(minEpoch, barrieredRanges, false, true, deadNodesExcluded); } } diff --git a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusMigrationRepairType.java b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusMigrationRepairType.java index 3866db0ad0..36452a9691 100644 --- a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusMigrationRepairType.java +++ b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusMigrationRepairType.java @@ -24,16 +24,22 @@ import org.apache.cassandra.utils.LocalizeString; public enum ConsensusMigrationRepairType { - ineligible(0), - paxos(1), - accord(2), - either(3); + ineligible(0, false, false), + paxos(1, false, true), + accord(2, true, false), + either(3, true, true); public final byte value; - ConsensusMigrationRepairType(int value) + public final boolean accordMigrationEligible; + + public final boolean paxosMigrationEligible; + + ConsensusMigrationRepairType(int value, boolean accordMigrationEligible, boolean paxosMigrationEligible) { this.value = SignedBytes.checkedCast(value); + this.accordMigrationEligible = accordMigrationEligible; + this.paxosMigrationEligible = paxosMigrationEligible; } public static ConsensusMigrationRepairType fromString(String repairType) diff --git a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusRequestRouter.java b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusRequestRouter.java index 70c2c39660..3763fc995f 100644 --- a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusRequestRouter.java +++ b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusRequestRouter.java @@ -18,6 +18,7 @@ package org.apache.cassandra.service.consensus.migration; +import java.util.Optional; import javax.annotation.Nonnull; import com.google.common.annotations.VisibleForTesting; @@ -26,9 +27,12 @@ import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.dht.Range; +import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.locator.EndpointsForToken; import org.apache.cassandra.locator.ReplicaLayout; import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.Keyspaces; +import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.consensus.TransactionalMode; @@ -41,12 +45,11 @@ import org.apache.cassandra.utils.FBUtilities; import static com.google.common.base.Preconditions.checkState; 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.ConsensusRequestRouter.ConsensusRoutingDecision.accord; import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.ConsensusRoutingDecision.paxosV1; import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.ConsensusRoutingDecision.paxosV2; -import static org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget.paxos; - /** * Helper class to decide where to route a request that requires consensus, migrating a key if necessary * before rerouting. @@ -84,13 +87,30 @@ public class ConsensusRequestRouter return pickPaxos(); } + /* + * Accord never handles local tables, but if the table doesn't exist then we need to generate the correct + * InvalidRequestException. + */ private static TableMetadata metadata(ClusterMetadata cm, String keyspace, String table) { - KeyspaceMetadata ksm = cm.schema.getKeyspaceMetadata(keyspace); - TableMetadata tbm = ksm != null ? ksm.getTableOrViewNullable(table) : null; - + Optional ksm = cm.schema.maybeGetKeyspaceMetadata(keyspace); + if (ksm.isEmpty()) + { + // It's a non-distributed table which is fine, but we want to error if it doesn't exist + // We should never actually reach here unless there is a race with dropping the table + Keyspaces localKeyspaces = Schema.instance.localKeyspaces(); + KeyspaceMetadata ksm2 = localKeyspaces.getNullable(keyspace); + if (ksm2 == null) + throw new InvalidRequestException("Keyspace " + keyspace + " does not exist"); + // Explicitly including views in case they get used in non-distributed tables + TableMetadata tbm2 = ksm2.getTableOrViewNullable(table); + if (tbm2 == null) + throw new InvalidRequestException("Table " + keyspace + "." + table + " does not exist"); + return null; + } + TableMetadata tbm = ksm.get().getTableNullable(table); if (tbm == null) - throw new IllegalStateException("Can't route consensus request to nonexistent CFS %s.%s".format(keyspace, table)); + throw new InvalidRequestException("Table " + keyspace + "." + table + " does not exist"); return tbm; } @@ -99,18 +119,39 @@ public class ConsensusRequestRouter { ClusterMetadata cm = ClusterMetadata.current(); TableMetadata metadata = metadata(cm, keyspace, table); + + // Non-distributed tables always take the Paxos path + if (metadata == null) + return pickPaxos(); return routeAndMaybeMigrate(cm, metadata, key, consistencyLevel, requestTime, timeoutNanos, isForWrite); } public ConsensusRoutingDecision routeAndMaybeMigrate(@Nonnull DecoratedKey key, @Nonnull TableId tableId, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime, long timeoutNanos, boolean isForWrite) { ClusterMetadata cm = ClusterMetadata.current(); - TableMetadata metadata = cm.schema.getTableMetadata(tableId); + TableMetadata metadata = getTableMetadata(cm, tableId); + // Non-distributed tables always take the Paxos path if (metadata == null) - throw new IllegalStateException("Can't route consensus request for nonexistent table %s".format(tableId.toString())); + pickPaxos(); return routeAndMaybeMigrate(cm, metadata, key, consistencyLevel, requestTime, timeoutNanos, isForWrite); } + public static TableMetadata getTableMetadata(ClusterMetadata cm, TableId tableId) + { + TableMetadata tm = cm.schema.getTableMetadata(tableId); + if (tm == null) + { + // It's a non-distributed table which is fine, but we want to error if it doesn't exist + // We should never actually reach here unless there is a race with dropping the table + Keyspaces localKeyspaces = Schema.instance.localKeyspaces(); + TableMetadata tm2 = localKeyspaces.getTableOrViewNullable(tableId); + if (tm2 == null) + throw new InvalidRequestException("Table with id " + tableId + " does not exist"); + return null; + } + return tm; + } + protected static boolean mayWriteThroughAccord(TableMetadata metadata) { return metadata.params.transactionalMode.writesThroughAccord || metadata.params.transactionalMigrationFrom.writesThroughAccord(); @@ -170,7 +211,7 @@ public class ConsensusRequestRouter ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(tmd.id); if (cfs == null) - throw new IllegalStateException("Can't route consensus request to nonexistent CFS %s.%s".format(tmd.keyspace, tmd.name)); + throw new InvalidRequestException("Can't route consensus request to nonexistent CFS %s.%s".format(tmd.keyspace, tmd.name)); // If it is locally replicated we can check our local migration state to see if it was already migrated EndpointsForToken naturalReplicas = ReplicaLayout.forNonLocalStrategyTokenRead(cm, cfs.keyspace.getMetadata(), key.getToken()); boolean isLocallyReplicated = naturalReplicas.lookup(FBUtilities.getBroadcastAddressAndPort()) != null; @@ -191,7 +232,7 @@ public class ConsensusRequestRouter // barrier transactions to accomplish the migration // They still might need to go through the fast local path for barrier txns // at each replica, but they won't create their own txn since we created it here - ConsensusKeyMigrationState.repairKeyAccord(key, tms.keyspaceName, tms.tableId, tms.minMigrationEpoch(key.getToken()).getEpoch(), requestTime, true, isForWrite); + ConsensusKeyMigrationState.repairKeyAccord(key, tms.tableId, tms.minMigrationEpoch(key.getToken()).getEpoch(), requestTime, true, isForWrite); return paxosV2; } // Fall through for repairKeyPaxos diff --git a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusTableMigration.java b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusTableMigration.java index d8c207dc64..62d449d5d6 100644 --- a/src/java/org/apache/cassandra/service/consensus/migration/ConsensusTableMigration.java +++ b/src/java/org/apache/cassandra/service/consensus/migration/ConsensusTableMigration.java @@ -46,6 +46,7 @@ import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.StorageService; +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.streaming.PreviewKind; @@ -56,6 +57,7 @@ import org.apache.cassandra.tcm.serialization.MetadataSerializer; import org.apache.cassandra.tcm.transformations.BeginConsensusMigrationForTableAndRange; import org.apache.cassandra.tcm.transformations.MaybeFinishConsensusMigrationForTableAndRange; +import static java.lang.String.format; import static java.util.Collections.emptyList; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -97,9 +99,24 @@ public abstract class ConsensusTableMigration if (!tms.targetProtocol.isMigratedBy(repairResult.consensusMigrationRepairResult.type)) return; + List> paxosRepairedRanges = ImmutableList.of(); + if (migrationResult.type.paxosMigrationEligible) + // Paxos always repairs all ranges requested by the repair although there should be nothing + // repaired in the migrated and Accord managed ranges + paxosRepairedRanges = ImmutableList.copyOf(desc.ranges); + + List> accordBarrieredRanges = ImmutableList.of(); + if (migrationResult.type.accordMigrationEligible) + // Accord only barriers ranges it thinks it manages and repair collects which it barriered + // precisely which doesn't have to match what the entire repair covers + accordBarrieredRanges = migrationResult.barrieredRanges.stream() + .map(range -> ((TokenRange)range).toKeyspaceRange()) + .collect(toImmutableList()); + accordBarrieredRanges = Range.normalize(accordBarrieredRanges); + ClusterMetadataService.instance().commit( new MaybeFinishConsensusMigrationForTableAndRange( - desc.keyspace, desc.columnFamily, ImmutableList.copyOf(desc.ranges), + desc.keyspace, desc.columnFamily, paxosRepairedRanges, accordBarrieredRanges, migrationResult.minEpoch, migrationResult.type)); } @@ -283,7 +300,7 @@ public abstract class ConsensusTableMigration .map(tableName -> { TableMetadata tm = Schema.instance.getTableMetadata(keyspaceName, tableName); if (tm == null) - throw new IllegalArgumentException("Unknown table %s.%s".format(keyspaceName, tableName)); + throw new IllegalArgumentException(format("Unknown table %s.%s", keyspaceName, tableName)); return tm.id; }) .collect(toImmutableList())); diff --git a/src/java/org/apache/cassandra/service/paxos/Commit.java b/src/java/org/apache/cassandra/service/paxos/Commit.java index 3aa8d65bce..12c3606f74 100644 --- a/src/java/org/apache/cassandra/service/paxos/Commit.java +++ b/src/java/org/apache/cassandra/service/paxos/Commit.java @@ -23,25 +23,25 @@ package org.apache.cassandra.service.paxos; import java.io.IOException; import java.util.function.BiFunction; - import javax.annotation.Nullable; import com.google.common.base.Objects; -import org.apache.cassandra.db.*; -import org.apache.cassandra.db.rows.*; +import org.apache.cassandra.db.DecoratedKey; +import org.apache.cassandra.db.Mutation; import org.apache.cassandra.db.partitions.PartitionUpdate; +import org.apache.cassandra.db.rows.DeserializationHelper; 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.TableMetadata; -import static org.apache.cassandra.db.SystemKeyspace.*; +import static org.apache.cassandra.db.SystemKeyspace.legacyPaxosTtlSec; import static org.apache.cassandra.service.paxos.Commit.CompareResult.AFTER; import static org.apache.cassandra.service.paxos.Commit.CompareResult.BEFORE; import static org.apache.cassandra.service.paxos.Commit.CompareResult.IS_REPROPOSAL; -import static org.apache.cassandra.service.paxos.Commit.CompareResult.WAS_REPROPOSED_BY; import static org.apache.cassandra.service.paxos.Commit.CompareResult.SAME; +import static org.apache.cassandra.service.paxos.Commit.CompareResult.WAS_REPROPOSED_BY; import static org.apache.cassandra.utils.FBUtilities.nowInSeconds; public class Commit @@ -314,7 +314,7 @@ public class Commit public Mutation makeMutation() { - return new Mutation(update); + return new Mutation(update, true); } @Override diff --git a/src/java/org/apache/cassandra/service/reads/ReadCoordinator.java b/src/java/org/apache/cassandra/service/reads/ReadCoordinator.java index d9d8a7b562..90d11f887f 100644 --- a/src/java/org/apache/cassandra/service/reads/ReadCoordinator.java +++ b/src/java/org/apache/cassandra/service/reads/ReadCoordinator.java @@ -71,9 +71,9 @@ public interface ReadCoordinator void sendReadCommand(Message message, InetAddressAndPort to, RequestCallback callback); default void notifyOfInitialContacts(EndpointsForToken fullDataRequests, EndpointsForToken transientRequests, EndpointsForToken digestRequests) {} void sendReadRepairMutation(Message message, InetAddressAndPort to, RequestCallback callback); - default Mutation maybeAllowOutOfRangeMutations(Mutation m) + default boolean allowsPotentialTransactionConflicts() { - return m; + return !isEventuallyConsistent(); } boolean isEventuallyConsistent(); } diff --git a/src/java/org/apache/cassandra/service/reads/repair/BlockingPartitionRepair.java b/src/java/org/apache/cassandra/service/reads/repair/BlockingPartitionRepair.java index 973fbcb45d..309bbeeb51 100644 --- a/src/java/org/apache/cassandra/service/reads/repair/BlockingPartitionRepair.java +++ b/src/java/org/apache/cassandra/service/reads/repair/BlockingPartitionRepair.java @@ -52,6 +52,7 @@ import org.apache.cassandra.utils.concurrent.AsyncFuture; import org.apache.cassandra.utils.concurrent.CountDownLatch; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; +import static com.google.common.base.Preconditions.checkArgument; 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.concurrent.CountDownLatch.newCountDownLatch; @@ -154,6 +155,7 @@ public class BlockingPartitionRepair @VisibleForTesting protected void sendRR(Message message, InetAddressAndPort endpoint) { + checkArgument(message.payload.allowsPotentialTransactionConflicts() == coordinator.allowsPotentialTransactionConflicts(), "Mutation allowing transaction conflicts should match coordinator"); coordinator.sendReadRepairMutation(message, endpoint, this); } @@ -165,8 +167,8 @@ public class BlockingPartitionRepair for (Map.Entry entry: pendingRepairs.entrySet()) { Replica destination = entry.getKey(); - Preconditions.checkArgument(destination.isFull(), "Can't send repairs to transient replicas: %s", destination); - Mutation mutation = coordinator.maybeAllowOutOfRangeMutations(entry.getValue()); + checkArgument(destination.isFull(), "Can't send repairs to transient replicas: %s", destination); + Mutation mutation = entry.getValue(); TableId tableId = extractUpdate(mutation).metadata().id; Tracing.trace("Sending read-repair-mutation to {}", destination); @@ -239,7 +241,7 @@ public class BlockingPartitionRepair if (mutation == null) { - mutation = BlockingReadRepairs.createRepairMutation(update, repairPlan.consistencyLevel(), replica.endpoint(), true); + mutation = BlockingReadRepairs.createRepairMutation(update, repairPlan.consistencyLevel(), replica.endpoint(), true, coordinator.allowsPotentialTransactionConflicts()); versionedMutations[versionIdx] = mutation; } @@ -250,7 +252,6 @@ public class BlockingPartitionRepair continue; } - mutation = coordinator.maybeAllowOutOfRangeMutations(mutation); Tracing.trace("Sending speculative read-repair-mutation to {}", replica); sendRR(Message.out(READ_REPAIR_REQ, mutation), replica.endpoint()); ReadRepairDiagnostics.speculatedWrite(this, replica.endpoint(), mutation); diff --git a/src/java/org/apache/cassandra/service/reads/repair/BlockingReadRepair.java b/src/java/org/apache/cassandra/service/reads/repair/BlockingReadRepair.java index 4f02b1a060..98b5dc99b2 100644 --- a/src/java/org/apache/cassandra/service/reads/repair/BlockingReadRepair.java +++ b/src/java/org/apache/cassandra/service/reads/repair/BlockingReadRepair.java @@ -18,7 +18,6 @@ package org.apache.cassandra.service.reads.repair; -import java.util.Collection; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -34,13 +33,11 @@ import accord.primitives.Keys; import accord.primitives.Txn; import com.codahale.metrics.Meter; import org.apache.cassandra.concurrent.Stage; -import org.apache.cassandra.service.consensus.TransactionalMode; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.Mutation; import org.apache.cassandra.db.ReadCommand; -import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators; import org.apache.cassandra.exceptions.ReadTimeoutException; import org.apache.cassandra.locator.Endpoints; @@ -54,6 +51,7 @@ import org.apache.cassandra.service.accord.txn.TxnQuery; import org.apache.cassandra.service.accord.txn.TxnRead; import org.apache.cassandra.service.accord.txn.TxnResult; import org.apache.cassandra.service.accord.txn.UnrecoverableRepairUpdate; +import org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper; import org.apache.cassandra.service.reads.ReadCoordinator; import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tracing.Tracing; @@ -195,70 +193,83 @@ public class BlockingReadRepair, P extends ReplicaPlan.Fo @Override public void repairPartition(DecoratedKey dk, Map mutations, ReplicaPlan.ForWrite writePlan) { - TransactionalMode transactionalMode = command.metadata().params.transactionalMode; - if (coordinator.isEventuallyConsistent() && transactionalMode.blockingReadRepairThroughAccord) + // non-Accord reads only ever touch one table and key so all mutations need to be applied either transactionally + // or non-transactionally (not a mix). There is no retry loop here because read repair is relatively rare so it racing + // with changes to migrating ranges should also be pretty rare so it isn't worth the added complexity. If you were + // to add a retry loop you would need to be careful to correctly set/unset allowPotentialTransactionConflicts in the mutations + // since that is set if it is routed to Accord + // + // If this is an Accord transaction that is in interoperability mode and executing a read repair + // then we take the non-transactional path and the mutations are intercepted in ReadCoordinator.sendRepairMutation + // which will ensure the repair mutation runs in the command store thread after preceding transactions are done + ClusterMetadata cm = ClusterMetadata.current(); + if (coordinator.isEventuallyConsistent() && ConsensusMigrationMutationHelper.tokenShouldBeWrittenThroughAccord(cm, command.metadata().id, dk.getToken())) + repairTransactionally(dk, mutations, writePlan); + else + repairNonTransactionally(dk, mutations, writePlan); + } + + private void repairTransactionally(DecoratedKey dk, Map accordMutations, ForWrite writePlan) + { + checkState(coordinator.isEventuallyConsistent(), "Should only repair transactionally for an eventually consistent read coordinator"); + PartitionKey partitionKey = new PartitionKey(command.metadata().id, dk); + Keys key = Keys.of(partitionKey); + // This is going create a new BlockingReadRepair inside an Accord transaction which will go down + // the !isEventuallyConsistent path and apply the repairs through Accord command stores using AccordInteropExecution + UnrecoverableRepairUpdate repairUpdate = UnrecoverableRepairUpdate.create(AccordService.instance().nodeId(), this, key, dk, accordMutations, writePlan); + Future repairFuture; + try { - Collection partitionUpdates = Mutation.merge(mutations.values()).getPartitionUpdates(); - checkState(partitionUpdates.size() == 1, "Expect only one PartitionUpdate"); - PartitionUpdate update = partitionUpdates.iterator().next(); - PartitionKey partitionKey = PartitionKey.of(update); - Keys key = Keys.of(partitionKey); - // This is going create a new BlockingReadRepair inside an Accord transaction which will go down - // the !isEventuallyConsistent path and apply the repairs through Accord command stores using AccordInteropExecution - UnrecoverableRepairUpdate repairUpdate = UnrecoverableRepairUpdate.create(AccordService.instance().nodeId(), this, key, dk, mutations, writePlan); - Future repairFuture; - try - { - Txn txn = new Txn.InMemory(Txn.Kind.Read, key, TxnRead.createNoOpRead(key), TxnQuery.NONE, repairUpdate); - repairFuture = Stage.ACCORD_MIGRATION.submit(() -> { - try - { - return AccordService.instance().coordinate(txn, ConsistencyLevel.ANY, requestTime); - } - finally - { - // If we successfully ran the repair txn then the update should definitely - // be there for us to clear which means we are sure it was there to be sent - checkNotNull(UnrecoverableRepairUpdate.removeInflightUpdate(repairUpdate.updateKey)); - } - }); - } - catch (Throwable t) - { - UnrecoverableRepairUpdate.removeInflightUpdate(repairUpdate.updateKey); - throw t; - } - - repairs.add(new PendingPartitionRepair() - { - @Override - public boolean awaitRepairs(long remaining, TimeUnit timeUnit) throws InterruptedException, ExecutionException + Txn txn = new Txn.InMemory(Txn.Kind.Read, key, TxnRead.createNoOpRead(key), TxnQuery.NONE, repairUpdate); + repairFuture = Stage.ACCORD_MIGRATION.submit(() -> { + try { - try - { - repairFuture.get(remaining, timeUnit); - return true; - } - catch (TimeoutException e) - { - - return false; - } + return AccordService.instance().coordinate(txn, ConsistencyLevel.ANY, requestTime); } - - @Override - public ForWrite repairPlan() + finally { - return writePlan; + // If we successfully ran the repair txn then the update should definitely + // be there for us to clear which means we are sure it was there to be sent + checkNotNull(UnrecoverableRepairUpdate.removeInflightUpdate(repairUpdate.updateKey)); } }); } - else + catch (Throwable t) { - BlockingPartitionRepair blockingRepair = new BlockingPartitionRepair(coordinator, dk, mutations, writePlan); - blockingRepair.sendInitialRepairs(); - repairs.add(blockingRepair); + UnrecoverableRepairUpdate.removeInflightUpdate(repairUpdate.updateKey); + throw t; } + + repairs.add(new PendingPartitionRepair() + { + @Override + public boolean awaitRepairs(long remaining, TimeUnit timeUnit) throws InterruptedException, ExecutionException + { + try + { + repairFuture.get(remaining, timeUnit); + return true; + } + catch (TimeoutException e) + { + + return false; + } + } + + @Override + public ForWrite repairPlan() + { + return writePlan; + } + }); + } + + private void repairNonTransactionally(DecoratedKey dk, Map mutations, ForWrite writePlan) + { + BlockingPartitionRepair blockingRepair = new BlockingPartitionRepair(coordinator, dk, mutations, writePlan); + blockingRepair.sendInitialRepairs(); + repairs.add(blockingRepair); } public void repairPartitionDirectly(ReadCoordinator readCoordinator, DecoratedKey dk, Map mutations, ForWrite writePlan) @@ -268,4 +279,10 @@ public class BlockingReadRepair, P extends ReplicaPlan.Fo delegateRR.maybeSendAdditionalWrites(); delegateRR.awaitWrites(); } + + @Override + public boolean coordinatorAllowsPotentialTransactionConflicts() + { + return coordinator.allowsPotentialTransactionConflicts(); + } } diff --git a/src/java/org/apache/cassandra/service/reads/repair/BlockingReadRepairs.java b/src/java/org/apache/cassandra/service/reads/repair/BlockingReadRepairs.java index de49f5a563..c16fba3674 100644 --- a/src/java/org/apache/cassandra/service/reads/repair/BlockingReadRepairs.java +++ b/src/java/org/apache/cassandra/service/reads/repair/BlockingReadRepairs.java @@ -46,13 +46,13 @@ public class BlockingReadRepairs * Create a read repair mutation from the given update, if the mutation is not larger than the maximum * mutation size, otherwise return null. Or, if we're configured to be strict, throw an exception. */ - public static Mutation createRepairMutation(PartitionUpdate update, ConsistencyLevel consistency, InetAddressAndPort destination, boolean suppressException) + public static Mutation createRepairMutation(PartitionUpdate update, ConsistencyLevel consistency, InetAddressAndPort destination, boolean suppressException, boolean allowPotentialTransactionConflicts) { if (update == null) return null; DecoratedKey key = update.partitionKey(); - Mutation mutation = new Mutation(update); + Mutation mutation = new Mutation(update, allowPotentialTransactionConflicts); int messagingVersion = MessagingService.instance().versions.get(destination); try diff --git a/src/java/org/apache/cassandra/service/reads/repair/ReadRepair.java b/src/java/org/apache/cassandra/service/reads/repair/ReadRepair.java index bff068a6d7..2b73ab83b1 100644 --- a/src/java/org/apache/cassandra/service/reads/repair/ReadRepair.java +++ b/src/java/org/apache/cassandra/service/reads/repair/ReadRepair.java @@ -100,4 +100,9 @@ public interface ReadRepair, P extends ReplicaPlan.ForRea * Repairs a partition using the provided read coordinator */ void repairPartitionDirectly(ReadCoordinator coordinator, DecoratedKey partitionKey, Map mutations, ReplicaPlan.ForWrite writePlan); + + default boolean coordinatorAllowsPotentialTransactionConflicts() + { + return false; + } } diff --git a/src/java/org/apache/cassandra/service/reads/repair/RowIteratorMergeListener.java b/src/java/org/apache/cassandra/service/reads/repair/RowIteratorMergeListener.java index acddf45d94..be6718fc47 100644 --- a/src/java/org/apache/cassandra/service/reads/repair/RowIteratorMergeListener.java +++ b/src/java/org/apache/cassandra/service/reads/repair/RowIteratorMergeListener.java @@ -390,7 +390,7 @@ public class RowIteratorMergeListener> else if (repairs[i] != null) update = repairs[i].build(); - Mutation mutation = BlockingReadRepairs.createRepairMutation(update, readPlan.consistencyLevel(), replica.endpoint(), false); + Mutation mutation = BlockingReadRepairs.createRepairMutation(update, readPlan.consistencyLevel(), replica.endpoint(), false, readRepair.coordinatorAllowsPotentialTransactionConflicts()); if (mutation == null) continue; diff --git a/src/java/org/apache/cassandra/tcm/transformations/MaybeFinishConsensusMigrationForTableAndRange.java b/src/java/org/apache/cassandra/tcm/transformations/MaybeFinishConsensusMigrationForTableAndRange.java index 1c1f3985fa..15bfdda65c 100644 --- a/src/java/org/apache/cassandra/tcm/transformations/MaybeFinishConsensusMigrationForTableAndRange.java +++ b/src/java/org/apache/cassandra/tcm/transformations/MaybeFinishConsensusMigrationForTableAndRange.java @@ -38,8 +38,8 @@ import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableParams; import org.apache.cassandra.service.consensus.migration.ConsensusMigrationRepairType; -import org.apache.cassandra.service.consensus.migration.ConsensusTableMigration; import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState; +import org.apache.cassandra.service.consensus.migration.ConsensusTableMigration; import org.apache.cassandra.service.consensus.migration.TableMigrationState; import org.apache.cassandra.service.consensus.migration.TransactionalMigrationFromMode; import org.apache.cassandra.tcm.ClusterMetadata; @@ -56,7 +56,8 @@ import static java.lang.String.format; import static org.apache.cassandra.dht.Range.intersects; import static org.apache.cassandra.dht.Range.normalize; import static org.apache.cassandra.exceptions.ExceptionCode.INVALID; - +import static org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget.accord; +import static org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget.paxos; public class MaybeFinishConsensusMigrationForTableAndRange implements Transformation { @@ -71,7 +72,10 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma public final String cf; @Nonnull - public final List> repairedRanges; + public final List> paxosRepairedRanges; + + @Nonnull + public final List> accordBarrieredRanges; @Nonnull public final Epoch minEpoch; @@ -81,21 +85,23 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma public MaybeFinishConsensusMigrationForTableAndRange(@Nonnull String keyspace, @Nonnull String cf, - @Nonnull List> repairedRanges, + @Nonnull List> paxosRepairedRanges, + @Nonnull List> accordBarrieredRanges, @Nonnull Epoch minEpoch, @Nonnull ConsensusMigrationRepairType repairType) { checkNotNull(keyspace, "keyspace should not be null"); checkNotNull(cf, "cf should not be null"); - checkNotNull(repairedRanges, "repairedRanges should not be null"); - checkArgument(!repairedRanges.isEmpty(), "repairedRanges should not be empty"); + checkNotNull(paxosRepairedRanges, "paxosRepairedRanges should not be null"); + checkNotNull(accordBarrieredRanges, "accordBarrierRanges should not be null"); checkNotNull(minEpoch, "minEpoch should not be null"); checkArgument(minEpoch.isAfter(Epoch.EMPTY), "minEpoch should not be empty"); checkNotNull(repairType, "repairType is null"); checkArgument(repairType != ConsensusMigrationRepairType.ineligible, "Shouldn't attempt to finish migration with ineligible repair"); this.keyspace = keyspace; this.cf = cf; - this.repairedRanges = repairedRanges; + this.paxosRepairedRanges = paxosRepairedRanges; + this.accordBarrieredRanges = accordBarrieredRanges; this.minEpoch = minEpoch; this.repairType = repairType; } @@ -125,7 +131,7 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma public Result execute(@Nonnull ClusterMetadata metadata) { - logger.info("Completed repair {} ranges {}", repairType, repairedRanges); + logger.info("Completed repair eligibiliy '{}' paxos repaired ranges {}, accord repaired ranges {}", repairType, paxosRepairedRanges, accordBarrieredRanges); checkNotNull(metadata, "clusterMetadata should not be null"); String ksAndCF = keyspace + "." + cf; TableMetadata tbm = Schema.instance.getTableMetadata(keyspace, cf); @@ -138,8 +144,15 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma return new Rejected(INVALID, format("Table %s is not currently performing consensus migration", ksAndCF)); if (!tms.targetProtocol.isMigratedBy(repairType)) - return new Rejected(INVALID, format("Table %s is not currently performing consensus migration to %s and the repair was a %s repair", ksAndCF, tms.targetProtocol, repairType)); + return new Rejected(INVALID, format("Table %s has a target protocol of %s and is the repair type %s is not eligible/needed to progress the migration", ksAndCF, tms.targetProtocol, repairType)); + List> repairedRanges; + if (tms.targetProtocol == accord) + repairedRanges = paxosRepairedRanges; + else if (tms.targetProtocol == paxos) + repairedRanges = accordBarrieredRanges; + else + throw new IllegalStateException("Unhandled migration target " + tms.targetProtocol); List> normalizedRepairedRanges = normalize(repairedRanges); // Bail out if repair doesn't actually intersect with any migrating ranges @@ -148,6 +161,8 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma Transformer next = metadata.transformer(); ConsensusMigrationState migrationState = metadata.consensusMigrationState.withRangesRepairedAtEpoch(tbm, normalizedRepairedRanges, minEpoch); + logger.debug("Original migration state {}"); + logger.debug("New migration state {}"); next = next.with(migrationState); // reset the migration value on the table if the migration has completed @@ -165,7 +180,8 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma MaybeFinishConsensusMigrationForTableAndRange v = (MaybeFinishConsensusMigrationForTableAndRange)t; out.writeUTF(v.keyspace); out.writeUTF(v.cf); - ConsensusTableMigration.rangesSerializer.serialize(v.repairedRanges, out, version); + ConsensusTableMigration.rangesSerializer.serialize(v.paxosRepairedRanges, out, version); + ConsensusTableMigration.rangesSerializer.serialize(v.accordBarrieredRanges, out, version); Epoch.serializer.serialize(v.minEpoch, out, version); out.write(v.repairType.value); } @@ -174,10 +190,11 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma { String keyspace = in.readUTF(); String cf = in.readUTF(); - List> repairedRanges = ConsensusTableMigration.rangesSerializer.deserialize(in, version); + List> paxosRepairedRanges = ConsensusTableMigration.rangesSerializer.deserialize(in, version); + List> accordBarrieredRanges = ConsensusTableMigration.rangesSerializer.deserialize(in, version); Epoch minEpoch = Epoch.serializer.deserialize(in, version); ConsensusMigrationRepairType repairType = ConsensusMigrationRepairType.fromValue(in.readByte()); - return new MaybeFinishConsensusMigrationForTableAndRange(keyspace, cf, repairedRanges, minEpoch, repairType); + return new MaybeFinishConsensusMigrationForTableAndRange(keyspace, cf, paxosRepairedRanges, accordBarrieredRanges, minEpoch, repairType); } public long serializedSize(Transformation t, Version version) @@ -185,7 +202,8 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma MaybeFinishConsensusMigrationForTableAndRange v = (MaybeFinishConsensusMigrationForTableAndRange)t; return TypeSizes.sizeof(v.keyspace) + TypeSizes.sizeof(v.cf) - + ConsensusTableMigration.rangesSerializer.serializedSize(v.repairedRanges, version) + + ConsensusTableMigration.rangesSerializer.serializedSize(v.paxosRepairedRanges, version) + + ConsensusTableMigration.rangesSerializer.serializedSize(v.accordBarrieredRanges, version) + Epoch.serializer.serializedSize(v.minEpoch) + TypeSizes.sizeof(v.repairType.value); } diff --git a/src/java/org/apache/cassandra/transport/Dispatcher.java b/src/java/org/apache/cassandra/transport/Dispatcher.java index f701434d00..6a38184829 100644 --- a/src/java/org/apache/cassandra/transport/Dispatcher.java +++ b/src/java/org/apache/cassandra/transport/Dispatcher.java @@ -151,6 +151,11 @@ public class Dispatcher implements CQLMessageHandler.MessageConsumer execute(Collection mutations) throws InvalidRequestException + public List execute(Collection mutations) throws InvalidRequestException { boolean hasCounters = false; List augmentedMutations = null; @@ -156,7 +164,7 @@ public class TriggerExecutor return mergeMutations(Iterables.concat(originalMutations, augmentedMutations)); } - private Collection mergeMutations(Iterable mutations) + private List mergeMutations(Iterable mutations) { ListMultimap, Mutation> groupedMutations = ArrayListMultimap.create(); diff --git a/src/java/org/apache/cassandra/utils/NoSpamLogger.java b/src/java/org/apache/cassandra/utils/NoSpamLogger.java index 5c37a043d8..e16caa930f 100644 --- a/src/java/org/apache/cassandra/utils/NoSpamLogger.java +++ b/src/java/org/apache/cassandra/utils/NoSpamLogger.java @@ -21,11 +21,10 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Supplier; +import com.google.common.annotations.VisibleForTesting; import org.cliffc.high_scale_lib.NonBlockingHashMap; import org.slf4j.Logger; -import com.google.common.annotations.VisibleForTesting; - import static org.apache.cassandra.utils.Clock.Global; /** @@ -78,7 +77,7 @@ public class NoSpamLogger this.minIntervalNanos = minIntervalNanos; } - private boolean shouldLog(long nowNanos) + public boolean shouldLog(long nowNanos) { long expected = get(); return nowNanos >= expected && compareAndSet(expected, nowNanos + minIntervalNanos); diff --git a/src/java/org/apache/cassandra/utils/Throwables.java b/src/java/org/apache/cassandra/utils/Throwables.java index 242c768808..df9ad11138 100644 --- a/src/java/org/apache/cassandra/utils/Throwables.java +++ b/src/java/org/apache/cassandra/utils/Throwables.java @@ -39,6 +39,8 @@ import org.apache.cassandra.io.FSWriteError; import org.apache.cassandra.io.util.File; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; +import static com.google.common.base.Throwables.getStackTraceAsString; + public final class Throwables { public enum FileOpType { READ, WRITE } @@ -48,6 +50,23 @@ public final class Throwables void perform() throws E; } + public interface ThrowingRunnable + { + void run() throws Exception; + } + + public static void runUnchecked(ThrowingRunnable runnable) + { + try + { + runnable.run(); + } + catch (Exception e) + { + throwAsUncheckedException(e); + } + } + public static boolean isCausedBy(Throwable t, Predicate cause) { return cause.test(t) || (t.getCause() != null && cause.test(t.getCause())); @@ -340,4 +359,16 @@ public final class Throwables if (Arrays.stream(causeClasses).noneMatch(c -> anyCauseMatches(err, c::isInstance))) throw new AssertionError("The exception is not caused by any of " + Arrays.toString(causeClasses), err); } + + public static Object getStackTraceAsToString(Throwable t) + { + return new Object() + { + @Override + public String toString() + { + return getStackTraceAsString(t); + } + }; + } } diff --git a/test/distributed/org/apache/cassandra/distributed/api/ICoordinator.java b/test/distributed/org/apache/cassandra/distributed/api/ICoordinator.java index 5b27d3d44f..4adaff4c4d 100644 --- a/test/distributed/org/apache/cassandra/distributed/api/ICoordinator.java +++ b/test/distributed/org/apache/cassandra/distributed/api/ICoordinator.java @@ -18,13 +18,13 @@ package org.apache.cassandra.distributed.api; +import org.apache.cassandra.distributed.shared.FutureUtils; + import java.util.Iterator; import java.util.UUID; import java.util.concurrent.Future; import java.util.function.BiConsumer; -import org.apache.cassandra.distributed.shared.FutureUtils; - // The cross-version API requires that a Coordinator can be constructed without any constructor arguments public interface ICoordinator { @@ -82,6 +82,7 @@ public interface ICoordinator } Future asyncExecuteWithTracingWithResult(UUID sessionId, String query, ConsistencyLevel consistencyLevel, Object... boundValues); + Future asyncExecuteWithResult(String query, ConsistencyLevel consistencyLevel, Object... boundValues); default Object[][] executeWithTracing(UUID sessionId, String query, ConsistencyLevel consistencyLevel, Object... boundValues) { diff --git a/test/distributed/org/apache/cassandra/distributed/impl/Coordinator.java b/test/distributed/org/apache/cassandra/distributed/impl/Coordinator.java index 8d17177884..5053735336 100644 --- a/test/distributed/org/apache/cassandra/distributed/impl/Coordinator.java +++ b/test/distributed/org/apache/cassandra/distributed/impl/Coordinator.java @@ -102,6 +102,12 @@ public class Coordinator implements ICoordinator }).call(); } + @Override + public Future asyncExecuteWithResult(String query, ConsistencyLevel consistencyLevelOrigin, Object... boundValues) + { + return instance.async(() -> unsafeExecuteInternal(query, consistencyLevelOrigin, boundValues)).call(); + } + public static org.apache.cassandra.db.ConsistencyLevel toCassandraCL(ConsistencyLevel cl) { try diff --git a/test/distributed/org/apache/cassandra/distributed/impl/TestChangeListener.java b/test/distributed/org/apache/cassandra/distributed/impl/TestChangeListener.java index a40ee9a96f..977a515170 100644 --- a/test/distributed/org/apache/cassandra/distributed/impl/TestChangeListener.java +++ b/test/distributed/org/apache/cassandra/distributed/impl/TestChangeListener.java @@ -18,10 +18,8 @@ package org.apache.cassandra.distributed.impl; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.function.Predicate; +import java.util.NavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,7 +28,9 @@ import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadataService; import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.listeners.ChangeListener; +import org.apache.cassandra.utils.concurrent.CountDownLatch; import org.apache.cassandra.utils.concurrent.WaitQueue; +import org.apache.cassandra.utils.concurrent.WaitQueue.Signal; public class TestChangeListener implements ChangeListener { @@ -43,77 +43,68 @@ public class TestChangeListener implements ChangeListener ClusterMetadataService.instance().log().addListener(instance); } - private final List> preCommitPredicates = new ArrayList<>(); - private final List> postCommitPredicates = new ArrayList<>(); + NavigableMap preCommitBarriers = new ConcurrentSkipListMap<>(); + NavigableMap postCommitBarriers = new ConcurrentSkipListMap<>(); private final WaitQueue waiters = WaitQueue.newWaitQueue(); + private class CommitBarrier + { + private final CountDownLatch waiting = CountDownLatch.newCountDownLatch(1); + private final Runnable onPaused; + private final String desc; + + private CommitBarrier(Runnable onPaused, String desc) + { + this.onPaused = onPaused; + this.desc = desc; + } + + private void await() + { + logger.debug("Notifying paused: {}", desc); + Signal s = waiters.register(); + waiting.decrement(); + onPaused.run(); + s.awaitUninterruptibly(); + logger.debug("Unpaused: {}", desc); + } + } @Override public void notifyPreCommit(ClusterMetadata prev, ClusterMetadata next, boolean fromSnapshot) { - Iterator> iter = preCommitPredicates.iterator(); - while (iter.hasNext()) - { - if (iter.next().test(next.epoch)) - { - logger.debug("Epoch matches pre-commit predicate, pausing"); - pause(); - iter.remove(); - } - } + CommitBarrier commitBarrier = preCommitBarriers.remove(next.epoch); + if (commitBarrier != null) + commitBarrier.await(); } @Override public void notifyPostCommit(ClusterMetadata prev, ClusterMetadata next, boolean fromSnapshot) { - Iterator> iter = postCommitPredicates.iterator(); - while (iter.hasNext()) - { - if (iter.next().test(next.epoch)) - { - logger.debug("Epoch matches post-commit predicate, pausing"); - pause(); - iter.remove(); - } - } + CommitBarrier commitBarrier = postCommitBarriers.remove(next.epoch); + if (commitBarrier != null) + commitBarrier.await(); } - public void pauseBefore(Epoch epoch, Runnable onMatch) + public void pauseBefore(Epoch epoch, Runnable onPaused) { - logger.debug("Requesting pause before enacting {}", epoch); - preCommitPredicates.add((e) -> { - if (e.is(epoch)) - { - onMatch.run(); - return true; - } - return false; - }); + preCommitBarriers.put(epoch, new CommitBarrier(onPaused, "pre-commit " + epoch)); } - public void pauseAfter(Epoch epoch, Runnable onMatch) + public void pauseAfter(Epoch epoch, Runnable onPaused) { - logger.debug("Requesting pause after enacting {}", epoch); - postCommitPredicates.add((e) -> { - if (e.is(epoch)) - { - onMatch.run(); - return true; - } - return false; - }); - } - - public void pause() - { - WaitQueue.Signal signal = waiters.register(); - logger.debug("Log follower is paused, waiting..."); - signal.awaitUninterruptibly(); - logger.debug("Resumed log follower..."); + postCommitBarriers.put(epoch, new CommitBarrier(onPaused, "post-commit " + epoch)); } public void unpause() { - logger.debug("Unpausing log follower"); + logger.info("Unpausing all precommit and post commit barriers"); + waiters.signalAll(); + } + + public void clearAndUnpause() + { + preCommitBarriers.clear(); + postCommitBarriers.clear(); waiters.signalAll(); } } diff --git a/test/distributed/org/apache/cassandra/distributed/shared/ClusterUtils.java b/test/distributed/org/apache/cassandra/distributed/shared/ClusterUtils.java index cbb6c42046..79daae1583 100644 --- a/test/distributed/org/apache/cassandra/distributed/shared/ClusterUtils.java +++ b/test/distributed/org/apache/cassandra/distributed/shared/ClusterUtils.java @@ -600,6 +600,11 @@ public class ClusterUtils instance.runOnInstance(() -> TestChangeListener.instance.unpause()); } + public static void clearAndUnpause(IInvokableInstance instance) + { + instance.runOnInstance(() -> TestChangeListener.instance.clearAndUnpause()); + } + public static boolean isMigrating(IInvokableInstance instance) { return instance.callOnInstance(() -> ClusterMetadataService.instance().isMigrating()); diff --git a/test/distributed/org/apache/cassandra/distributed/test/CASAddTest.java b/test/distributed/org/apache/cassandra/distributed/test/CASAddTest.java index 50ddd09158..37e44f7794 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/CASAddTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/CASAddTest.java @@ -72,13 +72,13 @@ public class CASAddTest extends TestBaseImpl assertRows(cluster.coordinator(1).execute("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = 1", ConsistencyLevel.SERIAL), row(1, null, null)); // this section is testing current limitations... if they start to fail due to the limitations going away... update this test to include those cases - Assertions.assertThatThrownBy(() -> cluster.coordinator(1).execute(batch( + Assertions.assertThatThrownBy(() -> cluster.coordinator(1).execute(unloggedBatch( "INSERT INTO " + KEYSPACE + ".tbl (pk, a, b) VALUES (1, 0, '') IF NOT EXISTS", "UPDATE " + KEYSPACE + ".tbl SET a = a + 1, b = b + 'success' WHERE pk = 1 IF EXISTS" ), ConsistencyLevel.QUORUM)) .is(AssertionUtils.is(InvalidRequestException.class)) .hasMessage("Cannot mix IF EXISTS and IF NOT EXISTS conditions for the same row"); - Assertions.assertThatThrownBy(() -> cluster.coordinator(1).execute(batch( + Assertions.assertThatThrownBy(() -> cluster.coordinator(1).execute(unloggedBatch( "INSERT INTO " + KEYSPACE + ".tbl (pk, a, b) VALUES (1, 0, '') IF NOT EXISTS", "UPDATE " + KEYSPACE + ".tbl SET a = a + 1, b = b + 'success' WHERE pk = 1" diff --git a/test/distributed/org/apache/cassandra/distributed/test/CASMultiDCTest.java b/test/distributed/org/apache/cassandra/distributed/test/CASMultiDCTest.java index 382cb543c7..8fa3b0fe94 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/CASMultiDCTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/CASMultiDCTest.java @@ -21,6 +21,7 @@ package org.apache.cassandra.distributed.test; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; @@ -36,7 +37,10 @@ import org.apache.cassandra.distributed.api.IInstanceConfig; import org.apache.cassandra.service.paxos.PaxosCommit; import org.apache.cassandra.utils.ByteBufferUtil; -import static org.apache.cassandra.distributed.api.ConsistencyLevel.*; +import static org.apache.cassandra.distributed.api.ConsistencyLevel.LOCAL_QUORUM; +import static org.apache.cassandra.distributed.api.ConsistencyLevel.LOCAL_SERIAL; +import static org.apache.cassandra.distributed.api.ConsistencyLevel.QUORUM; +import static org.apache.cassandra.distributed.api.ConsistencyLevel.SERIAL; public class CASMultiDCTest { @@ -75,6 +79,12 @@ public class CASMultiDCTest })); } + @AfterClass + public static void afterClass() throws Throwable + { + CLUSTER.close(); + } + @Before public void setUp() { diff --git a/test/distributed/org/apache/cassandra/distributed/test/MessageFiltersTest.java b/test/distributed/org/apache/cassandra/distributed/test/MessageFiltersTest.java index ffe65c49dc..ac4e952fe6 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/MessageFiltersTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/MessageFiltersTest.java @@ -275,7 +275,8 @@ public class MessageFiltersTest extends TestBaseImpl try (Cluster cluster = init(builder().withNodes(3) .withConfig(config -> config.with(GOSSIP) .with(NETWORK) - .set("hinted_handoff_enabled", true)) + .set("hinted_handoff_enabled", true) + .set("accord.enabled", false)) .start())) { cluster.schemaChange(withKeyspace("CREATE TABLE %s.tbl (k int PRIMARY KEY, v int)")); diff --git a/test/distributed/org/apache/cassandra/distributed/test/QueriesTableTest.java b/test/distributed/org/apache/cassandra/distributed/test/QueriesTableTest.java index 36220e3853..528e5ad00c 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/QueriesTableTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/QueriesTableTest.java @@ -146,7 +146,6 @@ public class QueriesTableTest extends TestBaseImpl // Issue a read to unblock the read generated by the original CAS operation: SESSION.executeAsync("SELECT * FROM " + KEYSPACE + ".cas_tbl WHERE k = 0"); - waitForQueriesToFinish(); } @@ -174,7 +173,7 @@ public class QueriesTableTest extends TestBaseImpl @Test public void shouldExposeTransaction() throws Throwable { - SHARED_CLUSTER.schemaChange("CREATE TABLE " + KEYSPACE + ".accord_tbl (k int primary key, v int) WITH transactional_mode='full'"); + SHARED_CLUSTER.schemaChange("CREATE TABLE " + KEYSPACE + ".accord_tbl (k int primary key, v int) WITH transactional_mode='mixed_reads'"); // Disable recovery to make sure only one local read occurs: for (IInvokableInstance instance : SHARED_CLUSTER) diff --git a/test/distributed/org/apache/cassandra/distributed/test/RepairDigestTrackingTest.java b/test/distributed/org/apache/cassandra/distributed/test/RepairDigestTrackingTest.java index ce3df571d8..2e1cb4b16d 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/RepairDigestTrackingTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/RepairDigestTrackingTest.java @@ -30,44 +30,45 @@ import java.util.stream.Stream; import com.google.common.util.concurrent.Uninterruptibles; import org.junit.Assert; - -import org.apache.cassandra.Util; -import org.apache.cassandra.concurrent.SEPExecutor; -import org.apache.cassandra.config.CassandraRelevantProperties; -import org.apache.cassandra.dht.Token; -import org.apache.cassandra.distributed.shared.WithProperties; -import org.apache.cassandra.locator.AbstractReplicationStrategy; -import org.apache.cassandra.locator.EndpointsForToken; -import org.apache.cassandra.locator.InetAddressAndPort; -import org.apache.cassandra.locator.ReplicaLayout; -import org.apache.cassandra.locator.ReplicaUtils; -import org.apache.cassandra.service.snapshot.SnapshotManager; -import org.apache.cassandra.tcm.ClusterMetadata; -import org.apache.cassandra.utils.Throwables; import org.junit.Test; import net.bytebuddy.ByteBuddy; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.bind.annotation.SuperCall; +import org.apache.cassandra.Util; +import org.apache.cassandra.concurrent.SEPExecutor; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.ReadCommand; import org.apache.cassandra.db.ReadExecutionController; import org.apache.cassandra.db.SinglePartitionReadCommand; import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator; +import org.apache.cassandra.dht.Token; import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.api.ConsistencyLevel; import org.apache.cassandra.distributed.api.IInvokableInstance; import org.apache.cassandra.distributed.api.IIsolatedExecutor; +import org.apache.cassandra.distributed.shared.WithProperties; import org.apache.cassandra.io.sstable.Descriptor; import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.io.sstable.format.StatsComponent; import org.apache.cassandra.io.sstable.metadata.StatsMetadata; +import org.apache.cassandra.locator.AbstractReplicationStrategy; +import org.apache.cassandra.locator.EndpointsForToken; +import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.locator.ReplicaLayout; +import org.apache.cassandra.locator.ReplicaUtils; +import org.apache.cassandra.schema.TableId; import org.apache.cassandra.service.ActiveRepairService; import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.service.StorageProxy.LocalReadRunnable; +import org.apache.cassandra.service.reads.ReadCoordinator; +import org.apache.cassandra.service.snapshot.SnapshotManager; +import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.utils.DiagnosticSnapshotService; +import org.apache.cassandra.utils.Throwables; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; @@ -440,7 +441,7 @@ public class RepairDigestTrackingTest extends TestBaseImpl .load(classLoader, ClassLoadingStrategy.Default.INJECTION); new ByteBuddy().rebase(ReplicaLayout.class) - .method(named("forTokenReadSorted").and(takesArguments(ClusterMetadata.class, Keyspace.class, AbstractReplicationStrategy.class, Token.class))) + .method(named("forTokenReadSorted").and(takesArguments(ClusterMetadata.class, Keyspace.class, AbstractReplicationStrategy.class, TableId.class, Token.class, ReadCoordinator.class))) .intercept(MethodDelegation.to(BBHelper.class)) .make() .load(classLoader, ClassLoadingStrategy.Default.INJECTION); @@ -475,7 +476,7 @@ public class RepairDigestTrackingTest extends TestBaseImpl } @SuppressWarnings({ "unused" }) - public static ReplicaLayout.ForTokenRead forTokenReadSorted(ClusterMetadata metadata, Keyspace keyspace, AbstractReplicationStrategy replicationStrategy, Token token) + public static ReplicaLayout.ForTokenRead forTokenReadSorted(ClusterMetadata metadata, Keyspace keyspace, AbstractReplicationStrategy replicationStrategy, TableId tableId, Token token, org.apache.cassandra.service.reads.ReadCoordinator coordinator) { try { diff --git a/test/distributed/org/apache/cassandra/distributed/test/ShortReadProtectionTest.java b/test/distributed/org/apache/cassandra/distributed/test/ShortReadProtectionTest.java index 103b1fc4c0..4d2ae2df7a 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/ShortReadProtectionTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/ShortReadProtectionTest.java @@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.IntStream; +import com.google.common.collect.ImmutableList; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -90,7 +91,7 @@ public class ShortReadProtectionTest extends TestBaseImpl public static Collection data() { List result = new ArrayList<>(); - for (TransactionalMode mode : TransactionalMode.values()) + for (TransactionalMode mode : ImmutableList.of(TransactionalMode.mixed_reads, TransactionalMode.off)) for (ConsistencyLevel readConsistencyLevel : Arrays.asList(ALL, QUORUM, SERIAL)) for (boolean flush : BOOLEANS) for (boolean paging : BOOLEANS) diff --git a/test/distributed/org/apache/cassandra/distributed/test/TestBaseImpl.java b/test/distributed/org/apache/cassandra/distributed/test/TestBaseImpl.java index b96c0463fd..7eadb7e0b4 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/TestBaseImpl.java +++ b/test/distributed/org/apache/cassandra/distributed/test/TestBaseImpl.java @@ -33,6 +33,8 @@ import java.util.function.Function; import java.util.stream.Collectors; import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListenableFutureTask; import org.junit.After; import org.junit.BeforeClass; @@ -58,8 +60,11 @@ import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.db.marshal.UUIDType; import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.api.ICluster; +import org.apache.cassandra.distributed.api.ICoordinator; +import org.apache.cassandra.distributed.api.IInstance; import org.apache.cassandra.distributed.api.IInstanceConfig; import org.apache.cassandra.distributed.api.IInvokableInstance; +import org.apache.cassandra.distributed.api.NodeToolResult; import org.apache.cassandra.distributed.shared.DistributedTestBase; import org.apache.cassandra.service.accord.AccordStateCache; @@ -69,6 +74,7 @@ import static org.apache.cassandra.config.CassandraRelevantProperties.JOIN_RING; import static org.apache.cassandra.config.CassandraRelevantProperties.RESET_BOOTSTRAP_PROGRESS; import static org.apache.cassandra.config.CassandraRelevantProperties.SKIP_GC_INSPECTOR; import static org.apache.cassandra.distributed.action.GossipHelper.withProperty; +import static org.assertj.core.api.Assertions.fail; // checkstyle: suppress below 'blockSystemPropertyUsage' public class TestBaseImpl extends DistributedTestBase @@ -140,10 +146,17 @@ public class TestBaseImpl extends DistributedTestBase return tupleType.pack(bbs, ByteBufferAccessor.instance); } - public static String batch(String... queries) + public static String unloggedBatch(String... queries) + { + return batch(false, queries); + } + + public static String batch(boolean logged, String... queries) { StringBuilder sb = new StringBuilder(); - sb.append("BEGIN UNLOGGED BATCH\n"); + sb.append("BEGIN "); + sb.append(logged ? "" : "UNLOGGED "); + sb.append("BATCH\n"); for (String q : queries) sb.append(q).append(";\n"); sb.append("APPLY BATCH;"); @@ -253,4 +266,31 @@ public class TestBaseImpl extends DistributedTestBase for (int i = 1; i < cluster.size() + 1; i++) cluster.get(i).nodetool("disableautocompaction", keyspace, table); } + + public static String nodetool(IInstance instance, String... commandAndArgs) + { + NodeToolResult nodetoolResult = instance.nodetoolResult(commandAndArgs); + if (!nodetoolResult.getStdout().isEmpty()) + System.out.println(nodetoolResult.getStdout()); + if (!nodetoolResult.getStderr().isEmpty()) + System.err.println(nodetoolResult.getStderr()); + if (nodetoolResult.getError() != null) + fail("Failed nodetool " + Arrays.asList(commandAndArgs), nodetoolResult.getError()); + // TODO why does standard out end up in stderr in nodetool? + return nodetoolResult.getStdout(); + } + + public static String nodetool(ICoordinator coordinator, String... commandAndArgs) + { + return nodetool(coordinator.instance(), commandAndArgs); + } + + public static ListenableFuture nodetoolAsync(ICoordinator coordinator, String... commandAndArgs) + { + ListenableFutureTask task = ListenableFutureTask.create(() -> nodetool(coordinator, commandAndArgs)); + Thread asyncThread = new Thread(task, "NodeTool: " + Arrays.asList(commandAndArgs)); + asyncThread.setDaemon(true); + asyncThread.start(); + return task; + } } diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordCQLTestBase.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordCQLTestBase.java index c5f92a6c70..7ede0b3cdd 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordCQLTestBase.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordCQLTestBase.java @@ -18,14 +18,37 @@ package org.apache.cassandra.distributed.test.accord; -import accord.primitives.Unseekables; -import accord.topology.Topologies; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import accord.primitives.Unseekables; +import accord.topology.Topologies; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.functions.types.utils.Bytes; -import org.apache.cassandra.db.marshal.*; +import org.apache.cassandra.db.marshal.Int32Type; +import org.apache.cassandra.db.marshal.ListType; +import org.apache.cassandra.db.marshal.MapType; +import org.apache.cassandra.db.marshal.SetType; +import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.api.ConsistencyLevel; import org.apache.cassandra.distributed.api.ICoordinator; @@ -37,24 +60,13 @@ import org.apache.cassandra.service.accord.AccordTestUtils; import org.apache.cassandra.service.consensus.TransactionalMode; import org.apache.cassandra.utils.ByteBufferUtil; import org.assertj.core.api.Assertions; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.*; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.stream.Collectors; import static java.util.Collections.singletonList; import static org.apache.cassandra.cql3.CQLTester.row; import static org.apache.cassandra.distributed.util.QueryResultUtil.assertThat; -import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; public abstract class AccordCQLTestBase extends AccordTestBase { @@ -86,11 +98,11 @@ public abstract class AccordCQLTestBase extends AccordTestBase for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + "(k, c, v) VALUES (?, ?, ?);", ConsistencyLevel.ALL, i, j, i + j); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + "(k, c, v) VALUES (?, ?, ?);", ConsistencyLevel.ALL, i, j, i + j); } // multi row String cql = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k=? AND c IN (?, ?);\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k=? AND c IN (?, ?);\n" + "COMMIT TRANSACTION"; SimpleQueryResult result = cluster.coordinator(1).executeWithResult(cql, ConsistencyLevel.ANY, 0, 0, 1); assertThat(result).isEqualTo(QueryResults.builder() @@ -101,7 +113,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase // Results should be in Partiton/Clustering order, so make sure // multi partition cql = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k IN (?, ?) AND c = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k IN (?, ?) AND c = ?;\n" + "COMMIT TRANSACTION"; for (boolean asc : Arrays.asList(true, false)) { @@ -116,7 +128,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase // multi-partition, multi-clustering cql = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k IN (?, ?) AND c IN (?, ?);\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k IN (?, ?) AND c IN (?, ?);\n" + "COMMIT TRANSACTION"; for (boolean asc : Arrays.asList(true, false)) { @@ -195,14 +207,14 @@ public abstract class AccordCQLTestBase extends AccordTestBase { test(cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 0, 3);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (1, 0, 3);", ConsistencyLevel.ALL); String query = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT v FROM " + qualifiedTableName + " WHERE k = ? AND c = ?);\n" + - " LET row2 = (SELECT v FROM " + qualifiedTableName + " WHERE k = ? AND c = ?);\n" + - " SELECT v FROM " + qualifiedTableName + " WHERE k = ? AND c = ?;\n" + + " LET row1 = (SELECT v FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = ?);\n" + + " LET row2 = (SELECT v FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = ?);\n" + + " SELECT v FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = ?;\n" + " IF row1 IS NULL AND row2.v = ? THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (?, ?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (?, ?, ?);\n" + " END IF\n" + "COMMIT TRANSACTION"; @@ -216,7 +228,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase assertEquals(3, result[0][0]); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k=0 AND c=0;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k=0 AND c=0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, 0, 1 }, check); }); @@ -225,13 +237,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testRegularScalarIsNull() throws Throwable { - testScalarIsNull("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'"); + testScalarIsNull("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testStaticScalarIsNull() throws Throwable { - testScalarIsNull("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int static, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'"); + testScalarIsNull("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v int static, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'"); } private void testScalarIsNull(String tableDDL) throws Exception { @@ -239,25 +251,25 @@ public abstract class AccordCQLTestBase extends AccordTestBase cluster -> { String insertNull = "BEGIN TRANSACTION\n" + - " LET row0 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 0 LIMIT 1);\n" + + " LET row0 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0 LIMIT 1);\n" + " SELECT row0.k, row0.v;\n" + " IF row0.v IS NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (?, ?, null);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (?, ?, null);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { null, null }, insertNull, 0, 0); String insert = "BEGIN TRANSACTION\n" + - " LET row0 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 0 LIMIT 1);\n" + + " LET row0 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0 LIMIT 1);\n" + " SELECT row0.k, row0.v;\n" + " IF row0.v IS NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (?, ?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (?, ?, ?);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, null }, insert, 0, 0, 1); String check = "BEGIN TRANSACTION\n" + - " SELECT k, c, v FROM " + qualifiedTableName + " WHERE k=0 AND c=0;\n" + + " SELECT k, c, v FROM " + qualifiedAccordTableName + " WHERE k=0 AND c=0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, 0, 1 }, check); }); @@ -266,36 +278,36 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testQueryStaticColumn() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int, c int, s int static, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, s int static, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'", cluster -> { // select partition key, clustering key and static column, restrict on partition and clustering testQueryStaticColumn(cluster, - "LET row0 = (SELECT k, c, s, v FROM " + qualifiedTableName + " WHERE k = ? AND c = 0);\n" + + "LET row0 = (SELECT k, c, s, v FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = 0);\n" + "SELECT row0.k, row0.c, row0.s, row0.v;\n", - "SELECT k, c, s, v FROM " + qualifiedTableName + " WHERE k = ? AND c = 0"); + "SELECT k, c, s, v FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = 0"); // select partition key, clustering key and static column, restrict on partition and limit to 1 row testQueryStaticColumn(cluster, - "LET row0 = (SELECT k, c, s, v FROM " + qualifiedTableName + " WHERE k = ? LIMIT 1);\n" + + "LET row0 = (SELECT k, c, s, v FROM " + qualifiedAccordTableName + " WHERE k = ? LIMIT 1);\n" + "SELECT row0.k, row0.c, row0.s, row0.v;\n", - "SELECT k, c, s, v FROM " + qualifiedTableName + " WHERE k = ? LIMIT 1"); + "SELECT k, c, s, v FROM " + qualifiedAccordTableName + " WHERE k = ? LIMIT 1"); // select static column and regular column, restrict on partition and clustering testQueryStaticColumn(cluster, - "LET row0 = (SELECT s, v FROM " + qualifiedTableName + " WHERE k = ? AND c = 0);\n" + + "LET row0 = (SELECT s, v FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = 0);\n" + "SELECT row0.s, row0.v;\n", - "SELECT s, v FROM " + qualifiedTableName + " WHERE k = ? AND c = 0"); + "SELECT s, v FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = 0"); // select just static column, restrict on partition and limit to 1 row testQueryStaticColumn(cluster, - "LET row0 = (SELECT s FROM " + qualifiedTableName + " WHERE k = ? LIMIT 1);\n" + + "LET row0 = (SELECT s FROM " + qualifiedAccordTableName + " WHERE k = ? LIMIT 1);\n" + "SELECT row0.s;\n", - "SELECT s FROM " + qualifiedTableName + " WHERE k = ? LIMIT 1"); + "SELECT s FROM " + qualifiedAccordTableName + " WHERE k = ? LIMIT 1"); }); } @@ -305,22 +317,22 @@ public abstract class AccordCQLTestBase extends AccordTestBase int key = 10; assertResultsFromAccordMatches(cluster, accordReadQuery, simpleReadQuery, key++); - cluster.get(1).coordinator().execute("INSERT INTO " + qualifiedTableName + " (k, s) VALUES (?, null);", ConsistencyLevel.ALL, key); + cluster.get(1).coordinator().execute("INSERT INTO " + qualifiedAccordTableName + " (k, s) VALUES (?, null);", ConsistencyLevel.ALL, key); logger().info("null -> static column"); assertResultsFromAccordMatches(cluster, accordReadQuery, simpleReadQuery, key++); - cluster.get(1).coordinator().execute("INSERT INTO " + qualifiedTableName + " (k, s) VALUES (?, 1);", ConsistencyLevel.ALL, key); + cluster.get(1).coordinator().execute("INSERT INTO " + qualifiedAccordTableName + " (k, s) VALUES (?, 1);", ConsistencyLevel.ALL, key); logger().info("Inserted 1 -> static column"); assertResultsFromAccordMatches(cluster, accordReadQuery, simpleReadQuery, key++); - cluster.get(1).coordinator().execute("INSERT INTO " + qualifiedTableName + " (k, c) VALUES (?, 0);", ConsistencyLevel.ALL, key); + cluster.get(1).coordinator().execute("INSERT INTO " + qualifiedAccordTableName + " (k, c) VALUES (?, 0);", ConsistencyLevel.ALL, key); logger().info("Inserted 0 -> clustering"); assertResultsFromAccordMatches(cluster, accordReadQuery, simpleReadQuery, key); } @Test public void testUpdateStaticColumn() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int, c int, s int static, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + '\'', + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, s int static, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + '\'', cluster -> { checkUpdateStatic(cluster, "SET s=1 WHERE k=?", 101, "[[101, null, 1, null]]", "[]"); @@ -336,16 +348,16 @@ public abstract class AccordCQLTestBase extends AccordTestBase private void checkUpdateStatic(Cluster cluster, String update, int key, String expPart, String expClust) { Object[][] r1, r2, r3, r4, r; - r = cluster.get(1).coordinator().execute("UPDATE " + qualifiedTableName + " " + update + " IF s = NULL;", ConsistencyLevel.QUORUM, key); + r = cluster.get(1).coordinator().execute("UPDATE " + qualifiedAccordTableName + " " + update + " IF s = NULL;", ConsistencyLevel.QUORUM, key); Assertions.assertThat(Arrays.deepToString(r)).isEqualTo("[[true]]"); - r1 = cluster.get(1).coordinator().execute("SELECT * FROM " + qualifiedTableName + " WHERE k = ? LIMIT 1;", ConsistencyLevel.SERIAL, key); - r2 = cluster.get(1).coordinator().execute("SELECT * FROM " + qualifiedTableName + " WHERE k = ? AND c = 0;", ConsistencyLevel.SERIAL, key); - cluster.get(1).coordinator().execute("TRUNCATE " + qualifiedTableName, ConsistencyLevel.ALL); + r1 = cluster.get(1).coordinator().execute("SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ? LIMIT 1;", ConsistencyLevel.SERIAL, key); + r2 = cluster.get(1).coordinator().execute("SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = 0;", ConsistencyLevel.SERIAL, key); + cluster.get(1).coordinator().execute("TRUNCATE " + qualifiedAccordTableName, ConsistencyLevel.ALL); - executeAsTxn(cluster, "UPDATE " + qualifiedTableName + " " + update + ";", key); - r3 = executeAsTxn(cluster, "SELECT * FROM " + qualifiedTableName + " WHERE k = ? LIMIT 1;", key).toObjectArrays(); - r4 = executeAsTxn(cluster, "SELECT * FROM " + qualifiedTableName + " WHERE k = ? AND c = 0;", key).toObjectArrays(); - cluster.get(1).coordinator().execute("TRUNCATE " + qualifiedTableName, ConsistencyLevel.ALL); + executeAsTxn(cluster, "UPDATE " + qualifiedAccordTableName + " " + update + ";", key); + r3 = executeAsTxn(cluster, "SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ? LIMIT 1;", key).toObjectArrays(); + r4 = executeAsTxn(cluster, "SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = 0;", key).toObjectArrays(); + cluster.get(1).coordinator().execute("TRUNCATE " + qualifiedAccordTableName, ConsistencyLevel.ALL); Assertions.assertThat(Arrays.deepToString(r1)).isEqualTo(expPart); Assertions.assertThat(Arrays.deepToString(r2)).isEqualTo(expClust); @@ -416,12 +428,12 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testStaticScalarEQ() throws Throwable { - testScalarCondition("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int static, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'", 3, "=", 3, "="); + testScalarCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v int static, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'", 3, "=", 3, "="); } private void testScalarCondition(int lhs, String operator, int rhs, String reversedOperator) throws Exception { - testScalarCondition("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'", lhs, operator, rhs, reversedOperator); + testScalarCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'", lhs, operator, rhs, reversedOperator); } private void testScalarCondition(String tableDDL, int lhs, String operator, int rhs, String reversedOperator) throws Exception @@ -429,27 +441,27 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(tableDDL, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, 0, " + lhs + ");", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (0, 0, " + lhs + ");", ConsistencyLevel.ALL); String query = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT v FROM " + qualifiedTableName + " WHERE k = ? LIMIT 1);\n" + + " LET row1 = (SELECT v FROM " + qualifiedAccordTableName + " WHERE k = ? LIMIT 1);\n" + " SELECT row1.v;\n" + " IF row1.v " + operator + " ? THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (?, ?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (?, ?, ?);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { lhs }, query, 0, rhs, 1, 0, 1); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ? AND c = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 1, 0, 1 }, check, 1, 0); String queryWithReversed = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT v FROM " + qualifiedTableName + " WHERE k = ? LIMIT 1);\n" + + " LET row1 = (SELECT v FROM " + qualifiedAccordTableName + " WHERE k = ? LIMIT 1);\n" + " SELECT row1.v;\n" + " IF ? " + reversedOperator + " row1.v THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (?, ?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (?, ?, ?);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { lhs }, queryWithReversed, 0, rhs, 2, 0, 1); @@ -463,7 +475,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(cluster -> { String query = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k=0 AND c=0;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k=0 AND c=0;\n" + "COMMIT TRANSACTION"; SimpleQueryResult result = cluster.coordinator(1).executeWithResult(query, ConsistencyLevel.ANY); assertFalse(result.hasNext()); @@ -476,13 +488,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(cluster -> { String query = "BEGIN TRANSACTION\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (?, ?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (?, ?, ?);\n" + "COMMIT TRANSACTION"; SimpleQueryResult result = cluster.coordinator(1).executeWithResult(query, ConsistencyLevel.ANY, 0, 0, 1); assertFalse(result.hasNext()); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k=? AND c=?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k=? AND c=?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {0, 0, 1}, check, 0, 0); }); @@ -493,14 +505,14 @@ public abstract class AccordCQLTestBase extends AccordTestBase { test(cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 0, 3);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (1, 0, 3);", ConsistencyLevel.ALL); String query = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ? AND c = ?);\n" + - " LET row2 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ? AND c = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = ?);\n" + + " LET row2 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = ?);\n" + " SELECT row1.v, row2.k, row2.c, row2.v;\n" + " IF row1 IS NULL AND row2.v = ? THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (?, ?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (?, ?, ?);\n" + " END IF\n" + "COMMIT TRANSACTION"; SimpleQueryResult result = cluster.coordinator(1).executeWithResult(query, ConsistencyLevel.ANY, 0, 0, 1, 0, 3, 0, 0, 1); @@ -508,7 +520,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase assertThat(result).hasSize(1).contains(null, 1, 0, 3); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k=0 AND c=0;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k=0 AND c=0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {0, 0, 1}, check); }); @@ -519,14 +531,14 @@ public abstract class AccordCQLTestBase extends AccordTestBase { test(cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 0, 3);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (1, 0, 3);", ConsistencyLevel.ALL); String query = "BEGIN TRANSACTION\n" + - " LET row0 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ? AND c = ?);\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ? AND c = ?);\n" + + " LET row0 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = ?);\n" + " SELECT row1.v;\n" + " IF row0 IS NULL AND row1.v = ? THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (?, ?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (?, ?, ?);\n" + " END IF\n" + "COMMIT TRANSACTION"; SimpleQueryResult result = cluster.coordinator(1).executeWithResult(query, ConsistencyLevel.ANY, 0, 0, 1, 0, 2, 0, 0, 1); @@ -534,7 +546,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase assertThat(result).hasSize(1).contains(3); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k=0 AND c=0;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k=0 AND c=0;\n" + "COMMIT TRANSACTION"; assertEmptyWithPreemptedRetry(cluster, check); }); @@ -543,22 +555,22 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testReversedClusteringReference() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH CLUSTERING ORDER BY (c DESC) AND transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH CLUSTERING ORDER BY (c DESC) AND transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 1, 1)", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (1, 1, 1)", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1 AND c = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1 AND c = 1);\n" + " SELECT row1.k, row1.c, row1.v;\n" + " IF row1.c = 1 THEN\n" + - " UPDATE " + qualifiedTableName + " SET v += row1.c WHERE k=1 AND c=1;\n" + + " UPDATE " + qualifiedAccordTableName + " SET v += row1.c WHERE k=1 AND c=1;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[]{1, 1, 1}, update); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = 1 AND c = 1;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1 AND c = 1;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[]{1, 1, 2}, check); }); @@ -578,20 +590,20 @@ public abstract class AccordCQLTestBase extends AccordTestBase private void testScalarShorthandOperation(int startingValue, String operation, int endingvalue) throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, v int) WITH transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, v) VALUES (1, ?)", ConsistencyLevel.ALL, startingValue); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, v) VALUES (1, ?)", ConsistencyLevel.ALL, startingValue); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.v;\n" + - " UPDATE " + qualifiedTableName + " SET v " + operation + " 1 WHERE k = 1;\n" + + " UPDATE " + qualifiedAccordTableName + " SET v " + operation + " 1 WHERE k = 1;\n" + "COMMIT TRANSACTION"; assertRowEquals(cluster, new Object[] { startingValue }, update); String check = "BEGIN TRANSACTION\n" + - " SELECT v FROM " + qualifiedTableName + " WHERE k = 1;\n" + + " SELECT v FROM " + qualifiedAccordTableName + " WHERE k = 1;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 2 }, check); }); @@ -600,20 +612,20 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testConstantNonStaticRowReadBeforeUpdate() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 2, ?)", ConsistencyLevel.ALL, 3); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (1, 2, ?)", ConsistencyLevel.ALL, 3); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1 AND c = 2);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1 AND c = 2);\n" + " SELECT row1.v;\n" + - " UPDATE " + qualifiedTableName + " SET v += 1 WHERE k = 1 AND c = 2;\n" + + " UPDATE " + qualifiedAccordTableName + " SET v += 1 WHERE k = 1 AND c = 2;\n" + "COMMIT TRANSACTION"; assertRowEquals(cluster, new Object[] { 3 }, update); String check = "BEGIN TRANSACTION\n" + - " SELECT v FROM " + qualifiedTableName + " WHERE k = 1 AND c = 2;\n" + + " SELECT v FROM " + qualifiedAccordTableName + " WHERE k = 1 AND c = 2;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 4 }, check); }); @@ -622,21 +634,21 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testRangeDeletion() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 2, ?)", ConsistencyLevel.ALL, 3); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 3, ?)", ConsistencyLevel.ALL, 4); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 4, ?)", ConsistencyLevel.ALL, 5); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (1, 2, ?)", ConsistencyLevel.ALL, 3); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (1, 3, ?)", ConsistencyLevel.ALL, 4); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (1, 4, ?)", ConsistencyLevel.ALL, 5); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1 AND c = 2);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1 AND c = 2);\n" + " SELECT row1.v;\n" + - " DELETE FROM " + qualifiedTableName + " WHERE k = 1 AND c >=3 AND c <= 4;\n" + + " DELETE FROM " + qualifiedAccordTableName + " WHERE k = 1 AND c >=3 AND c <= 4;\n" + "COMMIT TRANSACTION"; assertRowEquals(cluster, new Object[] { 3 }, update); - Object[][] check = cluster.coordinator(1).execute("SELECT * FROM " + qualifiedTableName + " WHERE k = 1;", ConsistencyLevel.SERIAL); + Object[][] check = cluster.coordinator(1).execute("SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1;", ConsistencyLevel.SERIAL); assertArrayEquals(new Object[] { 1, 2, 3 }, check[0]); assertEquals(1, check.length); }); @@ -646,22 +658,22 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testPartitionKeyReferenceCondition() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k INT, c INT, v INT, PRIMARY KEY (k, c)) WITH CLUSTERING ORDER BY (c DESC) AND transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k INT, c INT, v INT, PRIMARY KEY (k, c)) WITH CLUSTERING ORDER BY (c DESC) AND transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 1, 1)", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (1, 1, 1)", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1 AND c = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1 AND c = 1);\n" + " SELECT row1.k, row1.c, row1.v;\n" + " IF row1.k = 1 THEN\n" + - " UPDATE " + qualifiedTableName + " SET v += row1.k WHERE k=1 AND c=1;\n" + + " UPDATE " + qualifiedAccordTableName + " SET v += row1.k WHERE k=1 AND c=1;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[]{1, 1, 1}, update); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = 1 AND c = 1;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1 AND c = 1;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[]{1, 1, 2}, check); }); @@ -670,22 +682,22 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiPartitionKeyReferenceCondition() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (pk1 INT, pk2 INT, c INT, v INT, PRIMARY KEY ((pk1, pk2), c)) WITH CLUSTERING ORDER BY (c DESC)", + test("CREATE TABLE " + qualifiedAccordTableName + " (pk1 INT, pk2 INT, c INT, v INT, PRIMARY KEY ((pk1, pk2), c)) WITH CLUSTERING ORDER BY (c DESC) AND transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (pk1, pk2, c, v) VALUES (1, 1, 1, 1)", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (pk1, pk2, c, v) VALUES (1, 1, 1, 1)", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE pk1 = 1 AND pk2 = 1 AND c = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE pk1 = 1 AND pk2 = 1 AND c = 1);\n" + " SELECT row1.pk1, row1.pk2, row1.c, row1.v;\n" + " IF row1.pk1 = 1 THEN\n" + - " UPDATE " + qualifiedTableName + " SET v += row1.pk2 WHERE pk1 = 1 AND pk2 = 1 AND c=1;\n" + + " UPDATE " + qualifiedAccordTableName + " SET v += row1.pk2 WHERE pk1 = 1 AND pk2 = 1 AND c=1;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[]{1, 1, 1, 1}, update); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE pk1 = 1 AND pk2 = 1 AND c = 1;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE pk1 = 1 AND pk2 = 1 AND c = 1;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[]{1, 1, 1, 2}, check); }); @@ -694,13 +706,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellListEqCondition() throws Exception { - testListEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'"); + testListEqCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testFrozenListEqCondition() throws Exception { - testListEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testListEqCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testListEqCondition(String ddl) throws Exception @@ -713,7 +725,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase ByteBuffer initialListBytes = listType.getSerializer().serialize(initialList); String insert = "BEGIN TRANSACTION\n" + - " INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (?, ?);\n" + "COMMIT TRANSACTION"; SimpleQueryResult result = cluster.coordinator(1).executeWithResult(insert, ConsistencyLevel.ANY, 0, initialListBytes); assertFalse(result.hasNext()); @@ -722,16 +734,16 @@ public abstract class AccordCQLTestBase extends AccordTestBase ByteBuffer updatedListBytes = listType.getSerializer().serialize(updatedList); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_list;\n" + " IF row1.int_list = ? THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_list = ? WHERE k = ?;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_list = ? WHERE k = ?;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {initialList}, update, 0, initialListBytes, updatedListBytes, 0); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {0, updatedList}, check, 0); } @@ -741,13 +753,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellSetEqCondition() throws Exception { - testSetEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'"); + testSetEqCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testFrozenSetEqCondition() throws Exception { - testSetEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testSetEqCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testSetEqCondition(String ddl) throws Exception @@ -760,7 +772,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase ByteBuffer initialSetBytes = setType.getSerializer().serialize(initialSet); String insert = "BEGIN TRANSACTION\n" + - " INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (?, ?);\n" + "COMMIT TRANSACTION"; SimpleQueryResult result = cluster.coordinator(1).executeWithResult(insert, ConsistencyLevel.ANY, 0, initialSetBytes); assertFalse(result.hasNext()); @@ -769,16 +781,16 @@ public abstract class AccordCQLTestBase extends AccordTestBase ByteBuffer updatedSetBytes = setType.getSerializer().serialize(updatedSet); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_set;\n" + " IF row1.int_set = ? THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_set = ? WHERE k = ?;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_set = ? WHERE k = ?;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {initialSet}, update, 0, initialSetBytes, updatedSetBytes, 0); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {0, updatedSet}, check, 0); } @@ -788,13 +800,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellMapEqCondition() throws Exception { - testMapEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'", true); + testMapEqCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'", true); } @Test public void testFrozenMapEqCondition() throws Exception { - testMapEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen>) WITH transactional_mode='" + transactionalMode + "'", false); + testMapEqCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map frozen>) WITH transactional_mode='" + transactionalMode + "'", false); } private void testMapEqCondition(String ddl, boolean isMultiCell) throws Exception @@ -807,7 +819,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase ByteBuffer initialMapBytes = mapType.getSerializer().serialize(initialMap); String insert = "BEGIN TRANSACTION\n" + - " INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (?, ?);\n" + "COMMIT TRANSACTION"; SimpleQueryResult result = cluster.coordinator(1).executeWithResult(insert, ConsistencyLevel.ANY, 0, initialMapBytes); assertFalse(result.hasNext()); @@ -816,16 +828,16 @@ public abstract class AccordCQLTestBase extends AccordTestBase ByteBuffer updatedMapBytes = mapType.getSerializer().serialize(updatedMap); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_map;\n" + " IF row1.int_map = ? THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_map = ? WHERE k = ?;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_map = ? WHERE k = ?;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { initialMap }, update, 0, initialMapBytes, updatedMapBytes, 0); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, updatedMap }, check, 0); } @@ -835,13 +847,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellUDTEqCondition() throws Exception { - testUDTEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'"); + testUDTEqCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testFrozenUDTEqCondition() throws Exception { - testUDTEqCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); + testUDTEqCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); } private void testUDTEqCondition(String tableDDL) throws Exception @@ -853,7 +865,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase ByteBuffer initialPersonBuffer = CQLTester.makeByteBuffer(initialPersonValue, null); String insert = "BEGIN TRANSACTION\n" + - " INSERT INTO " + qualifiedTableName + " (k, customer) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, customer) VALUES (?, ?);\n" + "COMMIT TRANSACTION"; SimpleQueryResult result = cluster.coordinator(1).executeWithResult(insert, ConsistencyLevel.ANY, 0, initialPersonBuffer); assertFalse(result.hasNext()); @@ -862,16 +874,16 @@ public abstract class AccordCQLTestBase extends AccordTestBase ByteBuffer updatedPersonBuffer = CQLTester.makeByteBuffer(updatedPersonValue, null); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.customer;\n" + " IF row1.customer = ? THEN\n" + - " UPDATE " + qualifiedTableName + " SET customer = ? WHERE k = ?;\n" + + " UPDATE " + qualifiedAccordTableName + " SET customer = ? WHERE k = ?;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { initialPersonBuffer }, update, 0, initialPersonBuffer, updatedPersonBuffer, 0); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, updatedPersonBuffer }, check, 0); } @@ -881,14 +893,14 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testTupleEqCondition() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, pair tuple) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, pair tuple) WITH transactional_mode='" + transactionalMode + "'", cluster -> { Object initialTupleValue = CQLTester.tuple("age", 37); ByteBuffer initialTupleBuffer = CQLTester.makeByteBuffer(initialTupleValue, null); String insert = "BEGIN TRANSACTION\n" + - " INSERT INTO " + qualifiedTableName + " (k, pair) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, pair) VALUES (?, ?);\n" + "COMMIT TRANSACTION"; SimpleQueryResult result = cluster.coordinator(1).executeWithResult(insert, ConsistencyLevel.ANY, 0, initialTupleBuffer); assertFalse(result.hasNext()); @@ -897,16 +909,16 @@ public abstract class AccordCQLTestBase extends AccordTestBase ByteBuffer updatedTupleBuffer = CQLTester.makeByteBuffer(updatedTupleValue, null); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.pair;\n" + " IF row1.pair = ? THEN\n" + - " UPDATE " + qualifiedTableName + " SET pair = ? WHERE k = ?;\n" + + " UPDATE " + qualifiedAccordTableName + " SET pair = ? WHERE k = ?;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { initialTupleBuffer }, update, 0, initialTupleBuffer, updatedTupleBuffer, 0); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, updatedTupleBuffer }, check, 0); } @@ -916,31 +928,31 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testIsNullWithComplexDeletion() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int, c int, int_list list, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, int_list list, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'", cluster -> { ListType listType = ListType.getInstance(Int32Type.instance, true); List initialList = Arrays.asList(1, 2); ByteBuffer initialListBytes = listType.getSerializer().serialize(initialList); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, int_list) VALUES (0, 0, ?);", ConsistencyLevel.ALL, initialListBytes); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, int_list) VALUES (0, 0, ?);", ConsistencyLevel.ALL, initialListBytes); cluster.forEach(i -> i.flush(KEYSPACE)); - cluster.coordinator(1).execute("DELETE int_list FROM " + qualifiedTableName + " WHERE k = 0 AND c = 0;", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("DELETE int_list FROM " + qualifiedAccordTableName + " WHERE k = 0 AND c = 0;", ConsistencyLevel.ALL); List updatedList = Arrays.asList(1, 2, 3); ByteBuffer updatedListBytes = listType.getSerializer().serialize(updatedList); String insert = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ? AND c = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = ?);\n" + " SELECT row1.int_list;\n" + " IF row1.int_list IS NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, int_list) VALUES (?, ?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, int_list) VALUES (?, ?, ?);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { null }, insert, 0, 0, 0, 0, updatedListBytes); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ? AND c = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, 0, updatedList }, check, 0, 0); } @@ -950,13 +962,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testNullMultiCellListConditions() throws Exception { - testNullListConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'"); + testNullListConditions("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testNullFrozenListConditions() throws Exception { - testNullListConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testNullListConditions("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testNullListConditions(String ddl) throws Exception @@ -964,31 +976,31 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (0, null);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (0, null);", ConsistencyLevel.ALL); ListType listType = ListType.getInstance(Int32Type.instance, true); List initialList = Arrays.asList(1, 2); ByteBuffer initialListBytes = listType.getSerializer().serialize(initialList); String insert = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_list;\n" + " IF row1.int_list IS NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (?, ?);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {null}, insert, 0, 0, initialListBytes); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {0, initialList}, check, 0); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_list;\n" + " IF row1.int_list IS NOT NULL THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_list = ? WHERE k = ?;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_list = ? WHERE k = ?;\n" + " END IF\n" + "COMMIT TRANSACTION"; @@ -1002,13 +1014,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testNullMultiCellSetConditions() throws Exception { - testNullSetConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'"); + testNullSetConditions("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testNullFrozenSetConditions() throws Exception { - testNullSetConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testNullSetConditions("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testNullSetConditions(String ddl) throws Exception @@ -1016,31 +1028,31 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (0, null);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (0, null);", ConsistencyLevel.ALL); SetType setType = SetType.getInstance(Int32Type.instance, true); Set initialSet = ImmutableSet.of(1, 2); ByteBuffer initialSetBytes = setType.getSerializer().serialize(initialSet); String insert = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_set;\n" + " IF row1.int_set IS NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (?, ?);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {null}, insert, 0, 0, initialSetBytes); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {0, initialSet}, check, 0); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_set;\n" + " IF row1.int_set IS NOT NULL THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_set = ? WHERE k = ?;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_set = ? WHERE k = ?;\n" + " END IF\n" + "COMMIT TRANSACTION"; @@ -1054,13 +1066,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testNullMultiCellMapConditions() throws Exception { - testNullMapConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'", true); + testNullMapConditions("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'", true); } @Test public void testNullFrozenMapConditions() throws Exception { - testNullMapConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen>) WITH transactional_mode='" + transactionalMode + "'", false); + testNullMapConditions("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map frozen>) WITH transactional_mode='" + transactionalMode + "'", false); } private void testNullMapConditions(String ddl, boolean isMultiCell) throws Exception @@ -1068,31 +1080,31 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (0, null);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (0, null);", ConsistencyLevel.ALL); MapType mapType = MapType.getInstance(UTF8Type.instance, Int32Type.instance, isMultiCell); Map initialMap = ImmutableMap.of("one", 1, "two", 2); ByteBuffer initialMapBytes = mapType.getSerializer().serialize(initialMap); String insert = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_map;\n" + " IF row1.int_map IS NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (?, ?);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { null }, insert, 0, 0, initialMapBytes); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, initialMap }, check, 0); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_map;\n" + " IF row1.int_map IS NOT NULL THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_map = ? WHERE k = ?;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_map = ? WHERE k = ?;\n" + " END IF\n" + "COMMIT TRANSACTION"; @@ -1101,7 +1113,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase assertRowEqualsWithPreemptedRetry(cluster, new Object[] { initialMap }, update, 0, updatedMapBytes, 0); String checkUpdate = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, updatedMap }, checkUpdate, 0); } @@ -1111,13 +1123,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testNullMultiCellUDTCondition() throws Exception { - testNullUDTCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'"); + testNullUDTCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testNullFrozenUDTCondition() throws Exception { - testNullUDTCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); + testNullUDTCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); } private void testNullUDTCondition(String tableDDL) throws Exception @@ -1129,24 +1141,24 @@ public abstract class AccordCQLTestBase extends AccordTestBase ByteBuffer initialPersonBuffer = CQLTester.makeByteBuffer(initialPersonValue, null); String insert = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.customer;\n" + " IF row1.customer IS NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, customer) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, customer) VALUES (?, ?);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { null }, insert, 0, 0, initialPersonBuffer); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, initialPersonBuffer }, check, 0); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.customer;\n" + " IF row1.customer IS NOT NULL THEN\n" + - " UPDATE " + qualifiedTableName + " SET customer = ? WHERE k = ?;\n" + + " UPDATE " + qualifiedAccordTableName + " SET customer = ? WHERE k = ?;\n" + " END IF\n" + "COMMIT TRANSACTION"; @@ -1155,7 +1167,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase assertRowEqualsWithPreemptedRetry(cluster, new Object[] { initialPersonBuffer }, update, 0, updatedPersonBuffer, 0); String checkUpdate = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, updatedPersonBuffer }, checkUpdate, 0); } @@ -1165,13 +1177,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testNullMultiCellSetElementConditions() throws Exception { - testNullSetElementConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'"); + testNullSetElementConditions("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testNullFrozenSetElementConditions() throws Exception { - testNullSetElementConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testNullSetElementConditions("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testNullSetElementConditions(String ddl) throws Exception @@ -1179,31 +1191,31 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (0, {1});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (0, {1});", ConsistencyLevel.ALL); SetType setType = SetType.getInstance(Int32Type.instance, true); Set initialSet = ImmutableSet.of(1, 2); ByteBuffer initialSetBytes = setType.getSerializer().serialize(initialSet); String insert = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_set[2];\n" + " IF row1.int_set[2] IS NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (?, ?);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {null}, insert, 0, 0, initialSetBytes); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {0, initialSet}, check, 0); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_set;\n" + " IF row1.int_set[2] IS NOT NULL THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_set = ? WHERE k = ?;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_set = ? WHERE k = ?;\n" + " END IF\n" + "COMMIT TRANSACTION"; @@ -1217,13 +1229,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testNullMultiCellMapElementConditions() throws Exception { - testNullMapElementConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'", true); + testNullMapElementConditions("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'", true); } @Test public void testNullFrozenMapElementConditions() throws Exception { - testNullMapElementConditions("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen>) WITH transactional_mode='" + transactionalMode + "'", false); + testNullMapElementConditions("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map frozen>) WITH transactional_mode='" + transactionalMode + "'", false); } private void testNullMapElementConditions(String ddl, boolean isMultiCell) throws Exception @@ -1231,31 +1243,31 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (0, null);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (0, null);", ConsistencyLevel.ALL); MapType mapType = MapType.getInstance(UTF8Type.instance, Int32Type.instance, isMultiCell); Map initialMap = ImmutableMap.of("one", 1, "two", 2); ByteBuffer initialMapBytes = mapType.getSerializer().serialize(initialMap); String insert = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_map;\n" + " IF row1.int_map[?] IS NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (?, ?);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { null }, insert, 0, "one", 0, initialMapBytes); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, initialMap }, check, 0); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_map;\n" + " IF row1.int_map[?] IS NOT NULL THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_map = ? WHERE k = ?;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_map = ? WHERE k = ?;\n" + " END IF\n" + "COMMIT TRANSACTION"; @@ -1264,7 +1276,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase assertRowEqualsWithPreemptedRetry(cluster, new Object[] { initialMap }, update, 0, "two", updatedMapBytes, 0); String checkUpdate = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, updatedMap }, checkUpdate, 0); } @@ -1274,13 +1286,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testNullMultiCellUDTFieldCondition() throws Exception { - testNullUDTFieldCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'"); + testNullUDTFieldCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testNullFrozenUDTFieldCondition() throws Exception { - testNullUDTFieldCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); + testNullUDTFieldCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); } private void testNullUDTFieldCondition(String tableDDL) throws Exception @@ -1292,24 +1304,24 @@ public abstract class AccordCQLTestBase extends AccordTestBase ByteBuffer initialPersonBuffer = CQLTester.makeByteBuffer(initialPersonValue, null); String insert = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.customer;\n" + " IF row1.customer.age IS NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, customer) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, customer) VALUES (?, ?);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { null }, insert, 0, 0, initialPersonBuffer); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, initialPersonBuffer }, check, 0); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.customer;\n" + " IF row1.customer.age IS NOT NULL THEN\n" + - " UPDATE " + qualifiedTableName + " SET customer = ? WHERE k = ?;\n" + + " UPDATE " + qualifiedAccordTableName + " SET customer = ? WHERE k = ?;\n" + " END IF\n" + "COMMIT TRANSACTION"; @@ -1318,7 +1330,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase assertRowEqualsWithPreemptedRetry(cluster, new Object[] { initialPersonBuffer }, update, 0, updatedPersonBuffer, 0); String checkUpdate = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, updatedPersonBuffer }, checkUpdate, 0); } @@ -1328,13 +1340,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellListSubstitution() throws Exception { - testListSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'", true); + testListSubstitution("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'", true); } @Test public void testFrozenListSubstitution() throws Exception { - testListSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen>) WITH transactional_mode='" + transactionalMode + "'", false); + testListSubstitution("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list frozen>) WITH transactional_mode='" + transactionalMode + "'", false); } private void testListSubstitution(String ddl, boolean isMultiCell) throws Exception @@ -1346,19 +1358,19 @@ public abstract class AccordCQLTestBase extends AccordTestBase List initialList = Arrays.asList(1, 2); ByteBuffer initialListBytes = listType.getSerializer().serialize(initialList); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (0, ?);", ConsistencyLevel.ALL, initialListBytes); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (0, ?);", ConsistencyLevel.ALL, initialListBytes); String insert = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_list;\n" + " IF row1.int_list IS NOT NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (?, row1.int_list);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (?, row1.int_list);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { initialList }, insert, 0, 1); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 1, initialList }, check, 1); } @@ -1368,13 +1380,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellSetSubstitution() throws Exception { - testSetSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'", true); + testSetSubstitution("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'", true); } @Test public void testFrozenSetSubstitution() throws Exception { - testSetSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'", false); + testSetSubstitution("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'", false); } private void testSetSubstitution(String ddl, boolean isMultiCell) throws Exception @@ -1386,19 +1398,19 @@ public abstract class AccordCQLTestBase extends AccordTestBase Set initialSet = ImmutableSet.of(1, 2); ByteBuffer initialSetBytes = setType.getSerializer().serialize(initialSet); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (0, ?);", ConsistencyLevel.ALL, initialSetBytes); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (0, ?);", ConsistencyLevel.ALL, initialSetBytes); String insert = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_set;\n" + " IF row1.int_set IS NOT NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (?, row1.int_set);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (?, row1.int_set);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { initialSet }, insert, 0, 1); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 1, initialSet }, check, 1); } @@ -1408,13 +1420,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellMapSubstitution() throws Exception { - testMapSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'", true); + testMapSubstitution("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'", true); } @Test public void testFrozenMapSubstitution() throws Exception { - testMapSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen>) WITH transactional_mode='" + transactionalMode + "'", false); + testMapSubstitution("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map frozen>) WITH transactional_mode='" + transactionalMode + "'", false); } private void testMapSubstitution(String ddl, boolean isMultiCell) throws Exception @@ -1426,19 +1438,19 @@ public abstract class AccordCQLTestBase extends AccordTestBase Map initialMap = ImmutableMap.of("one", 1, "two", 2); ByteBuffer initialMapBytes = mapType.getSerializer().serialize(initialMap); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (0, ?);", ConsistencyLevel.ALL, initialMapBytes); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (0, ?);", ConsistencyLevel.ALL, initialMapBytes); String insert = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.int_map;\n" + " IF row1.int_map IS NOT NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (?, row1.int_map);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (?, row1.int_map);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[]{ initialMap }, insert, 0, 1); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 1, initialMap }, check, 1); } @@ -1448,13 +1460,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellUDTSubstitution() throws Exception { - testUDTSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'"); + testUDTSubstitution("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testFrozenUDTSubstitution() throws Exception { - testUDTSubstitution("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); + testUDTSubstitution("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); } private void testUDTSubstitution(String tableDDL) throws Exception @@ -1464,19 +1476,19 @@ public abstract class AccordCQLTestBase extends AccordTestBase { Object initialPersonValue = CQLTester.userType("height", 74, "age", 37); ByteBuffer initialPersonBuffer = CQLTester.makeByteBuffer(initialPersonValue, null); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, customer) VALUES (0, ?);", ConsistencyLevel.ALL, initialPersonBuffer); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, customer) VALUES (0, ?);", ConsistencyLevel.ALL, initialPersonBuffer); String insert = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.customer;\n" + " IF row1.customer IS NOT NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, customer) VALUES (?, row1.customer);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, customer) VALUES (?, row1.customer);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[]{ initialPersonBuffer }, insert, 0, 1); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 1, initialPersonBuffer }, check, 1); } @@ -1486,24 +1498,24 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testTupleSubstitution() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, pair tuple) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, pair tuple) WITH transactional_mode='" + transactionalMode + "'", cluster -> { Object initialTupleValue = CQLTester.tuple("age", 37); ByteBuffer initialTupleBuffer = CQLTester.makeByteBuffer(initialTupleValue, null); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, pair) VALUES (0, ?);", ConsistencyLevel.ALL, initialTupleBuffer); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, pair) VALUES (0, ?);", ConsistencyLevel.ALL, initialTupleBuffer); String insert = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.pair;\n" + " IF row1.pair IS NOT NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, pair) VALUES (?, row1.pair);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, pair) VALUES (?, row1.pair);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { initialTupleBuffer }, insert, 0, 1); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 1, initialTupleBuffer }, check, 1); } @@ -1513,13 +1525,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellListReplacement() throws Exception { - testListReplacement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'"); + testListReplacement("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testFrozenListReplacement() throws Exception { - testListReplacement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testListReplacement("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testListReplacement(String ddl) throws Exception @@ -1527,20 +1539,20 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (0, [1, 2]);", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (1, [3, 4]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (0, [1, 2]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (1, [3, 4]);", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_list;\n" + " IF row1.int_list = [3, 4] THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_list = row1.int_list WHERE k=0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_list = row1.int_list WHERE k=0;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {Arrays.asList(3, 4)}, update); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {0, Arrays.asList(3, 4)}, check); } @@ -1550,13 +1562,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellSetReplacement() throws Exception { - testSetReplacement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'"); + testSetReplacement("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testFrozenSetReplacement() throws Exception { - testSetReplacement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testSetReplacement("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testSetReplacement(String ddl) throws Exception @@ -1564,20 +1576,20 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (0, {1, 2});", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (1, {3, 4});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (0, {1, 2});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (1, {3, 4});", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_set;\n" + " IF row1.int_set = {3, 4} THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_set = row1.int_set WHERE k=0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_set = row1.int_set WHERE k=0;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { ImmutableSet.of(3, 4) }, update); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, ImmutableSet.of(3, 4) }, check); } @@ -1587,23 +1599,23 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testListAppendFromReference() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (0, [1, 2]);", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (1, [3, 4]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (0, [1, 2]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (1, [3, 4]);", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_list;\n" + " IF row1.int_list = [3, 4] THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_list += row1.int_list WHERE k=0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_list += row1.int_list WHERE k=0;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {Arrays.asList(3, 4)}, update); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {0, Arrays.asList(1, 2, 3, 4)}, check); } @@ -1613,13 +1625,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testSetByIndexFromMultiCellListElement() throws Exception { - testListSetByIndexFromListElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, src_int_list list, dest_int_list list) WITH transactional_mode='" + transactionalMode + "'"); + testListSetByIndexFromListElement("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, src_int_list list, dest_int_list list) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testSetByIndexFromFrozenListElement() throws Exception { - testListSetByIndexFromListElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, src_int_list frozen>, dest_int_list list) WITH transactional_mode='" + transactionalMode + "'"); + testListSetByIndexFromListElement("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, src_int_list frozen>, dest_int_list list) WITH transactional_mode='" + transactionalMode + "'"); } private void testListSetByIndexFromListElement(String ddl) throws Exception @@ -1627,18 +1639,18 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, dest_int_list) VALUES (0, [1, 2]);", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, src_int_list) VALUES (1, [3, 4]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, dest_int_list) VALUES (0, [1, 2]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, src_int_list) VALUES (1, [3, 4]);", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.src_int_list;\n" + - " UPDATE " + qualifiedTableName + " SET dest_int_list[0] = row1.src_int_list[0] WHERE k = 0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET dest_int_list[0] = row1.src_int_list[0] WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {Arrays.asList(3, 4)}, update); String check = "BEGIN TRANSACTION\n" + - " SELECT dest_int_list FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT dest_int_list FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {Arrays.asList(3, 2)}, check); } @@ -1648,20 +1660,20 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testListSetByIndexFromScalar() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (0, [1, 2]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (0, [1, 2]);", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row0 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 0);\n" + + " LET row0 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0);\n" + " SELECT row0.int_list;\n" + - " UPDATE " + qualifiedTableName + " SET int_list[0] = 2 WHERE k = 0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_list[0] = 2 WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {Arrays.asList(1, 2)}, update); String check = "BEGIN TRANSACTION\n" + - " SELECT int_list FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT int_list FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {Arrays.asList(2, 2)}, check); } @@ -1671,21 +1683,21 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testAutoReadSelectionConstruction() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int, c int, counter int, other_counter int, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, counter int, other_counter int, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, counter, other_counter) VALUES (0, 0, 1, 1);", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, counter, other_counter) VALUES (0, 1, 1, 1);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, counter, other_counter) VALUES (0, 0, 1, 1);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, counter, other_counter) VALUES (0, 1, 1, 1);", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row0 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 0 AND c = 0);\n" + + " LET row0 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0 AND c = 0);\n" + " SELECT row0.counter, row0.other_counter;\n" + - " UPDATE " + qualifiedTableName + " SET other_counter += 1, counter += row0.counter WHERE k = 0 AND c = 1;\n" + + " UPDATE " + qualifiedAccordTableName + " SET other_counter += 1, counter += row0.counter WHERE k = 0 AND c = 1;\n" + "COMMIT TRANSACTION"; assertRowEquals(cluster, new Object[] { 1, 1 }, update); String check = "BEGIN TRANSACTION\n" + - " SELECT counter, other_counter FROM " + qualifiedTableName + " WHERE k = 0 AND c = 1;\n" + + " SELECT counter, other_counter FROM " + qualifiedAccordTableName + " WHERE k = 0 AND c = 1;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 2, 2 }, check); } @@ -1695,21 +1707,21 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiMutationsSameKey() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int, c int, counter int, int_list list, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, counter int, int_list list, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, counter, int_list) VALUES (0, 0, 0, [1, 2]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, counter, int_list) VALUES (0, 0, 0, [1, 2]);", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row0 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 0 AND c = 0);\n" + + " LET row0 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0 AND c = 0);\n" + " SELECT row0.counter, row0.int_list;\n" + - " UPDATE " + qualifiedTableName + " SET int_list[0] = 42 WHERE k = 0 AND c = 0;\n" + - " UPDATE " + qualifiedTableName + " SET counter += 1 WHERE k = 0 AND c = 0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_list[0] = 42 WHERE k = 0 AND c = 0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET counter += 1 WHERE k = 0 AND c = 0;\n" + "COMMIT TRANSACTION"; assertRowEquals(cluster, new Object[] { 0, Arrays.asList(1, 2) }, update); String check = "BEGIN TRANSACTION\n" + - " SELECT counter, int_list FROM " + qualifiedTableName + " WHERE k = 0 AND c = 0;\n" + + " SELECT counter, int_list FROM " + qualifiedAccordTableName + " WHERE k = 0 AND c = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {1, Arrays.asList(42, 2)}, check); } @@ -1720,10 +1732,10 @@ public abstract class AccordCQLTestBase extends AccordTestBase public void testLetLargerThanOneWithPK() throws Exception { test(cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, 0, 0);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (0, 0, 0);", ConsistencyLevel.ALL); String cql = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k=0 AND c=0 LIMIT 2);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k=0 AND c=0 LIMIT 2);\n" + " SELECT row1.v;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[]{ 0 }, cql, 1); @@ -1734,10 +1746,10 @@ public abstract class AccordCQLTestBase extends AccordTestBase public void testLetLimitUsingBind() throws Exception { test(cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, 0, 0);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (0, 0, 0);", ConsistencyLevel.ALL); String cql = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 0 LIMIT ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0 LIMIT ?);\n" + " SELECT row1.v;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0 }, cql, 1); @@ -1747,24 +1759,24 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testListSetByIndexMultiRow() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int, c int, int_list list, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, int_list list, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, int_list) VALUES (0, 0, [1, 2]);", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, int_list) VALUES (0, 1, [3, 4]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, int_list) VALUES (0, 0, [1, 2]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, int_list) VALUES (0, 1, [3, 4]);", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row0 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 0 AND c = 0);\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 0 AND c = 1);\n" + + " LET row0 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0 AND c = 0);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0 AND c = 1);\n" + " SELECT row0.int_list;\n" + - " UPDATE " + qualifiedTableName + " SET int_list[0] = row1.int_list[0] WHERE k = 0 AND c = 0;\n" + - " UPDATE " + qualifiedTableName + " SET int_list[0] = row0.int_list[0] WHERE k = 0 AND c = 1;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_list[0] = row1.int_list[0] WHERE k = 0 AND c = 0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_list[0] = row0.int_list[0] WHERE k = 0 AND c = 1;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { Arrays.asList(1, 2) }, update); String check = "BEGIN TRANSACTION\n" + - " LET row0 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 0 AND c = 0);\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 0 AND c = 1);\n" + + " LET row0 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0 AND c = 0);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0 AND c = 1);\n" + " SELECT row0.int_list, row1.int_list;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {Arrays.asList(3, 2), Arrays.asList(1, 4)}, check); @@ -1775,21 +1787,21 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testSetAppend() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (0, {1, 2});", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (1, {3, 4});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (0, {1, 2});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (1, {3, 4});", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_set;\n" + - " UPDATE " + qualifiedTableName + " SET int_set += row1.int_set WHERE k=0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_set += row1.int_set WHERE k=0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { ImmutableSet.of(3, 4) }, update); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, ImmutableSet.of(1, 2, 3, 4) }, check); } @@ -1799,13 +1811,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testAssignmentFromMultiCellSetElement() throws Exception { - testAssignmentFromSetElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, int_set set) WITH transactional_mode='" + transactionalMode + "'"); + testAssignmentFromSetElement("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, v int, int_set set) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testAssignmentFromFrozenSetElement() throws Exception { - testAssignmentFromSetElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testAssignmentFromSetElement("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, v int, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testAssignmentFromSetElement(String ddl) throws Exception @@ -1813,18 +1825,18 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, v, int_set) VALUES (0, 0, {1, 2});", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, v, int_set) VALUES (1, 0, {3, 4});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, v, int_set) VALUES (0, 0, {1, 2});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, v, int_set) VALUES (1, 0, {3, 4});", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_set;\n" + - " UPDATE " + qualifiedTableName + " SET v = row1.int_set[4] WHERE k=0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET v = row1.int_set[4] WHERE k=0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { ImmutableSet.of(3, 4) }, update); String check = "BEGIN TRANSACTION\n" + - " SELECT v FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT v FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 4 }, check); } @@ -1834,21 +1846,21 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMapAppend() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (0, {'one': 2});", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (1, {'three': 4});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (0, {'one': 2});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (1, {'three': 4});", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_map;\n" + - " UPDATE " + qualifiedTableName + " SET int_map += row1.int_map WHERE k=0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_map += row1.int_map WHERE k=0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { ImmutableMap.of("three", 4) }, update); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, ImmutableMap.of("one", 2, "three", 4) }, check); } @@ -1858,13 +1870,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testAssignmentFromMultiCellMapElement() throws Exception { - testAssignmentFromMapElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, int_map map) WITH transactional_mode='" + transactionalMode + "'"); + testAssignmentFromMapElement("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, v int, int_map map) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testAssignmentFromFrozenMapElement() throws Exception { - testAssignmentFromMapElement("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, int_map frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testAssignmentFromMapElement("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, v int, int_map frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testAssignmentFromMapElement(String ddl) throws Exception @@ -1872,18 +1884,18 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, v, int_map) VALUES (0, 0, {'one': 2});", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, v, int_map) VALUES (1, 0, {'three': 4});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, v, int_map) VALUES (0, 0, {'one': 2});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, v, int_map) VALUES (1, 0, {'three': 4});", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_map;\n" + - " UPDATE " + qualifiedTableName + " SET v = row1.int_map[?] WHERE k=0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET v = row1.int_map[?] WHERE k=0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { ImmutableMap.of("three", 4) }, update, "three"); String check = "BEGIN TRANSACTION\n" + - " SELECT v FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT v FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 4 }, check); } @@ -1893,13 +1905,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testAssignmentFromMultiCellUDTField() throws Exception { - testAssignmentFromUDTField("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, customer person) WITH transactional_mode='" + transactionalMode + "'"); + testAssignmentFromUDTField("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, v int, customer person) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testAssignmentFromFrozenUDTField() throws Exception { - testAssignmentFromUDTField("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, v int, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); + testAssignmentFromUDTField("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, v int, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); } private void testAssignmentFromUDTField(String tableDDL) throws Exception @@ -1909,18 +1921,18 @@ public abstract class AccordCQLTestBase extends AccordTestBase { Object initialPersonValue = CQLTester.userType("height", 74, "age", 37); ByteBuffer initialPersonBuffer = CQLTester.makeByteBuffer(initialPersonValue, null); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, v, customer) VALUES (0, 0, null);", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, v, customer) VALUES (1, 0, ?);", ConsistencyLevel.ALL, initialPersonBuffer); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, v, customer) VALUES (0, 0, null);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, v, customer) VALUES (1, 0, ?);", ConsistencyLevel.ALL, initialPersonBuffer); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.customer;\n" + - " UPDATE " + qualifiedTableName + " SET v = row1.customer.age WHERE k=0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET v = row1.customer.age WHERE k=0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { initialPersonBuffer }, update); String check = "BEGIN TRANSACTION\n" + - " SELECT v FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT v FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 37 }, check); } @@ -1930,21 +1942,21 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testSetMapElementFromMapElementReference() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (0, {'one': 2});", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (1, {'three': 4});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (0, {'one': 2});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (1, {'three': 4});", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_map;\n" + - " UPDATE " + qualifiedTableName + " SET int_map[?] = row1.int_map[?] WHERE k=0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_map[?] = row1.int_map[?] WHERE k=0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { ImmutableMap.of("three", 4) }, update, "one", "three"); String check = "BEGIN TRANSACTION\n" + - " SELECT int_map[?] FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT int_map[?] FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 4 }, check, "one"); } @@ -1954,7 +1966,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testSetUDTFieldFromUDTFieldReference() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'", cluster -> { Object youngPerson = CQLTester.userType("height", 58, "age", 9); @@ -1962,18 +1974,18 @@ public abstract class AccordCQLTestBase extends AccordTestBase Object adultPerson = CQLTester.userType("height", 74, "age", 37); ByteBuffer adultPersonBuffer = CQLTester.makeByteBuffer(adultPerson, null); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, customer) VALUES (0, ?);", ConsistencyLevel.ALL, youngPersonBuffer); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, customer) VALUES (1, ?);", ConsistencyLevel.ALL, adultPersonBuffer); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, customer) VALUES (0, ?);", ConsistencyLevel.ALL, youngPersonBuffer); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, customer) VALUES (1, ?);", ConsistencyLevel.ALL, adultPersonBuffer); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.customer;\n" + - " UPDATE " + qualifiedTableName + " SET customer.age = row1.customer.age WHERE k = 0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET customer.age = row1.customer.age WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { adultPersonBuffer }, update); String check = "BEGIN TRANSACTION\n" + - " SELECT customer.height, customer.age FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT customer.height, customer.age FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 58, 37 }, check); } @@ -1983,13 +1995,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellListElementCondition() throws Exception { - testListElementCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'"); + testListElementCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testFrozenListElementCondition() throws Exception { - testListElementCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testListElementCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testListElementCondition(String ddl) throws Exception @@ -1997,20 +2009,20 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (0, [1, 2]);", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (1, [3, 4]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (0, [1, 2]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (1, [3, 4]);", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_list;\n" + " IF row1.int_list[1] = 4 THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_list = [3, 4] WHERE k = 0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_list = [3, 4] WHERE k = 0;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { ImmutableList.of(3, 4) }, update); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, ImmutableList.of(3, 4) }, check); } @@ -2020,13 +2032,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellMapElementCondition() throws Exception { - testMapElementCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'"); + testMapElementCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testFrozenMapElementCondition() throws Exception { - testMapElementCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testMapElementCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testMapElementCondition(String ddl) throws Exception @@ -2034,20 +2046,20 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (0, {'one': 2});", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (1, {'three': 4});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (0, {'one': 2});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (1, {'three': 4});", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_map;\n" + " IF row1.int_map[?] = 4 THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_map = {'three': 4} WHERE k = 0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_map = {'three': 4} WHERE k = 0;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { ImmutableMap.of("three", 4) }, update, "three"); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, ImmutableMap.of("three", 4) }, check); } @@ -2057,13 +2069,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellUDTFieldCondition() throws Exception { - testUDTFieldCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'"); + testUDTFieldCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testFrozenUDTFieldCondition() throws Exception { - testUDTFieldCondition("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); + testUDTFieldCondition("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); } private void testUDTFieldCondition(String tableDDL) throws Exception @@ -2075,21 +2087,21 @@ public abstract class AccordCQLTestBase extends AccordTestBase ByteBuffer initialPersonBuffer = CQLTester.makeByteBuffer(initialPersonValue, null); String insert = "BEGIN TRANSACTION\n" + - " INSERT INTO " + qualifiedTableName + " (k, customer) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, customer) VALUES (?, ?);\n" + "COMMIT TRANSACTION"; SimpleQueryResult result = cluster.coordinator(1).executeWithResult(insert, ConsistencyLevel.ANY, 0, initialPersonBuffer); assertFalse(result.hasNext()); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, initialPersonBuffer }, check, 0); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row1.customer;\n" + " IF row1.customer.age = 37 THEN\n" + - " UPDATE " + qualifiedTableName + " SET customer = ? WHERE k = ?;\n" + + " UPDATE " + qualifiedAccordTableName + " SET customer = ? WHERE k = ?;\n" + " END IF\n" + "COMMIT TRANSACTION"; @@ -2098,7 +2110,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase assertRowEqualsWithPreemptedRetry(cluster, new Object[] { initialPersonBuffer }, update, 0, updatedPersonBuffer, 0); String checkUpdate = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, updatedPersonBuffer }, checkUpdate, 0); } @@ -2108,23 +2120,23 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testListSubtraction() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (0, [1, 2, 3, 4]);", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (1, [3, 4]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (0, [1, 2, 3, 4]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (1, [3, 4]);", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_list;\n" + " IF row1.int_list = [3, 4] THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_list -= row1.int_list WHERE k=0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_list -= row1.int_list WHERE k=0;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {Arrays.asList(3, 4)}, update); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {0, Arrays.asList(1, 2)}, check); } @@ -2134,23 +2146,23 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testSetSubtraction() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'", cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (0, {1, 2, 3, 4});", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (1, {3, 4});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (0, {1, 2, 3, 4});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (1, {3, 4});", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_set;\n" + " IF row1.int_set = {3, 4} THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_set -= row1.int_set WHERE k=0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_set -= row1.int_set WHERE k=0;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { ImmutableSet.of(3, 4) }, update); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, ImmutableSet.of(1, 2) }, check); } @@ -2160,13 +2172,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellMapSubtraction() throws Exception { - testMapSubtraction("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map, int_set set) WITH transactional_mode='" + transactionalMode + "'"); + testMapSubtraction("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map map, int_set set) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testFrozenMapSubtraction() throws Exception { - testMapSubtraction("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testMapSubtraction("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map map, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testMapSubtraction(String ddl) throws Exception @@ -2174,20 +2186,20 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (0, { 'one': 2, 'three': 4 });", ConsistencyLevel.ALL); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (1, { 'three' });", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (0, { 'one': 2, 'three': 4 });", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (1, { 'three' });", ConsistencyLevel.ALL); String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_set;\n" + " IF row1.int_set = { 'three' } THEN\n" + - " UPDATE " + qualifiedTableName + " SET int_map -= row1.int_set WHERE k=0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET int_map -= row1.int_set WHERE k=0;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { ImmutableSet.of("three") }, update); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = 0;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, ImmutableMap.of("one", 2), null}, check); } @@ -2197,13 +2209,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellListSelection() throws Exception { - testListSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'"); + testListSelection("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list list) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testFrozenListSelection() throws Exception { - testListSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_list frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testListSelection("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_list frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testListSelection(String ddl) throws Exception @@ -2211,16 +2223,16 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_list) VALUES (1, [10, 20, 30, 40]);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_list) VALUES (1, [10, 20, 30, 40]);", ConsistencyLevel.ALL); String selectEntireSet = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_list;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { ImmutableList.of(10, 20, 30, 40) }, selectEntireSet); String selectSingleElement = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_list[0];\n" + "COMMIT TRANSACTION"; @@ -2235,13 +2247,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellSetSelection() throws Exception { - testSetSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'"); + testSetSelection("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_set set) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testFrozenSetSelection() throws Exception { - testSetSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testSetSelection("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_set frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testSetSelection(String ddl) throws Exception @@ -2249,16 +2261,16 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_set) VALUES (1, {10, 20, 30, 40});", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_set) VALUES (1, {10, 20, 30, 40});", ConsistencyLevel.ALL); String selectEntireSet = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_set;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { ImmutableSet.of(10, 20, 30, 40) }, selectEntireSet); String selectSingleElement = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_set[10];\n" + "COMMIT TRANSACTION"; @@ -2273,13 +2285,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiCellMapSelection() throws Exception { - testMapSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'"); + testMapSelection("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map map) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testFrozenMapSelection() throws Exception { - testMapSelection("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, int_map frozen>) WITH transactional_mode='" + transactionalMode + "'"); + testMapSelection("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, int_map frozen>) WITH transactional_mode='" + transactionalMode + "'"); } private void testMapSelection(String ddl) throws Exception @@ -2287,16 +2299,16 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(ddl, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, int_map) VALUES (1, { 'ten': 20, 'thirty': 40 });", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, int_map) VALUES (1, { 'ten': 20, 'thirty': 40 });", ConsistencyLevel.ALL); String selectEntireMap = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_map;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { ImmutableMap.of("ten", 20, "thirty", 40) }, selectEntireMap); String selectSingleElement = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 1);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1);\n" + " SELECT row1.int_map['ten'];\n" + "COMMIT TRANSACTION"; @@ -2312,25 +2324,25 @@ public abstract class AccordCQLTestBase extends AccordTestBase { String KEYSPACE = "ks" + System.currentTimeMillis(); SHARED_CLUSTER.schemaChange("CREATE KEYSPACE " + KEYSPACE + " WITH REPLICATION={'class':'SimpleStrategy', 'replication_factor': 2}"); - SHARED_CLUSTER.schemaChange("CREATE TABLE " + qualifiedTableName + "1 (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'"); - SHARED_CLUSTER.schemaChange("CREATE TABLE " + qualifiedTableName + "2 (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'"); + SHARED_CLUSTER.schemaChange("CREATE TABLE " + qualifiedAccordTableName + "1 (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'"); + SHARED_CLUSTER.schemaChange("CREATE TABLE " + qualifiedAccordTableName + "2 (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'"); SHARED_CLUSTER.forEach(node -> node.runOnInstance(() -> AccordService.instance().setCacheSize(0))); - SHARED_CLUSTER.coordinator(1).execute("INSERT INTO " + qualifiedTableName + "1 (k, c, v) VALUES (1, 2, 3);", ConsistencyLevel.ALL); - SHARED_CLUSTER.coordinator(1).execute("INSERT INTO " + qualifiedTableName + "2 (k, c, v) VALUES (2, 2, 4);", ConsistencyLevel.ALL); + SHARED_CLUSTER.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + "1 (k, c, v) VALUES (1, 2, 3);", ConsistencyLevel.ALL); + SHARED_CLUSTER.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + "2 (k, c, v) VALUES (2, 2, 4);", ConsistencyLevel.ALL); String query = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + "1 WHERE k=1 AND c=2);\n" + - " LET row2 = (SELECT * FROM " + qualifiedTableName + "2 WHERE k=2 AND c=2);\n" + - " SELECT v FROM " + qualifiedTableName + "1 WHERE k=1 AND c=2;\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + "1 WHERE k=1 AND c=2);\n" + + " LET row2 = (SELECT * FROM " + qualifiedAccordTableName + "2 WHERE k=2 AND c=2);\n" + + " SELECT v FROM " + qualifiedAccordTableName + "1 WHERE k=1 AND c=2;\n" + " IF row1.v = 3 AND row2.v = 4 THEN\n" + - " UPDATE " + qualifiedTableName + "1 SET v = row2.v WHERE k=1 AND c=2;\n" + + " UPDATE " + qualifiedAccordTableName + "1 SET v = row2.v WHERE k=1 AND c=2;\n" + " END IF\n" + "COMMIT TRANSACTION"; Object[][] result = SHARED_CLUSTER.coordinator(1).execute(query, ConsistencyLevel.ANY); assertEquals(3, result[0][0]); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + "1 WHERE k=1 AND c=2;\n" + + " SELECT * FROM " + qualifiedAccordTableName + "1 WHERE k=1 AND c=2;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(SHARED_CLUSTER, new Object[]{1, 2, 4}, check); } @@ -2338,13 +2350,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testRegularScalarInsertSubstitution() throws Exception { - testScalarInsertSubstitution("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'"); + testScalarInsertSubstitution("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testStaticScalarInsertSubstitution() throws Exception { - testScalarInsertSubstitution("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int static, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'"); + testScalarInsertSubstitution("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v int static, PRIMARY KEY (k, c)) WITH transactional_mode='" + transactionalMode + "'"); } private void testScalarInsertSubstitution(String tableDDL) throws Exception @@ -2352,19 +2364,19 @@ public abstract class AccordCQLTestBase extends AccordTestBase test(tableDDL, cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, 0, 1);", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (0, 0, 1);", ConsistencyLevel.ALL); String insert = "BEGIN TRANSACTION\n" + - " LET row0 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 0 LIMIT 1);\n" + + " LET row0 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0 LIMIT 1);\n" + " SELECT row0.v;\n" + " IF row0.v IS NOT NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, 1, row0.v);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (0, 1, row0.v);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 1 }, insert); String check = "BEGIN TRANSACTION\n" + - " SELECT k, c, v FROM " + qualifiedTableName + " WHERE k = 0 AND c = 1;\n" + + " SELECT k, c, v FROM " + qualifiedAccordTableName + " WHERE k = 0 AND c = 1;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, 1, 1 }, check); } @@ -2374,13 +2386,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testSelectMultiCellUDTReference() throws Exception { - testSelectUDTReference("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'"); + testSelectUDTReference("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testSelectFrozenUDTReference() throws Exception { - testSelectUDTReference("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); + testSelectUDTReference("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); } private void testSelectUDTReference(String tableDDL) throws Exception @@ -2392,13 +2404,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase ByteBuffer personBuffer = CQLTester.makeByteBuffer(personValue, null); String insert = "BEGIN TRANSACTION\n" + - " INSERT INTO " + qualifiedTableName + " (k, customer) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, customer) VALUES (?, ?);\n" + "COMMIT TRANSACTION"; SimpleQueryResult result = cluster.coordinator(1).executeWithResult(insert, ConsistencyLevel.ANY, 0, personBuffer); assertFalse(result.hasNext()); String read = "BEGIN TRANSACTION\n" + - " LET row0 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row0 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row0.customer;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { personBuffer }, read, 0); @@ -2409,13 +2421,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testSelectMultiCellUDTFieldReference() throws Exception { - testSelectUDTFieldReference("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'"); + testSelectUDTFieldReference("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer person) WITH transactional_mode='" + transactionalMode + "'"); } @Test public void testSelectFrozenUDTFieldReference() throws Exception { - testSelectUDTFieldReference("CREATE TABLE " + qualifiedTableName + " (k int PRIMARY KEY, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); + testSelectUDTFieldReference("CREATE TABLE " + qualifiedAccordTableName + " (k int PRIMARY KEY, customer frozen) WITH transactional_mode='" + transactionalMode + "'"); } private void testSelectUDTFieldReference(String tableDDL) throws Exception @@ -2427,13 +2439,13 @@ public abstract class AccordCQLTestBase extends AccordTestBase ByteBuffer personBuffer = CQLTester.makeByteBuffer(personValue, null); String insert = "BEGIN TRANSACTION\n" + - " INSERT INTO " + qualifiedTableName + " (k, customer) VALUES (?, ?);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, customer) VALUES (?, ?);\n" + "COMMIT TRANSACTION"; SimpleQueryResult result = cluster.coordinator(1).executeWithResult(insert, ConsistencyLevel.ANY, 0, personBuffer); assertFalse(result.hasNext()); String read = "BEGIN TRANSACTION\n" + - " LET row0 = (SELECT * FROM " + qualifiedTableName + " WHERE k = ?);\n" + + " LET row0 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?);\n" + " SELECT row0.customer.age;\n" + "COMMIT TRANSACTION"; result = assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 37 }, read, 0); @@ -2446,33 +2458,33 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testMultiKeyQueryAndInsert() throws Throwable { - test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v int, primary key (k, c)) WITH transactional_mode='" + transactionalMode + "'", cluster -> { String query1 = "BEGIN TRANSACTION\n" + - " LET select1 = (SELECT * FROM " + qualifiedTableName + " WHERE k=0 AND c=0);\n" + - " LET select2 = (SELECT * FROM " + qualifiedTableName + " WHERE k=1 AND c=0);\n" + - " SELECT v FROM " + qualifiedTableName + " WHERE k=0 AND c=0;\n" + + " LET select1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k=0 AND c=0);\n" + + " LET select2 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k=1 AND c=0);\n" + + " SELECT v FROM " + qualifiedAccordTableName + " WHERE k=0 AND c=0;\n" + " IF select1 IS NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, 0, 0);\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 0, 0);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (0, 0, 0);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (1, 0, 0);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertEmptyWithPreemptedRetry(cluster, query1); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ? AND c = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {0, 0, 0}, check, 0, 0); assertRowEqualsWithPreemptedRetry(cluster, new Object[] {1, 0, 0}, check, 1, 0); String query2 = "BEGIN TRANSACTION\n" + - " LET select1 = (SELECT * FROM " + qualifiedTableName + " WHERE k=1 AND c=0);\n" + - " LET select2 = (SELECT * FROM " + qualifiedTableName + " WHERE k=2 AND c=0);\n" + - " SELECT v FROM " + qualifiedTableName + " WHERE k=1 AND c=0;\n" + + " LET select1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k=1 AND c=0);\n" + + " LET select2 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k=2 AND c=0);\n" + + " SELECT v FROM " + qualifiedAccordTableName + " WHERE k=1 AND c=0;\n" + " IF select1.v = ? THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (1, 0, 1);\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (2, 0, 1);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (1, 0, 1);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (2, 0, 1);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0 }, query2, 0); @@ -2533,12 +2545,12 @@ public abstract class AccordCQLTestBase extends AccordTestBase public void testReferenceArithmeticInInsert() throws Exception { test(cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, 0, 0)", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (0, 0, 0)", ConsistencyLevel.ALL); String cql = "BEGIN TRANSACTION\n" + - " LET a = (SELECT * FROM " + qualifiedTableName + " WHERE k=0 AND c=0);\n" + + " LET a = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k=0 AND c=0);\n" + " IF a IS NOT NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, 1, a.v + 1);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (0, 1, a.v + 1);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertEmptyWithPreemptedRetry(cluster, cql); @@ -2551,12 +2563,12 @@ public abstract class AccordCQLTestBase extends AccordTestBase public void testReferenceArithmeticInUpdate() throws Exception { test(cluster -> { - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, 0, 0)", ConsistencyLevel.ALL); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (0, 0, 0)", ConsistencyLevel.ALL); String cql = "BEGIN TRANSACTION\n" + - " LET a = (SELECT * FROM " + qualifiedTableName + " WHERE k=0 AND c=0);\n" + + " LET a = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k=0 AND c=0);\n" + " IF a IS NOT NULL THEN\n" + - " UPDATE " + qualifiedTableName + " SET v = a.v + 1 WHERE k = 0 and c = 1;\n" + + " UPDATE " + qualifiedAccordTableName + " SET v = a.v + 1 WHERE k = 0 and c = 1;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertEmptyWithPreemptedRetry(cluster, cql); @@ -2568,39 +2580,39 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testCASAndSerialRead() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (id int, c int, v int, s int static, PRIMARY KEY ((id), c)) WITH transactional_mode='" + transactionalMode + "';", + test("CREATE TABLE " + qualifiedAccordTableName + " (id int, c int, v int, s int static, PRIMARY KEY ((id), c)) WITH transactional_mode='" + transactionalMode + "';", cluster -> { ICoordinator coordinator = cluster.coordinator(1); int startingAccordCoordinateCount = getAccordCoordinateCount(); - assertRowEquals(cluster, new Object[]{false}, "UPDATE " + qualifiedTableName + " SET v = 4 WHERE id = 1 AND c = 2 IF EXISTS"); - assertRowEquals(cluster, new Object[]{false}, "UPDATE " + qualifiedTableName + " SET v = 4 WHERE id = 1 AND c = 2 IF v = 3"); - coordinator.execute("INSERT INTO " + qualifiedTableName + " (id, c, v, s) VALUES (1, 2, 3, 5);", ConsistencyLevel.ALL); - assertRowSerial(cluster, "SELECT id, c, v, s FROM " + qualifiedTableName + " WHERE id = 1 AND c = 2", 1, 2, 3, 5); - assertRowEquals(cluster, new Object[]{true}, "UPDATE " + qualifiedTableName + " SET v = 4 WHERE id = 1 AND c = 2 IF v = 3"); - assertRowSerial(cluster, "SELECT id, c, v, s FROM " + qualifiedTableName + " WHERE id = 1 AND c = 2", 1, 2, 4, 5); - assertRowEquals(cluster, new Object[]{ false, 4 }, "UPDATE " + qualifiedTableName + " SET v = 4 WHERE id = 1 AND c = 2 IF v = 3"); - assertRowSerial(cluster, "SELECT id, c, v, s FROM " + qualifiedTableName + " WHERE id = 1 AND c = 2", 1, 2, 4, 5); + assertRowEquals(cluster, new Object[]{false}, "UPDATE " + qualifiedAccordTableName + " SET v = 4 WHERE id = 1 AND c = 2 IF EXISTS"); + assertRowEquals(cluster, new Object[]{false}, "UPDATE " + qualifiedAccordTableName + " SET v = 4 WHERE id = 1 AND c = 2 IF v = 3"); + coordinator.execute("INSERT INTO " + qualifiedAccordTableName + " (id, c, v, s) VALUES (1, 2, 3, 5);", ConsistencyLevel.ALL); + assertRowSerial(cluster, "SELECT id, c, v, s FROM " + qualifiedAccordTableName + " WHERE id = 1 AND c = 2", 1, 2, 3, 5); + assertRowEquals(cluster, new Object[]{true}, "UPDATE " + qualifiedAccordTableName + " SET v = 4 WHERE id = 1 AND c = 2 IF v = 3"); + assertRowSerial(cluster, "SELECT id, c, v, s FROM " + qualifiedAccordTableName + " WHERE id = 1 AND c = 2", 1, 2, 4, 5); + assertRowEquals(cluster, new Object[]{ false, 4 }, "UPDATE " + qualifiedAccordTableName + " SET v = 4 WHERE id = 1 AND c = 2 IF v = 3"); + assertRowSerial(cluster, "SELECT id, c, v, s FROM " + qualifiedAccordTableName + " WHERE id = 1 AND c = 2", 1, 2, 4, 5); // Test working with a static column - assertRowEquals(cluster, new Object[]{ false, 5 }, "UPDATE " + qualifiedTableName + " SET v = 5 WHERE id = 1 AND c = 2 IF s = 4"); - assertRowSerial(cluster, "SELECT id, c, v, s FROM " + qualifiedTableName + " WHERE id = 1 AND c = 2", 1, 2, 4, 5); - assertRowEquals(cluster, new Object[]{true}, "UPDATE " + qualifiedTableName + " SET v = 5 WHERE id = 1 AND c = 2 IF s = 5"); - assertRowSerial(cluster, "SELECT id, c, v, s FROM " + qualifiedTableName + " WHERE id = 1 AND c = 2", 1, 2, 5, 5); - assertRowEquals(cluster, new Object[]{true}, "UPDATE " + qualifiedTableName + " SET s = 6 WHERE id = 1 IF s = 5"); - assertRowSerial(cluster, "SELECT id, c, v, s FROM " + qualifiedTableName + " WHERE id = 1 AND c = 2", 1, 2, 5, 6); + assertRowEquals(cluster, new Object[]{ false, 5 }, "UPDATE " + qualifiedAccordTableName + " SET v = 5 WHERE id = 1 AND c = 2 IF s = 4"); + assertRowSerial(cluster, "SELECT id, c, v, s FROM " + qualifiedAccordTableName + " WHERE id = 1 AND c = 2", 1, 2, 4, 5); + assertRowEquals(cluster, new Object[]{true}, "UPDATE " + qualifiedAccordTableName + " SET v = 5 WHERE id = 1 AND c = 2 IF s = 5"); + assertRowSerial(cluster, "SELECT id, c, v, s FROM " + qualifiedAccordTableName + " WHERE id = 1 AND c = 2", 1, 2, 5, 5); + assertRowEquals(cluster, new Object[]{true}, "UPDATE " + qualifiedAccordTableName + " SET s = 6 WHERE id = 1 IF s = 5"); + assertRowSerial(cluster, "SELECT id, c, v, s FROM " + qualifiedAccordTableName + " WHERE id = 1 AND c = 2", 1, 2, 5, 6); // Test that read before write works with CAS - assertRowEquals(cluster, new Object[]{true}, "UPDATE " + qualifiedTableName + " SET s +=1, v += 1 WHERE id = 1 AND c = 2 IF EXISTS"); - assertRowSerial(cluster, "SELECT id, c, v, s FROM " + qualifiedTableName + " WHERE id = 1 AND c = 2", 1, 2, 6, 7); + assertRowEquals(cluster, new Object[]{true}, "UPDATE " + qualifiedAccordTableName + " SET s +=1, v += 1 WHERE id = 1 AND c = 2 IF EXISTS"); + assertRowSerial(cluster, "SELECT id, c, v, s FROM " + qualifiedAccordTableName + " WHERE id = 1 AND c = 2", 1, 2, 6, 7); // Check range deletion works - coordinator.execute("INSERT INTO " + qualifiedTableName + " (id, c, v, s) VALUES (1, 2, 6, 7);", ConsistencyLevel.ALL); - coordinator.execute("INSERT INTO " + qualifiedTableName + " (id, c, v) VALUES (1, 3, 3);", ConsistencyLevel.ALL); + coordinator.execute("INSERT INTO " + qualifiedAccordTableName + " (id, c, v, s) VALUES (1, 2, 6, 7);", ConsistencyLevel.ALL); + coordinator.execute("INSERT INTO " + qualifiedAccordTableName + " (id, c, v) VALUES (1, 3, 3);", ConsistencyLevel.ALL); assertRowEquals(cluster, new Object[]{true}, "BEGIN BATCH \n" + - "UPDATE " + qualifiedTableName + " SET s +=1, v += 1 WHERE id = 1 AND c = 2 IF EXISTS; \n" + - "DELETE FROM " + qualifiedTableName + " WHERE id = 1 AND c > 0 AND c < 10; \n" + + "UPDATE " + qualifiedAccordTableName + " SET s +=1, v += 1 WHERE id = 1 AND c = 2 IF EXISTS; \n" + + "DELETE FROM " + qualifiedAccordTableName + " WHERE id = 1 AND c > 0 AND c < 10; \n" + "APPLY BATCH;"); - Object[][] rangeDeletionCheck = coordinator.execute("SELECT id, c, v, s FROM " + qualifiedTableName + " WHERE id = 1", ConsistencyLevel.SERIAL); + Object[][] rangeDeletionCheck = coordinator.execute("SELECT id, c, v, s FROM " + qualifiedAccordTableName + " WHERE id = 1", ConsistencyLevel.SERIAL); assertArrayEquals(new Object[] { 1, 2, 7, 8 }, rangeDeletionCheck[0]); assertEquals(1, rangeDeletionCheck.length); @@ -2617,10 +2629,10 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testCASSimulatorLite() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (pk int, count int, seq1 text, seq2 list, PRIMARY KEY (pk)) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (pk int, count int, seq1 text, seq2 list, PRIMARY KEY (pk)) WITH transactional_mode='" + transactionalMode + "'", cluster -> { ICoordinator coordinator = cluster.coordinator(1); - coordinator.execute("INSERT INTO " + qualifiedTableName + " (pk, count, seq1, seq2) VALUES (1, 0, '', []) USING TIMESTAMP 0", ConsistencyLevel.ALL); + coordinator.execute("INSERT INTO " + qualifiedAccordTableName + " (pk, count, seq1, seq2) VALUES (1, 0, '', []) USING TIMESTAMP 0", ConsistencyLevel.ALL); ListType LIST_TYPE = ListType.getInstance(Int32Type.instance, true); ExecutorService es = Executors.newCachedThreadPool(); @@ -2628,12 +2640,12 @@ public abstract class AccordCQLTestBase extends AccordTestBase for (int ii = 0; ii < 10; ii++) { int id = ii; - futures.add(es.submit(() -> coordinator.execute("UPDATE " + qualifiedTableName + " SET count = count + 1, seq1 = seq1 + ?, seq2 = seq2 + ? WHERE pk = ? IF EXISTS", ConsistencyLevel.ALL, id + ",", ByteBufferUtil.getArray(LIST_TYPE.decompose(singletonList(id))), 1))); + futures.add(es.submit(() -> coordinator.execute("UPDATE " + qualifiedAccordTableName + " SET count = count + 1, seq1 = seq1 + ?, seq2 = seq2 + ? WHERE pk = ? IF EXISTS", ConsistencyLevel.ALL, id + ",", ByteBufferUtil.getArray(LIST_TYPE.decompose(singletonList(id))), 1))); } for (Future f : futures) f.get(); - Object[][] result = coordinator.execute("SELECT pk, count, seq1, seq2 FROM " + qualifiedTableName + " WHERE pk = 1", ConsistencyLevel.SERIAL); + Object[][] result = coordinator.execute("SELECT pk, count, seq1, seq2 FROM " + qualifiedAccordTableName + " WHERE pk = 1", ConsistencyLevel.SERIAL); int[] seq1 = Arrays.stream(((String) result[0][2]).split(",")) .filter(s -> !s.isEmpty()) @@ -2649,11 +2661,11 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testTransactionCasSimulatorLite() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (pk int, count int, seq1 text, seq2 list, PRIMARY KEY (pk)) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (pk int, count int, seq1 text, seq2 list, PRIMARY KEY (pk)) WITH transactional_mode='" + transactionalMode + "'", cluster -> { ICoordinator coordinator = cluster.coordinator(1); - coordinator.execute("INSERT INTO " + qualifiedTableName + " (pk, count, seq1, seq2) VALUES (1, 0, '', []) USING TIMESTAMP 0", ConsistencyLevel.ALL); + coordinator.execute("INSERT INTO " + qualifiedAccordTableName + " (pk, count, seq1, seq2) VALUES (1, 0, '', []) USING TIMESTAMP 0", ConsistencyLevel.ALL); ListType LIST_TYPE = ListType.getInstance(Int32Type.instance, true); ExecutorService es = Executors.newCachedThreadPool(); @@ -2662,8 +2674,8 @@ public abstract class AccordCQLTestBase extends AccordTestBase { int id = ii; String update = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE pk = 1);\n" + - " UPDATE " + qualifiedTableName + " SET count += 1, seq1 = seq1 + ?, seq2 = seq2 + ? WHERE pk=1;\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE pk = 1);\n" + + " UPDATE " + qualifiedAccordTableName + " SET count += 1, seq1 = seq1 + ?, seq2 = seq2 + ? WHERE pk=1;\n" + "COMMIT TRANSACTION"; futures.add(es.submit(() -> coordinator.executeWithResult(update, ConsistencyLevel.ANY, id + ",", ByteBufferUtil.getArray(LIST_TYPE.decompose(singletonList(id)))))); } @@ -2671,7 +2683,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase f.get(); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE pk = 1;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE pk = 1;\n" + "COMMIT TRANSACTION"; Object[][] result = coordinator.execute(check, ConsistencyLevel.ALL); @@ -2690,15 +2702,15 @@ public abstract class AccordCQLTestBase extends AccordTestBase @Test public void testSerialReadDescending() throws Throwable { - test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY(k, c)) WITH transactional_mode='" + transactionalMode + "'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v int, PRIMARY KEY(k, c)) WITH transactional_mode='" + transactionalMode + "'", cluster -> { ICoordinator coordinator = cluster.coordinator(1); for (int i = 1; i <= 10; i++) - coordinator.execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, ?, ?) USING TIMESTAMP 0;", ConsistencyLevel.ALL, i, i * 10); - assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedTableName + " WHERE k=0 ORDER BY c DESC LIMIT 1", AssertUtils.row(10, 100)); - assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedTableName + " WHERE k=0 ORDER BY c DESC LIMIT 2", AssertUtils.row(10, 100), AssertUtils.row(9, 90)); - assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedTableName + " WHERE k=0 ORDER BY c DESC LIMIT 3", AssertUtils.row(10, 100), AssertUtils.row(9, 90), AssertUtils.row(8, 80)); - assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedTableName + " WHERE k=0 ORDER BY c DESC LIMIT 4", AssertUtils.row(10, 100), AssertUtils.row(9, 90), AssertUtils.row(8, 80), AssertUtils.row(7, 70)); + coordinator.execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (0, ?, ?) USING TIMESTAMP 0;", ConsistencyLevel.ALL, i, i * 10); + assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedAccordTableName + " WHERE k=0 ORDER BY c DESC LIMIT 1", AssertUtils.row(10, 100)); + assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedAccordTableName + " WHERE k=0 ORDER BY c DESC LIMIT 2", AssertUtils.row(10, 100), AssertUtils.row(9, 90)); + assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedAccordTableName + " WHERE k=0 ORDER BY c DESC LIMIT 3", AssertUtils.row(10, 100), AssertUtils.row(9, 90), AssertUtils.row(8, 80)); + assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedAccordTableName + " WHERE k=0 ORDER BY c DESC LIMIT 4", AssertUtils.row(10, 100), AssertUtils.row(9, 90), AssertUtils.row(8, 80), AssertUtils.row(7, 70)); } ); } diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordIncrementalRepairTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordIncrementalRepairTest.java index ac7ab99f26..452d9dc53a 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordIncrementalRepairTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordIncrementalRepairTest.java @@ -36,12 +36,12 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import accord.local.cfk.CommandsForKey; import accord.impl.SimpleProgressLog; import accord.local.Node; import accord.local.PreLoadContext; import accord.local.SafeCommand; import accord.local.Status; +import accord.local.cfk.CommandsForKey; import accord.primitives.Seekables; import accord.primitives.Timestamp; import accord.primitives.TxnId; @@ -51,6 +51,7 @@ import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.distributed.api.Feature; import org.apache.cassandra.distributed.api.IInvokableInstance; import org.apache.cassandra.distributed.api.IIsolatedExecutor; import org.apache.cassandra.gms.FailureDetector; @@ -147,13 +148,15 @@ public class AccordIncrementalRepairTest extends AccordTestBase public static void setupClass() throws Throwable { CassandraRelevantProperties.ACCORD_AGENT_CLASS.setString(BarrierRecordingAgent.class.getName()); -// setupCluster(opt -> opt.withConfig(conf -> conf.with(Feature.NETWORK, Feature.GOSSIP)), 3); - setupCluster(opt -> opt, 3); + setupCluster(opt -> opt.withConfig(conf -> conf.with(Feature.NETWORK, Feature.GOSSIP)), 3); +// setupCluster(opt -> opt, 3); } @After public void tearDown() { + for (IInvokableInstance instance : SHARED_CLUSTER) + instance.runOnInstance(() -> SimpleProgressLog.PAUSE_FOR_TEST = false); SHARED_CLUSTER.filters().reset(); } @@ -248,16 +251,16 @@ public class AccordIncrementalRepairTest extends AccordTestBase @Test public void txnRepairTest() throws Throwable { - SHARED_CLUSTER.schemaChange(format("CREATE TABLE %s.%s (k int primary key, v int) WITH transactional_mode='full' AND fast_path={'size':2};", KEYSPACE, tableName)); + SHARED_CLUSTER.schemaChange(format("CREATE TABLE %s.%s (k int primary key, v int) WITH transactional_mode='full' AND fast_path={'size':2};", KEYSPACE, accordTableName)); final String keyspace = KEYSPACE; - final String table = tableName; + final String table = accordTableName; SHARED_CLUSTER.filters().allVerbs().to(3).drop(); awaitEndpointDown(SHARED_CLUSTER.get(1), SHARED_CLUSTER.get(3)); executeWithRetry(SHARED_CLUSTER, format("BEGIN TRANSACTION\n" + "INSERT INTO %s (k, v) VALUES (1, 1);\n" + - "COMMIT TRANSACTION", qualifiedTableName)); + "COMMIT TRANSACTION", qualifiedAccordTableName)); SHARED_CLUSTER.get(1, 2).forEach(instance -> instance.runOnInstance(() -> { TableMetadata metadata = Schema.instance.getTableMetadata(keyspace, table); @@ -291,7 +294,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase }); SHARED_CLUSTER.filters().reset(); awaitEndpointUp(SHARED_CLUSTER.get(1), SHARED_CLUSTER.get(3)); - SHARED_CLUSTER.get(1).nodetool("repair", KEYSPACE); + nodetool(SHARED_CLUSTER.get(1), "repair", KEYSPACE); SHARED_CLUSTER.forEach(instance -> { instance.runOnInstance(() -> { @@ -307,9 +310,9 @@ public class AccordIncrementalRepairTest extends AccordTestBase private void testSingleNodeWrite(TransactionalMode mode) { - SHARED_CLUSTER.schemaChange(format("CREATE TABLE %s.%s (k int primary key, v int) WITH transactional_mode='%s';", KEYSPACE, tableName, mode)); + SHARED_CLUSTER.schemaChange(format("CREATE TABLE %s.%s (k int primary key, v int) WITH transactional_mode='%s';", KEYSPACE, accordTableName, mode)); final String keyspace = KEYSPACE; - final String table = tableName; + final String table = accordTableName; SHARED_CLUSTER.get(3).runOnInstance(() -> { QueryProcessor.executeInternal(String.format("INSERT INTO %s.%s (k, v) VALUES (1, 2);", keyspace, table)); @@ -344,7 +347,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase agent().reset(); })); - SHARED_CLUSTER.get(1).nodetool("repair", KEYSPACE); + nodetool(SHARED_CLUSTER.get(1), "repair", KEYSPACE); SHARED_CLUSTER.forEach(instance -> instance.runOnInstance(() -> { Assert.assertFalse( agent().executedBarriers().isEmpty()); ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table); @@ -382,9 +385,9 @@ public class AccordIncrementalRepairTest extends AccordTestBase @Test public void onlyAccordTest() { - SHARED_CLUSTER.schemaChange(format("CREATE TABLE %s.%s (k int primary key, v int) WITH transactional_mode='full' AND fast_path={'size':2};", KEYSPACE, tableName)); + SHARED_CLUSTER.schemaChange(format("CREATE TABLE %s.%s (k int primary key, v int) WITH transactional_mode='full' AND fast_path={'size':2};", KEYSPACE, accordTableName)); final String keyspace = KEYSPACE; - final String table = tableName; + final String table = accordTableName; SHARED_CLUSTER.filters().allVerbs().to(3).drop(); awaitEndpointDown(SHARED_CLUSTER.get(1), SHARED_CLUSTER.get(3)); @@ -392,7 +395,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase executeWithRetry(SHARED_CLUSTER, format("BEGIN TRANSACTION\n" + "INSERT INTO %s (k, v) VALUES (1, 1);\n" + - "COMMIT TRANSACTION", qualifiedTableName)); + "COMMIT TRANSACTION", qualifiedAccordTableName)); SHARED_CLUSTER.get(1, 2).forEach(instance -> instance.runOnInstance(() -> { TableMetadata metadata = Schema.instance.getTableMetadata(keyspace, table); @@ -403,7 +406,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase SHARED_CLUSTER.filters().reset(); awaitEndpointUp(SHARED_CLUSTER.get(1), SHARED_CLUSTER.get(3)); - SHARED_CLUSTER.get(1).nodetool("repair", "--accord-only", KEYSPACE); + nodetool(SHARED_CLUSTER.get(1), "repair", "--accord-only", KEYSPACE); SHARED_CLUSTER.forEach(instance -> { logger().info("checking instance {}", instance.broadcastAddress()); diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordIntegrationTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordIntegrationTest.java index e269e4e27a..0ba67894d2 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordIntegrationTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordIntegrationTest.java @@ -59,10 +59,10 @@ public class AccordIntegrationTest extends AccordTestBase IMessageFilters.Filter lostCommit = cluster.filters().verbs(Verb.ACCORD_COMMIT_REQ.id).to(2).drop(); String query = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT v FROM " + qualifiedTableName + " WHERE k=0 AND c=0);\n" + + " LET row1 = (SELECT v FROM " + qualifiedAccordTableName + " WHERE k=0 AND c=0);\n" + " SELECT row1.v;\n" + " IF row1 IS NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, 0, 1);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (0, 0, 1);\n" + " END IF\n" + "COMMIT TRANSACTION"; // row1.v shouldn't have existed when the txn's SELECT was executed @@ -73,24 +73,24 @@ public class AccordIntegrationTest extends AccordTestBase // Querying again should trigger recovery... query = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT v FROM " + qualifiedTableName + " WHERE k=0 AND c=0);\n" + + " LET row1 = (SELECT v FROM " + qualifiedAccordTableName + " WHERE k=0 AND c=0);\n" + " SELECT row1.v;\n" + " IF row1.v = 1 THEN\n" + - " UPDATE " + qualifiedTableName + " SET v=2 WHERE k = 0 AND c = 0;\n" + + " UPDATE " + qualifiedAccordTableName + " SET v=2 WHERE k = 0 AND c = 0;\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 1 }, query); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ? AND c = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] {0, 0, 2}, check, 0, 0); query = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT v FROM " + qualifiedTableName + " WHERE k=0 AND c=0);\n" + + " LET row1 = (SELECT v FROM " + qualifiedAccordTableName + " WHERE k=0 AND c=0);\n" + " SELECT row1.v;\n" + " IF row1 IS NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, 0, 3);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (0, 0, 3);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 2 }, query); @@ -113,16 +113,16 @@ public class AccordIntegrationTest extends AccordTestBase })).drop(); String query = "BEGIN TRANSACTION\n" + - " LET row1 = (SELECT * FROM " + qualifiedTableName + " WHERE k = 0 AND c = 0);\n" + + " LET row1 = (SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 0 AND c = 0);\n" + " SELECT row1.v;\n" + " IF row1 IS NULL THEN\n" + - " INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, 0, 1);\n" + + " INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (0, 0, 1);\n" + " END IF\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { null }, query); String check = "BEGIN TRANSACTION\n" + - " SELECT * FROM " + qualifiedTableName + " WHERE k = ? AND c = ?;\n" + + " SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ? AND c = ?;\n" + "COMMIT TRANSACTION"; assertRowEqualsWithPreemptedRetry(cluster, new Object[] { 0, 0, 1 }, check, 0, 0); }); diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordInteroperabilityTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordInteroperabilityTest.java index acef419889..1b8bfe8985 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordInteroperabilityTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordInteroperabilityTest.java @@ -48,15 +48,15 @@ public class AccordInteroperabilityTest extends AccordTestBase @Test public void testSerialReadDescending() throws Throwable { - test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY(k, c)) WITH transactional_mode='full'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v int, PRIMARY KEY(k, c)) WITH transactional_mode='full'", cluster -> { ICoordinator coordinator = cluster.coordinator(1); for (int i = 1; i <= 10; i++) - coordinator.execute("INSERT INTO " + qualifiedTableName + " (k, c, v) VALUES (0, ?, ?) USING TIMESTAMP 0;", ConsistencyLevel.ALL, i, i * 10); - assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedTableName + " WHERE k=0 ORDER BY c DESC LIMIT 1", AssertUtils.row(10, 100)); - assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedTableName + " WHERE k=0 ORDER BY c DESC LIMIT 2", AssertUtils.row(10, 100), AssertUtils.row(9, 90)); - assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedTableName + " WHERE k=0 ORDER BY c DESC LIMIT 3", AssertUtils.row(10, 100), AssertUtils.row(9, 90), AssertUtils.row(8, 80)); - assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedTableName + " WHERE k=0 ORDER BY c DESC LIMIT 4", AssertUtils.row(10, 100), AssertUtils.row(9, 90), AssertUtils.row(8, 80), AssertUtils.row(7, 70)); + coordinator.execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v) VALUES (0, ?, ?) USING TIMESTAMP 0;", ConsistencyLevel.ALL, i, i * 10); + assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedAccordTableName + " WHERE k=0 ORDER BY c DESC LIMIT 1", AssertUtils.row(10, 100)); + assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedAccordTableName + " WHERE k=0 ORDER BY c DESC LIMIT 2", AssertUtils.row(10, 100), AssertUtils.row(9, 90)); + assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedAccordTableName + " WHERE k=0 ORDER BY c DESC LIMIT 3", AssertUtils.row(10, 100), AssertUtils.row(9, 90), AssertUtils.row(8, 80)); + assertRowSerial(cluster, "SELECT c, v FROM " + qualifiedAccordTableName + " WHERE k=0 ORDER BY c DESC LIMIT 4", AssertUtils.row(10, 100), AssertUtils.row(9, 90), AssertUtils.row(8, 80), AssertUtils.row(7, 70)); } ); } diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordLoadTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordLoadTest.java index 187f08a11c..c5aa059a4c 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordLoadTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordLoadTest.java @@ -64,7 +64,7 @@ public class AccordLoadTest extends AccordTestBase @Test public void testLoad() throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int, v int, PRIMARY KEY(k)) WITH transactional_mode = 'full'", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, v int, PRIMARY KEY(k)) WITH transactional_mode = 'full'", cluster -> { final ConcurrentHashMap verbs = new ConcurrentHashMap<>(); @@ -86,7 +86,7 @@ public class AccordLoadTest extends AccordTestBase final float readChance = 0.33f; long nextRepairAt = repairInterval; for (int i = 1; i <= keyCount; i++) - coordinator.execute("INSERT INTO " + qualifiedTableName + " (k, v) VALUES (0, 0) USING TIMESTAMP 0;", ConsistencyLevel.ALL, i); + coordinator.execute("INSERT INTO " + qualifiedAccordTableName + " (k, v) VALUES (0, 0) USING TIMESTAMP 0;", ConsistencyLevel.ALL, i); Random random = new Random(); // CopyOnWriteArrayList exceptions = new CopyOnWriteArrayList<>(); @@ -109,7 +109,7 @@ public class AccordLoadTest extends AccordTestBase inFlight.release(); if (fail == null) histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart)); // else exceptions.add(fail); - }, "SELECT * FROM " + qualifiedTableName + " WHERE k = ?;", ConsistencyLevel.SERIAL, random.nextInt(keyCount)); + }, "SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;", ConsistencyLevel.SERIAL, random.nextInt(keyCount)); } else { @@ -117,7 +117,7 @@ public class AccordLoadTest extends AccordTestBase inFlight.release(); if (fail == null) histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart)); // else exceptions.add(fail); - }, "UPDATE " + qualifiedTableName + " SET v += 1 WHERE k = ? IF EXISTS;", ConsistencyLevel.SERIAL, ConsistencyLevel.QUORUM, random.nextInt(keyCount)); + }, "UPDATE " + qualifiedAccordTableName + " SET v += 1 WHERE k = ? IF EXISTS;", ConsistencyLevel.SERIAL, ConsistencyLevel.QUORUM, random.nextInt(keyCount)); } } @@ -125,7 +125,7 @@ public class AccordLoadTest extends AccordTestBase { nextRepairAt += repairInterval; System.out.println("repairing..."); - cluster.coordinator(1).instance().nodetool("repair", qualifiedTableName); + cluster.coordinator(1).instance().nodetool("repair", qualifiedAccordTableName); } final Date date = new Date(); diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMetricsTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMetricsTest.java index 0e9a47f71c..f6fdba672f 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMetricsTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMetricsTest.java @@ -73,16 +73,16 @@ public class AccordMetricsTest extends AccordTestBase String writeCql() { return "BEGIN TRANSACTION\n" + - " LET val = (SELECT v FROM " + qualifiedTableName + " WHERE k=? AND c=?);\n" + + " LET val = (SELECT v FROM " + qualifiedAccordTableName + " WHERE k=? AND c=?);\n" + " SELECT val.v;\n" + - " UPDATE " + qualifiedTableName + " SET v = v + 1 WHERE k=? AND c=?;\n" + + " UPDATE " + qualifiedAccordTableName + " SET v = v + 1 WHERE k=? AND c=?;\n" + "COMMIT TRANSACTION"; } String readCql() { return "BEGIN TRANSACTION\n" + - " LET val = (SELECT v FROM " + qualifiedTableName + " WHERE k=? AND c=?);\n" + + " LET val = (SELECT v FROM " + qualifiedAccordTableName + " WHERE k=? AND c=?);\n" + " SELECT val.v;\n" + "COMMIT TRANSACTION"; } @@ -93,7 +93,7 @@ public class AccordMetricsTest extends AccordTestBase public void beforeTest() { SHARED_CLUSTER.filters().reset(); - SHARED_CLUSTER.schemaChange("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH " + TransactionalMode.full.asCqlParam()); + SHARED_CLUSTER.schemaChange("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v int, PRIMARY KEY (k, c)) WITH " + TransactionalMode.full.asCqlParam()); } @Test diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationRaceTestBase.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationRaceTestBase.java new file mode 100644 index 0000000000..e5865ef9d3 --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationRaceTestBase.java @@ -0,0 +1,771 @@ +/* + * 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.distributed.test.accord; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import com.google.common.base.Stopwatch; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListenableFutureTask; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import accord.api.RoutingKey; +import accord.messages.PreAccept; +import accord.primitives.PartialKeyRoute; +import accord.primitives.PartialRoute; +import accord.primitives.Routable.Domain; +import org.apache.cassandra.ServerTestUtils; +import org.apache.cassandra.Util; +import org.apache.cassandra.batchlog.BatchlogManager; +import org.apache.cassandra.config.CassandraRelevantProperties; +import org.apache.cassandra.config.Config.PaxosVariant; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.db.ColumnFamilyStore; +import org.apache.cassandra.db.Mutation; +import org.apache.cassandra.dht.IPartitioner; +import org.apache.cassandra.dht.Token; +import org.apache.cassandra.distributed.Cluster; +import org.apache.cassandra.distributed.api.ConsistencyLevel; +import org.apache.cassandra.distributed.api.ICoordinator; +import org.apache.cassandra.distributed.api.IInstance; +import org.apache.cassandra.distributed.api.IInvokableInstance; +import org.apache.cassandra.distributed.api.IMessage; +import org.apache.cassandra.distributed.api.IMessageSink; +import org.apache.cassandra.distributed.api.QueryResults; +import org.apache.cassandra.distributed.api.SimpleQueryResult; +import org.apache.cassandra.distributed.impl.Instance; +import org.apache.cassandra.distributed.impl.TestChangeListener; +import org.apache.cassandra.distributed.shared.ClusterUtils; +import org.apache.cassandra.exceptions.CoordinatorBehindException; +import org.apache.cassandra.exceptions.RequestFailure; +import org.apache.cassandra.exceptions.RequestFailureReason; +import org.apache.cassandra.exceptions.WriteTimeoutException; +import org.apache.cassandra.hints.HintsService; +import org.apache.cassandra.metrics.AccordClientRequestMetrics; +import org.apache.cassandra.metrics.ClientRequestsMetricsHolder; +import org.apache.cassandra.metrics.HintsServiceMetrics; +import org.apache.cassandra.net.Message; +import org.apache.cassandra.net.Verb; +import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.service.accord.AccordService; +import org.apache.cassandra.service.accord.api.AccordRoutingKey; +import org.apache.cassandra.service.consensus.TransactionalMode; +import org.apache.cassandra.service.consensus.migration.ConsensusKeyMigrationState; +import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.tcm.Epoch; +import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.Pair; +import org.apache.cassandra.utils.concurrent.AsyncPromise; +import org.apache.cassandra.utils.concurrent.Promise; +import org.eclipse.jetty.util.ConcurrentHashSet; + +import static java.lang.String.format; +import static org.apache.cassandra.Util.expectException; +import static org.apache.cassandra.Util.spinAssertEquals; +import static org.apache.cassandra.config.CassandraRelevantProperties.HINT_DISPATCH_INTERVAL_MS; +import static org.apache.cassandra.distributed.api.ConsistencyLevel.ALL; +import static org.apache.cassandra.distributed.shared.ClusterUtils.getNextEpoch; +import static org.apache.cassandra.distributed.shared.ClusterUtils.pauseAfterEnacting; +import static org.apache.cassandra.distributed.shared.ClusterUtils.pauseBeforeEnacting; +import static org.apache.cassandra.distributed.shared.ClusterUtils.unpauseEnactment; +import static org.apache.cassandra.distributed.test.accord.AccordMigrationRaceTestBase.Scenario.BATCHLOG_FAILED_ROUTING_THEN_HINT; +import static org.apache.cassandra.distributed.test.accord.AccordMigrationRaceTestBase.Scenario.BATCHLOG_FAILED_TIMEOUT_THEN_HINT; +import static org.apache.cassandra.distributed.test.accord.AccordMigrationRaceTestBase.Scenario.BATCHLOG_SUCCESSFUL_ROUTING; +import static org.apache.cassandra.distributed.test.accord.AccordMigrationRaceTestBase.Scenario.HINT; +import static org.apache.cassandra.distributed.test.accord.AccordMigrationRaceTestBase.Scenario.MUTATION; +import static org.apache.cassandra.distributed.util.QueryResultUtil.assertThat; +import static org.apache.cassandra.exceptions.RequestFailureReason.RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM; +import static org.apache.cassandra.utils.Throwables.runUnchecked; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/* + * Test that non-transactional write operations such as regular mutations, batch log, and hints + * all detect when a migration is in progress, and then retry on the correct system. + */ +public abstract class AccordMigrationRaceTestBase extends AccordTestBase +{ + private static final Logger logger = LoggerFactory.getLogger(AccordMigrationRaceTestBase.class); + + private static final int CLUSTERING_VALUE = 1; + + private static final String TABLE_FMT = "CREATE TABLE %s (id int, c int, v int, PRIMARY KEY ((id), c));"; + + public static final int PKEY_ACCORD = 3; + public static final int PKEY_NORMAL = 0; + + private static IPartitioner partitioner; + + private static Token minToken; + + private static Token maxToken; + + private static Token midToken; + + private static Token upperMidToken; + + private static Token lowerMidToken; + + private static ICoordinator coordinator; + + private final static TestMessageSink messageSink = new TestMessageSink(); + private static class TestMessageSink implements IMessageSink + { + private final Queue> messages = new ConcurrentLinkedQueue<>(); + private final Set blackholed = new ConcurrentHashSet<>(); + + public void reset() + { + messages.clear(); + blackholed.clear(); + } + + @Override + public void accept(InetSocketAddress to, IMessage message) { + messages.offer(Pair.create(to,message)); + IInstance i = SHARED_CLUSTER.get(to); + if (blackholed.contains(to) || blackholed.contains(message.from())) + return; + if (i != null) + i.receiveMessage(message); + } + } + + enum Scenario + { + // Apply the mutation from the coordinator directly without going through hinting + MUTATION(false, false, false, false, false), + // Hint from the initial mutation coordination + HINT(true, false, true, false, true), + // Apply the mutation from the batchlog directly + BATCHLOG_SUCCESSFUL_ROUTING(false, true, true, true, false), + // Have the batchlog use hints to apply the mutation after failing to route, migrating back from Accord this is a timeout because you can't get Accord to fail at routing + // it either executes correctly in the old epoch or times out waiting for the new one to arrive + BATCHLOG_FAILED_ROUTING_THEN_HINT(false, true, true, true, true), + // Have the batchlog use hints to apply the mutation after a timeout + BATCHLOG_FAILED_TIMEOUT_THEN_HINT(false, true, true, true, true), + ; + + final boolean initiallyEnableHints; + final boolean initiallyEnableBatchlogReplay; + final boolean initiallyBlockTestKeyspaceMutations; + final boolean passesThroughBatchlog; + final boolean deliversViaHint; + + Scenario(boolean initiallyEnableHints, boolean initiallyEnableBatchlogReplay, boolean initiallyBlockTestKeyspaceMutations, boolean passesThroughBatchlog, boolean deliversViaHint) + { + this.initiallyEnableHints = initiallyEnableHints; + this.initiallyEnableBatchlogReplay = initiallyEnableBatchlogReplay; + this.initiallyBlockTestKeyspaceMutations = initiallyBlockTestKeyspaceMutations; + this.passesThroughBatchlog = passesThroughBatchlog; + this.deliversViaHint = deliversViaHint; + } + } + + private final boolean migrateAwayFromAccord; + + protected AccordMigrationRaceTestBase() + { + this.migrateAwayFromAccord = migratingAwayFromAccord(); + } + + protected abstract boolean migratingAwayFromAccord(); + + @Override + protected Logger logger() + { + return logger; + } + + @BeforeClass + public static void setupClass() throws IOException + { + HINT_DISPATCH_INTERVAL_MS.setLong(100); + ServerTestUtils.daemonInitialization(); + // Otherwise repair complains if you don't specify a keyspace + CassandraRelevantProperties.SYSTEM_TRACES_DEFAULT_RF.setInt(3); + AccordTestBase.setupCluster(builder -> builder.appendConfig(config -> config.set("paxos_variant", PaxosVariant.v2.name()) + .set("write_request_timeout", "2s") + .set("accord.range_migration", "explicit")), 3); + partitioner = FBUtilities.newPartitioner(SHARED_CLUSTER.get(1).callsOnInstance(() -> DatabaseDescriptor.getPartitioner().getClass().getSimpleName()).call()); + StorageService.instance.setPartitionerUnsafe(partitioner); + ServerTestUtils.prepareServerNoRegister(); + minToken = partitioner.getMinimumToken(); + maxToken = partitioner.getMaximumTokenForSplitting(); + midToken = partitioner.midpoint(minToken, maxToken); + upperMidToken = partitioner.midpoint(midToken, maxToken); + lowerMidToken = partitioner.midpoint(minToken, midToken); + coordinator = SHARED_CLUSTER.coordinator(1); + SHARED_CLUSTER.setMessageSink(messageSink); + } + + @AfterClass + public static void tearDownClass() + { + StorageService.instance.resetPartitionerUnsafe(); + } + + @After + public void tearDown() throws Exception + { + messageSink.reset(); + forEach(() -> { + BatchlogManager.instance.resumeReplay(); + HintsService.instance.deleteAllHintsUnsafe(); + HintsService.instance.resumeDispatch(); + }); + SHARED_CLUSTER.forEach(ClusterUtils::clearAndUnpause); + super.tearDown(); + // Reset migration state + forEach(() -> { + ConsensusRequestRouter.resetInstance(); + ConsensusKeyMigrationState.reset(); + }); + truncateSystemTables(); + } + + private ListenableFuture alterTableTransactionalModeAsync(TransactionalMode mode) + { + ListenableFutureTask task = ListenableFutureTask.create(() -> { + coordinator.execute(format("ALTER TABLE %s WITH %s", qualifiedAccordTableName, mode.asCqlParam()), ALL); + }, null); + Thread asyncThread = new Thread(task, "Alter table transaction mode " + mode); + asyncThread.setDaemon(true); + asyncThread.start(); + return task; + } + + @Test + public void testSplitAndRetryNonSerialUnloggedBatchTwoTablesOnePkey() throws Throwable + { + testSplitAndRetryMutationCoordination(twoTableBatchInsert(false, PKEY_ACCORD, PKEY_ACCORD, 1), validateTwoTable(PKEY_ACCORD)); + } + + @Test + public void testSplitAndRetryNonSerialUnloggedBatchTwoTablesOnePkeyHinting() throws Throwable + { + // Accord doesn't hint if a write times out + if (!migrateAwayFromAccord) + testSplitAndRetryHintDelivery(twoTableBatchInsert(false, PKEY_ACCORD, PKEY_ACCORD, 1), validateTwoTable(PKEY_ACCORD)); + } + + @Test + public void testSplitAndRetryNonSerialUnloggedBatchTwoTablesTwoPkey() throws Throwable + { + testSplitAndRetryMutationCoordination(twoTableBatchInsert(false, PKEY_ACCORD, PKEY_NORMAL, 1), validateTwoTable(PKEY_NORMAL)); + } + + @Test + public void testSplitAndRetryNonSerialUnloggedBatchTwoTablesHinting() throws Throwable + { + // Accord doesn't hint if a write times out + if (!migrateAwayFromAccord) + testSplitAndRetryHintDelivery(twoTableBatchInsert(false, PKEY_ACCORD, PKEY_NORMAL, 1), validateTwoTable(PKEY_NORMAL)); + } + + @Test + public void testSplitAndRetryNonSerialUnloggedBatchSingleTable() throws Throwable + { + testSplitAndRetryMutationCoordination(singleTableBatchInsert(false, PKEY_ACCORD, PKEY_NORMAL, 1), this::validateSingleTable); + } + + @Test + public void testSplitAndRetryNonSerialUnloggedBatchSingleTableHinting() throws Throwable + { + // Accord doesn't hint if a write times out + if (!migrateAwayFromAccord) + testSplitAndRetryHintDelivery(singleTableBatchInsert(false, PKEY_ACCORD, PKEY_NORMAL, 1), this::validateSingleTable); + } + + /* + * This doesn't really test much since on top of testSplitAndRetryNonSerialUnloggedBatchTwoTablesOnePkey since it is + * a single table & key and will be converted to an unlogged batch + */ + @Test + public void testSplitAndRetryNonSerialLoggedBatchTwoTablesOnePkey() throws Throwable + { + testSplitAndRetryMutationCoordination(twoTableBatchInsert(true, PKEY_ACCORD, PKEY_ACCORD, 1), validateTwoTable(PKEY_ACCORD)); + } + + @Test + public void testSplitAndRetryNonSerialLoggedBatchTwoTablesTwoPkey() throws Throwable + { + testSplitAndRetryMutationCoordination(twoTableBatchInsert(true, PKEY_ACCORD, PKEY_NORMAL, 1), validateTwoTable(PKEY_NORMAL)); + } + + @Test + public void testSplitAndRetryNonSerialLoggedBatchTwoTablesTwoPkeyDeliverViaBatchLog() throws Throwable + { + testSplitAndRetryBatchlogDelivery(twoTableBatchInsert(true, PKEY_ACCORD, PKEY_NORMAL, 1), validateTwoTable(PKEY_NORMAL)); + } + + @Test + public void testSplitAndRetryNonSerialLoggedBatchTwoTablesTwoPkeyHintedViaBatchLogTimeout() throws Throwable + { + testSplitAndRetryHintDeliveryAfterBatchlogTimeout(twoTableBatchInsert(true, PKEY_ACCORD, PKEY_NORMAL, 1), validateTwoTable(PKEY_NORMAL)); + } + + @Test + public void testSplitAndRetryNonSerialLoggedBatchTwoTablesTwoPkeyHintedViaBatchLogRoutingFailure() throws Throwable + { + testSplitAndRetryHintDeliveryAfterBatchlogRoutingFailure(twoTableBatchInsert(true, PKEY_ACCORD, PKEY_NORMAL, 1), validateTwoTable(PKEY_NORMAL)); + } + + /* + * Test that a logged batch writing to a migrating table and a non-migrating table can + */ + @Test + public void testSplitAndRetryNonSerialLoggedBatchSingleTable() throws Throwable + { + testSplitAndRetryBatchlogDelivery(singleTableBatchInsert(true, PKEY_ACCORD, PKEY_NORMAL, 1), this::validateSingleTable); + } + + @Test + public void testSplitAndRetryNonSerialLoggedBatchSingleTableDeliverViaBatchLog() throws Throwable + { + testSplitAndRetryBatchlogDelivery(singleTableBatchInsert(true, PKEY_ACCORD, PKEY_NORMAL, 1), this::validateSingleTable); + } + + @Test + public void testSplitAndRetryNonSerialLoggedBatchSingleTableHintedViaBatchLogTimeout() throws Throwable + { + testSplitAndRetryHintDeliveryAfterBatchlogTimeout(singleTableBatchInsert(true, PKEY_ACCORD, PKEY_NORMAL, 1), this::validateSingleTable); + } + + @Test + public void testSplitAndRetryNonSerialLoggedBatchSingleTableHintedViaBatchLogRoutingFailure() throws Throwable + { + testSplitAndRetryHintDeliveryAfterBatchlogRoutingFailure(singleTableBatchInsert(true, PKEY_ACCORD, PKEY_NORMAL, 1), this::validateSingleTable); + } + + private void testSplitAndRetryMutationCoordination(String batchCQL, Consumer validation) throws Throwable + { + testSplitAndRetry(batchCQL, validation, MUTATION); + } + + private void testSplitAndRetryBatchlogDelivery(String batchCQL, Consumer validation) throws Throwable + { + testSplitAndRetry(batchCQL, validation, BATCHLOG_SUCCESSFUL_ROUTING); + } + + private void testSplitAndRetryHintDeliveryAfterBatchlogTimeout(String batchCQL, Consumer validation) throws Throwable + { + testSplitAndRetry(batchCQL, validation, BATCHLOG_FAILED_TIMEOUT_THEN_HINT); + } + + private void testSplitAndRetryHintDeliveryAfterBatchlogRoutingFailure(String batchCQL, Consumer validation) throws Throwable + { + testSplitAndRetry(batchCQL, validation, BATCHLOG_FAILED_ROUTING_THEN_HINT); + } + + private void testSplitAndRetryHintDelivery(String batchCQL, Consumer validation) throws Throwable + { + testSplitAndRetry(batchCQL, validation, HINT); + } + + private void validateSingleTable(Cluster cluster) + { + SimpleQueryResult expected = QueryResults.builder() + .columns("id", "c", "v") + .row(PKEY_NORMAL, 1, 1) + .row(PKEY_ACCORD, 1, 1) + .build(); + cluster.forEach(instance -> { + assertThat(instance.executeInternalWithResult("SELECT * FROM " + qualifiedAccordTableName)).isEqualTo(expected); + }); + } + + private Consumer validateTwoTable(int secondPkey) + { + return cluster -> { + SimpleQueryResult expectedAccord = QueryResults.builder() + .columns("id", "c", "v") + .row(PKEY_ACCORD, 1, 1) + .build(); + cluster.forEach(instance -> assertThat(instance.executeInternalWithResult("SELECT * FROM " + qualifiedAccordTableName)).isEqualTo(expectedAccord)); + + SimpleQueryResult expectedNormal = QueryResults.builder() + .columns("id", "c", "v") + .row(secondPkey, 1, 1) + .build(); + cluster.forEach(instance -> assertThat(instance.executeInternalWithResult("SELECT * FROM " + qualifiedRegularTableName)).isEqualTo(expectedNormal)); + }; + } + + /* + * Test if the coordinator is behind that the request can be re-split and routed to the correct systems + * without surfacing an error + */ + private void testSplitAndRetry(String batchCQL, Consumer validation, Scenario scenario) throws Throwable + { + test(createTables(TABLE_FMT, qualifiedRegularTableName, qualifiedAccordTableName), + cluster -> { + // Only enable these when testing it works from a specific instance + forEach(() -> BatchlogManager.instance.pauseReplay()); + forEach(() -> HintsService.instance.pauseDispatch()); + + // Node 3 is always the out of sync node + IInvokableInstance outOfSyncInstance = setUpOutOfSyncNode(cluster); + + // Force the batchlog Accord txn to run after this write txn in the new epoch where it + // will trigger RetryDifferentSystem + if (scenario == BATCHLOG_FAILED_ROUTING_THEN_HINT && migrateAwayFromAccord) + writeAccordRowViaAccord(); + + // Need to be able to block writing to the test keyspace forcing batchlog replay + // without also failing writes to the batch log + if (scenario.initiallyBlockTestKeyspaceMutations) + cluster.filters().outbound().messagesMatching((from, to, message) -> { + if (message.verb() == Verb.MUTATION_REQ.id) + { + String keyspace = cluster.get(to).callsOnInstance(() -> ((Message)Instance.deserializeMessage(message)).payload.getKeyspaceName()).call(); + if (keyspace.equals(KEYSPACE)) + return true; + } + if (message.verb() == Verb.ACCORD_PRE_ACCEPT_REQ.id && !migrateAwayFromAccord) + { + boolean drop = cluster.get(to).callsOnInstance(() -> { + PreAccept preAccept = (PreAccept)Instance.deserializeMessage(message).payload; + PartialRoute route = preAccept.scope; + if (route.domain() == Domain.Key) + for (RoutingKey key : (PartialKeyRoute)route) + { + AccordRoutingKey routingKey = (AccordRoutingKey)key; + ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(routingKey.table()); + if (cfs.getKeyspaceName().equals(KEYSPACE)) + return true; + } + return false; + }).call(); + if (drop) + return true; + } + return false; + }).drop(); + + forEach(() -> BatchlogManager.instance.pauseReplay()); + + // If testing batch log delivery the coordinator needs to be a node other than the node that is behind on + // topology updates so that the batch log writes (and thus replay) can be done on the node that is out of sync + int coordinatorIndex = scenario.initiallyEnableBatchlogReplay ? 2 : 3; + IInvokableInstance instance = cluster.get(coordinatorIndex); + ICoordinator coordinator = instance.coordinator(); + int startRetryCount = getRetryOnDifferentSystemCount(coordinatorIndex); + // If testing routing at mutation coordination then Node 1 and 2 will both rejected the mutation because it is in a migrating range + int startRejectedCount = getMutationsRejectedOnWrongSystemCount(); + logger.info("Executing batch insert"); + Future resultFuture = coordinator.asyncExecuteWithResult(batchCQL, ALL); + + // Testing either batch log delivery or hint delivery via batchlog + if (scenario.initiallyBlockTestKeyspaceMutations) + { + // Expect initial write failure + expectException(() -> { + try + { + return resultFuture.get(); + } + catch (ExecutionException e) + { + throw (Exception) e.getCause(); + } + }, WriteTimeoutException.class); + } + + if (scenario.passesThroughBatchlog) + { + // At this stage we want the batch log to fail because it misrouted the queries to the wrong system + // not because it timed out not getting a response. We only did that with mutations as a quick + // way to populate the batch log. Could almost as easily have constructed the mutation and put it + // in the batch log directly + if (scenario == Scenario.BATCHLOG_FAILED_ROUTING_THEN_HINT || scenario == BATCHLOG_SUCCESSFUL_ROUTING) + cluster.filters().reset(); + + // We only want the batch log to have access to the correct topology if we are testing its + // ability to handle misrouted things + if (scenario == Scenario.BATCHLOG_SUCCESSFUL_ROUTING) + unpauseEnactment(outOfSyncInstance); + + // Unfortunately the batch won't be replayed until some time has passed because the starting time + // for replay is the current time - timeout + // Don't wait here for the batchlog if we need to spin on the creation of the Accord transaction + // and then unpause to test Accord routing failure + boolean unpauseAfterBatchLogCreatesTransaction = migrateAwayFromAccord && scenario == BATCHLOG_FAILED_ROUTING_THEN_HINT; + if (!unpauseAfterBatchLogCreatesTransaction) + Thread.sleep(BatchlogManager.BATCHLOG_REPLAY_TIMEOUT + DatabaseDescriptor.getWriteRpcTimeout(TimeUnit.MILLISECONDS)); + messageSink.reset(); + + // Force batch log delivery (or hint delivery) on the node that was out of sync, but should be in sync once we unpause + // This demonstrates it can split the mutation correctly or forward it to hinting if it fails + outOfSyncInstance.runOnInstance(() -> runUnchecked(() -> { + // We don't want hints for any reason that might apply the mutation and make the test look like it succeeded + assertTrue(HintsService.instance.isDispatchPaused()); + // The failed write will have written hints + HintsService.instance.deleteAllHintsUnsafe(); + assertFalse(hasPendingHints()); + BatchlogManager.instance.resumeReplay(); + + // Unpausing needs to be done async because it waits for the batch log replay + Promise unpaused = new AsyncPromise<>(); + if (unpauseAfterBatchLogCreatesTransaction) + { + logger.info("Creating thread to unpause after batchlog creates Accord transaction"); + new Thread(() -> + { + try + { + // Unpause so it can route incorrectly instead of timing out waiting to fetch the epoch, need the transaction to be created first + // otherwise it will just be routed straight to non-Accord. + logger.info("Spinning waiting on a transaction"); + Util.spinUntilTrue(() -> !((AccordService)AccordService.instance()).node().coordinating().isEmpty(), 20); + logger.info("Foudn transaction, unpausing"); + TestChangeListener.instance.unpause(); + unpaused.trySuccess(null); + } + catch (Throwable t) + { + unpaused.tryFailure(t); + } + }).start(); + } + else + { + // Force replay so mosts tests don't have to wait + BatchlogManager.instance.forceBatchlogReplay(); + unpaused.trySuccess(null); + } + // Fetch errors + unpaused.get(); + // Ensure the batch log did or didn't create pending hints depending on the test scenario + spinAssertEquals(scenario == BATCHLOG_FAILED_TIMEOUT_THEN_HINT || scenario == BATCHLOG_FAILED_ROUTING_THEN_HINT, () -> hasPendingHints(), 20); + })); + } + + // Mutation successfully applied from the coordinator after retrying scenario + if (scenario == MUTATION) + { + // Don't want to mistakenly have hints applying the mutation + forEach(() -> assertTrue(HintsService.instance.isDispatchPaused())); + // Check for the error differently depending on what system should be seeing an error + if (migrateAwayFromAccord) + { + // Accord will block until we unpause enactment so to test the routing we wait until the transaction + // has started so the epoch it is created in is the old one + Util.spinUntilTrue(() -> outOfSyncInstance.callOnInstance(() -> !((AccordService)AccordService.instance()).node().coordinating().isEmpty()), 20); + logger.info("Accord node is now coordinating something"); + try + { + validation.accept(cluster); + throw new AssertionError("Expected validation to fail"); + } + catch (AssertionError e) + { + //ignored + } + } + else + { + Stopwatch sw = Stopwatch.createStarted(); + spinAssertEquals(startRejectedCount + 2, 10, () -> getMutationsRejectedOnWrongSystemCount() - startRejectedCount); + logger.info("Took {}ms to get mutations rejected on wrong system", sw.elapsed(TimeUnit.MILLISECONDS)); + } + + logger.info("Unpausing out of sync instance"); + // Testing regular mutation coordination retry loop let coordinator get up to date and retry + unpauseEnactment(outOfSyncInstance); + + try + { + resultFuture.get(); + } + catch (ExecutionException e) + { + // This is expected when inverting the migration + if (migrateAwayFromAccord && e.getCause() instanceof CoordinatorBehindException) + throw e; + throw e; + } + + if (!migrateAwayFromAccord) + { + int endRetryCount = getRetryOnDifferentSystemCount(coordinatorIndex); + int endRejectedCount = getMutationsRejectedOnWrongSystemCount(); + assertEquals(1, endRetryCount - startRetryCount); + // Expect only two nodes to reject since they enacted the new epoch + assertEquals(2, endRejectedCount - startRejectedCount); + } + } + + // Anything related to making sure hints are delivered goes here + if (scenario.deliversViaHint) + { + // Don't want to mistakenly have hints applying the mutation before we enable it on just one instance + forEach(() -> assertTrue(HintsService.instance.isDispatchPaused())); + // The filters wouldn't have been reset yet if they were needed to make the batchlog or original mutation time out + // Need to reset so Hints can use Accord txns + cluster.filters().reset(); + long startingAccordTimeouts = outOfSyncInstance.callOnInstance(() -> ClientRequestsMetricsHolder.accordWriteMetrics.timeouts.getCount()); + long startingAccordPreempted = outOfSyncInstance.callOnInstance(() -> ClientRequestsMetricsHolder.accordWriteMetrics.preempted.getCount()); + long startingAccordMigrationRejects = outOfSyncInstance.callOnInstance(() -> ClientRequestsMetricsHolder.accordWriteMetrics.accordMigrationRejects.getCount()); + long startingHintTimeouts = outOfSyncInstance.callOnInstance(() -> HintsServiceMetrics.hintsTimedOut.getCount()); + outOfSyncInstance.runOnInstance(() -> HintsService.instance.resumeDispatch()); + // The initial hinting attempt should fail, unless it's a batchlog routing failure in which + // case the coordinator has already caught up so the hint will succeed on the first try + // Can only really have this case for BATCHLOG_FAILED_TIMEOUT_THEN_HINT becuase Accord timeouts don't + // write hints so there is nothing to test + if (migrateAwayFromAccord && scenario == BATCHLOG_FAILED_TIMEOUT_THEN_HINT) + { + Callable test = () -> outOfSyncInstance.callOnInstance(() -> { + logger.info("startingAccordTimeouts {}, startingAccordPreempts {}, startingAccordMigrationRejects {}, startingHintTimeouts {}, accord timeouts {}, accordPreempts {}, accordMigrationRejects {}, hint timeouts {}", startingAccordTimeouts, startingAccordPreempted, startingAccordMigrationRejects, startingHintTimeouts, ClientRequestsMetricsHolder.accordWriteMetrics.timeouts.getCount(), ClientRequestsMetricsHolder.accordWriteMetrics.preempted.getCount(), ClientRequestsMetricsHolder.accordWriteMetrics.accordMigrationRejects.getCount(), HintsServiceMetrics.hintsTimedOut.getCount()); + AccordClientRequestMetrics accordMetrics = ClientRequestsMetricsHolder.accordWriteMetrics; + return accordMetrics.timeouts.getCount() >= (startingAccordTimeouts + 1) && HintsServiceMetrics.hintsTimedOut.getCount() >= (startingHintTimeouts + 1); + }); + Util.spinUntilTrue(test, 40); + } + else if (!migrateAwayFromAccord) + { + // Expect two retry on different system responses when migrating from Paxos to Accord, one from each + // node that knows it is on the wrong system + Util.spinUntilTrue(() -> messageSink.messages.stream().filter(p -> { + if (p.right.verb() != Verb.FAILURE_RSP.id) + return false; + if (!p.left.equals(outOfSyncInstance.broadcastAddress())) + return false; + RequestFailureReason reason = ((RequestFailure) Instance.deserializeMessage(p.right).payload).reason; + if (reason == RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM) + return true; + return false; + }).count() == 2, 20); + } + // After this hints should deliver and the final validation should succeed + // if we don't unpause enactment + unpauseEnactment(outOfSyncInstance); + } + + // Accord commit is async and might take a while, but the data should end up as expected + Util.spinUntilSuccess(() -> validation.accept(cluster)); + }); + } + + /* + * Set up 3 to be behind and unaware of the migration while 1 and 2 are aware + */ + private IInvokableInstance setUpOutOfSyncNode(Cluster cluster) throws Throwable + { + IInvokableInstance i1 = cluster.get(1); + IInvokableInstance i2 = cluster.get(2); + IInvokableInstance i3 = cluster.get(3); + alterTableTransactionalMode(TransactionalMode.full); + Epoch nextEpoch = getNextEpoch(i1); + // Node 3 will coordinate the query and not be aware that the migration has begun + Callable pausedBeforeEnacting = pauseBeforeEnacting(i3, nextEpoch); + // In batch log delivery cases i2 will be the coordinator and we need to be sure that it has enacted the latest epoch + Callable i2PausedAfterEnacting = pauseAfterEnacting(i2, nextEpoch); + + ListenableFuture result = nodetoolAsync(coordinator, "consensus_admin", "begin-migration", "-st", midToken.toString(), "-et", maxToken.toString(), "-tp", "accord", KEYSPACE, accordTableName); + + if (migrateAwayFromAccord) + { + pausedBeforeEnacting.call(); + i2PausedAfterEnacting.call(); + unpauseEnactment(i2); + unpauseEnactment(i3); + result.get(); + long migratingEpoch = nextEpoch.getEpoch(); + Util.spinUntilTrue(() -> cluster.stream().allMatch(instance -> instance.callOnInstance(() -> ClusterMetadata.current().epoch.equals(Epoch.create(migratingEpoch)))), 10); + nextEpoch = getNextEpoch(i1); + pausedBeforeEnacting = pauseBeforeEnacting(i3, nextEpoch); + i2PausedAfterEnacting = pauseAfterEnacting(i2, nextEpoch); + // In the reverse direction doing the alter automatically reverses the migratin without a need to call begin migration on any ranges + result = alterTableTransactionalModeAsync(TransactionalMode.off); + } + + // Wait for everyone to get to where they are supposed to be + try + { + pausedBeforeEnacting.call(); + } + catch (Throwable t) + { + if (result.isDone()) + { + try + { + result.get(); + } + catch (ExecutionException e) + { + t.addSuppressed(e); + throw t; + } + } + throw t; + } + i2PausedAfterEnacting.call(); + // Unpause on 1 and 2 where we want them aware of the migration + unpauseEnactment(i1); + unpauseEnactment(i2); + // nodetool should be able to complete now + result.get(); + return i3; + } + + private String twoTableBatchInsert(boolean logged, int pkey1, int pkey2, int value) + { + return batch(logged, + insertCQL(qualifiedAccordTableName, pkey1, value), + insertCQL(qualifiedRegularTableName, pkey2, value)); + } + + private String singleTableBatchInsert(boolean logged, int pkey1, int pkey2, int value) + { + return batch(logged, + insertCQL(qualifiedAccordTableName, pkey1, value), + insertCQL(qualifiedAccordTableName, pkey2, value)); + } + + private static String insertCQL(String qualifiedTableName, int pkey, int value) + { + return format("INSERT INTO %s ( id, c, v ) VALUES ( %d, %d, %d )", qualifiedTableName, pkey, CLUSTERING_VALUE, value); + } + + // Prevents the creation of transactions in an older epoch because later writes need to order after earlier + private void writeAccordRowViaAccord() + { + logger.info("Initiating Accord row write"); + SHARED_CLUSTER.coordinator(1).execute(insertCQL(qualifiedAccordTableName, PKEY_ACCORD, 99), ConsistencyLevel.QUORUM); + logger.info("Finished Accord row write"); + } +} diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationTest.java index 2eea1f5a39..73c15d99ce 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationTest.java @@ -21,7 +21,6 @@ package org.apache.cassandra.distributed.test.accord; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -52,14 +51,16 @@ import org.apache.cassandra.db.BufferDecoratedKey; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.DecoratedKey; +import org.apache.cassandra.db.Mutation; +import org.apache.cassandra.db.Mutation.SimpleBuilder; import org.apache.cassandra.db.SimpleBuilders.PartitionUpdateBuilder; import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.Murmur3Partitioner.LongToken; import org.apache.cassandra.dht.Range; import org.apache.cassandra.dht.Token; +import org.apache.cassandra.distributed.api.Feature; import org.apache.cassandra.distributed.api.ICoordinator; import org.apache.cassandra.distributed.api.IInvokableInstance; -import org.apache.cassandra.distributed.api.NodeToolResult; import org.apache.cassandra.distributed.api.Row; import org.apache.cassandra.distributed.api.SimpleQueryResult; import org.apache.cassandra.gms.EndpointState; @@ -97,8 +98,8 @@ import org.yaml.snakeyaml.Yaml; import static com.google.common.collect.ImmutableList.toImmutableList; import static java.lang.String.format; import static java.util.Collections.emptyList; +import static org.apache.cassandra.Util.dk; import static org.apache.cassandra.Util.spinUntilSuccess; -import static org.apache.cassandra.cql3.QueryProcessor.executeInternal; import static org.apache.cassandra.db.SystemKeyspace.CONSENSUS_MIGRATION_STATE; import static org.apache.cassandra.db.SystemKeyspace.PAXOS; import static org.apache.cassandra.dht.Range.normalize; @@ -160,7 +161,7 @@ public class AccordMigrationTest extends AccordTestBase ServerTestUtils.daemonInitialization(); // Otherwise repair complains if you don't specify a keyspace CassandraRelevantProperties.SYSTEM_TRACES_DEFAULT_RF.setInt(3); - AccordTestBase.setupCluster(builder -> builder.appendConfig(config -> config.set("paxos_variant", PaxosVariant.v2.name()) + AccordTestBase.setupCluster(builder -> builder.appendConfig(config -> config.with(Feature.NETWORK).set("paxos_variant", PaxosVariant.v2.name()) .set("accord.range_migration", "explicit")), 3); partitioner = FBUtilities.newPartitioner(SHARED_CLUSTER.get(1).callsOnInstance(() -> DatabaseDescriptor.getPartitioner().getClass().getSimpleName()).call()); StorageService.instance.setPartitionerUnsafe(partitioner); @@ -192,19 +193,6 @@ public class AccordMigrationTest extends AccordTestBase SHARED_CLUSTER.coordinators().forEach(coordinator -> coordinator.execute(format("TRUNCATE TABLE %s.%s", SYSTEM_KEYSPACE_NAME, PAXOS), ALL)); } - private static String nodetool(ICoordinator coordinator, String... commandAndArgs) - { - NodeToolResult nodetoolResult = coordinator.instance().nodetoolResult(commandAndArgs); - if (!nodetoolResult.getStdout().isEmpty()) - System.out.println(nodetoolResult.getStdout()); - if (!nodetoolResult.getStderr().isEmpty()) - System.err.println(nodetoolResult.getStderr()); - if (nodetoolResult.getError() != null) - fail("Failed nodetool " + Arrays.asList(commandAndArgs), nodetoolResult.getError()); - // TODO why does standard out end up in stderr in nodetool? - return nodetoolResult.getStdout(); - } - private static int getKeyBetweenTokens(Token left, Token right) { return getKeysBetweenTokens(left, right).next(); @@ -398,10 +386,10 @@ public class AccordMigrationTest extends AccordTestBase @Test public void testPaxosToAccordCAS() throws Exception { - test(format(TABLE_FMT, qualifiedTableName), + test(format(TABLE_FMT, qualifiedAccordTableName), cluster -> { List> expectedKeyMigrations = new ArrayList<>(); - String table = tableName; + String table = accordTableName; UUID tableUUID = cluster.get(1).callOnInstance(() -> ColumnFamilyStore.getIfExists(KEYSPACE, table).getTableId().asUUID()); cluster.forEach(node -> node.runOnInstance(() -> { TableMetadata tbl = Schema.instance.getTableMetadata(KEYSPACE, table); @@ -409,7 +397,7 @@ public class AccordMigrationTest extends AccordTestBase Assert.assertEquals(TransactionalMigrationFromMode.none, tbl.params.transactionalMigrationFrom); })); - cluster.schemaChange(format("ALTER TABLE %s.%s WITH transactional_mode='%s'", KEYSPACE, tableName, TransactionalMode.full)); + cluster.schemaChange(format("ALTER TABLE %s.%s WITH transactional_mode='%s'", KEYSPACE, accordTableName, TransactionalMode.full)); cluster.forEach(node -> node.runOnInstance(() -> { TableMetadata tbl = Schema.instance.getTableMetadata(KEYSPACE, table); @@ -417,11 +405,11 @@ public class AccordMigrationTest extends AccordTestBase Assert.assertEquals(TransactionalMigrationFromMode.off, tbl.params.transactionalMigrationFrom); })); - String casCQL = format(CAS_FMT, qualifiedTableName, CLUSTERING_VALUE); + String casCQL = format(CAS_FMT, qualifiedAccordTableName, CLUSTERING_VALUE); Consumer runCasNoApply = key -> assertRowEquals(cluster, new Object[]{false}, casCQL, key); Consumer runCasApplies = key -> assertRowEquals(cluster, new Object[]{true}, casCQL, key); Consumer runCasOnSecondNode = key -> assertEquals( "[applied]", cluster.coordinator(2).executeWithResult(casCQL, ANY, key).names().get(0)); - String tableName = qualifiedTableName.split("\\.")[1]; + String tableName = qualifiedAccordTableName.split("\\.")[1]; int migratingKey = getKeyBetweenTokens(midToken, maxToken); int notMigratingKey = getKeyBetweenTokens(minToken, midToken); Range migratingRange = new Range(midToken, maxToken); @@ -493,8 +481,15 @@ public class AccordMigrationTest extends AccordTestBase // Update inserted row so the condition can apply, if the condition check doesn't apply // then it won't get to propose/accept migratingKey = testingKeys.next(); - String query = "UPDATE " + qualifiedTableName + " SET v = 42 WHERE id = ? AND c = ?"; - Consumer makeCASApply = key -> cluster.forEach(instance -> instance.runOnInstance(() -> executeInternal(query, key, CLUSTERING_VALUE))); + String keyspace = KEYSPACE; + Integer clusteringValue = CLUSTERING_VALUE; + String mutationTableName = accordTableName; + Consumer makeCASApply = key -> cluster.forEach(instance -> instance.runOnInstance(() -> { + SimpleBuilder mutationBuilder = Mutation.simpleBuilder(keyspace, dk(key)).allowPotentialTransactionConflicts(); + mutationBuilder.update(mutationTableName).row(clusteringValue).add("v", 42); + Mutation m = mutationBuilder.build(); + m.applyUnsafe(); + })); makeCASApply.accept(migratingKey); // This will force the request to run on Paxos up to Accept @@ -552,13 +547,13 @@ public class AccordMigrationTest extends AccordTestBase @Test public void testPaxosToAccordSerialRead() throws Exception { - test(format(TABLE_FMT, qualifiedTableName), + test(format(TABLE_FMT, qualifiedAccordTableName), cluster -> { - String table = tableName; + String table = accordTableName; UUID tableUUID = cluster.get(1).callOnInstance(() -> ColumnFamilyStore.getIfExists(KEYSPACE, table).getTableId().asUUID()); List> expectedKeyMigrations = new ArrayList<>(); - cluster.schemaChange(format("ALTER TABLE %s.%s WITH transactional_mode='%s'", KEYSPACE, tableName, TransactionalMode.full)); - String readCQL = format("SELECT * FROM %s WHERE id = ? and c = %s", qualifiedTableName, CLUSTERING_VALUE); + cluster.schemaChange(format("ALTER TABLE %s.%s WITH transactional_mode='%s'", KEYSPACE, accordTableName, TransactionalMode.full)); + String readCQL = format("SELECT * FROM %s WHERE id = ? and c = %s", qualifiedAccordTableName, CLUSTERING_VALUE); Function runRead = key -> cluster.coordinator(1).execute(readCQL, SERIAL, key); Range migratingRange = new Range<>(new LongToken(Long.MIN_VALUE + 1), new LongToken(Long.MIN_VALUE)); List> migratingRanges = ImmutableList.of(migratingRange); @@ -566,15 +561,15 @@ public class AccordMigrationTest extends AccordTestBase assertTargetAccordRead(runRead, 1, key, expectedKeyMigrations, 0, 1, 0, 0, 0); // Mark wrap around range as migrating - nodetool(coordinator, "consensus_admin", "begin-migration", "-st", String.valueOf(Long.MIN_VALUE + 1), "-et", String.valueOf(Long.MIN_VALUE), "-tp", "accord", KEYSPACE, tableName); - assertMigrationState(tableName, ConsensusMigrationTarget.accord, emptyList(), migratingRanges, 1); + nodetool(coordinator, "consensus_admin", "begin-migration", "-st", String.valueOf(Long.MIN_VALUE + 1), "-et", String.valueOf(Long.MIN_VALUE), "-tp", "accord", KEYSPACE, accordTableName); + assertMigrationState(accordTableName, ConsensusMigrationTarget.accord, emptyList(), migratingRanges, 1); // Should run directly on accord, migrate the key, and perform a quorum read from Accord, Paxos repair will run prepare once addExpectedMigratedKey(expectedKeyMigrations, key, tableUUID); assertTargetAccordRead(runRead, 1, key, expectedKeyMigrations, 1, 1, 1, 0, 0); key++; // Should run up to accept with both nodes refusing to accept - savePromisedAndCommittedPaxosProposal(tableName, key); + savePromisedAndCommittedPaxosProposal(accordTableName, key); cluster.get(1).runOnInstance(() -> ConsensusRequestRouter.setInstance(new PaxosToAccordMigrationNotHappeningUpToBegin())); addExpectedMigratedKey(expectedKeyMigrations, key, tableUUID); assertTargetAccordRead(runRead, 1, key, expectedKeyMigrations, 1, 2, 1, 0, 1); @@ -582,11 +577,6 @@ public class AccordMigrationTest extends AccordTestBase }); } - private void alterTableTransactionalMode(TransactionalMode mode) - { - SHARED_CLUSTER.schemaChange(format("ALTER TABLE %s WITH %s", qualifiedTableName, mode.asCqlParam())); - } - private void assertTransactionalModes(String keyspace, String table, TransactionalMode mode, TransactionalMigrationFromMode migration) { forEach(() -> { @@ -598,17 +588,17 @@ public class AccordMigrationTest extends AccordTestBase private void assertTransactionalModes(TransactionalMode mode, TransactionalMigrationFromMode migration) { - assertTransactionalModes(KEYSPACE, tableName, mode, migration); + assertTransactionalModes(KEYSPACE, accordTableName, mode, migration); } @Test public void testAccordToPaxos() throws Exception { - test(format(TABLE_FMT, qualifiedTableName), + test(format(TABLE_FMT, qualifiedAccordTableName), cluster -> { - String casCQL = format(CAS_FMT, qualifiedTableName, CLUSTERING_VALUE); + String casCQL = format(CAS_FMT, qualifiedAccordTableName, CLUSTERING_VALUE); Consumer runCasNoApply = key -> assertRowEquals(cluster, new Object[]{false}, casCQL, key); - String tableName = qualifiedTableName.split("\\.")[1]; + String tableName = qualifiedAccordTableName.split("\\.")[1]; UUID tableUUID = cluster.get(1).callOnInstance(() -> ColumnFamilyStore.getIfExists(KEYSPACE, tableName).getTableId().asUUID()); alterTableTransactionalMode(TransactionalMode.mixed_reads); diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordTestBase.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordTestBase.java index 4b0a90b173..cfec7f44e9 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordTestBase.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordTestBase.java @@ -24,17 +24,17 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.stream.StreamSupport; -import accord.coordinate.Invalidated; import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; import com.google.common.primitives.Ints; -import org.apache.cassandra.schema.Schema; -import org.apache.cassandra.schema.TableMetadata; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; @@ -42,15 +42,25 @@ import org.junit.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import accord.api.RoutingKey; +import accord.coordinate.Invalidated; import accord.impl.SimpleProgressLog; +import accord.messages.PreAccept; +import accord.primitives.PartialKeyRoute; +import accord.primitives.PartialRoute; +import accord.primitives.Routable.Domain; import net.bytebuddy.ByteBuddy; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.bind.annotation.SuperCall; import net.bytebuddy.implementation.bind.annotation.This; +import org.apache.cassandra.batchlog.BatchlogManager; import org.apache.cassandra.cql3.statements.ModificationStatement; import org.apache.cassandra.cql3.statements.TransactionStatement; import org.apache.cassandra.cql3.transactions.ReferenceValue; +import org.apache.cassandra.db.ColumnFamilyStore; +import org.apache.cassandra.db.Mutation; +import org.apache.cassandra.db.SystemKeyspace; import org.apache.cassandra.dht.Murmur3Partitioner; import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.Cluster.Builder; @@ -60,24 +70,37 @@ import org.apache.cassandra.distributed.api.IInvokableInstance; import org.apache.cassandra.distributed.api.IIsolatedExecutor.SerializableRunnable; import org.apache.cassandra.distributed.api.QueryResults; import org.apache.cassandra.distributed.api.SimpleQueryResult; +import org.apache.cassandra.distributed.impl.Instance; import org.apache.cassandra.distributed.shared.AssertUtils; import org.apache.cassandra.distributed.shared.Metrics; import org.apache.cassandra.distributed.test.TestBaseImpl; import org.apache.cassandra.distributed.util.QueryResultUtil; +import org.apache.cassandra.hints.HintsService; import org.apache.cassandra.io.util.DataInputBuffer; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputBuffer; +import org.apache.cassandra.net.Message; +import org.apache.cassandra.net.Verb; +import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.accord.AccordService; import org.apache.cassandra.service.accord.AccordTestUtils; +import org.apache.cassandra.service.accord.api.AccordRoutingKey; import org.apache.cassandra.service.accord.exceptions.ReadPreemptedException; import org.apache.cassandra.service.accord.exceptions.WritePreemptedException; +import org.apache.cassandra.service.consensus.TransactionalMode; import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState; import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.serialization.Version; import org.apache.cassandra.utils.AssertionUtils; import org.apache.cassandra.utils.FailingConsumer; +import static java.lang.String.format; import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.cassandra.db.SystemKeyspace.CONSENSUS_MIGRATION_STATE; +import static org.apache.cassandra.db.SystemKeyspace.PAXOS; +import static org.apache.cassandra.distributed.api.ConsistencyLevel.ALL; +import static org.apache.cassandra.schema.SchemaConstants.SYSTEM_KEYSPACE_NAME; import static org.junit.Assert.assertArrayEquals; public abstract class AccordTestBase extends TestBaseImpl @@ -89,8 +112,10 @@ public abstract class AccordTestBase extends TestBaseImpl protected static Cluster SHARED_CLUSTER; - protected String tableName; - protected String qualifiedTableName; + protected String accordTableName; + protected String qualifiedAccordTableName; + protected String regularTableName; + protected String qualifiedRegularTableName; public static void setupCluster(Function options, int nodes) throws IOException { @@ -107,8 +132,10 @@ public abstract class AccordTestBase extends TestBaseImpl @Before public void setup() { - tableName = "tbl" + COUNTER.getAndIncrement(); - qualifiedTableName = KEYSPACE + '.' + tableName; + accordTableName = "accordtbl" + COUNTER.getAndIncrement(); + qualifiedAccordTableName = KEYSPACE + '.' + accordTableName; + regularTableName = "regulartbl" + COUNTER.getAndIncrement(); + qualifiedRegularTableName = KEYSPACE + '.' + regularTableName; } @After @@ -135,6 +162,14 @@ public abstract class AccordTestBase extends TestBaseImpl test(Collections.singletonList(tableDDL), fn); } + protected List createTables(String tableFormat, String... qualifiedTables) + { + ImmutableList.Builder builder = ImmutableList.builder(); + for (String qualifiedTable : qualifiedTables) + builder.add(format(tableFormat, qualifiedTable)); + return builder.build(); + } + public static void ensureTableIsAccordManaged(Cluster cluster, String ksname, String tableName) { cluster.get(1).runOnInstance(() -> { @@ -165,7 +200,7 @@ public abstract class AccordTestBase extends TestBaseImpl protected void test(FailingConsumer fn) throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'", fn); + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'", fn); } protected static ConsensusMigrationState getMigrationStateSnapshot(IInvokableInstance instance) throws IOException @@ -198,6 +233,19 @@ public abstract class AccordTestBase extends TestBaseImpl return Ints.checkedCast(getMetrics(coordinatorIndex).getCounter("org.apache.cassandra.metrics.ClientRequest.Latency.CASWrite")); } + protected static int getRetryOnDifferentSystemCount(int coordinatorIndex) + { + return Ints.checkedCast(getMetrics(coordinatorIndex).getCounter("org.apache.cassandra.metrics.ClientRequest.RetryDifferentSystem.Write")); + } + + protected int getMutationsRejectedOnWrongSystemCount() + { + long sum = 0; + for (IInvokableInstance instance : SHARED_CLUSTER) + sum += instance.metrics().getCounter("org.apache.cassandra.metrics.Table.MutationsRejectedOnWrongSystem." + qualifiedAccordTableName); + return Ints.checkedCast(sum); + } + protected static int getCasPrepareCount(int coordinatorIndex) { return Ints.checkedCast(getMetrics(coordinatorIndex).getCounter("org.apache.cassandra.metrics.keyspace.CasPrepareLatency.distributed_test_keyspace")); @@ -279,7 +327,7 @@ public abstract class AccordTestBase extends TestBaseImpl // disable vnode for now, but should enable before trunk Cluster.Builder builder = Cluster.build(nodes) .withoutVNodes() - .withConfig(c -> c.with(Feature.NETWORK, Feature.GOSSIP).set("write_request_timeout", "10s") + .withConfig(c -> c.with(Feature.GOSSIP).set("write_request_timeout", "10s") .set("transaction_timeout", "15s") .set("transaction_timeout", "15s")) .withInstanceInitializer(EnforceUpdateDoesNotPerformRead::install); @@ -464,4 +512,86 @@ public abstract class AccordTestBase extends TestBaseImpl } protected abstract Logger logger(); + + protected void alterTableTransactionalMode(TransactionalMode mode) + { + SHARED_CLUSTER.schemaChange(format("ALTER TABLE %s WITH %s", qualifiedAccordTableName, mode.asCqlParam())); + } + + protected static void pauseHints() + { + forEach(() -> HintsService.instance.pauseDispatch()); + } + + protected static void deleteAllHints() + { + forEach(() -> HintsService.instance.deleteAllHintsUnsafe()); + } + + protected static void pauseBatchlog() + { + forEach(() -> BatchlogManager.instance.pauseReplay()); + } + + protected static void unpauseHints() + { + forEach(() -> HintsService.instance.resumeDispatch()); + } + + protected static void unpauseBatchlog() + { + forEach(() -> BatchlogManager.instance.resumeReplay()); + } + + protected static void blockMutationAndPreAccept(Cluster cluster) + { + cluster.filters().outbound().messagesMatching((from, to, message) -> { + if (message.verb() == Verb.MUTATION_REQ.id) + { + String keyspace = cluster.get(to).callsOnInstance(() -> ((Message) Instance.deserializeMessage(message)).payload.getKeyspaceName()).call(); + if (keyspace.equals(KEYSPACE)) + return true; + } + if (message.verb() == Verb.ACCORD_PRE_ACCEPT_REQ.id) + { + boolean drop = cluster.get(to).callsOnInstance(() -> { + PreAccept preAccept = (PreAccept)Instance.deserializeMessage(message).payload; + PartialRoute route = preAccept.scope; + if (route.domain() == Domain.Key) + for (RoutingKey key : (PartialKeyRoute)route) + { + AccordRoutingKey routingKey = (AccordRoutingKey)key; + ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(routingKey.table()); + if (cfs.getKeyspaceName().equals(KEYSPACE)) + return true; + } + return false; + }).call(); + if (drop) + return true; + } + return false; + }).drop(); + } + + protected static void truncateSystemTables() + { + SHARED_CLUSTER.coordinator(1).execute("TRUNCATE " + SYSTEM_KEYSPACE_NAME + "." + SystemKeyspace.BATCHES, ALL); + SHARED_CLUSTER.coordinator(1).execute(format("TRUNCATE TABLE %s.%s", SYSTEM_KEYSPACE_NAME, CONSENSUS_MIGRATION_STATE), ALL); + SHARED_CLUSTER.coordinator(1).execute(format("TRUNCATE TABLE %s.%s", SYSTEM_KEYSPACE_NAME, PAXOS), ALL); + } + + protected static Stream hostIds() + { + return Stream.concat(ClusterMetadata.current().directory.peerIds() + .stream() + .map(ClusterMetadata.current().directory::hostId), + Stream.of(HintsService.RETRY_ON_DIFFERENT_SYSTEM_UUID)); + } + + protected static boolean hasPendingHints() + { + return hostIds().map(HintsService.instance::getTotalHintsSize) + .anyMatch(size -> size > 0); + } } diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordTimestampPreservationTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordTimestampPreservationTest.java new file mode 100644 index 0000000000..353c591c6d --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordTimestampPreservationTest.java @@ -0,0 +1,209 @@ +/* + * 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.distributed.test.accord; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import com.google.common.collect.ImmutableList; +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.ServerTestUtils; +import org.apache.cassandra.config.CassandraRelevantProperties; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.db.ConsistencyLevel; +import org.apache.cassandra.db.DecoratedKey; +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.db.Mutation; +import org.apache.cassandra.db.SimpleBuilders.MutationBuilder; +import org.apache.cassandra.db.marshal.Int32Type; +import org.apache.cassandra.distributed.api.ICoordinator; +import org.apache.cassandra.distributed.api.IInvokableInstance; +import org.apache.cassandra.exceptions.WriteTimeoutException; +import org.apache.cassandra.locator.Replica; +import org.apache.cassandra.locator.ReplicaPlan; +import org.apache.cassandra.locator.ReplicaPlans; +import org.apache.cassandra.service.StorageProxy; +import org.apache.cassandra.service.consensus.migration.ConsensusKeyMigrationState; +import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter; + +import static java.lang.String.format; +import static org.apache.cassandra.Util.spinAssertEquals; +import static org.apache.cassandra.config.CassandraRelevantProperties.HINT_DISPATCH_INTERVAL_MS; +import static org.apache.cassandra.distributed.api.ConsistencyLevel.ALL; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/* + * Test that non-transactional updates have their timestamps preserved when written through Accord so that + * `USING TIMESTAMP` continues to work and so that hints and batch log retry attempts are inserted with their + * original timestamp and not a later Accord timestamp which could cause data resurrection. + */ +public class AccordTimestampPreservationTest extends AccordTestBase +{ + private static final Logger logger = LoggerFactory.getLogger(AccordTimestampPreservationTest.class); + + private static final int CLUSTERING_VALUE = 1; + + private static final String NORMAL_TABLE_FMT = "CREATE TABLE %s (id int, c int, v int, PRIMARY KEY ((id), c))"; + + private static final String ACCORD_TABLE_FMT = NORMAL_TABLE_FMT + " WITH transactional_mode='full'"; + + private static ICoordinator coordinator; + + private static final String expectedResult = "[[42]]"; + + private static final int PKEY1 = 77; + private static final int PKEY2 = 78; + private static final int VALUE = 66; + + private static final long TIMESTAMP = 42; + + @Override + protected Logger logger() + { + return logger; + } + + @BeforeClass + public static void setupClass() throws IOException + { + HINT_DISPATCH_INTERVAL_MS.setLong(100); + ServerTestUtils.daemonInitialization(); + // Otherwise repair complains if you don't specify a keyspace + CassandraRelevantProperties.SYSTEM_TRACES_DEFAULT_RF.setInt(3); + AccordTestBase.setupCluster(builder -> builder.appendConfig(config -> config.set("write_request_timeout", "2s")), 3); + ServerTestUtils.prepareServerNoRegister(); + coordinator = SHARED_CLUSTER.coordinator(1); + } + + @After + public void tearDown() throws Exception + { + unpauseBatchlog(); + deleteAllHints(); + unpauseHints(); + super.tearDown(); + // Reset migration state + forEach(() -> { + ConsensusRequestRouter.resetInstance(); + ConsensusKeyMigrationState.reset(); + }); + truncateSystemTables(); + } + + @Test + public void testMutationPreservesTimestamp() throws Exception + { + test(createTables(ACCORD_TABLE_FMT, qualifiedAccordTableName), cluster -> { + long startCount = getAccordCoordinateCount(); + coordinator.executeWithResult(insertCQL(qualifiedAccordTableName, PKEY1, VALUE), ALL); + assertEquals(startCount + 1, getAccordWriteCount()); + int id = 1; + for (IInvokableInstance instance : cluster) + { + logger.info("Checking instance " + id); + id++; + spinAssertEquals(expectedResult, () -> instance.executeInternalWithResult(checkCQL()).toString(), 20); + } + }); + } + + @Test + public void testBatchlogPreservesTimestamp() throws Exception + { + test(ImmutableList.of(format(NORMAL_TABLE_FMT, qualifiedRegularTableName), format(ACCORD_TABLE_FMT, qualifiedAccordTableName)), cluster -> { + pauseHints(); + blockMutationAndPreAccept(cluster); + try + { + // Insert must span both Accord and non-Accord ranges or tables otherwise it bypasses the batchlog entirely + coordinator.executeWithResult(batchInsert(true, PKEY1, PKEY2, VALUE), ALL); + fail("Should have thrown WTE"); + } + catch (Throwable t) + { + assertEquals(t.getClass().getName(), WriteTimeoutException.class.getName()); + } + cluster.filters().reset(); + + int id = 1; + for (IInvokableInstance instance : cluster) + { + logger.info("Checking instance " + id); + id++; + spinAssertEquals(expectedResult, () -> instance.executeInternalWithResult(checkCQL()).toString(), 20); + } + }); + } + + @Test + public void testHintsPreservesTimestamp() throws Exception + { + test(createTables(ACCORD_TABLE_FMT, qualifiedAccordTableName), cluster -> { + String keyspace = KEYSPACE; + int pkey1 = PKEY1; + long timestamp = TIMESTAMP; + int clustering = CLUSTERING_VALUE; + String tableName = accordTableName; + cluster.get(1).runOnInstance(() -> { + ByteBuffer keyBuf = Int32Type.instance.fromString(Integer.toString(pkey1)); + DecoratedKey dk = DatabaseDescriptor.getPartitioner().decorateKey(keyBuf); + MutationBuilder mutationBuilder = new MutationBuilder(KEYSPACE, dk); + mutationBuilder.timestamp(timestamp); + mutationBuilder.update(tableName).row(clustering).add("v", VALUE); + Mutation m = mutationBuilder.build(); + ReplicaPlan.ForWrite plan = ReplicaPlans.forWrite(Keyspace.open(keyspace), ConsistencyLevel.ALL, dk.getToken(), ReplicaPlans.writeAll); + for (Replica replica : plan.live().withoutSelf()) + StorageProxy.submitHint(m, replica, null); + }); + for (int i = 2; i <= 3; i++) + { + int instance = i; + spinAssertEquals(expectedResult, () -> cluster.get(instance).executeInternalWithResult(checkCQL()).toString(), 20); + } + }); + } + + private String batchInsert(boolean logged, int pkey1, int pkey2, int value) + { + return batch(logged, + insertCQL(qualifiedAccordTableName, pkey1, value), + insertCQL(qualifiedRegularTableName, pkey2, value)); + } + + private String insertCQL(String table, int pkey, int value) + { + return insertCQL(table, pkey, value, false); + } + + private String insertCQL(String table, int pkey, int value, boolean cas) + { + return format("INSERT INTO %s ( id, c, v ) VALUES ( %d, %d, %d )%s USING TIMESTAMP %d", table, pkey, CLUSTERING_VALUE, value, cas ? " IF NOT EXISTS" : "", TIMESTAMP); + } + + private String checkCQL() + { + return format("SELECT WRITETIME(v) from %s WHERE id = %d", qualifiedAccordTableName, PKEY1); + } +} diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/MigrationFromAccordRaceTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/MigrationFromAccordRaceTest.java new file mode 100644 index 0000000000..985bdc7dd9 --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/MigrationFromAccordRaceTest.java @@ -0,0 +1,27 @@ +/* + * 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.distributed.test.accord; + +public class MigrationFromAccordRaceTest extends AccordMigrationRaceTestBase +{ + protected boolean migratingAwayFromAccord() + { + return true; + } +} diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/MigrationToAccordRaceTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/MigrationToAccordRaceTest.java new file mode 100644 index 0000000000..aa00d9564b --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/MigrationToAccordRaceTest.java @@ -0,0 +1,27 @@ +/* + * 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.distributed.test.accord; + +public class MigrationToAccordRaceTest extends AccordMigrationRaceTestBase +{ + protected boolean migratingAwayFromAccord() + { + return false; + } +} diff --git a/test/distributed/org/apache/cassandra/distributed/test/metrics/HintsServiceMetricsTest.java b/test/distributed/org/apache/cassandra/distributed/test/metrics/HintsServiceMetricsTest.java index 0fc9bff174..d834919cb8 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/metrics/HintsServiceMetricsTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/metrics/HintsServiceMetricsTest.java @@ -142,6 +142,7 @@ public class HintsServiceMetricsTest extends TestBaseImpl assertThat(countHintsSucceeded(node)).isEqualTo(0); assertThat(countHintsFailed(node)).isEqualTo(0); assertThat(countHintsTimedOut(node)).isEqualTo(0); + assertThat(countHintsRetryDifferentSystem(node)).isEqualTo(0); assertThat(countGlobalDelays(node)).isEqualTo(0); cluster.forEach(target -> assertThat(countEndpointDelays(node, target)).isEqualTo(0)); } @@ -180,6 +181,12 @@ public class HintsServiceMetricsTest extends TestBaseImpl return node.callOnInstance(() -> HintsServiceMetrics.hintsTimedOut.getCount()); } + @SuppressWarnings("Convert2MethodRef") + private static Long countHintsRetryDifferentSystem(IInvokableInstance node) + { + return node.callOnInstance(() -> HintsServiceMetrics.hintsRetryDifferentSystem.getCount()); + } + private static Long countGlobalDelays(IInvokableInstance node) { return getHistogramCount(node, "org.apache.cassandra.metrics.HintsService.Hint_delays"); diff --git a/test/simulator/test/org/apache/cassandra/simulator/test/ShortPaxosSimulationTest.java b/test/simulator/test/org/apache/cassandra/simulator/test/ShortPaxosSimulationTest.java index 431eb7e454..3dfbe1116b 100644 --- a/test/simulator/test/org/apache/cassandra/simulator/test/ShortPaxosSimulationTest.java +++ b/test/simulator/test/org/apache/cassandra/simulator/test/ShortPaxosSimulationTest.java @@ -105,11 +105,11 @@ public class ShortPaxosSimulationTest public void casOnAccordSimulationTest() throws IOException { PaxosSimulationRunner.main(new String[] { "run", - "--lwt-strategy", "migration", + "--lwt-strategy", "mixed_reads", "-n", "3...6", "-t", "1000", "--cluster-action-limit", "0", - "--consensus-action-limit", "-1", + "--consensus-action-limit", "0", "--consensus-actions", "ACCORD_MIGRATE", "-c", "10", "-s", "30"}); diff --git a/test/unit/org/apache/cassandra/Util.java b/test/unit/org/apache/cassandra/Util.java index 74cf51de92..7b4ab79a9f 100644 --- a/test/unit/org/apache/cassandra/Util.java +++ b/test/unit/org/apache/cassandra/Util.java @@ -110,11 +110,11 @@ import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.RandomPartitioner.BigIntegerToken; import org.apache.cassandra.dht.Range; import org.apache.cassandra.dht.Token; -import org.apache.cassandra.index.internal.CassandraIndex; import org.apache.cassandra.distributed.test.log.ClusterMetadataTestHelper; import org.apache.cassandra.gms.ApplicationState; import org.apache.cassandra.gms.Gossiper; import org.apache.cassandra.gms.VersionedValue; +import org.apache.cassandra.index.internal.CassandraIndex; import org.apache.cassandra.io.sstable.Descriptor; import org.apache.cassandra.io.sstable.SSTableId; import org.apache.cassandra.io.sstable.SSTableLoader; @@ -152,10 +152,10 @@ import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FilterFactory; import org.apache.cassandra.utils.OutputHandler; import org.awaitility.Awaitility; +import org.awaitility.core.ThrowingRunnable; import org.hamcrest.Matcher; import org.mockito.Mockito; import org.mockito.internal.stubbing.defaultanswers.ForwardsInvocations; -import org.awaitility.core.ThrowingRunnable; import static com.google.common.base.Preconditions.checkState; import static org.hamcrest.MatcherAssert.assertThat; @@ -179,6 +179,11 @@ public class Util return testPartitioner().decorateKey(ByteBufferUtil.bytes(key)); } + public static DecoratedKey dk(int key) + { + return dk(String.valueOf(key), Int32Type.instance); + } + public static DecoratedKey dk(String key, AbstractType type) { return testPartitioner().decorateKey(type.fromString(key)); @@ -375,7 +380,8 @@ public class Util } catch (Throwable e) { - assert e.getClass().equals(exception) : e.getClass().getName() + " is not " + exception.getName(); + // Use name because in-jvm dtests will have different instances of the class + assert e.getClass().getName().equals(exception.getName()) : e.getClass().getName() + " is not " + exception.getName(); thrown = true; } @@ -752,6 +758,21 @@ public class Util .untilAsserted(() -> assertThat(message, call.call(), equalTo(expected))); } + public static void spinUntilTrue(Callable test, long timeoutInSeconds) + { + spinUntilTrue(test, timeoutInSeconds, TimeUnit.SECONDS); + } + + public static void spinUntilTrue(Callable test, long timeout, TimeUnit unit) + { + Awaitility.await() + .pollInterval(Duration.ofMillis(100)) + .pollDelay(0, TimeUnit.MILLISECONDS) + .atMost(timeout, unit) + .ignoreExceptions() + .untilAsserted(() -> assertThat(test.call(), equalTo(true))); + } + public static void spinUntilSuccess(ThrowingRunnable runnable) { spinUntilSuccess(runnable, 10); diff --git a/test/unit/org/apache/cassandra/hints/HintsServiceTest.java b/test/unit/org/apache/cassandra/hints/HintsServiceTest.java index 1e12d3911e..eb94473c29 100644 --- a/test/unit/org/apache/cassandra/hints/HintsServiceTest.java +++ b/test/unit/org/apache/cassandra/hints/HintsServiceTest.java @@ -20,6 +20,7 @@ package org.apache.cassandra.hints; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.Nullable; import com.google.common.util.concurrent.Futures; @@ -32,6 +33,12 @@ import org.junit.Test; import com.datastax.driver.core.utils.MoreFutures; import org.apache.cassandra.SchemaLoader; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.db.ColumnFamilyStore; +import org.apache.cassandra.db.ConsistencyLevel; +import org.apache.cassandra.db.IMutation; +import org.apache.cassandra.db.Mutation; +import org.apache.cassandra.metrics.HintsServiceMetrics; import org.apache.cassandra.metrics.StorageMetrics; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.MockMessagingService; @@ -40,11 +47,24 @@ import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.StorageService; - +import org.apache.cassandra.service.accord.AccordTestUtils; +import org.apache.cassandra.service.accord.IAccordService.AsyncTxnResult; +import org.apache.cassandra.service.accord.txn.TxnData; +import org.apache.cassandra.service.consensus.migration.ConsensusMigrationMutationHelper; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.MockFailureDetector; + +import static org.apache.cassandra.Util.spinAssertEquals; +import static org.apache.cassandra.config.CassandraRelevantProperties.HINT_DISPATCH_INTERVAL_MS; import static org.apache.cassandra.hints.HintsTestUtil.sendHintsAndResponses; +import static org.apache.cassandra.hints.HintsTestUtil.sendHintsWithRetryDifferentSystemUUID; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; public class HintsServiceTest { @@ -63,12 +83,15 @@ public class HintsServiceTest KeyspaceParams.simple(1), SchemaLoader.standardCFMD(KEYSPACE, TABLE)); metadata = Schema.instance.getTableMetadata(KEYSPACE, TABLE); + HINT_DISPATCH_INTERVAL_MS.setLong(100); + DatabaseDescriptor.setHintsFlushPeriodInMS(100); } @After public void cleanup() { MockMessagingService.cleanup(); + ConsensusMigrationMutationHelper.resetInstanceForTest(); } @Before @@ -173,4 +196,57 @@ public class HintsServiceTest assertTrue(dispatchOffset != null); assertTrue(((ChecksummedDataInput.Position) dispatchOffset).sourcePosition > 0); } + + /* + * Make sure that if hints from the batchlog end up needing to be executed without Accord + * that they are turned into + */ + @Test + public void testHintsNeedingRehinting() throws Throwable + { + ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(metadata.id); + long startWrites = cfs.metric.writeLatency.latency.getCount(); + HintsService.instance = spy(HintsService.instance); + AtomicInteger accordTxnCount = new AtomicInteger(); + ConsensusMigrationMutationHelper.replaceInstanceForTest( + new ConsensusMigrationMutationHelper() + { + int count = 0; + + @Override + public SplitMutation splitMutationIntoAccordAndNormal(T mutation, ClusterMetadata cm) + { + if (count > 2) + return super.splitMutationIntoAccordAndNormal(mutation, cm); + + SplitMutation split; + if (count % 2 == 0) + split = new SplitMutation(mutation, null); + else + split = new SplitMutation<>(null, mutation); + count++; + return split; + } + + @Override + public AsyncTxnResult mutateWithAccordAsync(ClusterMetadata cm, Mutation mutation, @Nullable ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) + { + accordTxnCount.incrementAndGet(); + AsyncTxnResult asyncTxnResult = new AsyncTxnResult(AccordTestUtils.txnId(42, 43, 44)); + asyncTxnResult.setSuccess(new TxnData()); + return asyncTxnResult; + } + }); + sendHintsWithRetryDifferentSystemUUID(metadata); + // Two should be Accord transactions + spinAssertEquals(2, accordTxnCount::get, 10); + Thread.sleep(1000); + // An attempt should be made to write to all replicas + verify(HintsService.instance, times(1)).writeForAllReplicas(notNull()); + // And it should be written locally + spinAssertEquals(startWrites + 1L, cfs.metric.writeLatency.latency::getCount, 10); + + // Hints that are rehinted are treated as succeeding immediately for the ACCORD_HINT_ENDPOINT + assertEquals(3, HintsServiceMetrics.getDelayCount(HintsServiceMetrics.ACCORD_HINT_ENDPOINT)); + } } diff --git a/test/unit/org/apache/cassandra/hints/HintsTestUtil.java b/test/unit/org/apache/cassandra/hints/HintsTestUtil.java index 8ab65998c4..b3f3250481 100644 --- a/test/unit/org/apache/cassandra/hints/HintsTestUtil.java +++ b/test/unit/org/apache/cassandra/hints/HintsTestUtil.java @@ -32,12 +32,12 @@ import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.Clock; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.apache.cassandra.Util.dk; import static org.apache.cassandra.net.MockMessagingService.verb; import static org.apache.cassandra.net.Verb.HINT_REQ; import static org.apache.cassandra.net.Verb.HINT_RSP; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; final class HintsTestUtil { @@ -88,4 +88,20 @@ final class HintsTestUtil } return spy; } + + static void sendHintsWithRetryDifferentSystemUUID(TableMetadata metadata) + { + // create and write three hints, two that should be routed to Accord, and one should need rehinting since + // it doesn't end up routed to Accord + UUID hostId = HintsService.RETRY_ON_DIFFERENT_SYSTEM_UUID; + for (int i = 0; i < 3; i++) + { + long now = Clock.Global.currentTimeMillis(); + DecoratedKey dkey = dk(String.valueOf(i)); + PartitionUpdate.SimpleBuilder builder = PartitionUpdate.simpleBuilder(metadata, dkey).timestamp(now); + builder.row("column0").add("val", "value0"); + Hint hint = Hint.create(builder.buildAsMutation(), now); + HintsService.instance.write(hostId, hint); + } + } } diff --git a/test/unit/org/apache/cassandra/schema/TransactionalConfigSchemaTest.java b/test/unit/org/apache/cassandra/schema/TransactionalConfigSchemaTest.java index 59c03fc3c7..b9876e0eb8 100644 --- a/test/unit/org/apache/cassandra/schema/TransactionalConfigSchemaTest.java +++ b/test/unit/org/apache/cassandra/schema/TransactionalConfigSchemaTest.java @@ -92,4 +92,15 @@ public class TransactionalConfigSchemaTest table, TransactionalMode.off, TransactionalMigrationFromMode.none); assertTransactionalMode(table, TransactionalMode.off, TransactionalMigrationFromMode.none); } + + @Test + public void alterCanSkipMigration() + { + String table = "alter_skips_migration_table"; + process("CREATE TABLE ks.%s (k int primary key, v int)", table); + assertTransactionalMode(table, TransactionalMode.off, TransactionalMigrationFromMode.none); + + process("ALTER TABLE ks.%s WITH transactional_mode='%s' AND transactional_migration_from='%s'", table, TransactionalMode.full, TransactionalMigrationFromMode.none); + assertTransactionalMode(table, TransactionalMode.full, TransactionalMigrationFromMode.none); + } } diff --git a/test/unit/org/apache/cassandra/service/accord/AccordReadRepairTest.java b/test/unit/org/apache/cassandra/service/accord/AccordReadRepairTest.java index 901ed572cd..2711b20336 100644 --- a/test/unit/org/apache/cassandra/service/accord/AccordReadRepairTest.java +++ b/test/unit/org/apache/cassandra/service/accord/AccordReadRepairTest.java @@ -60,7 +60,7 @@ public class AccordReadRepairTest extends AccordTestBase @Test public void testSerialReadRepair() throws Exception { - testReadRepair(cluster -> cluster.coordinator(1).execute("SELECT * FROM " + qualifiedTableName + " WHERE k = 1 AND c = 1;", ConsistencyLevel.SERIAL), + testReadRepair(cluster -> cluster.coordinator(1).execute("SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1 AND c = 1;", ConsistencyLevel.SERIAL), new Object[][] {{1, 1, 1, 1}}); } @@ -68,7 +68,7 @@ public class AccordReadRepairTest extends AccordTestBase public void testCASFailedConditionReadRepair() throws Exception { // Even if the condition fails to apply the data checked when applying the condition should be repaired - testReadRepair(cluster -> cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v1) VALUES (1, 1, 99) IF NOT EXISTS;", ConsistencyLevel.SERIAL), + testReadRepair(cluster -> cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v1) VALUES (1, 1, 99) IF NOT EXISTS;", ConsistencyLevel.SERIAL), new Object[][] {{false, 1, 1, 1, 1}}); } @@ -76,7 +76,7 @@ public class AccordReadRepairTest extends AccordTestBase public void testCASReadRepair() throws Exception { // If the condition applies the read repair should preserve the existing timestamp - testReadRepair(cluster -> cluster.coordinator(1).execute("UPDATE " + qualifiedTableName + " SET v2 = 99 WHERE k = 1 and c = 1 IF EXISTS;", ConsistencyLevel.SERIAL), + testReadRepair(cluster -> cluster.coordinator(1).execute("UPDATE " + qualifiedAccordTableName + " SET v2 = 99 WHERE k = 1 and c = 1 IF EXISTS;", ConsistencyLevel.SERIAL), new Object[][] {{Boolean.TRUE}}); } @@ -88,20 +88,20 @@ public class AccordReadRepairTest extends AccordTestBase public void testNonSerialReadRepair() throws Exception { for (ConsistencyLevel cl : ImmutableList.of(ConsistencyLevel.QUORUM)) - testReadRepair(cluster -> cluster.coordinator(1).execute("SELECT * FROM " + qualifiedTableName + " WHERE k = 1 AND c = 1;", cl), + testReadRepair(cluster -> cluster.coordinator(1).execute("SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1 AND c = 1;", cl), new Object[][] {{1, 1, 1, 1}}); } void testReadRepair(Function accordTxn, Object[][] expected) throws Exception { - test("CREATE TABLE " + qualifiedTableName + " (k int, c int, v1 int, v2 int, PRIMARY KEY ((k), c)) WITH transactional_mode='unsafe_writes';", + test("CREATE TABLE " + qualifiedAccordTableName + " (k int, c int, v1 int, v2 int, PRIMARY KEY ((k), c)) WITH transactional_mode='unsafe_writes';", cluster -> { Filter mutationFilter = cluster.filters().verbs(Verb.MUTATION_REQ.id).to(2).drop().on(); cluster.filters().verbs(Verb.HINT_REQ.id, Verb.HINT_RSP.id).drop().on(); - cluster.coordinator(1).execute("INSERT INTO " + qualifiedTableName + " (k, c, v1, v2) VALUES (1, 1, 1, 1) USING TIMESTAMP 42;", ConsistencyLevel.ONE); + cluster.coordinator(1).execute("INSERT INTO " + qualifiedAccordTableName + " (k, c, v1, v2) VALUES (1, 1, 1, 1) USING TIMESTAMP 42;", ConsistencyLevel.ONE); mutationFilter.off(); Filter blockNodeOneReads = cluster.filters().verbs(Verb.READ_REQ.id).to(1).drop().on(); - assertThat(cluster.coordinator(2).executeWithResult("SELECT * FROM " + qualifiedTableName + " WHERE k = 1 AND c = 1;", ConsistencyLevel.ONE)) + assertThat(cluster.coordinator(2).executeWithResult("SELECT * FROM " + qualifiedAccordTableName + " WHERE k = 1 AND c = 1;", ConsistencyLevel.ONE)) .isEmpty(); blockNodeOneReads.off(); // Should perform read repair @@ -109,7 +109,7 @@ public class AccordReadRepairTest extends AccordTestBase assertRows(result, expected); blockNodeOneReads.on(); // Side effect of the read repair should be visible now - assertThat(cluster.coordinator(2).executeWithResult("SELECT k, c, v1, WRITETIME(v1) FROM " + qualifiedTableName + " WHERE k = 1 AND c = 1;", ConsistencyLevel.ONE)) + assertThat(cluster.coordinator(2).executeWithResult("SELECT k, c, v1, WRITETIME(v1) FROM " + qualifiedAccordTableName + " WHERE k = 1 AND c = 1;", ConsistencyLevel.ONE)) .isEqualTo(1, 1, 1, 42L); }); } diff --git a/test/unit/org/apache/cassandra/service/accord/AccordServiceTest.java b/test/unit/org/apache/cassandra/service/accord/AccordServiceTest.java index ea88d11977..3abdcf8080 100644 --- a/test/unit/org/apache/cassandra/service/accord/AccordServiceTest.java +++ b/test/unit/org/apache/cassandra/service/accord/AccordServiceTest.java @@ -22,13 +22,16 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.function.LongSupplier; +import java.util.function.Supplier; import org.junit.Test; import accord.coordinate.Exhausted; import accord.coordinate.Preempted; import accord.coordinate.Timeout; +import accord.impl.IntKey; +import accord.primitives.Ranges; +import accord.primitives.Seekables; import accord.primitives.TxnId; import org.apache.cassandra.utils.Blocking; import org.assertj.core.api.Condition; @@ -47,12 +50,12 @@ public class AccordServiceTest public void retryExpectedFailures() throws InterruptedException { Blocking blocking = Mockito.mock(Blocking.class); - class Task implements LongSupplier + class Task implements Supplier { private int attempts = 0; @Override - public long getAsLong() + public Seekables get() { switch (attempts) { @@ -75,12 +78,12 @@ public class AccordServiceTest attempts++; throw AccordService.newBarrierExhausted(TxnId.NONE, true); default: - return 42; + return Ranges.of(IntKey.range(1, 2)); } } } Task failing = new Task(); - assertThat(doWithRetries(blocking, failing, Integer.MAX_VALUE, 100, 1000)).isEqualTo(42); + assertThat(doWithRetries(blocking, failing, Integer.MAX_VALUE, 100, 1000)).isEqualTo(Ranges.of(IntKey.range(1,2))); verify(blocking).sleep(100); verify(blocking).sleep(200); verify(blocking).sleep(400); @@ -100,10 +103,10 @@ public class AccordServiceTest timeoutFailures.add(() -> {throw AccordService.newBarrierPreempted(TxnId.NONE, true);}); Collections.shuffle(timeoutFailures, rs.asJdkRandom()); Iterator it = timeoutFailures.iterator(); - LongSupplier failing = () -> { + Supplier failing = () -> { if (!it.hasNext()) throw new IllegalStateException("Called too many times"); it.next().run(); // this throws... - return 42; + return Ranges.EMPTY; }; assertThatThrownBy(() -> doWithRetries(blocking, failing, timeoutFailures.size(), 100, 1000)).is(new Condition<>(AccordService::isTimeout, "timeout")); assertThat(it).isExhausted(); @@ -123,10 +126,10 @@ public class AccordServiceTest timeoutFailures.add(() -> {throw new Exhausted(null, null);}); Collections.shuffle(timeoutFailures, rs.asJdkRandom()); Iterator it = timeoutFailures.iterator(); - LongSupplier failing = () -> { + Supplier failing = () -> { if (!it.hasNext()) throw new IllegalStateException("Called too many times"); it.next().run(); // this throws... - return 42; + return Ranges.EMPTY; }; assertThatThrownBy(() -> doWithRetries(blocking, failing, timeoutFailures.size(), 100, 1000)).isInstanceOf(Exhausted.class); assertThat(it).isExhausted(); @@ -172,10 +175,10 @@ public class AccordServiceTest } } Iterator it = failures.iterator(); - LongSupplier failing = () -> { + Supplier failing = () -> { if (!it.hasNext()) throw new IllegalStateException("Called too many times"); it.next().run(); // this throws... - return 42; + return Ranges.EMPTY; }; Blocking blocking = Mockito.mock(Blocking.class); assertThatThrownBy(() -> doWithRetries(blocking, failing, failures.size(), 100, 1000)).isInstanceOf(isError ? AssertionError.class : NullPointerException.class); diff --git a/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java b/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java index ffd0d31186..999ae87576 100644 --- a/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java +++ b/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java @@ -27,9 +27,9 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.LongSupplier; +import java.util.function.ToLongFunction; import java.util.stream.Collectors; import java.util.stream.IntStream; - import javax.annotation.Nullable; import com.google.common.collect.Sets; @@ -97,6 +97,7 @@ import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.accord.api.AccordAgent; import org.apache.cassandra.service.accord.api.PartitionKey; import org.apache.cassandra.service.accord.txn.TxnData; +import org.apache.cassandra.service.accord.txn.TxnQuery; import org.apache.cassandra.service.accord.txn.TxnRead; import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.concurrent.Condition; @@ -330,7 +331,7 @@ public class AccordTestUtils public static Txn createTxn(Txn.Kind kind, Seekables seekables) { - return AGENT.emptyTxn(kind, seekables); + return new Txn.InMemory(kind, seekables, TxnRead.EMPTY, TxnQuery.NONE, null); } public static Ranges fullRange(Txn txn) @@ -371,15 +372,16 @@ public class AccordTestUtils TableMetadata metadata = Schema.instance.getTableMetadata(keyspace, table); TokenRange range = TokenRange.fullRange(metadata.id); Node.Id node = new Id(1); - Topology topology = new Topology(1, new Shard(range, new SortedArrayList<>(new Id[] { node }), Sets.newHashSet(node), Collections.emptySet())); NodeTimeService time = new NodeTimeService() { + private ToLongFunction elapsed = NodeTimeService.elapsedWrapperFromNonMonotonicSource(TimeUnit.MICROSECONDS, this::now); + @Override public Id id() { return node;} @Override public long epoch() {return 1; } @Override public long now() {return now.getAsLong(); } @Override public Timestamp uniqueNow(Timestamp atLeast) { return Timestamp.fromValues(1, now.getAsLong(), node); } @Override - public long unix(TimeUnit timeUnit) { return NodeTimeService.unixWrapper(TimeUnit.MICROSECONDS, this::now).applyAsLong(timeUnit); } + public long elapsed(TimeUnit timeUnit) { return elapsed.applyAsLong(timeUnit); } }; SingleEpochRanges holder = new SingleEpochRanges(Ranges.of(range)); @@ -397,12 +399,14 @@ public class AccordTestUtils { NodeTimeService time = new NodeTimeService() { + private ToLongFunction elapsed = NodeTimeService.elapsedWrapperFromNonMonotonicSource(TimeUnit.MICROSECONDS, this::now); + @Override public Id id() { return node;} @Override public long epoch() {return 1; } @Override public long now() {return now.getAsLong(); } @Override public Timestamp uniqueNow(Timestamp atLeast) { return Timestamp.fromValues(1, now.getAsLong(), node); } @Override - public long unix(TimeUnit timeUnit) { return NodeTimeService.unixWrapper(TimeUnit.MICROSECONDS, this::now).applyAsLong(timeUnit); } + public long elapsed(TimeUnit timeUnit) { return elapsed.applyAsLong(timeUnit); } }; diff --git a/test/unit/org/apache/cassandra/service/accord/EpochSyncTest.java b/test/unit/org/apache/cassandra/service/accord/EpochSyncTest.java index ec0eb60121..6bc85b6f1e 100644 --- a/test/unit/org/apache/cassandra/service/accord/EpochSyncTest.java +++ b/test/unit/org/apache/cassandra/service/accord/EpochSyncTest.java @@ -45,14 +45,17 @@ import java.util.stream.LongStream; import com.google.common.collect.Sets; import org.junit.Test; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import accord.api.ConfigurationService; import accord.api.ConfigurationService.EpochReady; +import accord.api.Scheduler; +import accord.config.LocalConfig; import accord.impl.SizeOfIntersectionSorter; +import accord.impl.TestAgent; import accord.local.Node; +import accord.local.NodeTimeService; import accord.primitives.Ranges; import accord.topology.Topology; import accord.topology.TopologyManager; @@ -596,7 +599,8 @@ public class EpochSyncTest this.id = node; this.token = token; this.epoch = epoch; - this.topology = new TopologyManager(SizeOfIntersectionSorter.SUPPLIER, id); + // TODO (review): Should there be a real scheduler here? Is it possible to adapt the Scheduler interface to scheduler used in this test? + this.topology = new TopologyManager(SizeOfIntersectionSorter.SUPPLIER, new TestAgent.RethrowAgent(), id, Scheduler.NEVER_RUN_SCHEDULED, NodeTimeService.elapsedWrapperFromNonMonotonicSource(TimeUnit.MILLISECONDS, globalExecutor::currentTimeMillis), LocalConfig.DEFAULT); AccordConfigurationService.DiskStateManager instance = MockDiskStateManager.instance; config = new AccordConfigurationService(node, messagingService, failureDetector, instance, scheduler); config.registerListener(new ConfigurationService.Listener() diff --git a/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStore.java b/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStore.java index 83db8accef..dfcfbebdd4 100644 --- a/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStore.java +++ b/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStore.java @@ -108,7 +108,7 @@ public class SimulatedAccordCommandStore implements AutoCloseable this.nodeId = AccordTopology.tcmIdToAccord(ClusterMetadata.currentNullable().myNodeId()); this.timeService = new NodeTimeService() { - private final ToLongFunction unixWrapper = NodeTimeService.unixWrapper(TimeUnit.NANOSECONDS, this::now); + private final ToLongFunction elapsed = NodeTimeService.elapsedWrapperFromNonMonotonicSource(TimeUnit.NANOSECONDS, this::now); @Override public Node.Id id() @@ -129,9 +129,9 @@ public class SimulatedAccordCommandStore implements AutoCloseable } @Override - public long unix(TimeUnit unit) + public long elapsed(TimeUnit unit) { - return unixWrapper.applyAsLong(unit); + return elapsed.applyAsLong(unit); } @Override