From 1c269348a4522e2b188fa06b8382908aefee5239 Mon Sep 17 00:00:00 2001 From: Alex Petrov Date: Thu, 12 Sep 2024 13:45:10 +0200 Subject: [PATCH] Implement Journal replay on startup: * reconstruct CFK, TFK, progressLog * migrate CommandStore collection state from Accord table to the log * make memtable writes non-durable; reconstruct memtable state from Writes Patch by Alex Petrov and Benedict Elliott Smith; reviewed by Benedict Elliott Smith and Alex Petrov for CASSANDRA-19869 --- modules/accord | 2 +- .../org/apache/cassandra/cql3/Relation.java | 1 - .../cassandra/db/ColumnFamilyStore.java | 1 + .../db/compaction/CompactionIterator.java | 63 +-- .../cassandra/db/memtable/Memtable.java | 6 + .../db/memtable/ShardedSkipListMemtable.java | 20 + .../db/memtable/SkipListMemtable.java | 10 + .../db/virtual/AccordVirtualTables.java | 8 +- src/java/org/apache/cassandra/dht/Token.java | 28 +- .../index/accord/RangeMemoryIndex.java | 8 +- .../cassandra/index/accord/RouteIndex.java | 8 +- .../index/accord/RoutesSearcher.java | 6 +- .../io/LocalVersionedSerializer.java | 5 + .../apache/cassandra/journal/Compactor.java | 6 +- .../org/apache/cassandra/journal/Journal.java | 94 +++- .../cassandra/journal/SegmentCompactor.java | 4 +- .../cassandra/journal/ValueSerializer.java | 3 - .../org/apache/cassandra/schema/TableId.java | 2 +- .../cassandra/service/StorageService.java | 4 + .../service/accord/AccordCommandStore.java | 349 +++++++++--- .../accord/AccordConfigurationService.java | 20 +- .../service/accord/AccordDataStore.java | 138 +++++ .../accord/AccordFetchCoordinator.java | 2 +- .../service/accord/AccordJournal.java | 515 +++++++----------- .../service/accord/AccordJournalTable.java | 206 ++++++- .../accord/AccordJournalValueSerializers.java | 383 +++++++++++++ .../service/accord/AccordKeyspace.java | 462 ++++++---------- .../service/accord/AccordObjectSizes.java | 22 +- .../service/accord/AccordSafeCommand.java | 2 +- .../accord/AccordSafeCommandStore.java | 179 +++--- .../accord/AccordSafeCommandsForKey.java | 10 +- .../accord/AccordSafeTimestampsForKey.java | 12 +- .../accord/AccordSegmentCompactor.java | 77 ++- .../service/accord/AccordService.java | 100 ++-- .../service/accord/AccordStateCache.java | 19 + .../service/accord/AccordTopology.java | 50 +- .../service/accord/AccordVerbHandler.java | 10 +- .../accord/CommandStoreTxnBlockedGraph.java | 12 +- .../service/accord/CommandsForRanges.java | 6 +- .../accord/CommandsForRangesLoader.java | 4 +- .../service/accord/IAccordService.java | 14 +- .../cassandra/service/accord/IJournal.java | 26 +- .../cassandra/service/accord/JournalKey.java | 107 +++- .../service/accord/SavedCommand.java | 196 ++++--- .../service/accord/api/AccordAgent.java | 29 +- .../service/accord/api/AccordRoutingKey.java | 32 ++ .../service/accord/async/AsyncLoader.java | 36 +- .../service/accord/async/AsyncOperation.java | 50 +- .../accord/interop/AccordInteropAdapter.java | 4 +- .../accord/interop/AccordInteropApply.java | 23 +- .../accord/interop/AccordInteropCommit.java | 11 +- .../accord/interop/AccordInteropRead.java | 4 +- .../interop/AccordInteropReadRepair.java | 2 +- .../accord/repair/RepairSyncPointAdapter.java | 18 +- .../accord/serializers/AcceptSerializers.java | 44 +- .../AccordRoutingKeyByteSource.java | 66 ++- .../accord/serializers/ApplySerializers.java | 14 +- .../accord/serializers/AwaitSerializer.java | 7 +- .../BeginInvalidationSerializers.java | 15 +- .../serializers/CalculateDepsSerializers.java | 16 +- .../serializers/CheckStatusSerializers.java | 84 +-- .../serializers/CommandSerializers.java | 128 +++-- .../serializers/CommandStoreSerializers.java | 51 +- .../serializers/CommandsForKeySerializer.java | 203 ++++--- .../accord/serializers/CommitSerializers.java | 38 +- .../accord/serializers/DepsSerializer.java | 47 +- .../GetEphmrlReadDepsSerializers.java | 16 +- .../GetMaxConflictSerializers.java | 8 +- .../IVersionedWithKeysSerializer.java | 54 +- .../serializers/InformDurableSerializers.java | 2 +- .../accord/serializers/KeySerializers.java | 3 + .../serializers/PreacceptSerializers.java | 18 +- .../serializers/ReadDataSerializers.java | 7 +- .../serializers/RecoverySerializers.java | 24 +- .../serializers/SetDurableSerializers.java | 20 +- .../serializers/TopologySerializers.java | 4 +- .../serializers/WaitingOnSerializer.java | 4 +- .../service/accord/txn/TxnWrite.java | 7 +- .../cassandra/tcm/membership/Directory.java | 5 +- .../apache/cassandra/utils/FBUtilities.java | 16 +- .../utils/btree/AbstractBTreeMap.java | 5 +- .../cassandra/utils/vint/VIntCoding.java | 5 + .../test/accord/AccordBootstrapTest.java | 2 +- .../test/accord/AccordDropTableBase.java | 6 +- .../accord/AccordIncrementalRepairTest.java | 60 +- .../accord/AccordJournalIntegrationTest.java | 48 +- .../test/accord/AccordTestBase.java | 3 +- .../journal/AccordJournalCompactionTest.java | 137 ----- .../accord/AccordJournalCompactionTest.java | 186 +++++++ .../test/AccordJournalSimulationTest.java | 8 +- .../cql3/conditions/ColumnConditionTest.java | 5 - .../CompactionAccordIteratorsTest.java | 38 +- .../db/virtual/AccordVirtualTablesTest.java | 2 +- .../index/accord/AccordIndexStressTest.java | 13 +- .../index/accord/RouteIndexTest.java | 17 +- .../apache/cassandra/journal/JournalTest.java | 27 - .../cassandra/service/StorageServiceTest.java | 1 + .../accord/AccordCommandStoreTest.java | 16 +- .../service/accord/AccordCommandTest.java | 23 +- .../accord/AccordJournalOrderTest.java | 8 +- .../service/accord/AccordJournalTest.java | 22 +- .../service/accord/AccordKeyspaceTest.java | 37 +- .../accord/AccordSyncPropagatorTest.java | 4 +- .../service/accord/AccordTestUtils.java | 40 +- .../service/accord/CommandsForRangesTest.java | 2 +- .../cassandra/service/accord/MockJournal.java | 151 +++-- .../service/accord/SavedCommandTest.java | 6 +- ...SimpleSimulatedAccordCommandStoreTest.java | 6 +- .../accord/SimulatedAccordCommandStore.java | 20 +- .../SimulatedAccordCommandStoreTestBase.java | 36 +- .../service/accord/SimulatedDepsTest.java | 31 +- .../accord/SimulatedMultiKeyAndRangeTest.java | 12 +- ...ulatedRandomKeysWithRangeConflictTest.java | 13 +- .../service/accord/async/AsyncLoaderTest.java | 69 ++- .../accord/async/AsyncOperationTest.java | 45 +- .../async/SimulatedAsyncOperationTest.java | 17 +- .../CheckStatusSerializersTest.java | 12 +- .../CommandsForKeySerializerTest.java | 41 +- .../cassandra/utils/AccordGenerators.java | 101 +++- 119 files changed, 3534 insertions(+), 2051 deletions(-) create mode 100644 src/java/org/apache/cassandra/service/accord/AccordJournalValueSerializers.java delete mode 100644 test/distributed/org/apache/cassandra/journal/AccordJournalCompactionTest.java create mode 100644 test/distributed/org/apache/cassandra/service/accord/AccordJournalCompactionTest.java diff --git a/modules/accord b/modules/accord index 593e042535..2a7aceb96c 160000 --- a/modules/accord +++ b/modules/accord @@ -1 +1 @@ -Subproject commit 593e042535d60e773cfa5f7c4b6a63e2fb6e5b30 +Subproject commit 2a7aceb96cb1e03bcfe150403b9d245b1d2562f9 diff --git a/src/java/org/apache/cassandra/cql3/Relation.java b/src/java/org/apache/cassandra/cql3/Relation.java index 9fbd3adf68..31d6f771ce 100644 --- a/src/java/org/apache/cassandra/cql3/Relation.java +++ b/src/java/org/apache/cassandra/cql3/Relation.java @@ -35,7 +35,6 @@ import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.exceptions.InvalidRequestException; import static org.apache.cassandra.cql3.statements.RequestValidations.*; -import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue; /** * The parsed version of a {@code SimpleRestriction} as outputed by the CQL parser. diff --git a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java index 0fcdb192fd..e81c0d7be8 100644 --- a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java +++ b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java @@ -237,6 +237,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner SCHEMA_CHANGE, OWNED_RANGES_CHANGE, ACCORD, + ACCORD_TXN_GC, UNIT_TESTS // explicitly requested flush needed for a test } diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionIterator.java b/src/java/org/apache/cassandra/db/compaction/CompactionIterator.java index 924a960571..f9b4b9e8ce 100644 --- a/src/java/org/apache/cassandra/db/compaction/CompactionIterator.java +++ b/src/java/org/apache/cassandra/db/compaction/CompactionIterator.java @@ -35,9 +35,10 @@ import accord.local.Cleanup; import accord.local.CommandStores.RangesForEpoch; import accord.local.DurableBefore; import accord.local.RedundantBefore; -import accord.local.SaveStatus; -import accord.local.Status.Durability; -import accord.primitives.Route; +import accord.local.StoreParticipants; +import accord.primitives.SaveStatus; +import accord.primitives.Status; +import accord.primitives.Status.Durability; import accord.primitives.Timestamp; import accord.primitives.TxnId; import org.agrona.collections.Int2ObjectHashMap; @@ -85,19 +86,19 @@ import org.apache.cassandra.service.accord.AccordKeyspace.CommandsForKeyAccessor import org.apache.cassandra.service.accord.AccordKeyspace.TimestampsForKeyRows; 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.api.AccordRoutingKey.TokenKey; import org.apache.cassandra.service.paxos.PaxosRepairHistory; import org.apache.cassandra.service.paxos.uncommitted.PaxosRows; import org.apache.cassandra.utils.TimeUUID; import static accord.local.Cleanup.TRUNCATE_WITH_OUTCOME; -import static accord.local.Cleanup.shouldCleanup; +import static accord.local.Cleanup.shouldCleanupPartial; import static com.google.common.base.Preconditions.checkState; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static org.apache.cassandra.config.Config.PaxosStatePurging.legacy; import static org.apache.cassandra.config.DatabaseDescriptor.paxosStatePurging; -import static org.apache.cassandra.service.accord.AccordKeyspace.CommandRows.invalidated; -import static org.apache.cassandra.service.accord.AccordKeyspace.CommandRows.maybeDropTruncatedCommandColumns; +import static org.apache.cassandra.service.accord.AccordKeyspace.CommandRows.expungePartial; +import static org.apache.cassandra.service.accord.AccordKeyspace.CommandRows.saveStatusOnly; import static org.apache.cassandra.service.accord.AccordKeyspace.CommandRows.truncatedApply; import static org.apache.cassandra.service.accord.AccordKeyspace.CommandsForKeysAccessor; import static org.apache.cassandra.service.accord.AccordKeyspace.TimestampsForKeyColumns.last_executed_micros; @@ -105,7 +106,7 @@ import static org.apache.cassandra.service.accord.AccordKeyspace.TimestampsForKe import static org.apache.cassandra.service.accord.AccordKeyspace.TimestampsForKeyColumns.last_write_timestamp; import static org.apache.cassandra.service.accord.AccordKeyspace.TimestampsForKeyRows.truncateTimestampsForKeyRow; import static org.apache.cassandra.service.accord.AccordKeyspace.deserializeDurabilityOrNull; -import static org.apache.cassandra.service.accord.AccordKeyspace.deserializeRouteOrNull; +import static org.apache.cassandra.service.accord.AccordKeyspace.deserializeParticipantsOrNull; import static org.apache.cassandra.service.accord.AccordKeyspace.deserializeSaveStatusOrNull; import static org.apache.cassandra.service.accord.AccordKeyspace.deserializeTimestampOrNull; @@ -816,33 +817,31 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte if (redundantBefore == null) return row; - // When commands end up being sliced by compaction we need this to discard tombstones and slices - // without enough information to run the rest of the cleanup logic - if (Cleanup.isSafeToCleanup(durableBefore, txnId, ranges.get(storeId).allAt(txnId.epoch()))) - return null; - Cell durabilityCell = row.getCell(CommandsColumns.durability); Durability durability = deserializeDurabilityOrNull(durabilityCell); Cell executeAtCell = row.getCell(CommandsColumns.execute_at); - Timestamp executeAt = deserializeTimestampOrNull(executeAtCell); - Cell routeCell = row.getCell(CommandsColumns.route); - Route route = deserializeRouteOrNull(routeCell); + Cell participantsCell = row.getCell(CommandsColumns.participants); + StoreParticipants participants = deserializeParticipantsOrNull(participantsCell); Cell statusCell = row.getCell(CommandsColumns.status); SaveStatus saveStatus = deserializeSaveStatusOrNull(statusCell); - // With a sliced row we might not have enough columns to determine what to do so output the - // the row unmodified and we will try again later once it merges with the rest of the command state - // or is dropped by `durableBefore.min(txnId) == Universal` - if (executeAt == null || durability == null || saveStatus == null || route == null) + if (saveStatus == null) return row; - Cleanup cleanup = shouldCleanup(txnId, saveStatus.status, - durability, executeAt, route, - redundantBefore, durableBefore, - false); + if (saveStatus.is(Status.Invalidated)) + return saveStatusOnly(saveStatus, row, nowInSec); + + Cleanup cleanup = shouldCleanupPartial(txnId, saveStatus, durability, participants, + redundantBefore, durableBefore); switch (cleanup) { default: throw new AssertionError(String.format("Unexpected cleanup task: %s", cleanup)); + case EXPUNGE: + return null; + + case EXPUNGE_PARTIAL: + return expungePartial(row, durabilityCell, executeAtCell, participantsCell); + case ERASE: // Emit a tombstone so if this is slicing the command and making it not possible to determine if it // can be truncated later it can still be dropped via the tombstone. @@ -850,24 +849,20 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte // We can still encounter sliced command state just because compaction inputs are random return BTreeRow.emptyDeletedRow(row.clustering(), new Row.Deletion(DeletionTime.build(row.primaryKeyLivenessInfo().timestamp(), nowInSec), false)); + case VESTIGIAL: case INVALIDATE: - return invalidated(cleanup.appliesIfNot, row, nowInSec); + return saveStatusOnly(cleanup.appliesIfNot, row, nowInSec); case TRUNCATE_WITH_OUTCOME: case TRUNCATE: - if (saveStatus.compareTo(cleanup.appliesIfNot) >= 0) - return maybeDropTruncatedCommandColumns(row, durabilityCell, executeAtCell, routeCell, statusCell); - return truncatedApply(cleanup.appliesIfNot, - row, nowInSec, durability, durabilityCell, executeAtCell, routeCell, cleanup == TRUNCATE_WITH_OUTCOME); + return truncatedApply(cleanup.appliesIfNot, row, nowInSec, durability, durabilityCell, executeAtCell, participantsCell, cleanup == TRUNCATE_WITH_OUTCOME); case NO: + // TODO (required): when we port this to journal, make sure to expunge extra fields beyond those we need to retain return row; } } - - - @Override protected Row applyToStatic(Row row) { @@ -880,7 +875,7 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte { final Int2ObjectHashMap redundantBefores; int storeId; - PartitionKey partitionKey; + TokenKey partitionKey; AccordTimestampsForKeyPurger(Supplier accordService) { @@ -955,7 +950,7 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte final CommandsForKeyAccessor accessor; final Int2ObjectHashMap redundantBefores; int storeId; - PartitionKey partitionKey; + TokenKey partitionKey; AccordCommandsForKeyPurger(CommandsForKeyAccessor accessor, Supplier accordService) { diff --git a/src/java/org/apache/cassandra/db/memtable/Memtable.java b/src/java/org/apache/cassandra/db/memtable/Memtable.java index f722ec20a9..b34a617ef4 100644 --- a/src/java/org/apache/cassandra/db/memtable/Memtable.java +++ b/src/java/org/apache/cassandra/db/memtable/Memtable.java @@ -30,6 +30,7 @@ import org.apache.cassandra.db.partitions.Partition; import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.db.rows.EncodingStats; import org.apache.cassandra.db.rows.UnfilteredSource; +import org.apache.cassandra.dht.Token; import org.apache.cassandra.index.transactions.UpdateTransaction; import org.apache.cassandra.io.sstable.format.SSTableWriter; import org.apache.cassandra.schema.TableMetadata; @@ -441,4 +442,9 @@ public interface Memtable extends Comparable, UnfilteredSource super(copy.segmentId, copy.position); } } + + default Token lastToken() + { + throw new UnsupportedOperationException("lastToken is not supported"); + } } diff --git a/src/java/org/apache/cassandra/db/memtable/ShardedSkipListMemtable.java b/src/java/org/apache/cassandra/db/memtable/ShardedSkipListMemtable.java index 92cdbbad9f..eb4a44ebd2 100644 --- a/src/java/org/apache/cassandra/db/memtable/ShardedSkipListMemtable.java +++ b/src/java/org/apache/cassandra/db/memtable/ShardedSkipListMemtable.java @@ -50,6 +50,7 @@ import org.apache.cassandra.dht.AbstractBounds; import org.apache.cassandra.dht.Bounds; import org.apache.cassandra.dht.IncludingExcludingBounds; import org.apache.cassandra.dht.Range; +import org.apache.cassandra.dht.Token; import org.apache.cassandra.index.transactions.UpdateTransaction; import org.apache.cassandra.io.sstable.SSTableReadsListener; import org.apache.cassandra.schema.TableMetadata; @@ -112,6 +113,25 @@ public class ShardedSkipListMemtable extends AbstractShardedMemtable return true; } + @Override + public Token lastToken() + { + Token lastToken = null; + for (MemtableShard shard : shards) + { + Iterator ppIterator = shard.partitions.descendingKeySet().iterator(); + if (ppIterator.hasNext()) + { + Token token = ppIterator.next().getToken(); + if (lastToken == null) + lastToken = token; + else if (lastToken.compareTo(token) < 0) + lastToken = token; + } + } + return lastToken; + } + /** * Should only be called by ColumnFamilyStore.apply via Keyspace.apply, which supplies the appropriate * OpOrdering. diff --git a/src/java/org/apache/cassandra/db/memtable/SkipListMemtable.java b/src/java/org/apache/cassandra/db/memtable/SkipListMemtable.java index 8871b03bd6..3f6fbcbd52 100644 --- a/src/java/org/apache/cassandra/db/memtable/SkipListMemtable.java +++ b/src/java/org/apache/cassandra/db/memtable/SkipListMemtable.java @@ -50,6 +50,7 @@ import org.apache.cassandra.dht.AbstractBounds; import org.apache.cassandra.dht.Bounds; import org.apache.cassandra.dht.IncludingExcludingBounds; import org.apache.cassandra.dht.Range; +import org.apache.cassandra.dht.Token; import org.apache.cassandra.index.transactions.UpdateTransaction; import org.apache.cassandra.io.sstable.SSTableReadsListener; import org.apache.cassandra.schema.TableMetadata; @@ -97,6 +98,15 @@ public class SkipListMemtable extends AbstractAllocatorMemtable return partitions.isEmpty(); } + @Override + public Token lastToken() + { + Iterator iterator = partitions.keySet().iterator(); + if (iterator.hasNext()) + return iterator.next().getToken(); + return null; + } + /** * Should only be called by ColumnFamilyStore.apply via Keyspace.apply, which supplies the appropriate * OpOrdering. diff --git a/src/java/org/apache/cassandra/db/virtual/AccordVirtualTables.java b/src/java/org/apache/cassandra/db/virtual/AccordVirtualTables.java index b75d081d1d..0918d73a5d 100644 --- a/src/java/org/apache/cassandra/db/virtual/AccordVirtualTables.java +++ b/src/java/org/apache/cassandra/db/virtual/AccordVirtualTables.java @@ -37,7 +37,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import accord.local.CommandStores; -import accord.local.Status; +import accord.primitives.Status; import accord.primitives.TxnId; import accord.utils.Invariants; import accord.utils.async.AsyncChain; @@ -62,7 +62,7 @@ import org.apache.cassandra.service.accord.AccordKeyspace; import org.apache.cassandra.service.accord.AccordService; import org.apache.cassandra.service.accord.AccordStateCache; import org.apache.cassandra.service.accord.CommandStoreTxnBlockedGraph; -import org.apache.cassandra.service.accord.api.PartitionKey; +import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState; import org.apache.cassandra.service.consensus.migration.TableMigrationState; import org.apache.cassandra.tcm.ClusterMetadata; @@ -288,7 +288,7 @@ public class AccordVirtualTables Arrays.asList(UTF8Type.instance, UTF8Type.instance), false); } - private ByteBuffer pk(PartitionKey pk) + private ByteBuffer pk(TokenKey pk) { TableMetadata tm = Schema.instance.getTableMetadata(pk.table()); return partitionKeyType.pack(UTF8Type.instance.decompose(tm.toString()), @@ -346,7 +346,7 @@ public class AccordVirtualTables if (processed.contains(blockedBy)) continue; // already listed process(ds, shard, processed, userTxn, depth + 1, blockedBy, Reason.Txn, null); } - for (PartitionKey blockedBy : txn.blockedByKey) + for (TokenKey blockedBy : txn.blockedByKey) { TxnId blocking = shard.keys.get(blockedBy); if (processed.contains(blocking)) continue; // already listed diff --git a/src/java/org/apache/cassandra/dht/Token.java b/src/java/org/apache/cassandra/dht/Token.java index b3fee2a080..92481745f4 100644 --- a/src/java/org/apache/cassandra/dht/Token.java +++ b/src/java/org/apache/cassandra/dht/Token.java @@ -38,6 +38,7 @@ import org.apache.cassandra.tcm.serialization.Version; import org.apache.cassandra.utils.bytecomparable.ByteComparable; import org.apache.cassandra.utils.bytecomparable.ByteSource; import org.apache.cassandra.utils.bytecomparable.ByteSourceInverse; +import org.apache.cassandra.utils.vint.VIntCoding; public abstract class Token implements RingPosition, Serializable { @@ -95,7 +96,7 @@ public abstract class Token implements RingPosition, Serializable out.write(toByteArray(token)); } - public void serialize(Token token, ByteBuffer out) throws IOException + public void serialize(Token token, ByteBuffer out) { out.put(toByteArray(token)); } @@ -202,6 +203,26 @@ public abstract class Token implements RingPosition, Serializable p.getTokenFactory().serialize(token, out); } + public void serialize(Token token, ByteBuffer out) + { + IPartitioner p = token.getPartitioner(); + if (logPartitioner && serializePartitioners.add(p.getClass())) + logger.debug("Serializing token with partitioner " + p); + if (!p.isFixedLength()) + VIntCoding.writeUnsignedVInt32(p.getTokenFactory().byteSize(token), out); + p.getTokenFactory().serialize(token, out); + } + + public Token deserialize(ByteBuffer in, IPartitioner p) + { + int size = p.isFixedLength() ? p.getMaxTokenSize() : VIntCoding.readUnsignedVInt32(in); + if (logPartitioner && deserializePartitioners.add(p.getClass())) + logger.debug("Deserializing token with partitioner " + p); + byte[] bytes = new byte[size]; + in.get(bytes); + return p.getTokenFactory().fromByteArray(ByteBuffer.wrap(bytes)); + } + public Token deserialize(DataInputPlus in, IPartitioner p, int version) throws IOException { int size = p.isFixedLength() ? p.getMaxTokenSize() : in.readUnsignedVInt32(); @@ -213,6 +234,11 @@ public abstract class Token implements RingPosition, Serializable } public long serializedSize(Token object, int version) + { + return serializedSize(object); + } + + public long serializedSize(Token object) { IPartitioner p = object.getPartitioner(); int byteSize = p.getTokenFactory().byteSize(object); diff --git a/src/java/org/apache/cassandra/index/accord/RangeMemoryIndex.java b/src/java/org/apache/cassandra/index/accord/RangeMemoryIndex.java index 5581708171..804f092643 100644 --- a/src/java/org/apache/cassandra/index/accord/RangeMemoryIndex.java +++ b/src/java/org/apache/cassandra/index/accord/RangeMemoryIndex.java @@ -105,7 +105,7 @@ public class RangeMemoryIndex Route route; try { - route = AccordKeyspace.deserializeRouteOrNull(value); + route = AccordKeyspace.deserializeParticipantsRouteOnlyOrNull(value); } catch (IOException e) { @@ -118,7 +118,7 @@ public class RangeMemoryIndex public synchronized long add(DecoratedKey key, Route route) { - if (route.domain() != Routable.Domain.Range) + if (route == null || route.domain() != Routable.Domain.Range) return 0; long sum = 0; for (Unseekable keyOrRange : route) @@ -146,7 +146,7 @@ public class RangeMemoryIndex return TableId.EMPTY_SIZE + range.unsharedHeapSize(); } - public NavigableSet search(int storeId, TableId tableId, byte[] start, boolean startInclusive, byte[] end, boolean endInclusive) + public synchronized NavigableSet search(int storeId, TableId tableId, byte[] start, boolean startInclusive, byte[] end, boolean endInclusive) { RangeTree rangesToPks = map.get(new Group(storeId, tableId)); if (rangesToPks == null || rangesToPks.isEmpty()) @@ -172,7 +172,7 @@ public class RangeMemoryIndex return map.isEmpty(); } - public Segment write(IndexDescriptor id) throws IOException + public synchronized Segment write(IndexDescriptor id) throws IOException { if (map.isEmpty()) throw new AssertionError("Unable to write empty index"); diff --git a/src/java/org/apache/cassandra/index/accord/RouteIndex.java b/src/java/org/apache/cassandra/index/accord/RouteIndex.java index 8dcaf2067c..2f5026a8a3 100644 --- a/src/java/org/apache/cassandra/index/accord/RouteIndex.java +++ b/src/java/org/apache/cassandra/index/accord/RouteIndex.java @@ -117,11 +117,11 @@ public class RouteIndex implements Index, INotificationConsumer TableMetadata tableMetadata = baseCfs.metadata(); Pair target = TargetParser.parse(tableMetadata, indexMetadata); - if (!AccordKeyspace.CommandsColumns.route.name.equals(target.left.name)) - throw new IllegalArgumentException("Attempted to index the wrong column; needed " + AccordKeyspace.CommandsColumns.route.name + " but given " + target.left.name); + if (!AccordKeyspace.CommandsColumns.participants.name.equals(target.left.name)) + throw new IllegalArgumentException("Attempted to index the wrong column; needed " + AccordKeyspace.CommandsColumns.participants.name + " but given " + target.left.name); if (target.right != IndexTarget.Type.VALUES) - throw new IllegalArgumentException("Attempted to index " + AccordKeyspace.CommandsColumns.route.name + " with index type " + target.right + "; only " + IndexTarget.Type.VALUES + " is supported"); + throw new IllegalArgumentException("Attempted to index " + AccordKeyspace.CommandsColumns.participants.name + " with index type " + target.right + "; only " + IndexTarget.Type.VALUES + " is supported"); this.baseCfs = baseCfs; this.indexMetadata = indexMetadata; @@ -383,7 +383,7 @@ public class RouteIndex implements Index, INotificationConsumer Integer storeId = null; for (RowFilter.Expression e : expressions) { - if (e.column() == AccordKeyspace.CommandsColumns.route) + if (e.column() == AccordKeyspace.CommandsColumns.participants) { switch (e.operator()) { diff --git a/src/java/org/apache/cassandra/index/accord/RoutesSearcher.java b/src/java/org/apache/cassandra/index/accord/RoutesSearcher.java index 1ade3b8a4a..7975b95c7b 100644 --- a/src/java/org/apache/cassandra/index/accord/RoutesSearcher.java +++ b/src/java/org/apache/cassandra/index/accord/RoutesSearcher.java @@ -48,7 +48,7 @@ public class RoutesSearcher { private final ColumnFamilyStore cfs = Keyspace.open("system_accord").getColumnFamilyStore("commands"); private final Index index = cfs.indexManager.getIndexByName("route");; - private final ColumnMetadata route = AccordKeyspace.CommandsColumns.route; + private final ColumnMetadata participants = AccordKeyspace.CommandsColumns.participants; private final ColumnMetadata store_id = AccordKeyspace.CommandsColumns.store_id; private final ColumnMetadata txn_id = AccordKeyspace.CommandsColumns.txn_id; private final ColumnFilter columnFilter = ColumnFilter.selectionBuilder().add(store_id).add(txn_id).build(); @@ -58,8 +58,8 @@ public class RoutesSearcher private CloseableIterator searchKeysAccord(int store, AccordRoutingKey start, AccordRoutingKey end) { RowFilter rowFilter = RowFilter.create(false); - rowFilter.add(route, Operator.GT, OrderedRouteSerializer.serializeRoutingKey(start)); - rowFilter.add(route, Operator.LTE, OrderedRouteSerializer.serializeRoutingKey(end)); + rowFilter.add(participants, Operator.GT, OrderedRouteSerializer.serializeRoutingKey(start)); + rowFilter.add(participants, Operator.LTE, OrderedRouteSerializer.serializeRoutingKey(end)); rowFilter.add(store_id, Operator.EQ, Int32Type.instance.decompose(store)); PartitionRangeReadCommand cmd = PartitionRangeReadCommand.create(cfs.metadata(), diff --git a/src/java/org/apache/cassandra/io/LocalVersionedSerializer.java b/src/java/org/apache/cassandra/io/LocalVersionedSerializer.java index 739f007846..cc1cdb2953 100644 --- a/src/java/org/apache/cassandra/io/LocalVersionedSerializer.java +++ b/src/java/org/apache/cassandra/io/LocalVersionedSerializer.java @@ -48,6 +48,11 @@ public class LocalVersionedSerializer this.serializer = Objects.requireNonNull(serializer); } + public MessageVersionProvider deserializeVersion(DataInputPlus in) throws IOException + { + return versionSerializer.deserialize(in, currentVersion.messageVersion()); + } + public IVersionedSerializer serializer() { return serializer; diff --git a/src/java/org/apache/cassandra/journal/Compactor.java b/src/java/org/apache/cassandra/journal/Compactor.java index 846dd62ba8..a4638266fe 100644 --- a/src/java/org/apache/cassandra/journal/Compactor.java +++ b/src/java/org/apache/cassandra/journal/Compactor.java @@ -62,7 +62,11 @@ final class Compactor implements Runnable, Shutdownable try { - Collection> newSegments = segmentCompactor.compact(toCompact, journal.keySupport); + Collection> newSegments = segmentCompactor.compact(toCompact); + // No-op compaction + if (newSegments == null) + return; + for (StaticSegment segment : newSegments) toCompact.remove(segment); diff --git a/src/java/org/apache/cassandra/journal/Journal.java b/src/java/org/apache/cassandra/journal/Journal.java index 0baf8e5af3..624250d605 100644 --- a/src/java/org/apache/cassandra/journal/Journal.java +++ b/src/java/org/apache/cassandra/journal/Journal.java @@ -17,12 +17,14 @@ */ package org.apache.cassandra.journal; +import java.io.Closeable; import java.io.IOException; import java.nio.channels.ClosedByInterruptException; import java.nio.file.FileStore; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.PriorityQueue; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; @@ -59,7 +61,6 @@ import org.apache.cassandra.utils.concurrent.WaitQueue; import org.jctools.queues.MpscUnboundedArrayQueue; import static java.lang.String.format; -import static java.util.Comparator.comparing; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Daemon.NON_DAEMON; import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.SYNCHRONIZED; @@ -203,7 +204,8 @@ public class Journal implements Shutdownable public void onFlush(RecordPointer recordPointer, Runnable runnable) { - flusherCallbacks.submit(recordPointer, runnable); + if (isFlushed(recordPointer)) runnable.run(); + else flusherCallbacks.submit(recordPointer, runnable); } public void start() @@ -229,7 +231,7 @@ public class Journal implements Shutdownable } @VisibleForTesting - void runCompactorForTesting() + public void runCompactorForTesting() { compactor.run(); } @@ -254,6 +256,7 @@ public class Journal implements Shutdownable try { allocator.shutdown(); + wakeAllocator(); // Wake allocator to force it into shutdown allocator.awaitTermination(1, TimeUnit.MINUTES); compactor.shutdown(); compactor.awaitTermination(1, TimeUnit.MINUTES); @@ -329,7 +332,6 @@ public class Journal implements Shutdownable try (ReferencedSegments segments = selectAndReference(id)) { consumer.init(); - for (Segment segment : segments.allSorted()) segment.readAll(id, holder, consumer); } @@ -736,6 +738,17 @@ public class Journal implements Shutdownable } } + @SuppressWarnings("unused") + ReferencedSegments selectAndReference(Predicate> selector) + { + while (true) + { + ReferencedSegments referenced = segments().selectAndReference(selector); + if (null != referenced) + return referenced; + } + } + Segments segments() { return segments.get(); @@ -854,24 +867,6 @@ public class Journal implements Shutdownable closer.execute(new CloseActiveSegmentRunnable(activeSegment)); } - /* - * Replay logic - */ - - /** - * Iterate over and invoke the supplied callback on every record, - * with segments iterated in segment timestamp order. Only visits - * finished, on-disk segments. - */ - public void replayStaticSegments(RecordConsumer consumer) - { - List> staticSegments = new ArrayList<>(); - segments().selectStatic(staticSegments); - staticSegments.sort(comparing(s -> s.descriptor)); - for (StaticSegment segment : staticSegments) - segment.forEachRecord(consumer); - } - @VisibleForTesting public void closeCurrentSegmentForTesting() { @@ -954,4 +949,59 @@ public class Journal implements Shutdownable { void write(DataOutputPlus out, int userVersion) throws IOException; } + + public StaticSegmentIterator staticSegmentIterator() + { + return new StaticSegmentIterator(); + } + + /** + * Static segment iterator iterates all _static_ segments in _key_ order. + */ + public class StaticSegmentIterator implements Closeable + { + private final PriorityQueue> readers; + private final ReferencedSegments segments; + + private StaticSegmentIterator() + { + this.segments = selectAndReference(Segment::isStatic); + this.readers = new PriorityQueue<>((o1, o2) -> keySupport.compare(o1.key(), o2.key())); + for (Segment segment : this.segments.all()) + { + StaticSegment staticSegment = (StaticSegment)segment; + StaticSegment.KeyOrderReader reader = staticSegment.keyOrderReader(); + if (reader.advance()) + this.readers.add(reader); + } + } + + public K key() + { + StaticSegment.KeyOrderReader reader = readers.peek(); + if (reader == null) + return null; + return reader.key(); + } + + public void readAllForKey(K key, RecordConsumer reader) + { + while (true) + { + StaticSegment.KeyOrderReader next = readers.peek(); + if (next == null || !next.key().equals(key)) + break; + Invariants.checkState(next == readers.poll()); + + reader.accept(next.descriptor.timestamp, next.offset, next.key(), next.record(), next.hosts(), next.descriptor.userVersion); + if (next.advance()) + readers.add(next); + } + } + + public void close() + { + segments.close(); + } + } } diff --git a/src/java/org/apache/cassandra/journal/SegmentCompactor.java b/src/java/org/apache/cassandra/journal/SegmentCompactor.java index 5c95b539fc..7b84ea82e1 100644 --- a/src/java/org/apache/cassandra/journal/SegmentCompactor.java +++ b/src/java/org/apache/cassandra/journal/SegmentCompactor.java @@ -22,7 +22,7 @@ import java.util.Collection; public interface SegmentCompactor { - SegmentCompactor NOOP = (SegmentCompactor) (segments, keySupport) -> segments; + SegmentCompactor NOOP = (SegmentCompactor) (segments) -> segments; static SegmentCompactor noop() { @@ -30,5 +30,5 @@ public interface SegmentCompactor return (SegmentCompactor) NOOP; } - Collection> compact(Collection> segments, KeySupport keySupport) throws IOException; + Collection> compact(Collection> segments) throws IOException; } diff --git a/src/java/org/apache/cassandra/journal/ValueSerializer.java b/src/java/org/apache/cassandra/journal/ValueSerializer.java index 610770ca66..69690d39b2 100644 --- a/src/java/org/apache/cassandra/journal/ValueSerializer.java +++ b/src/java/org/apache/cassandra/journal/ValueSerializer.java @@ -24,9 +24,6 @@ import org.apache.cassandra.io.util.DataOutputPlus; public interface ValueSerializer { - // TODO (required): this is completely unused in Journal - int serializedSize(K key, V value, int userVersion); - void serialize(K key, V value, DataOutputPlus out, int userVersion) throws IOException; /** diff --git a/src/java/org/apache/cassandra/schema/TableId.java b/src/java/org/apache/cassandra/schema/TableId.java index def82d1c72..ac486f71d9 100644 --- a/src/java/org/apache/cassandra/schema/TableId.java +++ b/src/java/org/apache/cassandra/schema/TableId.java @@ -190,7 +190,7 @@ public class TableId implements Comparable return new TableId(new UUID(in.readLong(), in.readLong())); } - public static TableId deserialize(V src, ValueAccessor accessor, int offset) throws IOException + public static TableId deserialize(V src, ValueAccessor accessor, int offset) { return new TableId(new UUID(accessor.getLong(src, offset), accessor.getLong(src, offset + TypeSizes.LONG_SIZE))); } diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index 60d6c329ee..98da35158b 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -168,6 +168,7 @@ import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadataRef; import org.apache.cassandra.schema.ViewMetadata; +import org.apache.cassandra.service.accord.AccordService; import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState; import org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget; import org.apache.cassandra.service.disk.usage.DiskUsageBroadcaster; @@ -3794,6 +3795,9 @@ public class StorageService extends NotificationBroadcasterSupport implements IE logger.debug(msg); transientMode = Optional.of(Mode.DRAINING); + if (DatabaseDescriptor.getAccordTransactionsEnabled()) + AccordService.instance().shutdownAndWait(1, MINUTES); + try { /* not clear this is reasonable time, but propagated from prior embedded behaviour */ diff --git a/src/java/org/apache/cassandra/service/accord/AccordCommandStore.java b/src/java/org/apache/cassandra/service/accord/AccordCommandStore.java index c1ce231da9..ca654ddaae 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordCommandStore.java +++ b/src/java/org/apache/cassandra/service/accord/AccordCommandStore.java @@ -18,7 +18,6 @@ package org.apache.cassandra.service.accord; -import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; @@ -32,7 +31,6 @@ import java.util.function.Consumer; import java.util.function.Function; import javax.annotation.Nullable; -import accord.api.Key; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,22 +39,31 @@ import accord.api.Agent; import accord.api.DataStore; import accord.api.LocalListeners; import accord.api.ProgressLog; +import accord.api.RoutingKey; import accord.local.cfk.CommandsForKey; import accord.impl.TimestampsForKey; +import accord.local.Cleanup; import accord.local.Command; import accord.local.CommandStore; +import accord.local.CommandStores; +import accord.local.Commands; import accord.local.DurableBefore; +import accord.local.KeyHistory; import accord.local.NodeTimeService; import accord.local.PreLoadContext; import accord.local.RedundantBefore; +import accord.local.SafeCommand; import accord.local.SafeCommandStore; -import accord.primitives.Keys; +import accord.primitives.Deps; +import accord.primitives.Participants; +import accord.primitives.Range; import accord.primitives.Ranges; +import accord.primitives.Routable; import accord.primitives.RoutableKey; +import accord.primitives.Status; import accord.primitives.Timestamp; import accord.primitives.TxnId; import accord.utils.Invariants; -import accord.utils.ReducingRangeMap; import accord.utils.async.AsyncChain; import accord.utils.async.AsyncChains; import org.apache.cassandra.cache.CacheSize; @@ -66,12 +73,21 @@ import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.Mutation; import org.apache.cassandra.metrics.AccordStateCacheMetrics; -import org.apache.cassandra.service.accord.api.PartitionKey; +import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; import org.apache.cassandra.service.accord.async.AsyncOperation; import org.apache.cassandra.service.accord.events.CacheEvents; import org.apache.cassandra.utils.Clock; +import org.apache.cassandra.utils.concurrent.AsyncPromise; +import org.apache.cassandra.utils.concurrent.Promise; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; +import static accord.primitives.SaveStatus.Applying; +import static accord.primitives.Status.Committed; +import static accord.primitives.Status.Invalidated; +import static accord.primitives.Status.PreApplied; +import static accord.primitives.Status.Stable; +import static accord.primitives.Status.Truncated; +import static accord.utils.Invariants.checkState; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; public class AccordCommandStore extends CommandStore implements CacheSize @@ -103,8 +119,8 @@ public class AccordCommandStore extends CommandStore implements CacheSize private final ExecutorService executor; private final AccordStateCache stateCache; private final AccordStateCache.Instance commandCache; - private final AccordStateCache.Instance timestampsForKeyCache; - private final AccordStateCache.Instance commandsForKeyCache; + private final AccordStateCache.Instance timestampsForKeyCache; + private final AccordStateCache.Instance commandsForKeyCache; private AsyncOperation currentOperation = null; private AccordSafeCommandStore current = null; private long lastSystemTimestampMicros = Long.MIN_VALUE; @@ -120,7 +136,17 @@ public class AccordCommandStore extends CommandStore implements CacheSize IJournal journal, AccordStateCacheMetrics cacheMetrics) { - this(id, time, agent, dataStore, progressLogFactory, listenerFactory, epochUpdateHolder, journal, Stage.READ.executor(), Stage.MUTATION.executor(), cacheMetrics); + this(id, + time, + agent, + dataStore, + progressLogFactory, + listenerFactory, + epochUpdateHolder, + journal, + Stage.READ.executor(), + Stage.MUTATION.executor(), + cacheMetrics); } private static void registerJfrListener(int id, AccordStateCache.Instance instance, String name) @@ -219,7 +245,7 @@ public class AccordCommandStore extends CommandStore implements CacheSize AccordObjectSizes::command); registerJfrListener(id, commandCache, "Command"); timestampsForKeyCache = - stateCache.instance(Key.class, + stateCache.instance(RoutingKey.class, AccordSafeTimestampsForKey.class, AccordSafeTimestampsForKey::new, this::loadTimestampsForKey, @@ -228,7 +254,7 @@ public class AccordCommandStore extends CommandStore implements CacheSize AccordObjectSizes::timestampsForKey); registerJfrListener(id, timestampsForKeyCache, "TimestampsForKey"); commandsForKeyCache = - stateCache.instance(Key.class, + stateCache.instance(RoutingKey.class, AccordSafeCommandsForKey.class, AccordSafeCommandsForKey::new, this::loadCommandsForKey, @@ -240,20 +266,12 @@ public class AccordCommandStore extends CommandStore implements CacheSize this.commandsForRangesLoader = new CommandsForRangesLoader(this); - AccordKeyspace.loadCommandStoreMetadata(id, ((rejectBefore, durableBefore, redundantBefore, bootstrapBeganAt, safeToRead) -> { - executor.submit(() -> { - if (rejectBefore != null) - super.setRejectBefore(rejectBefore); - if (durableBefore != null) - super.setDurableBefore(durableBefore); - if (redundantBefore != null) - super.setRedundantBefore(redundantBefore); - if (bootstrapBeganAt != null) - super.setBootstrapBeganAt(bootstrapBeganAt); - if (safeToRead != null) - super.setSafeToRead(safeToRead); - }); - })); + loadRedundantBefore(journal.loadRedundantBefore(id())); + loadDurableBefore(journal.loadDurableBefore(id())); + loadBootstrapBeganAt(journal.loadBootstrapBeganAt(id())); + loadSafeToRead(journal.loadSafeToRead(id())); + loadRangesForEpoch(journal.loadRangesForEpoch(id())); + loadHistoricalTransactions(journal.loadHistoricalTransactions(id())); executor.execute(() -> CommandStore.register(this)); } @@ -269,6 +287,12 @@ public class AccordCommandStore extends CommandStore implements CacheSize return commandsForRangesLoader; } + public void markShardDurable(SafeCommandStore safeStore, TxnId globalSyncId, Ranges ranges) + { + store.snapshot(ranges, globalSyncId); + super.markShardDurable(safeStore, globalSyncId, ranges); + } + @Override public boolean inStore() { @@ -304,14 +328,14 @@ public class AccordCommandStore extends CommandStore implements CacheSize public void checkInStoreThread() { - Invariants.checkState(inStore()); + checkState(inStore()); } public void checkNotInStoreThread() { if (!CHECK_THREADS) return; - Invariants.checkState(!inStore()); + checkState(!inStore()); } public ExecutorService executor() @@ -324,12 +348,12 @@ public class AccordCommandStore extends CommandStore implements CacheSize return commandCache; } - public AccordStateCache.Instance timestampsForKeyCache() + public AccordStateCache.Instance timestampsForKeyCache() { return timestampsForKeyCache; } - public AccordStateCache.Instance commandsForKeyCache() + public AccordStateCache.Instance commandsForKeyCache() { return commandsForKeyCache; } @@ -338,7 +362,7 @@ public class AccordCommandStore extends CommandStore implements CacheSize @VisibleForTesting public Runnable appendToKeyspace(Command before, Command after) { - if (after.keysOrRanges() != null && after.keysOrRanges() instanceof Keys) + if (after.txnId().is(Routable.Domain.Key)) return null; Mutation mutation = AccordKeyspace.getCommandMutation(this.id, before, after, nextSystemTimestampMicros()); @@ -350,13 +374,17 @@ public class AccordCommandStore extends CommandStore implements CacheSize return null; } + public void persistFieldUpdates(AccordSafeCommandStore.FieldUpdates fieldUpdates, Runnable onFlush) + { + journal.persistStoreState(id, fieldUpdates, onFlush); + } + + @Nullable @VisibleForTesting - public void appendToLog(Command before, Command after, Runnable runnable) + public void appendToLog(Command before, Command after, Runnable onFlush) { - journal.appendCommand(id, - Collections.singletonList(SavedCommand.diffWriter(before, after)), - null, runnable); + journal.appendCommand(id, SavedCommand.diff(before, after), onFlush); } boolean validateCommand(TxnId txnId, Command evicting) @@ -368,23 +396,29 @@ public class AccordCommandStore extends CommandStore implements CacheSize return Objects.equals(evicting, reloaded); } + @VisibleForTesting + public void sanityCheckCommand(Command command) + { + ((AccordJournal) journal).sanityCheck(id, command); + } + boolean validateTimestampsForKey(RoutableKey key, TimestampsForKey evicting) { if (!Invariants.isParanoid()) return true; - TimestampsForKey reloaded = AccordKeyspace.unsafeLoadTimestampsForKey(this, (PartitionKey) key); + TimestampsForKey reloaded = AccordKeyspace.unsafeLoadTimestampsForKey(this, (TokenKey) key); return Objects.equals(evicting, reloaded); } TimestampsForKey loadTimestampsForKey(RoutableKey key) { - return AccordKeyspace.loadTimestampsForKey(this, (PartitionKey) key); + return AccordKeyspace.loadTimestampsForKey(this, (TokenKey) key); } CommandsForKey loadCommandsForKey(RoutableKey key) { - return AccordKeyspace.loadCommandsForKey(this, (PartitionKey) key); + return AccordKeyspace.loadCommandsForKey(this, (TokenKey) key); } boolean validateCommandsForKey(RoutableKey key, CommandsForKey evicting) @@ -392,7 +426,7 @@ public class AccordCommandStore extends CommandStore implements CacheSize if (!Invariants.isParanoid()) return true; - CommandsForKey reloaded = AccordKeyspace.loadCommandsForKey(this, (PartitionKey) key); + CommandsForKey reloaded = AccordKeyspace.loadCommandsForKey(this, (TokenKey) key); return Objects.equals(evicting, reloaded); } @@ -424,19 +458,19 @@ public class AccordCommandStore extends CommandStore implements CacheSize public void setCurrentOperation(AsyncOperation operation) { - Invariants.checkState(currentOperation == null); + checkState(currentOperation == null); currentOperation = operation; } public AsyncOperation getContext() { - Invariants.checkState(currentOperation != null); + checkState(currentOperation != null); return currentOperation; } public void unsetCurrentOperation(AsyncOperation operation) { - Invariants.checkState(currentOperation == operation); + checkState(currentOperation == operation); currentOperation = null; } @@ -496,11 +530,11 @@ public class AccordCommandStore extends CommandStore implements CacheSize public AccordSafeCommandStore beginOperation(PreLoadContext preLoadContext, Map commands, - NavigableMap timestampsForKeys, - NavigableMap commandsForKeys, + NavigableMap timestampsForKeys, + NavigableMap commandsForKeys, @Nullable AccordSafeCommandsForRanges commandsForRanges) { - Invariants.checkState(current == null); + checkState(current == null); commands.values().forEach(AccordSafeState::preExecute); commandsForKeys.values().forEach(AccordSafeState::preExecute); timestampsForKeys.values().forEach(AccordSafeState::preExecute); @@ -518,7 +552,7 @@ public class AccordCommandStore extends CommandStore implements CacheSize public void completeOperation(AccordSafeCommandStore store) { - Invariants.checkState(current == store); + checkState(current == store); try { current.postExecute(); @@ -538,50 +572,63 @@ public class AccordCommandStore extends CommandStore implements CacheSize public void shutdown() { executor.shutdown(); + try + { + executor.awaitTermination(20, TimeUnit.SECONDS); + } + catch (InterruptedException t) + { + throw new RuntimeException("Could not shut down command store " + this); + } } - protected void setRejectBefore(ReducingRangeMap newRejectBefore) + public void registerHistoricalTransactions(Deps deps, SafeCommandStore safeStore) { - super.setRejectBefore(newRejectBefore); - // TODO (required, correctness): rework to persist via journal once available, this can lose updates in some edge cases - AccordKeyspace.updateRejectBefore(this, newRejectBefore); + if (deps.isEmpty()) return; + + CommandStores.RangesForEpoch ranges = safeStore.ranges(); + // used in places such as accord.local.CommandStore.fetchMajorityDeps + // We find a set of dependencies for a range then update CommandsFor to know about them + Ranges allRanges = safeStore.ranges().all(); + deps.keyDeps.keys().forEach(allRanges, key -> { + // TODO (now): batch register to minimise GC + deps.keyDeps.forEach(key, (txnId, txnIdx) -> { + // TODO (desired, efficiency): this can be made more efficient by batching by epoch + if (ranges.coordinates(txnId).contains(key)) + return; // already coordinates, no need to replicate + if (!ranges.allBefore(txnId.epoch()).contains(key)) + return; + + safeStore.get(key).registerHistorical(safeStore, txnId); + }); + }); + for (int i = 0; i < deps.rangeDeps.rangeCount(); i++) + { + Range range = deps.rangeDeps.range(i); + if (!allRanges.intersects(range)) + continue; + deps.rangeDeps.forEach(range, txnId -> { + // TODO (desired, efficiency): this can be made more efficient by batching by epoch + if (ranges.coordinates(txnId).intersects(range)) + return; // already coordinates, no need to replicate + if (!ranges.allBefore(txnId.epoch()).intersects(range)) + return; + + diskCommandsForRanges().mergeHistoricalTransaction(txnId, Ranges.single(range).slice(allRanges), Ranges::with); + }); + } } - protected void setBootstrapBeganAt(NavigableMap newBootstrapBeganAt) - { - super.setBootstrapBeganAt(newBootstrapBeganAt); - // TODO (required, correctness): rework to persist via journal once available, this can lose updates in some edge cases - AccordKeyspace.updateBootstrapBeganAt(this, newBootstrapBeganAt); - } - - protected void setSafeToRead(NavigableMap newSafeToRead) - { - super.setSafeToRead(newSafeToRead); - // TODO (required, correctness): rework to persist via journal once available, this can lose updates in some edge cases - AccordKeyspace.updateSafeToRead(this, newSafeToRead); - } - - @Override - public void setDurableBefore(DurableBefore newDurableBefore) - { - super.setDurableBefore(newDurableBefore); - AccordKeyspace.updateDurableBefore(this, newDurableBefore); - } - - @Override - protected void setRedundantBefore(RedundantBefore newRedundantBefore) - { - super.setRedundantBefore(newRedundantBefore); - // TODO (required): this needs to be synchronous, or at least needs to take effect before we rely upon it - AccordKeyspace.updateRedundantBefore(this, newRedundantBefore); - } - - public NavigableMap bootstrapBeganAt() { return super.bootstrapBeganAt(); } public NavigableMap safeToRead() { return super.safeToRead(); } - public void appendCommands(List> commands, List sanityCheck, Runnable onFlush) + public void appendCommands(List diffs, Runnable onFlush) { - journal.appendCommand(id, commands, sanityCheck, onFlush); + for (int i = 0; i < diffs.size(); i++) + { + boolean isLast = i == diffs.size() - 1; + SavedCommand.DiffWriter writer = diffs.get(i); + journal.appendCommand(id, writer, isLast ? onFlush : null); + } } @VisibleForTesting @@ -589,4 +636,146 @@ public class AccordCommandStore extends CommandStore implements CacheSize { return journal.loadCommand(id, txnId); } + + public interface Loader + { + Promise load(Command next); + Promise apply(Command next); + } + + public Loader loader() + { + return new Loader() + { + private PreLoadContext context(Command command, KeyHistory keyHistory) + { + TxnId txnId = command.txnId(); + Participants keys = null; + List deps = null; + if (CommandsForKey.manages(txnId)) + keys = command.hasBeen(Committed) ? command.participants().hasTouched() : command.participants().touches(); + else if (!CommandsForKey.managesExecution(txnId) && command.hasBeen(Status.Stable) && !command.hasBeen(Status.Truncated)) + keys = command.asCommitted().waitingOn.keys; + + if (command.partialDeps() != null) + deps = command.partialDeps().txnIds(); + + if (keys != null) + { + if (deps != null) + return PreLoadContext.contextFor(txnId, deps, keys, keyHistory); + + return PreLoadContext.contextFor(txnId, keys, keyHistory); + } + + return PreLoadContext.contextFor(txnId); + } + + public Promise load(Command command) + { + TxnId txnId = command.txnId(); + + AsyncPromise future = new AsyncPromise<>(); + execute(context(command, KeyHistory.COMMANDS), + safeStore -> { + Command local = command; + if (local.status() != Truncated && local.status() != Invalidated) + { + Cleanup cleanup = Cleanup.shouldCleanup(AccordCommandStore.this, local, local.participants()); + switch (cleanup) + { + case NO: + break; + case INVALIDATE: + case TRUNCATE_WITH_OUTCOME: + case TRUNCATE: + case ERASE: + local = Commands.purge(local, local.participants(), cleanup); + } + } + + local = safeStore.unsafeGet(txnId).update(safeStore, local); + if (local.status() == Truncated) + safeStore.progressLog().clear(local.txnId()); + }) + .begin((unused, throwable) -> { + if (throwable != null) + future.setFailure(throwable); + else + future.setSuccess(null); + }); + return future; + } + + public Promise apply(Command command) + { + TxnId txnId = command.txnId(); + + AsyncPromise future = new AsyncPromise<>(); + PreLoadContext context = context(command, KeyHistory.TIMESTAMPS); + execute(context, + safeStore -> { + SafeCommand safeCommand = safeStore.unsafeGet(txnId); + Command local = safeCommand.current(); + if (local.is(Stable) || local.is(PreApplied)) + Commands.maybeExecute(safeStore, safeCommand, local, true, true); + else if (local.saveStatus().compareTo(Applying) >= 0 && !local.hasBeen(Truncated)) + Commands.applyWrites(safeStore, context, local).begin(agent); + }) + .begin((unused, throwable) -> { + if (throwable != null) + future.setFailure(throwable); + else + future.setSuccess(null); + }); + return future; + } + }; + } + + /** + * Replay/state reloading + */ + + void loadRedundantBefore(RedundantBefore redundantBefore) + { + if (redundantBefore != null) + unsafeSetRedundantBefore(redundantBefore); + } + + void loadDurableBefore(DurableBefore durableBefore) + { + if (durableBefore != null) + unsafeSetDurableBefore(durableBefore); + } + + void loadBootstrapBeganAt(NavigableMap bootstrapBeganAt) + { + if (bootstrapBeganAt != null) + unsafeSetBootstrapBeganAt(bootstrapBeganAt); + } + + void loadSafeToRead(NavigableMap safeToRead) + { + if (safeToRead != null) + unsafeSetSafeToRead(safeToRead); + } + + void loadRangesForEpoch(CommandStores.RangesForEpoch.Snapshot rangesForEpoch) + { + if (rangesForEpoch != null) + unsafeSetRangesForEpoch(new CommandStores.RangesForEpoch(rangesForEpoch.epochs, rangesForEpoch.ranges, this)); + } + + void loadHistoricalTransactions(List deps) + { + if (deps != null) + { + execute(PreLoadContext.empty(), + safeStore -> { + for (Deps dep : deps) + registerHistoricalTransactions(dep, safeStore); + }); + } + } } diff --git a/src/java/org/apache/cassandra/service/accord/AccordConfigurationService.java b/src/java/org/apache/cassandra/service/accord/AccordConfigurationService.java index e0d1d84020..09a04140c9 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordConfigurationService.java +++ b/src/java/org/apache/cassandra/service/accord/AccordConfigurationService.java @@ -29,6 +29,9 @@ import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Sets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import accord.impl.AbstractConfigurationService; import accord.local.Node; import accord.primitives.Ranges; @@ -50,7 +53,10 @@ import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.service.accord.AccordKeyspace.EpochDiskState; import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadataService; +import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.listeners.ChangeListener; +import org.apache.cassandra.tcm.membership.NodeId; +import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.Simulate; import org.apache.cassandra.utils.concurrent.AsyncPromise; import org.apache.cassandra.utils.concurrent.Future; @@ -61,6 +67,8 @@ import static org.apache.cassandra.utils.Simulate.With.MONITORS; @Simulate(with=MONITORS) public class AccordConfigurationService extends AbstractConfigurationService implements ChangeListener, AccordEndpointMapper, AccordSyncPropagator.Listener, Shutdownable { + private static final Logger logger = LoggerFactory.getLogger(AccordConfigurationService.class); + private final AccordSyncPropagator syncPropagator; private final DiskStateManager diskStateManager; @@ -393,8 +401,16 @@ public class AccordConfigurationService extends AbstractConfigurationService> ranges = new ArrayList<>(); + CommitLogPosition position; + } + + @Override + public AsyncResult snapshot(Ranges ranges, TxnId before) // TODO: does this have to go to journal, too? + { + AsyncResults.SettableResult result = new AsyncResults.SettableResult<>(); + // TODO: maintain a list of Accord tables, perhaps in ClusterMetadata? + ClusterMetadata metadata = ClusterMetadata.current(); + Object2ObjectHashMap tables = new Object2ObjectHashMap<>(); + for (Range range : ranges) + { + tables.computeIfAbsent(((TokenRange)range).table(), ignore -> new SnapshotBounds()) + .ranges.add(((TokenRange) range).toKeyspaceRange()); + } + + for (Map.Entry e : tables.entrySet()) + { + TableMetadata tableMetadata = metadata.schema.getTableMetadata(e.getKey()); + if (!tableMetadata.isAccordEnabled()) + continue; + + ColumnFamilyStore cfs = Keyspace.openAndGetStoreIfExists(tableMetadata); + // TODO (required): when we can safely map TxnId.hlc() -> local timestamp, consult Memtable timestamps + e.getValue().position = cfs.getCurrentMemtable().getCommitLogLowerBound(); + } + + ScheduledExecutors.scheduledTasks.schedule(() -> { + List> futures = new ArrayList<>(); + for (Map.Entry e : tables.entrySet()) + { + TableMetadata tableMetadata = metadata.schema.getTableMetadata(e.getKey()); + SnapshotBounds bounds = e.getValue(); + ColumnFamilyStore cfs = Keyspace.openAndGetStoreIfExists(tableMetadata); + View view = cfs.getTracker().getView(); + for (Memtable memtable : view.getAllMemtables()) + { + if (memtable.getCommitLogLowerBound().compareTo(bounds.position) > 0) continue; + if (!intersects(cfs, memtable, bounds.ranges)) continue; + + futures.add(cfs.forceFlush(ACCORD_TXN_GC)); + break; + } + } + + FutureCombiner.allOf(futures).addCallback((objects, throwable) -> { + if (throwable != null) + result.setFailure(throwable); + else + result.setSuccess(null); + }); + }, 5L, TimeUnit.MINUTES); + + return result; + } + + private boolean intersects(ColumnFamilyStore cfs, Memtable memtable, List> tableRanges) + { + boolean intersects = false; + // TrieMemtable doesn't support reverse iteration so can't find the last token + if (memtable instanceof TrieMemtable) + intersects = true; + else + { + Token firstToken = null; + try (UnfilteredPartitionIterator iterator = memtable.partitionIterator(ColumnFilter.all(cfs.metadata()), DataRange.allData(cfs.getPartitioner()), SSTableReadsListener.NOOP_LISTENER)) + { + if (iterator.hasNext()) + firstToken = iterator.next().partitionKey().getToken(); + } + Token lastToken = memtable.lastToken(); + + if (firstToken != null) + { + checkState(lastToken != null); + if (firstToken.equals(lastToken)) + { + for (org.apache.cassandra.dht.Range tableRange : tableRanges) + { + if (tableRange.contains(firstToken)) + { + intersects = true; + break; + } + } + } + else + { + checkState(firstToken.compareTo(lastToken) < 0); + org.apache.cassandra.dht.Range memtableRange = new org.apache.cassandra.dht.Range<>(firstToken, lastToken); + for (org.apache.cassandra.dht.Range tableRange : tableRanges) + { + if (tableRange.intersects(memtableRange)) + { + intersects = true; + break; + } + } + } + } + } + + return intersects; + } } diff --git a/src/java/org/apache/cassandra/service/accord/AccordFetchCoordinator.java b/src/java/org/apache/cassandra/service/accord/AccordFetchCoordinator.java index e4ad2aa559..61cb7a4e28 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordFetchCoordinator.java +++ b/src/java/org/apache/cassandra/service/accord/AccordFetchCoordinator.java @@ -292,7 +292,7 @@ public class AccordFetchCoordinator extends AbstractFetchCoordinator implements public Read slice(Ranges ranges) { return new StreamingRead(to, this.ranges.slice(ranges)); } @Override - public Read intersecting(Participants participants) { return new StreamingRead(to, this.ranges.intersecting(ranges)); } + public Read intersecting(Participants participants) { return new StreamingRead(to, this.ranges.slice(ranges)); } @Override public Read merge(Read other) { throw new UnsupportedOperationException(); } diff --git a/src/java/org/apache/cassandra/service/accord/AccordJournal.java b/src/java/org/apache/cassandra/service/accord/AccordJournal.java index 80ff9739ce..f497b591b2 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordJournal.java +++ b/src/java/org/apache/cassandra/service/accord/AccordJournal.java @@ -20,31 +20,33 @@ package org.apache.cassandra.service.accord; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.NavigableMap; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BiConsumer; +import java.util.concurrent.atomic.AtomicBoolean; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import accord.coordinate.Timeout; import accord.local.Command; +import accord.local.CommandStores; +import accord.local.CommandStores.RangesForEpoch; +import accord.local.DurableBefore; import accord.local.Node; -import accord.messages.ReplyContext; -import accord.messages.Request; +import accord.local.RedundantBefore; +import accord.primitives.SaveStatus; +import accord.primitives.Deps; +import accord.primitives.Ranges; +import accord.primitives.Timestamp; import accord.primitives.TxnId; import accord.utils.Invariants; -import org.agrona.collections.Long2ObjectHashMap; -import org.agrona.collections.LongArrayList; -import org.apache.cassandra.concurrent.InfiniteLoopExecutor; -import org.apache.cassandra.concurrent.Interruptible; -import org.apache.cassandra.concurrent.ManyToOneConcurrentLinkedQueue; import org.apache.cassandra.concurrent.Shutdownable; import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.io.util.DataInputBuffer; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.io.util.File; @@ -52,19 +54,20 @@ import org.apache.cassandra.journal.Journal; import org.apache.cassandra.journal.Params; import org.apache.cassandra.journal.RecordPointer; import org.apache.cassandra.journal.ValueSerializer; -import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.net.MessagingService; -import org.apache.cassandra.net.ResponseContext; -import org.apache.cassandra.net.Verb; +import org.apache.cassandra.service.accord.AccordJournalValueSerializers.HistoricalTransactionsAccumulator; +import org.apache.cassandra.service.accord.AccordJournalValueSerializers.IdentityAccumulator; import org.apache.cassandra.utils.ExecutorUtils; -import org.apache.cassandra.utils.concurrent.Condition; -import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; -import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe.SAFE; -import static org.apache.cassandra.concurrent.Interruptible.State.NORMAL; +import static accord.primitives.Status.Truncated; +import static org.apache.cassandra.service.accord.AccordJournalValueSerializers.DurableBeforeAccumulator; +import static org.apache.cassandra.service.accord.AccordJournalValueSerializers.RedundantBeforeAccumulator; public class AccordJournal implements IJournal, Shutdownable { + + private final AtomicBoolean isReplay = new AtomicBoolean(false); + static { // make noise early if we forget to update our version mappings @@ -75,13 +78,10 @@ public class AccordJournal implements IJournal, Shutdownable private static final Set SENTINEL_HOSTS = Collections.singleton(0); - static final ThreadLocal keyCRCBytes = ThreadLocal.withInitial(() -> new byte[22]); + static final ThreadLocal keyCRCBytes = ThreadLocal.withInitial(() -> new byte[23]); private final Journal journal; private final AccordJournalTable journalTable; - private final AccordEndpointMapper endpointMapper; - - private final DelayedRequestProcessor delayedRequestProcessor = new DelayedRequestProcessor(); Node node; @@ -89,30 +89,26 @@ public class AccordJournal implements IJournal, Shutdownable private volatile Status status = Status.INITIALIZED; @VisibleForTesting - public AccordJournal(AccordEndpointMapper endpointMapper, Params params) + public AccordJournal(Params params) { File directory = new File(DatabaseDescriptor.getAccordJournalDirectory()); this.journal = new Journal<>("AccordJournal", directory, params, JournalKey.SUPPORT, // In Accord, we are using streaming serialization, i.e. Reader/Writer interfaces instead of materializing objects new ValueSerializer<>() { - public int serializedSize(JournalKey key, Object value, int userVersion) - { - throw new UnsupportedOperationException(); - } - + @Override public void serialize(JournalKey key, Object value, DataOutputPlus out, int userVersion) { throw new UnsupportedOperationException(); } + @Override public Object deserialize(JournalKey key, DataInputPlus in, int userVersion) { throw new UnsupportedOperationException(); } }, - new AccordSegmentCompactor<>()); - this.endpointMapper = endpointMapper; + new AccordSegmentCompactor<>(JournalKey.SUPPORT, params.userVersion())); this.journalTable = new AccordJournalTable<>(journal, JournalKey.SUPPORT, params.userVersion()); } @@ -122,7 +118,6 @@ public class AccordJournal implements IJournal, Shutdownable this.node = node; status = Status.STARTING; journal.start(); - delayedRequestProcessor.start(); status = Status.STARTED; return this; } @@ -138,7 +133,6 @@ public class AccordJournal implements IJournal, Shutdownable { Invariants.checkState(status == Status.STARTED); status = Status.TERMINATING; - delayedRequestProcessor.shutdown(); journal.shutdown(); status = Status.TERMINATED; } @@ -164,67 +158,120 @@ public class AccordJournal implements IJournal, Shutdownable } } - /** - * Accord protocol messages originating from remote nodes. - */ - public void processRemoteRequest(Request request, ResponseContext context) - { - RemoteRequestContext requestContext = RemoteRequestContext.forLive(request, context); - if (node.topology().hasEpoch(request.waitForEpoch())) - requestContext.process(node, endpointMapper); - else - delayedRequestProcessor.delay(requestContext); - } - @Override public Command loadCommand(int commandStoreId, TxnId txnId) { - try + return loadDiffs(commandStoreId, txnId).construct(); + } + + @VisibleForTesting + public RedundantBefore loadRedundantBefore(int store) + { + RedundantBeforeAccumulator accumulator = readAll(new JournalKey(Timestamp.NONE, JournalKey.Type.REDUNDANT_BEFORE, store)); + return accumulator.get(); + } + + @Override + public DurableBefore loadDurableBefore(int store) + { + DurableBeforeAccumulator accumulator = readAll(new JournalKey(Timestamp.NONE, JournalKey.Type.DURABLE_BEFORE, store)); + return accumulator.get(); + } + + @Override + public NavigableMap loadBootstrapBeganAt(int store) + { + IdentityAccumulator> accumulator = readAll(new JournalKey(Timestamp.NONE, JournalKey.Type.BOOTSTRAP_BEGAN_AT, store)); + return accumulator.get(); + } + + @Override + public NavigableMap loadSafeToRead(int store) + { + IdentityAccumulator> accumulator = readAll(new JournalKey(Timestamp.NONE, JournalKey.Type.SAFE_TO_READ, store)); + return accumulator.get(); + } + + @Override + public CommandStores.RangesForEpoch.Snapshot loadRangesForEpoch(int store) + { + IdentityAccumulator accumulator = readAll(new JournalKey(Timestamp.NONE, JournalKey.Type.RANGES_FOR_EPOCH, store)); + return accumulator.get(); + } + + @Override + public List loadHistoricalTransactions(int store) + { + HistoricalTransactionsAccumulator accumulator = readAll(new JournalKey(Timestamp.NONE, JournalKey.Type.HISTORICAL_TRANSACTIONS, store)); + return accumulator.get(); + } + + @Override + public void appendCommand(int store, SavedCommand.DiffWriter value, Runnable onFlush) + { + if (value == null || isReplay.get()) { - return loadDiffs(commandStoreId, txnId).construct(); - } - catch (IOException e) - { - // can only throw if serializer is buggy - throw new RuntimeException(e); + if (onFlush != null) + onFlush.run(); + return; } + + // TODO: use same API for commands as for the other states? + JournalKey key = new JournalKey(value.key(), JournalKey.Type.COMMAND_DIFF, store); + RecordPointer pointer = journal.asyncWrite(key, value, SENTINEL_HOSTS); + if (onFlush != null) + journal.onFlush(pointer, onFlush); + } + + @Override + public void persistStoreState(int store, AccordSafeCommandStore.FieldUpdates fieldUpdates, Runnable onFlush) + { + RecordPointer pointer = null; + // TODO: avoid allocating keys + if (fieldUpdates.redundantBefore != null) + pointer = appendInternal(new JournalKey(Timestamp.NONE, JournalKey.Type.REDUNDANT_BEFORE, store), fieldUpdates.redundantBefore); + if (fieldUpdates.durableBefore != null) + pointer = appendInternal(new JournalKey(Timestamp.NONE, JournalKey.Type.DURABLE_BEFORE, store), fieldUpdates.durableBefore); + if (fieldUpdates.bootstrapBeganAt != null) + pointer = appendInternal(new JournalKey(Timestamp.NONE, JournalKey.Type.BOOTSTRAP_BEGAN_AT, store), fieldUpdates.bootstrapBeganAt); + if (fieldUpdates.safeToRead != null) + pointer = appendInternal(new JournalKey(Timestamp.NONE, JournalKey.Type.SAFE_TO_READ, store), fieldUpdates.safeToRead); + if (fieldUpdates.rangesForEpoch != null) + pointer = appendInternal(new JournalKey(Timestamp.NONE, JournalKey.Type.RANGES_FOR_EPOCH, store), fieldUpdates.rangesForEpoch); + if (fieldUpdates.historicalTransactions != null) + pointer = appendInternal(new JournalKey(Timestamp.NONE, JournalKey.Type.HISTORICAL_TRANSACTIONS, store), fieldUpdates.historicalTransactions); + + if (onFlush == null) + return; + + if (pointer != null) + journal.onFlush(pointer, onFlush); + else + onFlush.run(); } @VisibleForTesting public SavedCommand.Builder loadDiffs(int commandStoreId, TxnId txnId) { - JournalKey key = new JournalKey(txnId, commandStoreId); + JournalKey key = new JournalKey(txnId, JournalKey.Type.COMMAND_DIFF, commandStoreId); SavedCommand.Builder builder = new SavedCommand.Builder(); - journalTable.readAll(key, (ignore, in, userVersion) -> builder.deserializeNext(in, userVersion)); + journalTable.readAll(key, builder::deserializeNext); return builder; } - @Override - public void appendCommand(int commandStoreId, List> outcomes, List sanityCheck, Runnable onFlush) + private BUILDER readAll(JournalKey key) { - RecordPointer pointer = null; - for (SavedCommand.Writer outcome : outcomes) - { - JournalKey key = new JournalKey(outcome.key(), commandStoreId); - pointer = journal.asyncWrite(key, outcome, SENTINEL_HOSTS); - } + BUILDER builder = (BUILDER) key.type.serializer.mergerFor(key); + // TODO: this can be further improved to avoid allocating lambdas + AccordJournalValueSerializers.FlyweightSerializer serializer = (AccordJournalValueSerializers.FlyweightSerializer) key.type.serializer; + journalTable.readAll(key, (in, userVersion) -> serializer.deserialize(key, builder, in, userVersion)); + return builder; + } - // If we need to perform sanity check, we can only rely on blocking flushes. Otherwise, we may see into the future. - if (sanityCheck != null) - { - Condition condition = Condition.newOneTimeCondition(); - journal.onFlush(pointer, condition::signal); - condition.awaitUninterruptibly(); - - for (Command check : sanityCheck) - sanityCheck(commandStoreId, check); - - onFlush.run(); - } - else - { - journal.onFlush(pointer, onFlush); - } + private RecordPointer appendInternal(JournalKey key, Object write) + { + AccordJournalValueSerializers.FlyweightSerializer serializer = (AccordJournalValueSerializers.FlyweightSerializer) key.type.serializer; + return journal.asyncWrite(key, (out, userVersion) -> serializer.serialize(key, write, out, userVersion), SENTINEL_HOSTS); } @VisibleForTesting @@ -235,234 +282,15 @@ public class AccordJournal implements IJournal, Shutdownable public void sanityCheck(int commandStoreId, Command orig) { - try - { - SavedCommand.Builder diffs = loadDiffs(commandStoreId, orig.txnId()); - diffs.forceResult(orig.result()); - // We can only use strict equality if we supply result. - Command reconstructed = diffs.construct(); - Invariants.checkState(orig.equals(reconstructed), - '\n' + - "Original: %s\n" + - "Reconstructed: %s\n" + - "Diffs: %s", orig, reconstructed, diffs); - } - catch (IOException e) - { - // can only throw if serializer is buggy - throw new RuntimeException(e); - } - } - - /* - * Context necessary to process log records - */ - - static abstract class RequestContext implements ReplyContext - { - final long waitForEpoch; - - RequestContext(long waitForEpoch) - { - this.waitForEpoch = waitForEpoch; - } - - public abstract void process(Node node, AccordEndpointMapper endpointMapper); - } - - /** - * Barebones response context not holding a reference to the entire message - */ - private abstract static class RemoteRequestContext extends RequestContext implements ResponseContext - { - private final Request request; - - RemoteRequestContext(long waitForEpoch, Request request) - { - super(waitForEpoch); - this.request = request; - } - - static LiveRemoteRequestContext forLive(Request request, ResponseContext context) - { - return new LiveRemoteRequestContext(request, context.id(), context.from(), context.verb(), context.expiresAtNanos()); - } - - @Override - public void process(Node node, AccordEndpointMapper endpointMapper) - { - this.request.process(node, endpointMapper.mappedId(from()), this); - } - - @Override public abstract long id(); - @Override public abstract InetAddressAndPort from(); - @Override public abstract Verb verb(); - @Override public abstract long expiresAtNanos(); - } - - // TODO: avoid distinguishing between live and non live - private static class LiveRemoteRequestContext extends RemoteRequestContext - { - private final long id; - private final InetAddressAndPort from; - private final Verb verb; - private final long expiresAtNanos; - - LiveRemoteRequestContext(Request request, long id, InetAddressAndPort from, Verb verb, long expiresAtNanos) - { - super(request.waitForEpoch(), request); - this.id = id; - this.from = from; - this.verb = verb; - this.expiresAtNanos = expiresAtNanos; - } - - - @Override - public long id() - { - return id; - } - @Override - public InetAddressAndPort from() - { - return from; - } - @Override - public Verb verb() - { - return verb; - } - @Override - public long expiresAtNanos() - { - return expiresAtNanos; - } - } - - /* - * Handling topology changes / epoch shift - */ - - private class DelayedRequestProcessor implements Interruptible.Task - { - private final ManyToOneConcurrentLinkedQueue delayedRequests = new ManyToOneConcurrentLinkedQueue<>(); - private final LongArrayList waitForEpochs = new LongArrayList(); - private final Long2ObjectHashMap> byEpoch = new Long2ObjectHashMap<>(); - private final AtomicReference signal = new AtomicReference<>(Condition.newOneTimeCondition()); - private volatile Interruptible executor; - - public void start() - { - executor = executorFactory().infiniteLoop("AccordJournal-delayed-request-processor", this, SAFE, InfiniteLoopExecutor.Daemon.NON_DAEMON, InfiniteLoopExecutor.Interrupts.SYNCHRONIZED); - } - - private void delay(RequestContext requestContext) - { - delayedRequests.add(requestContext); - runOnce(); - } - - private void runOnce() - { - signal.get().signal(); - } - - @Override - public void run(Interruptible.State state) - { - if (state != NORMAL || Thread.currentThread().isInterrupted() || !isRunnable(status)) - return; - - try - { - Condition signal = Condition.newOneTimeCondition(); - this.signal.set(signal); - // First, poll delayed requests, put them into by epoch - while (!delayedRequests.isEmpty()) - { - RequestContext context = delayedRequests.poll(); - long waitForEpoch = context.waitForEpoch; - - List l = byEpoch.computeIfAbsent(waitForEpoch, (ignore) -> new ArrayList<>()); - if (l.isEmpty()) - waitForEpochs.pushLong(waitForEpoch); - l.add(context); - 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 - for (int i = 0; i < waitForEpochs.size(); i++) - { - long epoch = waitForEpochs.getLong(i); - if (node.topology().hasEpoch(epoch)) - { - List requests = byEpoch.remove(epoch); - assert requests != null : String.format("%s %s (%d)", byEpoch, waitForEpochs, epoch); - for (RequestContext request : requests) - { - try - { - request.process(node, endpointMapper); - } - catch (Throwable t) - { - logger.error("Caught an exception while processing a delayed request {}", request, t); - } - } - } - } - - waitForEpochs.removeIfLong(epoch -> !byEpoch.containsKey(epoch)); - - signal.await(); - } - catch (InterruptedException e) - { - logger.info("Delayed request processor thread interrupted. Shutting down."); - } - catch (Throwable t) - { - logger.error("Caught an exception in delayed processor", t); - } - } - - private void shutdown() - { - executor.shutdown(); - try - { - executor.awaitTermination(1, TimeUnit.MINUTES); - } - catch (InterruptedException e) - { - throw new RuntimeException(e); - } - } - } - - private boolean isRunnable(Status status) - { - return status != Status.TERMINATING && status != Status.TERMINATED; + SavedCommand.Builder diffs = loadDiffs(commandStoreId, orig.txnId()); + diffs.forceResult(orig.result()); + // We can only use strict equality if we supply result. + Command reconstructed = diffs.construct(); + Invariants.checkState(orig.equals(reconstructed), + '\n' + + "Original: %s\n" + + "Reconstructed: %s\n" + + "Diffs: %s", orig, reconstructed, diffs); } @VisibleForTesting @@ -470,4 +298,83 @@ public class AccordJournal implements IJournal, Shutdownable { journal.truncateForTesting(); } + + @VisibleForTesting + public void runCompactorForTesting() + { + journal.runCompactorForTesting(); + } + + public void replay() + { + // TODO: optimize replay memory footprint + class ToApply + { + final JournalKey key; + final Command command; + + ToApply(JournalKey key, Command command) + { + this.key = key; + this.command = command; + } + } + + List toApply = new ArrayList<>(); + try (AccordJournalTable.KeyOrderIterator iter = journalTable.readAll()) + { + isReplay.set(true); + + JournalKey key = null; + SavedCommand.Builder builder = new SavedCommand.Builder(); + while ((key = iter.key()) != null) + { + builder.clear(); + if (key.type != JournalKey.Type.COMMAND_DIFF) + { + // TODO (required): add "skip" for the key to avoid getting stuck + iter.readAllForKey(key, (segment, position, key1, buffer, hosts, userVersion) -> {}); + continue; + } + + JournalKey finalKey = key; + iter.readAllForKey(key, (segment, position, local, buffer, hosts, userVersion) -> { + Invariants.checkState(finalKey.equals(local)); + try (DataInputBuffer in = new DataInputBuffer(buffer, false)) + { + builder.deserializeNext(in, userVersion); + } + catch (IOException e) + { + // can only throw if serializer is buggy + throw new RuntimeException(e); + } + }); + + if (builder.nextCalled) + { + Command command = builder.construct(); + AccordCommandStore commandStore = (AccordCommandStore) node.commandStores().forId(key.commandStoreId); + commandStore.loader().load(command).get(); + if (command.saveStatus().compareTo(SaveStatus.Stable) >= 0 && !command.hasBeen(Truncated)) + toApply.add(new ToApply(key, command)); + } + } + + toApply.sort(Comparator.comparing(v -> v.command.executeAt())); + for (ToApply apply : toApply) + { + AccordCommandStore commandStore = (AccordCommandStore) node.commandStores().forId(apply.key.commandStoreId); + commandStore.loader().apply(apply.command); + } + } + catch (Throwable t) + { + throw new RuntimeException("Can not replay journal.", t); + } + finally + { + isReplay.set(false); + } + } } \ No newline at end of file diff --git a/src/java/org/apache/cassandra/service/accord/AccordJournalTable.java b/src/java/org/apache/cassandra/service/accord/AccordJournalTable.java index 642f49e417..ef3ac9eff3 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordJournalTable.java +++ b/src/java/org/apache/cassandra/service/accord/AccordJournalTable.java @@ -17,6 +17,7 @@ */ package org.apache.cassandra.service.accord; +import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -29,6 +30,7 @@ import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ColumnFamilyStore.RefViewFragment; import org.apache.cassandra.db.DecoratedKey; +import org.apache.cassandra.db.EmptyIterators; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.Slices; import org.apache.cassandra.db.StorageHook; @@ -37,10 +39,13 @@ import org.apache.cassandra.db.lifecycle.SSTableSet; import org.apache.cassandra.db.lifecycle.View; import org.apache.cassandra.db.marshal.Int32Type; import org.apache.cassandra.db.marshal.LongType; +import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator; +import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators; import org.apache.cassandra.db.rows.Row; import org.apache.cassandra.db.rows.Unfiltered; import org.apache.cassandra.db.rows.UnfilteredRowIterator; import org.apache.cassandra.db.rows.UnfilteredRowIterators; +import org.apache.cassandra.io.sstable.ISSTableScanner; import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.io.util.DataInputBuffer; import org.apache.cassandra.io.util.DataInputPlus; @@ -76,16 +81,16 @@ public class AccordJournalTable this.accordJournalVersion = accordJournalVersion; } - public interface Reader + public interface Reader { - void read(K key, DataInputPlus input, int userVersion) throws IOException; + void read(DataInputPlus input, int userVersion) throws IOException; } private abstract class AbstractRecordConsumer implements RecordConsumer { - protected final Reader reader; + protected final Reader reader; - AbstractRecordConsumer(Reader reader) + AbstractRecordConsumer(Reader reader) { this.reader = reader; } @@ -93,15 +98,7 @@ public class AccordJournalTable @Override public void accept(long segment, int position, K key, ByteBuffer buffer, IntHashSet hosts, int userVersion) { - try (DataInputBuffer in = new DataInputBuffer(buffer, false)) - { - reader.read(key, in, userVersion); - } - catch (IOException e) - { - // can only throw if serializer is buggy - throw new RuntimeException(e); - } + readBuffer(buffer, reader, userVersion); } } @@ -109,7 +106,7 @@ public class AccordJournalTable { protected LongHashSet visited = null; - TableRecordConsumer(Reader reader) + TableRecordConsumer(Reader reader) { super(reader); } @@ -139,7 +136,7 @@ public class AccordJournalTable private final K key; private final TableRecordConsumer tableRecordConsumer; - JournalAndTableRecordConsumer(K key, Reader reader) + JournalAndTableRecordConsumer(K key, Reader reader) { super(reader); this.key = key; @@ -165,7 +162,7 @@ public class AccordJournalTable *

* When reading from journal segments, skip descriptors that were read from the table. */ - public void readAll(K key, Reader reader) + public void readAll(K key, Reader reader) { journal.readAll(key, new JournalAndTableRecordConsumer(key, reader)); } @@ -195,20 +192,6 @@ public class AccordJournalTable } } - public static DecoratedKey makePartitionKey(ColumnFamilyStore cfs, K key, KeySupport keySupport, int version) - { - try (DataOutputBuffer out = new DataOutputBuffer(keySupport.serializedSize(version))) - { - keySupport.serialize(key, out, version); - return cfs.decorateKey(out.buffer(false)); - } - catch (IOException e) - { - // can only throw if (key) serializer is buggy - throw new RuntimeException("Could not serialize key " + key + ", this shouldn't be possible", e); - } - } - private void readRow(K key, Unfiltered unfiltered, EntryHolder into, RecordConsumer onEntry) { Invariants.checkState(unfiltered.isRow()); @@ -224,4 +207,167 @@ public class AccordJournalTable onEntry.accept(descriptor, position, into.key, into.value, into.hosts, into.userVersion); } + + public static DecoratedKey makePartitionKey(ColumnFamilyStore cfs, K key, KeySupport keySupport, int version) + { + try (DataOutputBuffer out = new DataOutputBuffer(keySupport.serializedSize(version))) + { + keySupport.serialize(key, out, version); + return cfs.decorateKey(out.buffer(false)); + } + catch (IOException e) + { + // can only throw if (key) serializer is buggy + throw new RuntimeException("Could not serialize key " + key + ", this shouldn't be possible", e); + } + } + + @SuppressWarnings("resource") // Auto-closeable iterator will release related resources + public KeyOrderIterator readAll() + { + return new JournalAndTableKeyIterator(); + } + + private class TableIterator implements Closeable + { + private final UnfilteredPartitionIterator mergeIterator; + private final RefViewFragment view; + + private UnfilteredRowIterator partition; + private LongHashSet visited = null; + + private TableIterator() + { + view = cfs.selectAndReference(v -> v.select(SSTableSet.LIVE)); + List scanners = new ArrayList<>(); + for (SSTableReader sstable : view.sstables) + scanners.add(sstable.getScanner()); + + mergeIterator = view.sstables.isEmpty() + ? EmptyIterators.unfilteredPartition(cfs.metadata()) + : UnfilteredPartitionIterators.merge(scanners, UnfilteredPartitionIterators.MergeListener.NOOP); + } + + public K key() + { + if (partition == null) + { + if (mergeIterator.hasNext()) + partition = mergeIterator.next(); + else + return null; + } + + return keySupport.deserialize(partition.partitionKey().getKey(), 0, accordJournalVersion); + } + + protected void readAllForKey(K key, RecordConsumer recordConsumer) + { + while (partition.hasNext()) + { + EntryHolder into = new EntryHolder<>(); + // TODO: use flyweight to avoid allocating extra lambdas? + readRow(key, partition.next(), into, (segment, position, key1, buffer, hosts, userVersion) -> { + visit(segment); + recordConsumer.accept(segment, position, key1, buffer, hosts, userVersion); + }); + } + + partition = null; + } + + void visit(long segment) + { + if (visited == null) + visited = new LongHashSet(); + visited.add(segment); + } + + boolean visited(long segment) + { + return visited != null && visited.contains(segment); + } + + + void clear() + { + visited = null; + } + + + @Override + public void close() + { + mergeIterator.close(); + view.close(); + } + } + + private class JournalAndTableKeyIterator implements KeyOrderIterator + { + final TableIterator tableIterator; + final Journal.StaticSegmentIterator staticSegmentIterator; + + private JournalAndTableKeyIterator() + { + this.tableIterator = new TableIterator(); + this.staticSegmentIterator = journal.staticSegmentIterator(); + } + + @Override + public K key() + { + K tableKey = tableIterator.key(); + K journalKey = staticSegmentIterator.key(); + if (tableKey == null) + return journalKey; + if (journalKey == null || keySupport.compare(tableKey, journalKey) > 0) + return journalKey; + + return tableKey; + } + + @Override + public void readAllForKey(K key, RecordConsumer reader) + { + K tableKey = tableIterator.key(); + K journalKey = staticSegmentIterator.key(); + if (tableKey != null && keySupport.compare(tableKey, key) == 0) + tableIterator.readAllForKey(key, reader); + + if (journalKey != null && keySupport.compare(journalKey, key) == 0) + staticSegmentIterator.readAllForKey(key, (segment, position, key1, buffer, hosts, userVersion) -> { + if (!tableIterator.visited(segment)) + reader.accept(segment, position, key1, buffer, hosts, userVersion); + }); + + tableIterator.clear(); + } + + public void close() + { + tableIterator.close(); + staticSegmentIterator.close(); + } + } + + public interface KeyOrderIterator extends Closeable + { + K key(); + void readAllForKey(K key, RecordConsumer reader); + void close(); + } + + public static void readBuffer(ByteBuffer buffer, Reader reader, int userVersion) + { + try (DataInputBuffer in = new DataInputBuffer(buffer, false)) + { + reader.read(in, userVersion); + } + catch (IOException e) + { + // can only throw if serializer is buggy + throw new RuntimeException(e); + } + } } \ No newline at end of file diff --git a/src/java/org/apache/cassandra/service/accord/AccordJournalValueSerializers.java b/src/java/org/apache/cassandra/service/accord/AccordJournalValueSerializers.java new file mode 100644 index 0000000000..f06ccd4c4f --- /dev/null +++ b/src/java/org/apache/cassandra/service/accord/AccordJournalValueSerializers.java @@ -0,0 +1,383 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.cassandra.service.accord; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.NavigableMap; + +import com.google.common.collect.ImmutableSortedMap; + +import accord.local.DurableBefore; +import accord.local.RedundantBefore; +import accord.primitives.Deps; +import accord.primitives.Ranges; +import accord.primitives.Timestamp; +import accord.primitives.TxnId; +import org.apache.cassandra.io.util.DataInputPlus; +import org.apache.cassandra.io.util.DataOutputPlus; +import org.apache.cassandra.net.MessagingService; +import org.apache.cassandra.service.accord.serializers.CommandStoreSerializers; +import org.apache.cassandra.service.accord.serializers.KeySerializers; + +import static accord.local.CommandStores.RangesForEpoch; +import static org.apache.cassandra.service.accord.serializers.DepsSerializer.deps; + +// TODO (required): test with large collection values, and perhaps split out some fields if they have a tendency to grow larger +// TODO (required): alert on metadata size +// TODO (required): versioning +public class AccordJournalValueSerializers +{ + private static final int messagingVersion = MessagingService.VERSION_40; + public interface FlyweightSerializer + { + IMAGE mergerFor(JournalKey key); + + void serialize(JournalKey key, ENTRY from, DataOutputPlus out, int userVersion) throws IOException; + + void reserialize(JournalKey key, IMAGE from, DataOutputPlus out, int userVersion) throws IOException; + + void deserialize(JournalKey key, IMAGE into, DataInputPlus in, int userVersion) throws IOException; + } + + public static class CommandDiffSerializer + implements FlyweightSerializer + { + @Override + public SavedCommand.Builder mergerFor(JournalKey journalKey) + { + return new SavedCommand.Builder(); + } + + @Override + public void serialize(JournalKey key, SavedCommand.DiffWriter writer, DataOutputPlus out, int userVersion) + { + try + { + writer.write(out, userVersion); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Override + public void reserialize(JournalKey key, SavedCommand.Builder from, DataOutputPlus out, int userVersion) throws IOException + { + from.serialize(out, userVersion); + } + + @Override + public void deserialize(JournalKey journalKey, SavedCommand.Builder into, DataInputPlus in, int userVersion) throws IOException + { + into.deserializeNext(in, userVersion); + } + } + + public abstract static class Accumulator + { + protected A accumulated; + + public Accumulator(A initial) + { + this.accumulated = initial; + } + + protected void update(V newValue) + { + accumulated = accumulate(accumulated, newValue); + } + + protected abstract A accumulate(A oldValue, V newValue); + + public A get() + { + return accumulated; + } + } + + public static class IdentityAccumulator extends Accumulator + { + public IdentityAccumulator(T initial) + { + super(initial); + } + + @Override + protected T accumulate(T oldValue, T newValue) + { + return newValue; + } + } + + public static class RedundantBeforeAccumulator extends Accumulator + { + public RedundantBeforeAccumulator() + { + super(RedundantBefore.EMPTY); + } + + @Override + protected RedundantBefore accumulate(RedundantBefore oldValue, RedundantBefore newValue) + { + return RedundantBefore.merge(oldValue, newValue); + } + } + + public static class RedundantBeforeSerializer + implements FlyweightSerializer + { + @Override + public RedundantBeforeAccumulator mergerFor(JournalKey journalKey) + { + return new RedundantBeforeAccumulator(); + } + + @Override + public void serialize(JournalKey key, RedundantBefore entry, DataOutputPlus out, int userVersion) + { + try + { + if (entry == RedundantBefore.EMPTY) + { + out.writeInt(0); + return; + } + out.writeInt(1); + CommandStoreSerializers.redundantBefore.serialize(entry, out, messagingVersion); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Override + public void reserialize(JournalKey key, RedundantBeforeAccumulator from, DataOutputPlus out, int userVersion) throws IOException + { + serialize(key, from.get(), out, userVersion); + } + + @Override + public void deserialize(JournalKey journalKey, RedundantBeforeAccumulator into, DataInputPlus in, int userVersion) throws IOException + { + if (in.readInt() == 0) + { + into.update(RedundantBefore.EMPTY); + return; + } + into.update(CommandStoreSerializers.redundantBefore.deserialize(in, messagingVersion)); + } + } + + public static class DurableBeforeAccumulator extends Accumulator + { + public DurableBeforeAccumulator() + { + super(DurableBefore.EMPTY); + } + + @Override + protected DurableBefore accumulate(DurableBefore oldValue, DurableBefore newValue) + { + return DurableBefore.merge(oldValue, newValue); + } + } + + public static class DurableBeforeSerializer implements FlyweightSerializer + { + public DurableBeforeAccumulator mergerFor(JournalKey journalKey) + { + return new DurableBeforeAccumulator(); + } + + @Override + public void serialize(JournalKey key, DurableBefore entry, DataOutputPlus out, int userVersion) + { + try + { + CommandStoreSerializers.durableBefore.serialize(entry, out, messagingVersion); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Override + public void reserialize(JournalKey key, DurableBeforeAccumulator from, DataOutputPlus out, int userVersion) throws IOException + { + serialize(key, from.get(), out, userVersion); + } + + @Override + public void deserialize(JournalKey journalKey, DurableBeforeAccumulator into, DataInputPlus in, int userVersion) throws IOException + { + // TODO: maybe using local serializer is not the best call here, but how do we distinguish + // between messaging and disk versioning? + into.update(CommandStoreSerializers.durableBefore.deserialize(in, messagingVersion)); + } + } + + public static class BootstrapBeganAtSerializer + implements FlyweightSerializer, IdentityAccumulator>> + { + @Override + public IdentityAccumulator> mergerFor(JournalKey key) + { + return new IdentityAccumulator<>(ImmutableSortedMap.of(TxnId.NONE, Ranges.EMPTY)); + } + + @Override + public void serialize(JournalKey key, NavigableMap entry, DataOutputPlus out, int userVersion) throws IOException + { + CommandStoreSerializers.bootstrapBeganAt.serialize(entry, out, messagingVersion); + } + + @Override + public void reserialize(JournalKey key, IdentityAccumulator> image, DataOutputPlus out, int userVersion) throws IOException + { + serialize(key, image.get(), out, userVersion); + } + + @Override + public void deserialize(JournalKey key, IdentityAccumulator> into, DataInputPlus in, int userVersion) throws IOException + { + into.update(CommandStoreSerializers.bootstrapBeganAt.deserialize(in, messagingVersion)); + } + } + + public static class SafeToReadSerializer implements FlyweightSerializer, IdentityAccumulator>> + { + @Override + public IdentityAccumulator> mergerFor(JournalKey key) + { + return new IdentityAccumulator<>(ImmutableSortedMap.of(Timestamp.NONE, Ranges.EMPTY)); + } + + @Override + public void serialize(JournalKey key, NavigableMap from, DataOutputPlus out, int userVersion) throws IOException + { + CommandStoreSerializers.safeToRead.serialize(from, out, messagingVersion); + } + + @Override + public void reserialize(JournalKey key, IdentityAccumulator> from, DataOutputPlus out, int userVersion) throws IOException + { + serialize(key, from.get(), out, userVersion); + } + + @Override + public void deserialize(JournalKey key, IdentityAccumulator> into, DataInputPlus in, int userVersion) throws IOException + { + into.update(CommandStoreSerializers.safeToRead.deserialize(in, messagingVersion)); + } + } + + public static class RangesForEpochSerializer + implements FlyweightSerializer> + { + + public IdentityAccumulator mergerFor(JournalKey key) + { + return new IdentityAccumulator<>(null); + } + + @Override + public void serialize(JournalKey key, RangesForEpoch.Snapshot from, DataOutputPlus out, int userVersion) throws IOException + { + out.writeUnsignedVInt32(from.ranges.length); + for (Ranges ranges : from.ranges) + KeySerializers.ranges.serialize(ranges, out, messagingVersion); + + out.writeUnsignedVInt32(from.epochs.length); + for (long epoch : from.epochs) + out.writeLong(epoch); + } + + @Override + public void reserialize(JournalKey key, IdentityAccumulator from, DataOutputPlus out, int userVersion) throws IOException + { + serialize(key, from.get(), out, messagingVersion); + } + + @Override + public void deserialize(JournalKey key, IdentityAccumulator into, DataInputPlus in, int userVersion) throws IOException + { + Ranges[] ranges = new Ranges[in.readUnsignedVInt32()]; + for (int i = 0; i < ranges.length; i++) + ranges[i] = KeySerializers.ranges.deserialize(in, messagingVersion); + + long[] epochs = new long[in.readUnsignedVInt32()]; + for (int i = 0; i < epochs.length; i++) + epochs[i] = in.readLong(); // TODO: assert lengths equal? + + into.update(new RangesForEpoch.Snapshot(epochs, ranges)); + } + } + + public static class HistoricalTransactionsAccumulator extends Accumulator, Deps> + { + public HistoricalTransactionsAccumulator() + { + super(new ArrayList<>()); + } + + @Override + protected List accumulate(List oldValue, Deps deps) + { + accumulated.add(deps); // we can keep it mutable + return accumulated; + } + } + + public static class HistoricalTransactionsSerializer implements FlyweightSerializer + { + @Override + public HistoricalTransactionsAccumulator mergerFor(JournalKey key) + { + return new HistoricalTransactionsAccumulator(); + } + + @Override + public void serialize(JournalKey key, Deps from, DataOutputPlus out, int userVersion) throws IOException + { + out.writeUnsignedVInt32(1); + deps.serialize(from, out, messagingVersion); + } + + @Override + public void reserialize(JournalKey key, HistoricalTransactionsAccumulator from, DataOutputPlus out, int userVersion) throws IOException + { + out.writeUnsignedVInt32(from.get().size()); + for (Deps d : from.get()) + deps.serialize(d, out, messagingVersion); + } + + @Override + public void deserialize(JournalKey key, HistoricalTransactionsAccumulator into, DataInputPlus in, int userVersion) throws IOException + { + int count = in.readUnsignedVInt32(); + for (int i = 0; i < count; i++) + into.update(deps.deserialize(in, messagingVersion)); + } + } +} \ No newline at end of file diff --git a/src/java/org/apache/cassandra/service/accord/AccordKeyspace.java b/src/java/org/apache/cassandra/service/accord/AccordKeyspace.java index e51017996e..104096930a 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordKeyspace.java +++ b/src/java/org/apache/cassandra/service/accord/AccordKeyspace.java @@ -24,14 +24,11 @@ import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.NavigableMap; import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -39,36 +36,31 @@ import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import accord.api.Key; +import accord.api.RoutingKey; +import accord.local.StoreParticipants; +import accord.local.cfk.CommandsForKey; import accord.impl.TimestampsForKey; import accord.local.Command; import accord.local.CommandStore; -import accord.local.DurableBefore; import accord.local.Node; import accord.local.RedundantBefore; -import accord.local.SaveStatus; -import accord.local.Status; -import accord.local.Status.Durability; -import accord.local.cfk.CommandsForKey; +import accord.primitives.SaveStatus; +import accord.primitives.Status; +import accord.primitives.Status.Durability; import accord.primitives.Ranges; import accord.primitives.Route; import accord.primitives.Timestamp; import accord.primitives.TxnId; import accord.topology.Topology; import accord.utils.Invariants; -import accord.utils.ReducingRangeMap; import accord.utils.async.Observable; import org.apache.cassandra.concurrent.Stage; import org.apache.cassandra.cql3.ColumnIdentifier; -import org.apache.cassandra.cql3.QueryOptions; -import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; -import org.apache.cassandra.cql3.statements.ModificationStatement; import org.apache.cassandra.cql3.statements.schema.CreateTableStatement; import org.apache.cassandra.db.Clustering; import org.apache.cassandra.db.ClusteringComparator; @@ -76,7 +68,6 @@ import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Columns; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.DeletionTime; -import org.apache.cassandra.db.IMutation; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.LivenessInfo; import org.apache.cassandra.db.Mutation; @@ -121,6 +112,7 @@ import org.apache.cassandra.dht.Token; import org.apache.cassandra.index.accord.RouteIndex; import org.apache.cassandra.io.IVersionedSerializer; import org.apache.cassandra.io.LocalVersionedSerializer; +import org.apache.cassandra.io.MessageVersionProvider; import org.apache.cassandra.io.util.DataInputBuffer; import org.apache.cassandra.io.util.DataOutputBuffer; import org.apache.cassandra.schema.ColumnMetadata; @@ -138,17 +130,14 @@ import org.apache.cassandra.schema.Types; import org.apache.cassandra.schema.UserFunctions; import org.apache.cassandra.schema.Views; import org.apache.cassandra.serializers.UUIDSerializer; -import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.accord.AccordConfigurationService.SyncStatus; import org.apache.cassandra.service.accord.api.AccordRoutingKey; -import org.apache.cassandra.service.accord.api.PartitionKey; +import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; import org.apache.cassandra.service.accord.serializers.AccordRoutingKeyByteSource; import org.apache.cassandra.service.accord.serializers.CommandSerializers; -import org.apache.cassandra.service.accord.serializers.CommandStoreSerializers; import org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer; import org.apache.cassandra.service.accord.serializers.KeySerializers; import org.apache.cassandra.service.accord.serializers.TopologySerializers; -import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.Clock.Global; import org.apache.cassandra.utils.CloseableIterator; import org.apache.cassandra.utils.btree.BTree; @@ -160,12 +149,12 @@ import static accord.utils.Invariants.checkArgument; import static accord.utils.Invariants.checkState; import static java.lang.String.format; import static org.apache.cassandra.cql3.QueryProcessor.executeInternal; -import static org.apache.cassandra.cql3.QueryProcessor.executeOnceInternal; import static org.apache.cassandra.db.partitions.PartitionUpdate.singleRowUpdate; import static org.apache.cassandra.db.rows.BTreeRow.singleCellRow; import static org.apache.cassandra.db.rows.BufferCell.live; import static org.apache.cassandra.db.rows.BufferCell.tombstone; import static org.apache.cassandra.schema.SchemaConstants.ACCORD_KEYSPACE_NAME; +import static org.apache.cassandra.service.accord.serializers.AccordRoutingKeyByteSource.currentVersion; import static org.apache.cassandra.service.accord.serializers.KeySerializers.blobMapToRanges; import static org.apache.cassandra.utils.ByteBufferUtil.EMPTY_BYTE_BUFFER; import static org.apache.cassandra.utils.ByteBufferUtil.bytes; @@ -180,11 +169,9 @@ public class AccordKeyspace public static final String COMMANDS_FOR_KEY = "commands_for_key"; public static final String TOPOLOGIES = "topologies"; public static final String EPOCH_METADATA = "epoch_metadata"; - public static final String COMMAND_STORE_METADATA = "command_store_metadata"; public static final Set TABLE_NAMES = ImmutableSet.of(COMMANDS, TIMESTAMPS_FOR_KEY, COMMANDS_FOR_KEY, TOPOLOGIES, EPOCH_METADATA, - COMMAND_STORE_METADATA, JOURNAL); private static final TupleType TIMESTAMP_TYPE = new TupleType(Lists.newArrayList(LongType.instance, LongType.instance, Int32Type.instance)); @@ -262,27 +249,22 @@ public class AccordKeyspace + "domain int," // this is stored as part of txn_id, used currently for cheaper scans of the table + format("txn_id %s,", TIMESTAMP_TUPLE) + "status int," - + "route blob," + + "participants blob," + "durability int," + format("execute_at %s,", TIMESTAMP_TUPLE) + "PRIMARY KEY((store_id, domain, txn_id))" + ')') .partitioner(new LocalPartitioner(CompositeType.getInstance(Int32Type.instance, Int32Type.instance, TIMESTAMP_TYPE))) .indexes(Indexes.builder() - .add(IndexMetadata.fromSchemaMetadata("route", IndexMetadata.Kind.CUSTOM, ImmutableMap.of("class_name", RouteIndex.class.getCanonicalName(), "target", "route"))) + .add(IndexMetadata.fromSchemaMetadata("route", IndexMetadata.Kind.CUSTOM, ImmutableMap.of("class_name", RouteIndex.class.getCanonicalName(), "target", "participants"))) .build()) .build(); // TODO: naming is not very clearly distinct from the base serializers public static class LocalVersionedSerializers { - static final LocalVersionedSerializer> route = localSerializer(KeySerializers.route); + static final LocalVersionedSerializer participants = localSerializer(CommandSerializers.participants); static final LocalVersionedSerializer topology = localSerializer(TopologySerializers.topology); - static final LocalVersionedSerializer> rejectBefore = localSerializer(CommandStoreSerializers.rejectBefore); - static final LocalVersionedSerializer durableBefore = localSerializer(CommandStoreSerializers.durableBefore); - static final LocalVersionedSerializer redundantBefore = localSerializer(CommandStoreSerializers.redundantBefore); - static final LocalVersionedSerializer> bootstrapBeganAt = localSerializer(CommandStoreSerializers.bootstrapBeganAt); - static final LocalVersionedSerializer> safeToRead = localSerializer(CommandStoreSerializers.safeToRead); private static LocalVersionedSerializer localSerializer(IVersionedSerializer serializer) { @@ -305,12 +287,12 @@ public class AccordKeyspace public static final ColumnMetadata txn_id = getColumn(Commands, "txn_id"); public static final ColumnMetadata store_id = getColumn(Commands, "store_id"); public static final ColumnMetadata status = getColumn(Commands, "status"); - public static final ColumnMetadata route = getColumn(Commands, "route"); + public static final ColumnMetadata participants = getColumn(Commands, "participants"); public static final ColumnMetadata durability = getColumn(Commands, "durability"); public static final ColumnMetadata execute_at = getColumn(Commands, "execute_at"); - public static final ColumnMetadata[] TRUNCATE_FIELDS = new ColumnMetadata[] { durability, execute_at, route, status }; - public static final ColumnMetadata[] INVALIDATE_FIELDS = new ColumnMetadata[] { status }; + public static final ColumnMetadata[] TRUNCATE_FIELDS = new ColumnMetadata[] { durability, execute_at, participants, status }; + public static final ColumnMetadata[] SAVE_STATUS_ONLY_FIELDS = new ColumnMetadata[] { status }; static { @@ -374,33 +356,67 @@ public class AccordKeyspace @Nullable public static Route getRoute(Row row) { - Cell cell = row.getCell(route); - return deserializeRouteOrNull(cell); + Cell cell = row.getCell(participants); + StoreParticipants participants = deserializeParticipantsOrNull(cell); + return participants == null ? null : participants.route(); } - private static Object[] truncatedApplyLeaf(long newTimestamp, SaveStatus newSaveStatus, Cell durabilityCell, Cell executeAtCell, Cell routeCell, boolean updateTimestamps) + private static Object[] truncatedApplyLeaf(long newTimestamp, SaveStatus newSaveStatus, @Nullable Cell durabilityCell, @Nullable Cell executeAtCell, @Nullable Cell participantsCell, boolean updateTimestamps) { - checkArgument(durabilityCell.column() == CommandsColumns.durability); - checkArgument(executeAtCell.column() == CommandsColumns.execute_at); - checkArgument(routeCell.column() == CommandsColumns.route); - Object[] newLeaf = BTree.unsafeAllocateNonEmptyLeaf(TRUNCATE_FIELDS.length); + int count = 1 + (durabilityCell != null ? 1 : 0) + (executeAtCell != null ? 1 : 0) + (participantsCell != null ? 1 : 0); + Object[] newLeaf = BTree.unsafeAllocateNonEmptyLeaf(count); int colIndex = 0; - newLeaf[colIndex++] = updateTimestamps ? durabilityCell.withUpdatedTimestamp(newTimestamp) : durabilityCell; - newLeaf[colIndex++] = updateTimestamps ? executeAtCell.withUpdatedTimestamp(newTimestamp) : executeAtCell; - newLeaf[colIndex++] = updateTimestamps ? routeCell.withUpdatedTimestamp(newTimestamp) : routeCell; - // Status always needs to use the new timestamp since we are replacing the existing value - // All the other columns are being retained unmodified with at most updated timestamps to accomdate deletion - //noinspection UnusedAssignment - newLeaf[colIndex++] = BufferCell.live(status, newTimestamp, ByteBufferAccessor.instance.valueOf(newSaveStatus.ordinal())); + if (durabilityCell != null) + { + checkArgument(durabilityCell.column() == CommandsColumns.durability); + newLeaf[colIndex++] = updateTimestamps ? durabilityCell.withUpdatedTimestamp(newTimestamp) : durabilityCell; + } + if (executeAtCell != null) + { + checkArgument(executeAtCell.column() == CommandsColumns.execute_at); + newLeaf[colIndex++] = updateTimestamps ? executeAtCell.withUpdatedTimestamp(newTimestamp) : executeAtCell; + } + if (participantsCell != null) + { + checkArgument(participantsCell.column() == CommandsColumns.participants); + newLeaf[colIndex++] = updateTimestamps ? participantsCell.withUpdatedTimestamp(newTimestamp) : participantsCell; + } + newLeaf[colIndex] = BufferCell.live(status, newTimestamp, ByteBufferAccessor.instance.valueOf(newSaveStatus.ordinal())); return newLeaf; } - public static Row invalidated(SaveStatus newSaveStatus, Row row, long nowInSec) + private static Object[] expungePartialLeaf(Cell durabilityCell, Cell executeAtCell, Cell participantsCell) + { + int count = (durabilityCell != null ? 1 : 0) + (executeAtCell != null ? 1 : 0) + (participantsCell != null ? 1 : 0); + if (count == 0) + return null; + + Object[] newLeaf = BTree.unsafeAllocateNonEmptyLeaf(count); + int colIndex = 0; + if (durabilityCell != null) + { + checkArgument(durabilityCell.column() == CommandsColumns.durability); + newLeaf[colIndex++] = durabilityCell; + } + if (executeAtCell != null) + { + checkArgument(executeAtCell.column() == CommandsColumns.execute_at); + newLeaf[colIndex++] = executeAtCell; + } + if (participantsCell != null) + { + checkArgument(participantsCell.column() == CommandsColumns.participants); + newLeaf[colIndex] = participantsCell; + } + return newLeaf; + } + + public static Row saveStatusOnly(SaveStatus newSaveStatus, Row row, long nowInSec) { long oldTimestamp = row.primaryKeyLivenessInfo().timestamp(); long newTimestamp = oldTimestamp + 1; - Object[] newLeaf = invalidatedLeaf(newTimestamp, newSaveStatus); + Object[] newLeaf = saveStatusOnlyLeaf(newTimestamp, newSaveStatus); // Including a deletion allows future compactions to drop data before it gets to the purger // but it is pretty optional because maybeDropTruncatedCommandColumns will drop the extra columns @@ -409,9 +425,9 @@ public class AccordKeyspace return BTreeRow.create(row.clustering(), LivenessInfo.create(newTimestamp, nowInSec), deletion, newLeaf); } - private static Object[] invalidatedLeaf(long newTimestamp, SaveStatus newSaveStatus) + private static Object[] saveStatusOnlyLeaf(long newTimestamp, SaveStatus newSaveStatus) { - Object[] newLeaf = BTree.unsafeAllocateNonEmptyLeaf(INVALIDATE_FIELDS.length); + Object[] newLeaf = BTree.unsafeAllocateNonEmptyLeaf(SAVE_STATUS_ONLY_FIELDS.length); int colIndex = 0; // Status always needs to use the new timestamp since we are replacing the existing value // All the other columns are being retained unmodified with at most updated timestamps to accomdate deletion @@ -420,17 +436,10 @@ public class AccordKeyspace return newLeaf; } - public static Row truncatedApply(SaveStatus newSaveStatus, Row row, long nowInSec, Durability durability, Cell durabilityCell, Cell executeAtCell, Cell routeCell, boolean withOutcome) + public static Row truncatedApply(SaveStatus newSaveStatus, Row row, long nowInSec, Durability durability, @Nullable Cell durabilityCell, @Nullable Cell executeAtCell, @Nullable Cell participantsCell, boolean withOutcome) { - checkArgument(durabilityCell.column() == CommandsColumns.durability); - checkArgument(executeAtCell.column() == CommandsColumns.execute_at); - checkArgument(routeCell.column() == CommandsColumns.route); long oldTimestamp = row.primaryKeyLivenessInfo().timestamp(); long newTimestamp = oldTimestamp + 1; - // If durability is not universal we don't want to delete older versions of the row that might have recorded - // a higher durability value. maybeDropTruncatedCommandColumns will take care of dropping things even if we don't drop via tombstones. - // durability should be the only column that could have an older value that is insufficient for propagating forward - // TODO (now): with UniversalOrInvalidated should this change? boolean doDeletion = durability == Durability.Universal; // We may not have what we need to generate a deletion and include the outcome in the truncated row @@ -438,7 +447,7 @@ public class AccordKeyspace if (withOutcome) doDeletion = false; - Object[] newLeaf = truncatedApplyLeaf(newTimestamp, newSaveStatus, durabilityCell, executeAtCell, routeCell, doDeletion); + Object[] newLeaf = truncatedApplyLeaf(newTimestamp, newSaveStatus, durabilityCell, executeAtCell, participantsCell, doDeletion); // Including a deletion allows future compactions to drop data before it gets to the purger // but it is pretty optional because maybeDropTruncatedCommandColumns will drop the extra columns @@ -447,47 +456,26 @@ public class AccordKeyspace return BTreeRow.create(row.clustering(), LivenessInfo.create(newTimestamp, nowInSec), deletion, newLeaf); } - public static Row maybeDropTruncatedCommandColumns(Row row, Cell durabilityCell, Cell executeAtCell, Cell routeCell, Cell statusCell) + public static Row expungePartial(Row row, @Nullable Cell durabilityCell, @Nullable Cell executeAtCell, @Nullable Cell participantsCell) { - checkArgument(durabilityCell.column() == CommandsColumns.durability); - checkArgument(executeAtCell.column() == CommandsColumns.execute_at); - checkArgument(routeCell.column() == CommandsColumns.route); - checkArgument(statusCell.column() == CommandsColumns.status); - int colCount = row.columnCount(); - // If it's the exact length of the post truncate column count without outcome fields - // then it is exactly the columns needed for getting this far and withOutcome doesn't matter since - // nothing additional is available to include anyway - if (colCount == TRUNCATE_FIELDS.length) - return row; - - // Construct a replacement with just the available columns that are still needed - Object[] newLeaf = BTree.unsafeAllocateNonEmptyLeaf(TRUNCATE_FIELDS.length); - int colIndex = 0; - newLeaf[colIndex++] = durabilityCell; - newLeaf[colIndex++] = executeAtCell; - newLeaf[colIndex++] = routeCell; - //noinspection UnusedAssignment - newLeaf[colIndex++] = statusCell; - - return BTreeRow.create(row.clustering(), row.primaryKeyLivenessInfo(), row.deletion(), newLeaf); + Object[] newLeaf = expungePartialLeaf(durabilityCell, executeAtCell, participantsCell); + return BTreeRow.create(row.clustering(), row.primaryKeyLivenessInfo(), Deletion.LIVE, newLeaf); } } - //TODO (now, performance): do we actually care about the sort ordering? We don't do range scans on this table - //TODO (now, performance): should we remove key_token? We don't need it so its just added space private static final TableMetadata TimestampsForKeys = parse(TIMESTAMPS_FOR_KEY, "accord timestamps per key", "CREATE TABLE %s (" + "store_id int, " - + "key_token blob, " // can't use "token" as this is restricted word in CQL - + format("key %s, ", KEY_TUPLE) + + "routing_key blob, " // can't use "token" as this is restricted word in CQL + format("last_executed_timestamp %s, ", TIMESTAMP_TUPLE) + "last_executed_micros bigint, " + + format("last_write_id %s, ", TIMESTAMP_TUPLE) + format("last_write_timestamp %s, ", TIMESTAMP_TUPLE) - + "PRIMARY KEY((store_id, key_token, key))" + + "PRIMARY KEY((store_id, routing_key))" + ')') - .partitioner(new LocalPartitioner(CompositeType.getInstance(Int32Type.instance, BytesType.instance, KEY_TYPE))) + .partitioner(new LocalPartitioner(CompositeType.getInstance(Int32Type.instance, BytesType.instance))) .build(); public static class TimestampsForKeyColumns @@ -495,23 +483,22 @@ public class AccordKeyspace static final ClusteringComparator keyComparator = TimestampsForKeys.partitionKeyAsClusteringComparator(); static final CompositeType partitionKeyType = (CompositeType) TimestampsForKeys.partitionKeyType; static final ColumnMetadata store_id = getColumn(TimestampsForKeys, "store_id"); - static final ColumnMetadata key_token = getColumn(TimestampsForKeys, "key_token"); - static final ColumnMetadata key = getColumn(TimestampsForKeys, "key"); + static final ColumnMetadata routing_key = getColumn(TimestampsForKeys, "routing_key"); public static final ColumnMetadata last_executed_timestamp = getColumn(TimestampsForKeys, "last_executed_timestamp"); public static final ColumnMetadata last_executed_micros = getColumn(TimestampsForKeys, "last_executed_micros"); public static final ColumnMetadata last_write_timestamp = getColumn(TimestampsForKeys, "last_write_timestamp"); static final Columns columns = Columns.from(Lists.newArrayList(last_executed_timestamp, last_executed_micros, last_write_timestamp)); - static ByteBuffer makePartitionKey(int storeId, Key key) + static ByteBuffer makeKey(int storeId, RoutingKey key) { - PartitionKey pk = (PartitionKey) key; - return keyComparator.make(storeId, serializeToken(pk.token()), serializeKey(pk)).serializeAsPartitionKey(); + TokenKey pk = (TokenKey) key; + return keyComparator.make(storeId, serializeRoutingKey(pk)).serializeAsPartitionKey(); } - static ByteBuffer makePartitionKey(int storeId, TimestampsForKey timestamps) + static ByteBuffer makeKey(int storeId, TimestampsForKey timestamps) { - return makePartitionKey(storeId, timestamps.key()); + return makeKey(storeId, timestamps.key()); } } @@ -527,9 +514,9 @@ public class AccordKeyspace return Int32Type.instance.compose(partitionKeyComponents[store_id.position()]); } - public static PartitionKey getKey(ByteBuffer[] partitionKeyComponents) + public static TokenKey getKey(ByteBuffer[] partitionKeyComponents) { - return deserializeKey(partitionKeyComponents[key.position()]); + return (TokenKey) deserializeRoutingKey(partitionKeyComponents[routing_key.position()]); } @Nullable @@ -596,18 +583,17 @@ public class AccordKeyspace private static TableMetadata commandsForKeysTable(String tableName) { return parse(tableName, - "accord commands per key", - "CREATE TABLE %s (" - + "store_id int, " - + "table_id uuid, " - + "key_token blob, " // can't use "token" as this is restricted word in CQL - + "key blob, " - + "data blob, " - + "PRIMARY KEY((store_id, table_id, key_token, key))" - + ')' - + " WITH compression = {'class':'NoopCompressor'};") - .partitioner(CFKPartitioner) - .build(); + "accord commands per key", + "CREATE TABLE %s (" + + "store_id int, " + + "table_id uuid, " + + "key_token blob, " // can't use "token" as this is restricted word in CQL + + "data blob, " + + "PRIMARY KEY((store_id, table_id, key_token))" + + ')' + + " WITH compression = {'class':'NoopCompressor'};") + .partitioner(CFKPartitioner) + .build(); } public static class CommandsForKeyAccessor @@ -619,7 +605,6 @@ public class AccordKeyspace final ColumnMetadata store_id; final ColumnMetadata table_id; final ColumnMetadata key_token; - final ColumnMetadata key; final ColumnMetadata data; final RegularAndStaticColumns columns; @@ -633,7 +618,6 @@ public class AccordKeyspace this.store_id = getColumn(table, "store_id"); this.table_id = getColumn(table, "table_id"); this.key_token = getColumn(table, "key_token"); - this.key = getColumn(table, "key"); this.data = getColumn(table, "data"); this.columns = new RegularAndStaticColumns(Columns.NONE, Columns.from(Lists.newArrayList(data))); } @@ -653,22 +637,18 @@ public class AccordKeyspace return TableId.fromUUID(UUIDType.instance.compose(partitionKeyComponents[table_id.position()])); } - public PartitionKey getKey(DecoratedKey key) + public TokenKey getKey(DecoratedKey key) { return getKey(splitPartitionKey(key)); } - public PartitionKey getKey(ByteBuffer[] partitionKeyComponents) + public TokenKey getKey(ByteBuffer[] partitionKeyComponents) { TableId tableId = TableId.fromUUID(UUIDSerializer.instance.deserialize(partitionKeyComponents[table_id.position()])); - ByteBuffer keyBytes = partitionKeyComponents[key.position()]; - IPartitioner partitioner = SchemaHolder.schema.getTablePartitioner(tableId); - if (partitioner == null) - throw new IllegalStateException("Table with id " + tableId + " could not be found; was it deleted?"); - return new PartitionKey(tableId, partitioner.decorateKey(keyBytes)); + return deserializeTokenKeySeparateTable(tableId, partitionKeyComponents[key_token.position()]); } - public CommandsForKey getCommandsForKey(PartitionKey key, Row row) + public CommandsForKey getCommandsForKey(TokenKey key, Row row) { Cell cell = row.getCell(data); if (cell == null) @@ -685,7 +665,7 @@ public class AccordKeyspace } // TODO (expected): garbage-free filtering, reusing encoding - public Row withoutRedundantCommands(PartitionKey key, Row row, RedundantBefore.Entry redundantBefore) + public Row withoutRedundantCommands(TokenKey key, Row row, RedundantBefore.Entry redundantBefore) { Invariants.checkState(row.columnCount() == 1); Cell cell = row.getCell(data); @@ -745,21 +725,6 @@ public class AccordKeyspace "max_epoch bigint " + ')').build(); - private static final TableMetadata CommandStoreMetadata = - parse(COMMAND_STORE_METADATA, - "command store state", - "CREATE TABLE %s (" + - "store_id int, " + - "reject_before blob, " + - "bootstrap_began_at blob, " + - "safe_to_read blob, " + - "redundant_before blob, " + - "durable_before blob, " + - "PRIMARY KEY(store_id)" + - ')').build(); - - private static final AtomicLong commandStoreMetadataTimestamp = new AtomicLong(); - private static TableMetadata.Builder parse(String name, String description, String cql) { return CreateTableStatement.parse(format(cql, name), ACCORD_KEYSPACE_NAME) @@ -780,7 +745,7 @@ public class AccordKeyspace public static Tables tables() { - return Tables.of(Commands, TimestampsForKeys, CommandsForKeys, Topologies, EpochMetadata, CommandStoreMetadata, Journal); + return Tables.of(Commands, TimestampsForKeys, CommandsForKeys, Topologies, EpochMetadata, Journal); } private static ByteBuffer serialize(T obj, LocalVersionedSerializer serializer) throws IOException @@ -863,7 +828,7 @@ public class AccordKeyspace builder.addPrimaryKeyLivenessInfo(LivenessInfo.create(timestampMicros, nowInSeconds)); addEnumCellIfModified(CommandsColumns.durability, Command::durability, builder, timestampMicros, nowInSeconds, original, command); - addCellIfModified(CommandsColumns.route, Command::route, LocalVersionedSerializers.route, builder, timestampMicros, nowInSeconds, original, command); + addCellIfModified(CommandsColumns.participants, Command::participants, LocalVersionedSerializers.participants, builder, timestampMicros, nowInSeconds, original, command); addEnumCellIfModified(CommandsColumns.status, Command::saveStatus, builder, timestampMicros, nowInSeconds, original, command); addCellIfModified(CommandsColumns.execute_at, Command::executeAt, AccordKeyspace::serializeTimestamp, builder, timestampMicros, nowInSeconds, original, command); @@ -888,6 +853,13 @@ public class AccordKeyspace return serializeToken(token, ByteBufferAccessor.instance); } + public static ByteBuffer serializeTableId(TableId tableId) + { + ByteBuffer buffer = ByteBuffer.allocate(tableId.serializedSize()); + tableId.serialize(buffer, ByteBufferAccessor.instance, 0); + return buffer; + } + private static V serializeToken(Token token, ValueAccessor accessor) { TokenType type = TokenType.valueOf(token); @@ -898,12 +870,6 @@ public class AccordKeyspace return value; } - @VisibleForTesting - public static ByteBuffer serializeKey(PartitionKey key) - { - return KEY_TYPE.pack(UUIDSerializer.instance.serialize(key.table().asUUID()), key.partitionKey().getKey()); - } - public static ByteBuffer serializeTimestamp(Timestamp timestamp) { return TIMESTAMP_TYPE.pack(bytes(timestamp.msb), bytes(timestamp.lsb), bytes(timestamp.node.id)); @@ -991,7 +957,7 @@ public class AccordKeyspace public static void findAllKeysBetween(int commandStore, AccordRoutingKey start, boolean startInclusive, AccordRoutingKey end, boolean endInclusive, - Observable callback) + Observable callback) { Token startToken = CommandsForKeysAccessor.getPrefixToken(commandStore, start); @@ -1026,7 +992,7 @@ public class AccordKeyspace { while (iter.hasNext()) { - PartitionKey pk = CommandsForKeysAccessor.getKey(iter.next()); + TokenKey pk = CommandsForKeysAccessor.getKey(iter.next()); callback.onNext(pk); } callback.onCompleted(); @@ -1062,29 +1028,37 @@ public class AccordKeyspace return Status.Durability.values()[row.getInt("durability", 0)]; } - public static Route deserializeRouteOrNull(ByteBuffer bytes) throws IOException + public static StoreParticipants deserializeParticipantsOrNull(ByteBuffer bytes) throws IOException { - return bytes != null && !ByteBufferAccessor.instance.isEmpty(bytes) ? deserialize(bytes, LocalVersionedSerializers.route) : null; + return bytes != null && !ByteBufferAccessor.instance.isEmpty(bytes) ? deserialize(bytes, LocalVersionedSerializers.participants) : null; } - public static ByteBuffer serializeRoute(Route route) throws IOException + public static Route deserializeParticipantsRouteOnlyOrNull(ByteBuffer bytes) throws IOException { - return serialize(route, LocalVersionedSerializers.route); + if (bytes == null ||ByteBufferAccessor.instance.isEmpty(bytes)) + return null; + + try (DataInputBuffer in = new DataInputBuffer(bytes, true)) + { + MessageVersionProvider versionProvider = LocalVersionedSerializers.participants.deserializeVersion(in); + return CommandSerializers.participants.deserializeRouteOnly(in, versionProvider.messageVersion()); + } + } - private static Route deserializeRouteOrNull(UntypedResultSet.Row row) throws IOException + public static ByteBuffer serializeParticipants(StoreParticipants participants) throws IOException { - return deserializeRouteOrNull(row.getBlob("route")); + return serialize(participants, LocalVersionedSerializers.participants); } - public static Route deserializeRouteOrNull(Cell cell) + public static StoreParticipants deserializeParticipantsOrNull(Cell cell) { if (cell == null) return null; try { - return deserializeRouteOrNull(cell.buffer()); + return deserializeParticipantsOrNull(cell.buffer()); } catch (IOException e) { @@ -1092,21 +1066,9 @@ public class AccordKeyspace } } - public static PartitionKey deserializeKey(ByteBuffer buffer) + public static TokenKey deserializeTokenKeySeparateTable(TableId tableId, ByteBuffer tokenBytes) { - List split = KEY_TYPE.unpack(buffer, ByteBufferAccessor.instance); - TableId tableId = TableId.fromUUID(UUIDSerializer.instance.deserialize(split.get(0))); - ByteBuffer key = split.get(1); - - IPartitioner partitioner = SchemaHolder.schema.getTablePartitioner(tableId); - if (partitioner == null) - throw new IllegalStateException("Table with id " + tableId + " could not be found; was it deleted?"); - return new PartitionKey(tableId, partitioner.decorateKey(key)); - } - - public static PartitionKey deserializeKey(UntypedResultSet.Row row) - { - return deserializeKey(row.getBytes("key")); + return (TokenKey) AccordRoutingKeyByteSource.Serializer.fromComparableBytes(ByteBufferAccessor.instance, tokenBytes, tableId, currentVersion, null); } public static Mutation getTimestampsForKeyMutation(int storeId, TimestampsForKey original, TimestampsForKey current, long timestampMicros) @@ -1130,7 +1092,7 @@ public class AccordKeyspace if (row.columnCount() == 0) return null; - ByteBuffer key = TimestampsForKeyColumns.makePartitionKey(storeId, current.key()); + ByteBuffer key = TimestampsForKeyColumns.makeKey(storeId, current.key()); PartitionUpdate update = singleRowUpdate(TimestampsForKeys, key, row); return new Mutation(update); } @@ -1145,26 +1107,22 @@ public class AccordKeyspace return getTimestampsForKeyMutation(commandStore.id(), liveTimestamps.original(), liveTimestamps.current(), timestampMicros); } - public static UntypedResultSet loadTimestampsForKeyRow(CommandStore commandStore, PartitionKey key) + public static UntypedResultSet loadTimestampsForKeyRow(CommandStore commandStore, TokenKey key) { String cql = "SELECT * FROM " + ACCORD_KEYSPACE_NAME + '.' + TIMESTAMPS_FOR_KEY + ' ' + "WHERE store_id = ? " + - "AND key_token = ? " + - "AND key=(?, ?)"; + "AND routing_key = ?"; - return executeInternal(cql, - commandStore.id(), - serializeToken(key.token()), - key.table().asUUID(), key.partitionKey().getKey()); + return executeInternal(cql, commandStore.id(), serializeRoutingKey(key)); } - public static TimestampsForKey loadTimestampsForKey(AccordCommandStore commandStore, PartitionKey key) + public static TimestampsForKey loadTimestampsForKey(AccordCommandStore commandStore, TokenKey key) { commandStore.checkNotInStoreThread(); return unsafeLoadTimestampsForKey(commandStore, key); } - public static TimestampsForKey unsafeLoadTimestampsForKey(AccordCommandStore commandStore, PartitionKey key) + public static TimestampsForKey unsafeLoadTimestampsForKey(AccordCommandStore commandStore, TokenKey key) { UntypedResultSet rows = loadTimestampsForKeyRow(commandStore, key); @@ -1174,21 +1132,22 @@ public class AccordKeyspace } UntypedResultSet.Row row = rows.one(); - checkState(deserializeKey(row).equals(key)); + TokenKey checkKey = (TokenKey) deserializeRoutingKey(row.getBytes("routing_key")); + checkState(checkKey.equals(key)); Timestamp lastExecutedTimestamp = deserializeTimestampOrDefault(row, "last_executed_timestamp", Timestamp::fromBits, Timestamp.NONE); long lastExecutedMicros = row.has("last_executed_micros") ? row.getLong("last_executed_micros") : 0; + TxnId lastWriteId = deserializeTimestampOrDefault(row, "last_write_id", TxnId::fromBits, TxnId.NONE); Timestamp lastWriteTimestamp = deserializeTimestampOrDefault(row, "last_write_timestamp", Timestamp::fromBits, Timestamp.NONE); - return TimestampsForKey.SerializerSupport.create(key, lastExecutedTimestamp, lastExecutedMicros, lastWriteTimestamp); + return TimestampsForKey.SerializerSupport.create(key, lastExecutedTimestamp, lastExecutedMicros, lastWriteId, lastWriteTimestamp); } - private static DecoratedKey makeKey(CommandsForKeyAccessor accessor, int storeId, PartitionKey key) + private static DecoratedKey makeKeySeparateTable(CommandsForKeyAccessor accessor, int storeId, TokenKey key) { ByteBuffer pk = accessor.keyComparator.make(storeId, UUIDSerializer.instance.serialize(key.table().asUUID()), - serializeRoutingKey(key.toUnseekable()), - key.partitionKey().getKey()).serializeAsPartitionKey(); + serializeRoutingKeyNoTable(key)).serializeAsPartitionKey(); return accessor.table.partitioner.decorateKey(pk); } @@ -1204,23 +1163,44 @@ public class AccordKeyspace return CommandsForKeysAccessor.serializeKeyNoTable(key); } - private static PartitionUpdate getCommandsForKeyPartitionUpdate(int storeId, PartitionKey key, CommandsForKey commandsForKey, long timestampMicros) + public static AccordRoutingKey deserializeRoutingKey(ByteBuffer buffer) + { + return AccordRoutingKeyByteSource.Serializer.fromComparableBytes(ByteBufferAccessor.instance, buffer, currentVersion, null); + } + + private static AccordRoutingKeyByteSource.Serializer serializer(AccordRoutingKey routingKey) + { + return serializer(routingKey.table()); + } + + public static AccordRoutingKeyByteSource.Serializer serializer(TableId tableId) + { + return TABLE_SERIALIZERS.computeIfAbsent(tableId, id -> AccordRoutingKeyByteSource.variableLength(partitioner(tableId))); + } + + public static IPartitioner partitioner(TableId tableId) + { + + return SchemaHolder.schema.getTablePartitioner(tableId); + } + + private static PartitionUpdate getCommandsForKeyPartitionUpdate(int storeId, TokenKey key, CommandsForKey commandsForKey, long timestampMicros) { ByteBuffer bytes = CommandsForKeySerializer.toBytesWithoutKey(commandsForKey); return getCommandsForKeyPartitionUpdate(storeId, key, timestampMicros, bytes); } @VisibleForTesting - public static PartitionUpdate getCommandsForKeyPartitionUpdate(int storeId, PartitionKey key, long timestampMicros, ByteBuffer bytes) + public static PartitionUpdate getCommandsForKeyPartitionUpdate(int storeId, TokenKey key, long timestampMicros, ByteBuffer bytes) { return singleRowUpdate(CommandsForKeysAccessor.table, - makeKey(CommandsForKeysAccessor, storeId, key), + makeKeySeparateTable(CommandsForKeysAccessor, storeId, key), singleCellRow(Clustering.EMPTY, BufferCell.live(CommandsForKeysAccessor.data, timestampMicros, bytes))); } public static Mutation getCommandsForKeyMutation(int storeId, CommandsForKey update, long timestampMicros) { - return new Mutation(getCommandsForKeyPartitionUpdate(storeId, (PartitionKey) update.key(), update, timestampMicros)); + return new Mutation(getCommandsForKeyPartitionUpdate(storeId, (TokenKey)update.key(), update, timestampMicros)); } private static ByteBuffer cellValue(Cell cell) @@ -1240,22 +1220,22 @@ public class AccordKeyspace return clustering.accessor().toBuffer(clustering.get(idx)); } - private static SinglePartitionReadCommand getCommandsForKeyRead(CommandsForKeyAccessor accessor, int storeId, PartitionKey key, long nowInSeconds) + private static SinglePartitionReadCommand getCommandsForKeyRead(CommandsForKeyAccessor accessor, int storeId, TokenKey key, long nowInSeconds) { return SinglePartitionReadCommand.create(accessor.table, nowInSeconds, accessor.allColumns, RowFilter.none(), DataLimits.NONE, - makeKey(accessor, storeId, key), + makeKeySeparateTable(accessor, storeId, key), FULL_PARTITION); } - public static SinglePartitionReadCommand getCommandsForKeyRead(int storeId, PartitionKey key, int nowInSeconds) + public static SinglePartitionReadCommand getCommandsForKeyRead(int storeId, TokenKey key, int nowInSeconds) { return getCommandsForKeyRead(CommandsForKeysAccessor, storeId, key, nowInSeconds); } - static CommandsForKey unsafeLoadCommandsForKey(CommandsForKeyAccessor accessor, AccordCommandStore commandStore, PartitionKey key) + static CommandsForKey unsafeLoadCommandsForKey(CommandsForKeyAccessor accessor, AccordCommandStore commandStore, TokenKey key) { long timestampMicros = TimeUnit.MILLISECONDS.toMicros(Global.currentTimeMillis()); int nowInSeconds = (int) TimeUnit.MICROSECONDS.toSeconds(timestampMicros); @@ -1283,12 +1263,12 @@ public class AccordKeyspace } } - public static CommandsForKey unsafeLoadCommandsForKey(AccordCommandStore commandStore, PartitionKey key) + public static CommandsForKey unsafeLoadCommandsForKey(AccordCommandStore commandStore, TokenKey key) { return unsafeLoadCommandsForKey(CommandsForKeysAccessor, commandStore, key); } - public static CommandsForKey loadCommandsForKey(AccordCommandStore commandStore, PartitionKey key) + public static CommandsForKey loadCommandsForKey(AccordCommandStore commandStore, TokenKey key) { commandStore.checkNotInStoreThread(); return unsafeLoadCommandsForKey(CommandsForKeysAccessor, commandStore, key); @@ -1557,104 +1537,6 @@ public class AccordKeyspace } } - private static IMutation getCommandStoreMetadataMutation(String cql, ByteBuffer... values) - { - ClientState clientState = ClientState.forInternalCalls(); - ModificationStatement statement = (ModificationStatement) QueryProcessor.parseStatement(cql).prepare(ClientState.forInternalCalls()); - QueryOptions options = QueryOptions.forInternalCalls(Arrays.asList(values)); - - long tsMicros = TimeUnit.MILLISECONDS.toMicros(Global.currentTimeMillis()); - - while (true) - { - long prev = commandStoreMetadataTimestamp.get(); - if (prev >= tsMicros) - tsMicros = prev + 1; - - if (commandStoreMetadataTimestamp.compareAndSet(prev, tsMicros)) - break; - } - - return Iterables.getOnlyElement(statement.getMutations(clientState, options, true, tsMicros, (int) TimeUnit.MICROSECONDS.toSeconds(tsMicros), Dispatcher.RequestTime.forImmediateExecution(), false)); - } - - - private static Future updateCommandStoreMetadata(CommandStore commandStore, String column, T value, LocalVersionedSerializer serializer) - { - String cql = format("UPDATE %s.%s SET %s=? WHERE store_id=?", ACCORD_KEYSPACE_NAME, COMMAND_STORE_METADATA, column); - try - { - IMutation mutation = getCommandStoreMetadataMutation(cql, serialize(value, serializer), bytes(commandStore.id())); - return Stage.MUTATION.submit(mutation::apply); - } - catch (IOException e) - { - throw new UncheckedIOException(e); - } - } - - public static Future updateRejectBefore(CommandStore commandStore, ReducingRangeMap rejectBefore) - { - return updateCommandStoreMetadata(commandStore, "reject_before", rejectBefore, LocalVersionedSerializers.rejectBefore); - } - - public static Future updateDurableBefore(CommandStore commandStore, DurableBefore durableBefore) - { - return updateCommandStoreMetadata(commandStore, "durable_before", durableBefore, LocalVersionedSerializers.durableBefore); - } - - public static Future updateRedundantBefore(CommandStore commandStore, RedundantBefore redundantBefore) - { - return updateCommandStoreMetadata(commandStore, "redundant_before", redundantBefore, LocalVersionedSerializers.redundantBefore); - } - - public static Future updateBootstrapBeganAt(CommandStore commandStore, NavigableMap bootstrapBeganAt) - { - return updateCommandStoreMetadata(commandStore, "bootstrap_began_at", bootstrapBeganAt, LocalVersionedSerializers.bootstrapBeganAt); - } - - public static Future updateSafeToRead(CommandStore commandStore, NavigableMap safeToRead) - { - return updateCommandStoreMetadata(commandStore, "safe_to_read", safeToRead, LocalVersionedSerializers.safeToRead); - } - - public interface CommandStoreMetadataConsumer - { - void accept(ReducingRangeMap rejectBefore, DurableBefore durableBefore, RedundantBefore redundantBefore, NavigableMap bootstrapBeganAt, NavigableMap safeToRead); - } - - public static void loadCommandStoreMetadata(int id, CommandStoreMetadataConsumer consumer) - { - UntypedResultSet result = executeOnceInternal(format("SELECT * FROM %s.%s WHERE store_id=?", ACCORD_KEYSPACE_NAME, COMMAND_STORE_METADATA), id); - ReducingRangeMap rejectBefore = null; - DurableBefore durableBefore = null; - RedundantBefore redundantBefore = null; - NavigableMap bootstrapBeganAt = null; - NavigableMap safeToRead = null; - if (!result.isEmpty()) - { - UntypedResultSet.Row row = Iterables.getOnlyElement(result); - try - { - if (row.has("reject_before")) - rejectBefore = deserialize(row.getBlob("reject_before"), LocalVersionedSerializers.rejectBefore); - if (row.has("durable_before")) - durableBefore = deserialize(row.getBlob("durable_before"), LocalVersionedSerializers.durableBefore); - if (row.has("redundant_before")) - redundantBefore = deserialize(row.getBlob("redundant_before"), LocalVersionedSerializers.redundantBefore); - if (row.has("bootstrap_began_at")) - bootstrapBeganAt = deserialize(row.getBlob("bootstrap_began_at"), LocalVersionedSerializers.bootstrapBeganAt); - if (row.has("safe_to_read")) - safeToRead = deserialize(row.getBlob("safe_to_read"), LocalVersionedSerializers.safeToRead); - } - catch (IOException e) - { - throw new UncheckedIOException(e); - } - } - consumer.accept(rejectBefore, durableBefore, redundantBefore, bootstrapBeganAt, safeToRead); - } - @VisibleForTesting public static void unsafeSetSchema(SchemaProvider provider) { diff --git a/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java b/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java index 53fd581551..ff86a41ed0 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java +++ b/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java @@ -24,6 +24,7 @@ import java.util.function.ToLongFunction; import accord.api.Key; import accord.api.Result; import accord.api.RoutingKey; +import accord.local.StoreParticipants; import accord.local.cfk.CommandsForKey; import accord.local.cfk.CommandsForKey.TxnInfo; import accord.impl.TimestampsForKey; @@ -32,8 +33,8 @@ import accord.local.Command.WaitingOn; import accord.local.cfk.CommandsForKey.TxnInfoExtra; import accord.local.CommonAttributes; import accord.local.Node; -import accord.local.SaveStatus; -import accord.local.Status; +import accord.primitives.SaveStatus; +import accord.primitives.Status; import accord.primitives.AbstractKeys; import accord.primitives.AbstractRanges; import accord.primitives.Ballot; @@ -69,6 +70,7 @@ import org.apache.cassandra.service.accord.txn.TxnResult; import org.apache.cassandra.service.accord.txn.TxnWrite; import org.apache.cassandra.utils.ObjectSizes; +import static accord.local.cfk.CommandsForKey.InternalStatus.ACCEPTED; import static accord.primitives.TxnId.NO_TXNIDS; import static org.apache.cassandra.utils.ObjectSizes.measure; @@ -235,7 +237,7 @@ public class AccordObjectSizes // doesn't account for txnIdToKeys, txnIdToRanges, and searchable fields; // fix to accunt for, in case caching isn't redone long size = EMPTY_DEPS_SIZE - EMPTY_KEYS_SIZE - ObjectSizes.sizeOfReferenceArray(0); - size += keys(dependencies.keyDeps.keys()); + size += routingKeys(dependencies.keyDeps.keys()); for (int i = 0 ; i < dependencies.rangeDeps.rangeCount() ; ++i) size += range(dependencies.rangeDeps.range(i)); size += ObjectSizes.sizeOfReferenceArray(dependencies.rangeDeps.rangeCount()); @@ -273,7 +275,9 @@ public class AccordObjectSizes private static CommonAttributes attrs(boolean hasDeps, boolean hasTxn) { - CommonAttributes.Mutable attrs = new CommonAttributes.Mutable(EMPTY_TXNID).route(new FullKeyRoute(EMPTY_KEY, new RoutingKey[]{ EMPTY_KEY })); + FullKeyRoute route = new FullKeyRoute(EMPTY_KEY, new RoutingKey[]{ EMPTY_KEY }); + CommonAttributes.Mutable attrs = new CommonAttributes.Mutable(EMPTY_TXNID) + .setParticipants(StoreParticipants.empty(EMPTY_TXNID, route)); attrs.durability(Status.Durability.NotDurable); if (hasDeps) attrs.partialDeps(PartialDeps.NONE); @@ -284,14 +288,14 @@ public class AccordObjectSizes return attrs; } - private static final Writes EMPTY_WRITES = new Writes(EMPTY_TXNID, EMPTY_TXNID, Keys.EMPTY, (key, safeStore, executeAt, store, txn) -> null); + private static final Writes EMPTY_WRITES = new Writes(EMPTY_TXNID, EMPTY_TXNID, Keys.EMPTY, (key, safeStore, txnId, executeAt, store, txn) -> null); private static final Result EMPTY_RESULT = new Result() {}; final static long NOT_DEFINED = measure(Command.SerializerSupport.notDefined(attrs(false, false), Ballot.ZERO)); final static long PREACCEPTED = measure(Command.SerializerSupport.preaccepted(attrs(false, true), EMPTY_TXNID, null));; final static long ACCEPTED = measure(Command.SerializerSupport.accepted(attrs(true, false), SaveStatus.Accepted, EMPTY_TXNID, Ballot.ZERO, Ballot.ZERO)); final static long COMMITTED = measure(Command.SerializerSupport.committed(attrs(true, true), SaveStatus.Committed, EMPTY_TXNID, Ballot.ZERO, Ballot.ZERO, null)); - final static long EXECUTED = measure(Command.SerializerSupport.executed(attrs(true, true), SaveStatus.Applied, EMPTY_TXNID, Ballot.ZERO, Ballot.ZERO, WaitingOn.empty(EMPTY_TXNID.domain()), EMPTY_WRITES, EMPTY_RESULT)); + final static long EXECUTED = measure(Command.SerializerSupport.executed(attrs(true, true), SaveStatus.Applied, EMPTY_TXNID, Ballot.ZERO, Ballot.ZERO, WaitingOn.empty(Domain.Key), EMPTY_WRITES, EMPTY_RESULT)); final static long TRUNCATED = measure(Command.SerializerSupport.truncatedApply(attrs(false, false), SaveStatus.TruncatedApply, EMPTY_TXNID, null, null)); final static long INVALIDATED = measure(Command.SerializerSupport.invalidated(EMPTY_TXNID)); @@ -353,7 +357,7 @@ public class AccordObjectSizes return size; } - private static long EMPTY_TFK_SIZE = measure(TimestampsForKey.SerializerSupport.create(null, null, 0, null)); + private static long EMPTY_TFK_SIZE = measure(TimestampsForKey.SerializerSupport.create(null, null, 0, null, null)); public static long timestampsForKey(TimestampsForKey timestamps) { @@ -364,8 +368,8 @@ public class AccordObjectSizes } private static long EMPTY_CFK_SIZE = measure(new CommandsForKey(null)); - private static long EMPTY_INFO_SIZE = measure(TxnInfo.createMock(TxnId.NONE, null, null, NO_TXNIDS, Ballot.ZERO)); - private static long EMPTY_INFO_EXTRA_ADDITIONAL_SIZE = EMPTY_INFO_SIZE - measure(TxnInfo.createMock(TxnId.NONE, null, null, null, null)); + private static long EMPTY_INFO_SIZE = measure(CommandsForKey.NO_INFO); + private static long EMPTY_INFO_EXTRA_ADDITIONAL_SIZE = measure(TxnInfo.create(TxnId.NONE, ACCEPTED, false, TxnId.NONE, NO_TXNIDS, Ballot.MAX)) - EMPTY_INFO_SIZE; public static long commandsForKey(CommandsForKey cfk) { long size = EMPTY_CFK_SIZE; diff --git a/src/java/org/apache/cassandra/service/accord/AccordSafeCommand.java b/src/java/org/apache/cassandra/service/accord/AccordSafeCommand.java index 43bc8d54b9..0221e9f394 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordSafeCommand.java +++ b/src/java/org/apache/cassandra/service/accord/AccordSafeCommand.java @@ -125,7 +125,7 @@ public class AccordSafeCommand extends SafeCommand implements AccordSafeState diff() + public SavedCommand.DiffWriter diff() { return SavedCommand.diff(original, current); } diff --git a/src/java/org/apache/cassandra/service/accord/AccordSafeCommandStore.java b/src/java/org/apache/cassandra/service/accord/AccordSafeCommandStore.java index 8dabbc7bb3..fae5e4634f 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordSafeCommandStore.java +++ b/src/java/org/apache/cassandra/service/accord/AccordSafeCommandStore.java @@ -30,36 +30,40 @@ import accord.api.Agent; import accord.api.DataStore; import accord.api.Key; import accord.api.ProgressLog; +import accord.api.RoutingKey; import accord.impl.AbstractSafeCommandStore; -import accord.local.cfk.CommandsForKey; import accord.impl.CommandsSummary; +import accord.local.CommandStores; import accord.local.CommandStores.RangesForEpoch; +import accord.local.DurableBefore; import accord.local.NodeTimeService; import accord.local.PreLoadContext; +import accord.local.RedundantBefore; +import accord.local.cfk.CommandsForKey; import accord.primitives.AbstractKeys; import accord.primitives.AbstractRanges; import accord.primitives.Deps; -import accord.primitives.Range; import accord.primitives.Ranges; import accord.primitives.Routables; -import accord.primitives.Seekables; import accord.primitives.Timestamp; import accord.primitives.Txn; import accord.primitives.TxnId; +import accord.primitives.Unseekables; public class AccordSafeCommandStore extends AbstractSafeCommandStore { private final Map commands; - private final NavigableMap commandsForKeys; - private final NavigableMap timestampsForKeys; + private final NavigableMap commandsForKeys; + private final NavigableMap timestampsForKeys; private final @Nullable AccordSafeCommandsForRanges commandsForRanges; private final AccordCommandStore commandStore; - private final RangesForEpoch ranges; + private RangesForEpoch ranges; + private FieldUpdates fieldUpdates; private AccordSafeCommandStore(PreLoadContext context, Map commands, - NavigableMap timestampsForKey, - NavigableMap commandsForKey, + NavigableMap timestampsForKey, + NavigableMap commandsForKey, @Nullable AccordSafeCommandsForRanges commandsForRanges, AccordCommandStore commandStore) { @@ -69,13 +73,15 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore commands, - NavigableMap timestampsForKey, - NavigableMap commandsForKey, + NavigableMap timestampsForKey, + NavigableMap commandsForKey, @Nullable AccordSafeCommandsForRanges commandsForRanges, AccordCommandStore commandStore) { @@ -83,7 +89,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore commandsForKeysKeys() + public Set commandsForKeysKeys() { return commandsForKeys.keySet(); } @@ -109,7 +115,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore O mapReduce(Routables keysOrRanges, BiFunction map, O accumulate) { - if (deps.isEmpty()) return; - // used in places such as accord.local.CommandStore.fetchMajorityDeps - // We find a set of dependencies for a range then update CommandsFor to know about them - Ranges allRanges = ranges.all(); - deps.keyDeps.keys().forEach(allRanges, key -> { - // TODO (now): batch register to minimise GC - deps.keyDeps.forEach(key, (txnId, txnIdx) -> { - // TODO (desired, efficiency): this can be made more efficient by batching by epoch - if (ranges.coordinates(txnId).contains(key)) - return; // already coordinates, no need to replicate - if (!ranges.allBefore(txnId.epoch()).contains(key)) - return; - - get(key).registerHistorical(this, txnId); - }); - }); - for (int i = 0; i < deps.rangeDeps.rangeCount(); i++) - { - Range range = deps.rangeDeps.range(i); - if (!allRanges.intersects(range)) - continue; - deps.rangeDeps.forEach(range, txnId -> { - // TODO (desired, efficiency): this can be made more efficient by batching by epoch - if (ranges.coordinates(txnId).intersects(range)) - return; // already coordinates, no need to replicate - if (!ranges.allBefore(txnId.epoch()).intersects(range)) - return; - - commandStore.diskCommandsForRanges().mergeHistoricalTransaction(txnId, Ranges.single(range).slice(allRanges), Ranges::with); - }); - } + accumulate = mapReduceForRange(keysOrRanges, map, accumulate); + return mapReduceForKey(keysOrRanges, map, accumulate); } - private O mapReduce(Routables keysOrRanges, Ranges slice, BiFunction map, O accumulate) - { - accumulate = mapReduceForRange(keysOrRanges, slice, map, accumulate); - return mapReduceForKey(keysOrRanges, slice, map, accumulate); - } - - private O mapReduceForRange(Routables keysOrRanges, Ranges slice, BiFunction map, O accumulate) + private O mapReduceForRange(Routables keysOrRanges, BiFunction map, O accumulate) { if (commandsForRanges == null) return accumulate; - CommandsForRanges cfr = commandsForRanges.current().slice(slice); + CommandsForRanges cfr = commandsForRanges.current(); switch (keysOrRanges.domain()) { case Key: { - AbstractKeys keys = (AbstractKeys) keysOrRanges.slice(slice, Routables.Slice.Minimal); + AbstractKeys keys = (AbstractKeys) keysOrRanges; if (!cfr.ranges.intersects(keys)) return accumulate; } break; case Range: { - AbstractRanges ranges = (AbstractRanges) keysOrRanges.slice(slice, Routables.Slice.Minimal); + AbstractRanges ranges = (AbstractRanges) keysOrRanges; if (!cfr.ranges.intersects(ranges)) return accumulate; } @@ -254,7 +224,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore O mapReduceForKey(Routables keysOrRanges, Ranges slice, BiFunction map, O accumulate) + private O mapReduceForKey(Routables keysOrRanges, BiFunction map, O accumulate) { switch (keysOrRanges.domain()) { @@ -263,10 +233,9 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore keys = (AbstractKeys) keysOrRanges; - for (Key key : keys) + AbstractKeys keys = (AbstractKeys) keysOrRanges; + for (RoutingKey key : keys) { - if (!slice.contains(key)) continue; CommandsForKey commands = get(key).current(); accumulate = map.apply(commands, accumulate); } @@ -276,13 +245,11 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore sliced = keysOrRanges.slice(slice, Routables.Slice.Minimal); - if (!context.keys().slice(slice, Routables.Slice.Minimal).containsAll(sliced)) + if (!context.keys().containsAll(keysOrRanges)) throw new AssertionError("Range(s) detected not present in the PreLoadContext: expected " + context.keys() + " but given " + keysOrRanges); - for (Key key : commandsForKeys.keySet()) + for (RoutingKey key : commandsForKeys.keySet()) { //TODO (duplicate code): this is a repeat of Key... only change is checking contains in range - if (!sliced.contains(key)) continue; CommandsForKey commands = get(key).current(); accumulate = map.apply(commands, accumulate); } @@ -293,17 +260,17 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore T mapReduceActive(Seekables keysOrRanges, Ranges slice, @Nullable Timestamp withLowerTxnId, Txn.Kind.Kinds testKind, CommandFunction map, P1 p1, T accumulate) + public T mapReduceActive(Unseekables keysOrRanges, @Nullable Timestamp withLowerTxnId, Txn.Kind.Kinds testKind, CommandFunction map, P1 p1, T accumulate) { - return mapReduce(keysOrRanges, slice, (summary, in) -> { + return mapReduce(keysOrRanges, (summary, in) -> { return summary.mapReduceActive(withLowerTxnId, testKind, map, p1, in); }, accumulate); } @Override - public T mapReduceFull(Seekables keysOrRanges, Ranges slice, TxnId testTxnId, Txn.Kind.Kinds testKind, TestStartedAt testStartedAt, TestDep testDep, TestStatus testStatus, CommandFunction map, P1 p1, T accumulate) + public T mapReduceFull(Unseekables keysOrRanges, TxnId testTxnId, Txn.Kind.Kinds testKind, TestStartedAt testStartedAt, TestDep testDep, TestStatus testStatus, CommandFunction map, P1 p1, T accumulate) { - return mapReduce(keysOrRanges, slice, (summary, in) -> { + return mapReduce(keysOrRanges, (summary, in) -> { return summary.mapReduceFull(testTxnId, testKind, testStartedAt, testDep, testStatus, map, p1, in); }, accumulate); } @@ -313,4 +280,70 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore newBootstrapBeganAt) + { + ensureFieldUpdates().bootstrapBeganAt = newBootstrapBeganAt; + super.setBootstrapBeganAt(newBootstrapBeganAt); + } + + @Override + public void upsertDurableBefore(DurableBefore addDurableBefore) + { + ensureFieldUpdates().durableBefore = addDurableBefore; + super.upsertDurableBefore(addDurableBefore); + } + + @Override + public void setSafeToRead(NavigableMap newSafeToRead) + { + ensureFieldUpdates().safeToRead = newSafeToRead; + super.setSafeToRead(newSafeToRead); + } + + @Override + public void setRangesForEpoch(CommandStores.RangesForEpoch rangesForEpoch) + { + ensureFieldUpdates().rangesForEpoch = rangesForEpoch.snapshot(); + super.setRangesForEpoch(rangesForEpoch); + ranges = rangesForEpoch; + } + + @Override + protected void registerHistoricalTransactions(Deps deps) + { + ensureFieldUpdates().historicalTransactions = deps; + super.registerHistoricalTransactions(deps); + } + + private FieldUpdates ensureFieldUpdates() + { + if (fieldUpdates == null) fieldUpdates = new FieldUpdates(); + return fieldUpdates; + } + + public FieldUpdates fieldUpdates() + { + return fieldUpdates; + } + + public static class FieldUpdates + { + public RedundantBefore redundantBefore; + public DurableBefore durableBefore; + public NavigableMap bootstrapBeganAt; + public NavigableMap safeToRead; + public RangesForEpoch.Snapshot rangesForEpoch; + public Deps historicalTransactions; + } +} \ No newline at end of file diff --git a/src/java/org/apache/cassandra/service/accord/AccordSafeCommandsForKey.java b/src/java/org/apache/cassandra/service/accord/AccordSafeCommandsForKey.java index 6f5e8f72d5..5874442528 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordSafeCommandsForKey.java +++ b/src/java/org/apache/cassandra/service/accord/AccordSafeCommandsForKey.java @@ -22,18 +22,18 @@ import java.util.Objects; import com.google.common.annotations.VisibleForTesting; -import accord.api.Key; +import accord.api.RoutingKey; import accord.local.cfk.CommandsForKey; import accord.local.cfk.SafeCommandsForKey; -public class AccordSafeCommandsForKey extends SafeCommandsForKey implements AccordSafeState +public class AccordSafeCommandsForKey extends SafeCommandsForKey implements AccordSafeState { private boolean invalidated; - private final AccordCachingState global; + private final AccordCachingState global; private CommandsForKey original; private CommandsForKey current; - public AccordSafeCommandsForKey(AccordCachingState global) + public AccordSafeCommandsForKey(AccordCachingState global) { super(global.key()); this.global = global; @@ -82,7 +82,7 @@ public class AccordSafeCommandsForKey extends SafeCommandsForKey implements Acco } @Override - public AccordCachingState global() + public AccordCachingState global() { checkNotInvalidated(); return global; diff --git a/src/java/org/apache/cassandra/service/accord/AccordSafeTimestampsForKey.java b/src/java/org/apache/cassandra/service/accord/AccordSafeTimestampsForKey.java index 89baee84b9..77ad56c3fd 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordSafeTimestampsForKey.java +++ b/src/java/org/apache/cassandra/service/accord/AccordSafeTimestampsForKey.java @@ -23,21 +23,21 @@ import java.util.concurrent.TimeUnit; import com.google.common.annotations.VisibleForTesting; -import accord.api.Key; +import accord.api.RoutingKey; import accord.impl.SafeTimestampsForKey; import accord.impl.TimestampsForKey; import accord.primitives.Timestamp; -public class AccordSafeTimestampsForKey extends SafeTimestampsForKey implements AccordSafeState +public class AccordSafeTimestampsForKey extends SafeTimestampsForKey implements AccordSafeState { private boolean invalidated; - private final AccordCachingState global; + private final AccordCachingState global; private TimestampsForKey original; private TimestampsForKey current; - public AccordSafeTimestampsForKey(AccordCachingState global) + public AccordSafeTimestampsForKey(AccordCachingState global) { - super((Key) global.key()); + super(global.key()); this.global = global; this.original = null; this.current = null; @@ -70,7 +70,7 @@ public class AccordSafeTimestampsForKey extends SafeTimestampsForKey implements } @Override - public AccordCachingState global() + public AccordCachingState global() { checkNotInvalidated(); return global; diff --git a/src/java/org/apache/cassandra/service/accord/AccordSegmentCompactor.java b/src/java/org/apache/cassandra/service/accord/AccordSegmentCompactor.java index e3f10cb644..1dc8389c20 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordSegmentCompactor.java +++ b/src/java/org/apache/cassandra/service/accord/AccordSegmentCompactor.java @@ -17,11 +17,11 @@ */ package org.apache.cassandra.service.accord; +import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.PriorityQueue; -import com.google.common.base.Throwables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,31 +30,43 @@ import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.SerializationHeader; import org.apache.cassandra.db.partitions.PartitionUpdate; +import org.apache.cassandra.db.partitions.PartitionUpdate.SimpleBuilder; import org.apache.cassandra.db.rows.EncodingStats; import org.apache.cassandra.io.sstable.Descriptor; import org.apache.cassandra.io.sstable.SSTableTxnWriter; +import org.apache.cassandra.io.util.DataInputBuffer; +import org.apache.cassandra.io.util.DataOutputBuffer; import org.apache.cassandra.journal.KeySupport; import org.apache.cassandra.journal.SegmentCompactor; import org.apache.cassandra.journal.StaticSegment; import org.apache.cassandra.journal.StaticSegment.KeyOrderReader; +import org.apache.cassandra.service.accord.AccordJournalValueSerializers.FlyweightSerializer; /** * Segment compactor: takes static segments and compacts them into a single SSTable. */ -public class AccordSegmentCompactor implements SegmentCompactor +public class AccordSegmentCompactor implements SegmentCompactor { private static final Logger logger = LoggerFactory.getLogger(AccordSegmentCompactor.class); + private final int userVersion; + private final KeySupport keySupport; + + public AccordSegmentCompactor(KeySupport keySupport, int userVersion) + { + this.userVersion = userVersion; + this.keySupport = keySupport; + } @Override - public Collection> compact(Collection> segments, KeySupport keySupport) + public Collection> compact(Collection> segments) { Invariants.checkState(segments.size() >= 2, () -> String.format("Can only compact 2 or more segments, but got %d", segments.size())); logger.info("Compacting {} static segments: {}", segments.size(), segments); - PriorityQueue> readers = new PriorityQueue<>(); - for (StaticSegment segment : segments) + PriorityQueue> readers = new PriorityQueue<>(); + for (StaticSegment segment : segments) { - KeyOrderReader reader = segment.keyOrderReader(); + KeyOrderReader reader = segment.keyOrderReader(); if (reader.advance()) readers.add(reader); } @@ -62,7 +74,7 @@ public class AccordSegmentCompactor implements SegmentCompactor // nothing to compact (all segments empty, should never happen, but it is theoretically possible?) - exit early // TODO: investigate how this comes to be, check if there is a cleanup issue if (readers.isEmpty()) - return segments; + return null; ColumnFamilyStore cfs = Keyspace.open(AccordKeyspace.metadata().name).getColumnFamilyStore(AccordKeyspace.JOURNAL); Descriptor descriptor = cfs.newSSTableDescriptor(cfs.getDirectories().getDirectoryForNewSSTables()); @@ -70,50 +82,67 @@ public class AccordSegmentCompactor implements SegmentCompactor try (SSTableTxnWriter writer = SSTableTxnWriter.create(cfs, descriptor, 0, 0, null, false, header)) { - K key = null; - PartitionUpdate.SimpleBuilder partitionBuilder = null; - + JournalKey key = null; + Object builder = null; + FlyweightSerializer serializer = null; + long lastDescriptor = -1; + int lastOffset = -1; try { - KeyOrderReader reader; + KeyOrderReader reader; while ((reader = readers.poll()) != null) { - if (!reader.key().equals(key)) // first ever - or new - key + if (key == null || !reader.key().equals(key)) { - if (partitionBuilder != null) // append previous partition if any - writer.append(partitionBuilder.build().unfilteredIterator()); + maybeWritePartition(cfs, writer, key, builder, serializer, lastDescriptor, lastOffset); key = reader.key(); - partitionBuilder = PartitionUpdate.simpleBuilder( - AccordKeyspace.Journal, AccordJournalTable.makePartitionKey(cfs, key, keySupport, reader.descriptor.userVersion) - ); + serializer = (FlyweightSerializer) key.type.serializer; + builder = serializer.mergerFor(key); } boolean advanced; do { - partitionBuilder.row(reader.descriptor.timestamp, reader.offset()) - .add("record", reader.record()) - .add("user_version", reader.descriptor.userVersion); + try (DataInputBuffer in = new DataInputBuffer(reader.record(), false)) + { + serializer.deserialize(key, builder, in, reader.descriptor.userVersion); + lastDescriptor = reader.descriptor.timestamp; + lastOffset = reader.offset(); + } } while ((advanced = reader.advance()) && reader.key().equals(key)); if (advanced) readers.offer(reader); // there is more to this reader, but not with this key } - //noinspection DataFlowIssue - writer.append(partitionBuilder.build().unfilteredIterator()); // append the last partition + maybeWritePartition(cfs, writer, key, builder, serializer, lastDescriptor, lastOffset); } catch (Throwable t) { Throwable accumulate = writer.abort(t); - Throwables.throwIfUnchecked(accumulate); - throw new RuntimeException(accumulate); + throw new RuntimeException(String.format("Caught exception while serializing. Last seen key: %s", key), accumulate); } cfs.addSSTables(writer.finish(true)); return Collections.emptyList(); } } + + private void maybeWritePartition(ColumnFamilyStore cfs, SSTableTxnWriter writer, JournalKey key, Object builder, FlyweightSerializer serializer, long descriptor, int offset) throws IOException + { + if (builder != null) + { + SimpleBuilder partitionBuilder = PartitionUpdate.simpleBuilder(AccordKeyspace.Journal, AccordJournalTable.makePartitionKey(cfs, key, keySupport, userVersion)); + try (DataOutputBuffer out = DataOutputBuffer.scratchBuffer.get()) + { + serializer.reserialize(key, builder, out, userVersion); + partitionBuilder.row(descriptor, offset) + .add("record", out.asNewBuffer()) + .add("user_version", userVersion); + } + writer.append(partitionBuilder.build().unfilteredIterator()); + } + } } diff --git a/src/java/org/apache/cassandra/service/accord/AccordService.java b/src/java/org/apache/cassandra/service/accord/AccordService.java index a90ca24351..b2f22b2ad1 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordService.java +++ b/src/java/org/apache/cassandra/service/accord/AccordService.java @@ -78,6 +78,8 @@ import org.slf4j.LoggerFactory; import accord.api.BarrierType; import accord.api.LocalConfig; import accord.api.Result; +import accord.coordinate.Barrier.AsyncSyncPoint; +import accord.coordinate.CoordinationAdapter.Adapters.SyncPointAdapter; import accord.coordinate.CoordinationFailed; import accord.coordinate.Preempted; import accord.coordinate.Timeout; @@ -93,15 +95,14 @@ import accord.impl.SizeOfIntersectionSorter; import accord.impl.progresslog.DefaultProgressLogs; import accord.local.CommandStore; import accord.local.CommandStores; +import accord.local.CommandStores.RangesForEpoch; import accord.local.DurableBefore; import accord.local.KeyHistory; import accord.local.Node; import accord.local.Node.Id; import accord.local.NodeTimeService; import accord.local.RedundantBefore; -import accord.local.SaveStatus; import accord.local.ShardDistributor.EvenSplit; -import accord.local.Status; import accord.local.cfk.CommandsForKey; import accord.messages.Callback; import accord.messages.ReadData; @@ -110,6 +111,10 @@ import accord.messages.WaitUntilApplied; import accord.primitives.Keys; import accord.primitives.Seekable; import accord.primitives.Seekables; +import accord.primitives.FullRoute; +import accord.primitives.RoutingKeys; +import accord.primitives.SaveStatus; +import accord.primitives.Status; import accord.primitives.Timestamp; import accord.primitives.Txn; import accord.primitives.Txn.Kind; @@ -142,6 +147,7 @@ import org.apache.cassandra.schema.TableId; 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.AccordRoutingKey.TokenKey; import org.apache.cassandra.service.accord.api.AccordScheduler; import org.apache.cassandra.service.accord.api.AccordTopologySorter; import org.apache.cassandra.service.accord.api.CompositeTopologySorter; @@ -174,6 +180,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.apache.cassandra.config.DatabaseDescriptor.getPartitioner; import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.accordReadMetrics; import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.accordWriteMetrics; +import static org.apache.cassandra.service.consensus.migration.ConsensusKeyMigrationState.maybeSaveAccordKeyMigrationLocally; import static org.apache.cassandra.utils.Clock.Global.nanoTime; public class AccordService implements IAccordService, Shutdownable @@ -207,19 +214,19 @@ public class AccordService implements IAccordService, Shutdownable } @Override - public Seekables barrierWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException + 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) + 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 Seekables 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) { throw new UnsupportedOperationException("No accord repairs should be executed when accord.enabled = false in cassandra.yaml"); } @@ -357,6 +364,8 @@ public class AccordService implements IAccordService, Shutdownable as.configurationService().notifyPostCommit(current, current, false); } instance = as; + + as.journal().replay(); } public static void shutdownServiceAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException @@ -393,7 +402,7 @@ public class AccordService implements IAccordService, Shutdownable this.scheduler = new AccordScheduler(); this.dataStore = new AccordDataStore(); this.configuration = new AccordConfiguration(DatabaseDescriptor.getRawConfig()); - this.journal = new AccordJournal(configService, DatabaseDescriptor.getAccord().journal); + this.journal = new AccordJournal(DatabaseDescriptor.getAccord().journal); this.node = new Node(localId, messageSink, configService, @@ -415,7 +424,7 @@ public class AccordService implements IAccordService, Shutdownable configuration); this.nodeShutdown = toShutdownable(node); this.durabilityScheduling = new CoordinateDurabilityScheduling(node); - this.requestHandler = new AccordVerbHandler<>(node, configService, journal); + this.requestHandler = new AccordVerbHandler<>(node, configService); } @Override @@ -473,7 +482,7 @@ public class AccordService implements IAccordService, Shutdownable durabilityScheduling.setShardCycleTime(Ints.checkedCast(DatabaseDescriptor.getAccordShardDurabilityCycle(SECONDS)), SECONDS); durabilityScheduling.setTxnIdLag(Ints.checkedCast(DatabaseDescriptor.getAccordScheduleDurabilityTxnIdLag(SECONDS)), TimeUnit.SECONDS); durabilityScheduling.setFrequency(Ints.checkedCast(DatabaseDescriptor.getAccordScheduleDurabilityFrequency(SECONDS)), SECONDS); -// durabilityScheduling.start(); + durabilityScheduling.start(); state = State.STARTED; } @@ -551,10 +560,10 @@ public class AccordService implements IAccordService, Shutdownable return requestHandler; } - private > Seekables barrier(@Nonnull S keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, BiFunction>> syncPoint) + private Seekables barrier(@Nonnull Seekables keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, BiFunction, AsyncSyncPoint> syncPoint) { Stopwatch sw = Stopwatch.createStarted(); - keysOrRanges = (S) intersectionWithAccordManagedRanges(keysOrRanges); + keysOrRanges = 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()) { @@ -562,15 +571,22 @@ public class AccordService implements IAccordService, Shutdownable return keysOrRanges; } + FullRoute route = node.computeRoute(epoch, keysOrRanges); AccordClientRequestMetrics metrics = isForWrite ? accordWriteMetrics : accordReadMetrics; try { logger.debug("Starting barrier key: {} epoch: {} barrierType: {} isForWrite {}", keysOrRanges, epoch, barrierType, isForWrite); AsyncResult asyncResult = syncPoint == null - ? Barrier.barrier(node, keysOrRanges, epoch, barrierType) - : Barrier.barrier(node, keysOrRanges, epoch, barrierType, syncPoint); + ? Barrier.barrier(node, keysOrRanges, route, epoch, barrierType) + : Barrier.barrier(node, keysOrRanges, route, epoch, barrierType, syncPoint); + if (keysOrRanges.domain() == Key) + { + PartitionKey key = (PartitionKey)keysOrRanges.get(0); + asyncResult.accept(txnId -> maybeSaveAccordKeyMigrationLocally(key, Epoch.create(txnId.epoch()))); + } long deadlineNanos = requestTime.startedAtNanos() + timeoutNanos; - Timestamp barrierExecuteAt = AsyncChains.getBlocking(asyncResult, deadlineNanos - nanoTime(), NANOSECONDS); + TxnId txnId = AsyncChains.getBlocking(asyncResult, deadlineNanos - nanoTime(), NANOSECONDS); + ((AccordAgent) node.agent()).onSuccessfulBarrier(txnId, keysOrRanges); logger.debug("Completed barrier attempt in {}ms, {}ms since attempts start, barrier key: {} epoch: {} barrierType: {} isForWrite {}", sw.elapsed(MILLISECONDS), NANOSECONDS.toMillis(nanoTime() - requestTime.startedAtNanos()), @@ -627,33 +643,37 @@ public class AccordService implements IAccordService, Shutdownable } @Override - public Seekables 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); } - public static > BiFunction>> repairSyncPoint(Set allNodes) + public static BiFunction, AsyncSyncPoint> repairSyncPoint(Set allNodes) { - return (node, seekables) -> CoordinateSyncPoint.coordinate(node, Kind.SyncPoint, seekables, RepairSyncPointAdapter.create(allNodes)); + return (node, route) -> { + TxnId txnId = node.nextTxnId(Kind.SyncPoint, route.domain()); + AsyncResult> async = CoordinateSyncPoint.coordinate(node, Kind.SyncPoint, route, (SyncPointAdapter)RepairSyncPointAdapter.create(allNodes)); + return new AsyncSyncPoint(txnId, async); + }; } @Override - public Seekables 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) + private static Seekables intersectionWithAccordManagedRanges(Seekables keysOrRanges) { TableId tableId = null; - for (Seekable seekable : keysOrRanges) + for (Seekable keyOrRange : keysOrRanges) { TableId newTableId; if (keysOrRanges.domain() == Key) - newTableId = ((PartitionKey) seekable).table(); + newTableId = ((PartitionKey)keyOrRange).table(); else if (keysOrRanges.domain() == Range) - newTableId = ((TokenRange) seekable).table(); + newTableId = ((TokenRange) keyOrRange).table(); else throw new IllegalStateException("Unexpected domain " + keysOrRanges.domain()); @@ -780,7 +800,7 @@ public class AccordService implements IAccordService, Shutdownable } @Override - public Seekables 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(), @@ -1030,9 +1050,9 @@ public class AccordService implements IAccordService, Shutdownable return submit.flatMap(Function.identity()); } - private static AsyncChain populate(CommandStoreTxnBlockedGraph.Builder state, CommandStore commandStore, PartitionKey blockedBy, TxnId txnId, Timestamp executeAt) + private static AsyncChain populate(CommandStoreTxnBlockedGraph.Builder state, CommandStore commandStore, TokenKey blockedBy, TxnId txnId, Timestamp executeAt) { - AsyncChain> submit = commandStore.submit(PreLoadContext.contextFor(txnId, Keys.of(blockedBy), KeyHistory.COMMANDS), in -> { + AsyncChain> submit = commandStore.submit(PreLoadContext.contextFor(txnId, RoutingKeys.of(blockedBy.toUnseekable()), KeyHistory.COMMANDS), in -> { AsyncChain chain = populate(state, (AccordSafeCommandStore) in, blockedBy, txnId, executeAt); return chain == null ? AsyncChains.success(null) : chain; }); @@ -1067,7 +1087,7 @@ public class AccordService implements IAccordService, Shutdownable chains.add(populate(state, safeStore.commandStore(), blockedBy)); } } - for (PartitionKey blockedBy : cmdTxnState.blockedByKey) + for (TokenKey blockedBy : cmdTxnState.blockedByKey) { if (state.keys.containsKey(blockedBy)) continue; if (safeStore.getCommandsForKeyIfLoaded(blockedBy) != null) @@ -1087,7 +1107,7 @@ public class AccordService implements IAccordService, Shutdownable return AsyncChains.all(chains).map(ignore -> null); } - private static AsyncChain populate(CommandStoreTxnBlockedGraph.Builder state, AccordSafeCommandStore safeStore, PartitionKey pk, TxnId txnId, Timestamp executeAt) + private static AsyncChain populate(CommandStoreTxnBlockedGraph.Builder state, AccordSafeCommandStore safeStore, TokenKey pk, TxnId txnId, Timestamp executeAt) { AccordSafeCommandsForKey commandsForKey = safeStore.getCommandsForKeyIfLoaded(pk); TxnId blocking = commandsForKey.current().blockedOnTxnId(txnId, executeAt); @@ -1115,7 +1135,7 @@ public class AccordService implements IAccordService, Shutdownable else { // blocked on key - cmdTxnState.blockedByKey.add((PartitionKey) waitingOn.keys.get(i - waitingOn.txnIdCount())); + cmdTxnState.blockedByKey.add((TokenKey) waitingOn.keys.get(i - waitingOn.txnIdCount())); } }); } @@ -1138,9 +1158,9 @@ public class AccordService implements IAccordService, Shutdownable tryMarkRemoved(ranges, 0).begin(node().agent()); } - private AsyncChain> tryMarkRemoved(Ranges ranges, int attempt) + private AsyncChain> tryMarkRemoved(Ranges ranges, int attempt) { - return CoordinateSyncPoint.exclusive(node, ranges) + return CoordinateSyncPoint.exclusiveSyncPoint(node, ranges) .recover(t -> //TODO (operability): make this configurable / monitorable? attempt <= 3 && t instanceof Invalidated || t instanceof Preempted || t instanceof Timeout ? tryMarkRemoved(ranges, attempt + 1) : null); @@ -1234,7 +1254,7 @@ public class AccordService implements IAccordService, Shutdownable public CompactionInfo getCompactionInfo() { Int2ObjectHashMap redundantBefores = new Int2ObjectHashMap<>(); - Int2ObjectHashMap ranges = new Int2ObjectHashMap<>(); + Int2ObjectHashMap ranges = new Int2ObjectHashMap<>(); AtomicReference durableBefore = new AtomicReference<>(DurableBefore.EMPTY); AsyncChains.getBlockingAndRethrow(node.commandStores().forEach(safeStore -> { synchronized (redundantBefores) @@ -1334,10 +1354,10 @@ public class AccordService implements IAccordService, Shutdownable .flatMap(s -> s == null ? AsyncChains.success(null) : Await.coordinate(node, s)); } - private AsyncChain> exclusiveSyncPoint(Ranges ranges, int attempt) + private AsyncChain> exclusiveSyncPoint(Ranges ranges, int attempt) { //TODO (on merge): CASSANDRA-19769 has the same logic... should this be refactored? Would make it nice so we could split the range on retries? - return CoordinateSyncPoint.exclusive(node, ranges) + return CoordinateSyncPoint.exclusiveSyncPoint(node, ranges) .recover(t -> { //TODO (operability): make this configurable / monitorable? if (attempt > 3) return null; @@ -1368,21 +1388,21 @@ public class AccordService implements IAccordService, Shutdownable // TODO (duplication): this is 95% of accord.coordinate.CoordinateShardDurable // we already report all this information to EpochState; would be better to use that // Taken from ListStore... - private static class Await extends AsyncResults.SettableResult> implements Callback + private static class Await extends AsyncResults.SettableResult> implements Callback { private final Node node; private final AllTracker tracker; - private final SyncPoint exclusiveSyncPoint; + private final SyncPoint exclusiveSyncPoint; - private Await(Node node, SyncPoint exclusiveSyncPoint) + private Await(Node node, SyncPoint exclusiveSyncPoint) { - Topologies topologies = node.topology().forEpoch(exclusiveSyncPoint.keysOrRanges, exclusiveSyncPoint.sourceEpoch()); + Topologies topologies = node.topology().forEpoch(exclusiveSyncPoint.route, exclusiveSyncPoint.sourceEpoch()); this.node = node; this.tracker = new AllTracker(topologies); this.exclusiveSyncPoint = exclusiveSyncPoint; } - public static AsyncChain coordinate(Node node, SyncPoint sp) + public static AsyncChain coordinate(Node node, SyncPoint sp) { return node.withEpoch(sp.sourceEpoch(), () -> { Await coordinate = new Await(node, sp); @@ -1402,7 +1422,7 @@ public class AccordService implements IAccordService, Shutdownable private void start() { - node.send(tracker.nodes(), to -> new WaitUntilApplied(to, tracker.topologies(), exclusiveSyncPoint.syncId, exclusiveSyncPoint.keysOrRanges, exclusiveSyncPoint.syncId.epoch()), this); + node.send(tracker.nodes(), to -> new WaitUntilApplied(to, tracker.topologies(), exclusiveSyncPoint.syncId, exclusiveSyncPoint.route, exclusiveSyncPoint.syncId.epoch()), this); } @Override public void onSuccess(Node.Id from, ReadData.ReadReply reply) @@ -1423,7 +1443,7 @@ public class AccordService implements IAccordService, Shutdownable tryFailure(new ExecuteSyncPoint.SyncPointErased()); return; case Invalid: - tryFailure(new Invalidated(exclusiveSyncPoint.syncId, exclusiveSyncPoint.homeKey)); + tryFailure(new Invalidated(exclusiveSyncPoint.syncId, exclusiveSyncPoint.route.homeKey())); return; } } @@ -1431,7 +1451,7 @@ public class AccordService implements IAccordService, Shutdownable { if (tracker.recordSuccess(from) == RequestStatus.Success) { - node.configService().reportEpochRedundant(exclusiveSyncPoint.keysOrRanges, exclusiveSyncPoint.syncId.epoch()); + node.configService().reportEpochRedundant(exclusiveSyncPoint.route.toRanges(), exclusiveSyncPoint.syncId.epoch()); trySuccess(exclusiveSyncPoint); } } diff --git a/src/java/org/apache/cassandra/service/accord/AccordStateCache.java b/src/java/org/apache/cassandra/service/accord/AccordStateCache.java index 42d4a4d243..04f618e338 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordStateCache.java +++ b/src/java/org/apache/cassandra/service/accord/AccordStateCache.java @@ -379,6 +379,25 @@ public class AccordStateCache extends IntrusiveLinkedList node = (AccordCachingState) cache.get(key); + if (node == null) + { + node = nodeFactory.create(key, index); + node.initialize(initial); + Object prev = cache.put(key, node); + Invariants.checkState(prev == null, "%s not absent from cache: %s already present", key, node); + if (listeners != null) + { + AccordCachingState finalNode = node; + listeners.forEach(l -> l.onAdd(finalNode)); + } + maybeUpdateSize(node, heapEstimator); + } + } + public S acquire(K key) { AccordCachingState node = acquire(key, false); diff --git a/src/java/org/apache/cassandra/service/accord/AccordTopology.java b/src/java/org/apache/cassandra/service/accord/AccordTopology.java index eb8f660b0a..2489929c16 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordTopology.java +++ b/src/java/org/apache/cassandra/service/accord/AccordTopology.java @@ -23,12 +23,12 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; +import accord.local.Node.Id; import accord.primitives.Ranges; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; -import accord.local.Node; import accord.topology.Shard; import accord.topology.Topology; import accord.utils.Invariants; @@ -58,14 +58,14 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; */ public class AccordTopology { - public static Node.Id tcmIdToAccord(NodeId nodeId) + public static Id tcmIdToAccord(NodeId nodeId) { - return new Node.Id(nodeId.id()); + return new Id(nodeId.id()); } private static class ShardLookup extends HashMap { - private Shard createOrReuse(boolean pendingRemoval, accord.primitives.Range range, SortedArrayList nodes, Set fastPathElectorate, Set joining) + private Shard createOrReuse(boolean pendingRemoval, accord.primitives.Range range, SortedArrayList nodes, Set fastPathElectorate, Set joining) { Shard prev = get(range); if (prev != null @@ -83,10 +83,10 @@ public class AccordTopology { private final KeyspaceMetadata keyspace; private final Range range; - private final SortedArrayList nodes; - private final Set pending; + private final SortedArrayList nodes; + private final Set pending; - private KeyspaceShard(KeyspaceMetadata keyspace, Range range, SortedArrayList nodes, Set pending) + private KeyspaceShard(KeyspaceMetadata keyspace, Range range, SortedArrayList nodes, Set pending) { this.keyspace = keyspace; this.range = range; @@ -100,15 +100,15 @@ public class AccordTopology FastPathStrategy tableStrategy = metadata.params.fastPath; FastPathStrategy strategy = tableStrategy.kind() != FastPathStrategy.Kind.INHERIT_KEYSPACE ? tableStrategy : keyspace.params.fastPath; - Invariants.checkState(strategy.kind() != FastPathStrategy.Kind.INHERIT_KEYSPACE);; + Invariants.checkState(strategy.kind() != FastPathStrategy.Kind.INHERIT_KEYSPACE); return strategy; } - Shard createForTable(TableMetadata metadata, Set unavailable, Map dcMap, ShardLookup lookup) + Shard createForTable(TableMetadata metadata, Set unavailable, Map dcMap, ShardLookup lookup) { TokenRange tokenRange = AccordTopology.range(metadata.id, range); - SortedArrayList fastPath = strategyFor(metadata).calculateFastPath(nodes, unavailable, dcMap); + SortedArrayList fastPath = strategyFor(metadata).calculateFastPath(nodes, unavailable, dcMap); return lookup.createOrReuse(metadata.params.pendingDrop, tokenRange, nodes, fastPath, pending); } @@ -124,14 +124,14 @@ public class AccordTopology Sets.SetView readOnly = Sets.difference(readEndpoints, writeEndpoints); Invariants.checkState(readOnly.isEmpty(), "Read only replicas detected: %s", readOnly); - SortedArrayList nodes = new SortedArrayList<>(writes.endpoints().stream() - .map(directory::peerId) - .map(AccordTopology::tcmIdToAccord) - .sorted().toArray(Node.Id[]::new)); + SortedArrayList nodes = new SortedArrayList<>(writes.endpoints().stream() + .map(directory::peerId) + .map(AccordTopology::tcmIdToAccord) + .sorted().toArray(Id[]::new)); - Set pending = readEndpoints.equals(writeEndpoints) ? - Collections.emptySet() : - writeEndpoints.stream() + Set pending = readEndpoints.equals(writeEndpoints) ? + Collections.emptySet() : + writeEndpoints.stream() .filter(e -> !readEndpoints.contains(e)) .map(directory::peerId) .map(AccordTopology::tcmIdToAccord) @@ -156,7 +156,7 @@ public class AccordTopology return shards; } - public List nodes() + public List nodes() { return nodes; } @@ -213,9 +213,9 @@ public class AccordTopology return accordRanges; } - private static Map createDCMap(Directory directory) + private static Map createDCMap(Directory directory) { - ImmutableMap.Builder builder = ImmutableMap.builder(); + ImmutableMap.Builder builder = ImmutableMap.builder(); directory.knownDatacenters().forEach(dc -> { Set dcEndpoints = directory.datacenterEndpoints(dc); // nodes aren't added to the endpointsToDCMap until they've joined @@ -223,7 +223,7 @@ public class AccordTopology return; dcEndpoints.forEach(ep -> { NodeId tid = directory.peerId(ep); - Node.Id aid = tcmIdToAccord(tid); + Id aid = tcmIdToAccord(tid); builder.put(aid, dc); }); }); @@ -235,8 +235,8 @@ public class AccordTopology AccordStaleReplicas staleReplicas) { List shards = new ArrayList<>(); - Set unavailable = accordFastPath.unavailableIds(); - Map dcMap = createDCMap(directory); + Set unavailable = accordFastPath.unavailableIds(); + Map dcMap = createDCMap(directory); for (KeyspaceMetadata keyspace : schema.getKeyspaces()) { @@ -249,7 +249,7 @@ public class AccordTopology shards.sort((a, b) -> a.range.compare(b.range)); - return new Topology(epoch.getEpoch(), staleReplicas.ids(), shards.toArray(new Shard[0])); + return new Topology(epoch.getEpoch(), SortedArrayList.copyUnsorted(staleReplicas.ids(), Id[]::new), shards.toArray(new Shard[0])); } public static Topology createAccordTopology(ClusterMetadata metadata, ShardLookup lookup) @@ -275,7 +275,7 @@ public class AccordTopology // There are cases where nodes are removed from the cluster (host replacement, decom, etc.), but inflight events may still be happening; // keep the ids around so pending events do not fail with a mapping error - for (Node.Id id : mapping.differenceIds(builder)) + for (Id id : mapping.differenceIds(builder)) builder.add(mapping.mappedEndpoint(id), id); return builder.build(); } diff --git a/src/java/org/apache/cassandra/service/accord/AccordVerbHandler.java b/src/java/org/apache/cassandra/service/accord/AccordVerbHandler.java index 5bf50308ea..9bda1950e9 100644 --- a/src/java/org/apache/cassandra/service/accord/AccordVerbHandler.java +++ b/src/java/org/apache/cassandra/service/accord/AccordVerbHandler.java @@ -33,13 +33,11 @@ public class AccordVerbHandler implements IVerbHandler private final Node node; private final AccordEndpointMapper endpointMapper; - private final AccordJournal journal; - public AccordVerbHandler(Node node, AccordEndpointMapper endpointMapper, AccordJournal journal) + public AccordVerbHandler(Node node, AccordEndpointMapper endpointMapper) { this.node = node; this.endpointMapper = endpointMapper; - this.journal = journal; } @Override @@ -50,12 +48,6 @@ public class AccordVerbHandler implements IVerbHandler logger.trace("Receiving {} from {}", message.payload, message.from()); T request = message.payload; - if (request.type().hasSideEffects()) - { - journal.processRemoteRequest(request, message); - return; - } - /* * TODO (desired): messages without side-effects don't go through the journal, * and as such are retained on heap until the node catches up to waitForEpoch, diff --git a/src/java/org/apache/cassandra/service/accord/CommandStoreTxnBlockedGraph.java b/src/java/org/apache/cassandra/service/accord/CommandStoreTxnBlockedGraph.java index c7d0147add..5fdc1c2d62 100644 --- a/src/java/org/apache/cassandra/service/accord/CommandStoreTxnBlockedGraph.java +++ b/src/java/org/apache/cassandra/service/accord/CommandStoreTxnBlockedGraph.java @@ -29,16 +29,16 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import accord.local.SaveStatus; +import accord.primitives.SaveStatus; import accord.primitives.Timestamp; import accord.primitives.TxnId; -import org.apache.cassandra.service.accord.api.PartitionKey; +import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; public class CommandStoreTxnBlockedGraph { public final int storeId; public final Map txns; - public final Map keys; + public final Map keys; public CommandStoreTxnBlockedGraph(Builder builder) { @@ -53,7 +53,7 @@ public class CommandStoreTxnBlockedGraph public final Timestamp executeAt; public final SaveStatus saveStatus; public final List blockedBy; - public final Set blockedByKey; + public final Set blockedByKey; public TxnState(Builder.TxnBuilder builder) { @@ -79,7 +79,7 @@ public class CommandStoreTxnBlockedGraph { final int storeId; final Map txns = new LinkedHashMap<>(); - final Map keys = new LinkedHashMap<>(); + final Map keys = new LinkedHashMap<>(); public Builder(int storeId) { @@ -107,7 +107,7 @@ public class CommandStoreTxnBlockedGraph final Timestamp executeAt; final SaveStatus saveStatus; List blockedBy = new ArrayList<>(); - Set blockedByKey = new LinkedHashSet<>(); + Set blockedByKey = new LinkedHashSet<>(); public TxnBuilder(TxnId txnId, Timestamp executeAt, SaveStatus saveStatus) { diff --git a/src/java/org/apache/cassandra/service/accord/CommandsForRanges.java b/src/java/org/apache/cassandra/service/accord/CommandsForRanges.java index 4da915448c..c664ff62c3 100644 --- a/src/java/org/apache/cassandra/service/accord/CommandsForRanges.java +++ b/src/java/org/apache/cassandra/service/accord/CommandsForRanges.java @@ -33,9 +33,9 @@ import accord.local.SafeCommandStore.CommandFunction; import accord.local.SafeCommandStore.TestDep; import accord.local.SafeCommandStore.TestStartedAt; import accord.local.SafeCommandStore.TestStatus; -import accord.local.SaveStatus; import accord.primitives.Range; import accord.primitives.Ranges; +import accord.primitives.SaveStatus; import accord.primitives.Timestamp; import accord.primitives.Txn; import accord.primitives.TxnId; @@ -44,9 +44,9 @@ import static accord.local.SafeCommandStore.TestDep.ANY_DEPS; import static accord.local.SafeCommandStore.TestDep.WITH; import static accord.local.SafeCommandStore.TestStartedAt.STARTED_BEFORE; import static accord.local.SafeCommandStore.TestStatus.ANY_STATUS; -import static accord.local.Status.Stable; -import static accord.local.Status.Truncated; import static accord.primitives.Routables.Slice.Minimal; +import static accord.primitives.Status.Stable; +import static accord.primitives.Status.Truncated; public class CommandsForRanges implements CommandsSummary { diff --git a/src/java/org/apache/cassandra/service/accord/CommandsForRangesLoader.java b/src/java/org/apache/cassandra/service/accord/CommandsForRangesLoader.java index c6eb33cc63..bdf1aa9888 100644 --- a/src/java/org/apache/cassandra/service/accord/CommandsForRangesLoader.java +++ b/src/java/org/apache/cassandra/service/accord/CommandsForRangesLoader.java @@ -36,9 +36,9 @@ import com.google.common.collect.ImmutableMap; import accord.local.Command; import accord.local.DurableBefore; -import accord.local.SaveStatus; -import accord.local.Status; import accord.primitives.PartialDeps; +import accord.primitives.SaveStatus; +import accord.primitives.Status; import accord.primitives.Range; import accord.primitives.Ranges; import accord.primitives.Routable; diff --git a/src/java/org/apache/cassandra/service/accord/IAccordService.java b/src/java/org/apache/cassandra/service/accord/IAccordService.java index a27a29f104..c1c7651a80 100644 --- a/src/java/org/apache/cassandra/service/accord/IAccordService.java +++ b/src/java/org/apache/cassandra/service/accord/IAccordService.java @@ -29,7 +29,7 @@ import javax.annotation.Nullable; import com.google.common.collect.ImmutableSet; import accord.api.BarrierType; -import accord.local.CommandStores; +import accord.local.CommandStores.RangesForEpoch; import accord.local.DurableBefore; import accord.local.Node; import accord.local.Node.Id; @@ -69,16 +69,16 @@ public interface IAccordService IVerbHandler verbHandler(); - Seekables barrierWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException; + Seekables barrierWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException; - Seekables 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 Seekables 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(); } - Seekables 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) { @@ -136,10 +136,10 @@ public interface IAccordService static final Supplier NO_OP = () -> new CompactionInfo(new Int2ObjectHashMap<>(), new Int2ObjectHashMap<>(), DurableBefore.EMPTY); public final Int2ObjectHashMap redundantBefores; - public final Int2ObjectHashMap ranges; + public final Int2ObjectHashMap ranges; public final DurableBefore durableBefore; - public CompactionInfo(Int2ObjectHashMap redundantBefores, Int2ObjectHashMap ranges, DurableBefore durableBefore) + public CompactionInfo(Int2ObjectHashMap redundantBefores, Int2ObjectHashMap ranges, DurableBefore durableBefore) { this.redundantBefores = redundantBefores; this.ranges = ranges; diff --git a/src/java/org/apache/cassandra/service/accord/IJournal.java b/src/java/org/apache/cassandra/service/accord/IJournal.java index 8e44ad2aac..721d69c5a6 100644 --- a/src/java/org/apache/cassandra/service/accord/IJournal.java +++ b/src/java/org/apache/cassandra/service/accord/IJournal.java @@ -19,19 +19,31 @@ package org.apache.cassandra.service.accord; import java.util.List; +import java.util.NavigableMap; import accord.local.Command; +import accord.local.CommandStores; +import accord.local.DurableBefore; +import accord.local.RedundantBefore; +import accord.primitives.Deps; +import accord.primitives.Ranges; +import accord.primitives.Timestamp; import accord.primitives.TxnId; public interface IJournal { Command loadCommand(int commandStoreId, TxnId txnId); - /** - * Append outcomes to the log. - */ - void appendCommand(int commandStoreId, - List> command, - List sanityCheck, - Runnable onFlush); + RedundantBefore loadRedundantBefore(int commandStoreId); + DurableBefore loadDurableBefore(int commandStoreId); + NavigableMap loadBootstrapBeganAt(int commandStoreId); + NavigableMap loadSafeToRead(int commandStoreId); + CommandStores.RangesForEpoch.Snapshot loadRangesForEpoch(int commandStoreId); + List loadHistoricalTransactions(int store); + + void appendCommand(int store, SavedCommand.DiffWriter value, Runnable onFlush); + void persistStoreState(int store, + // TODO: this class should not live under ASCS + AccordSafeCommandStore.FieldUpdates fieldUpdates, + Runnable onFlush); } \ No newline at end of file diff --git a/src/java/org/apache/cassandra/service/accord/JournalKey.java b/src/java/org/apache/cassandra/service/accord/JournalKey.java index c31c337882..97c2e2e0ba 100644 --- a/src/java/org/apache/cassandra/service/accord/JournalKey.java +++ b/src/java/org/apache/cassandra/service/accord/JournalKey.java @@ -23,31 +23,38 @@ import java.nio.ByteBuffer; import java.util.Objects; import java.util.zip.Checksum; -import accord.local.Node; +import accord.local.Node.Id; import accord.primitives.Timestamp; +import accord.utils.Invariants; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.journal.KeySupport; +import org.apache.cassandra.service.accord.AccordJournalValueSerializers.BootstrapBeganAtSerializer; +import org.apache.cassandra.service.accord.AccordJournalValueSerializers.CommandDiffSerializer; +import org.apache.cassandra.service.accord.AccordJournalValueSerializers.DurableBeforeSerializer; +import org.apache.cassandra.service.accord.AccordJournalValueSerializers.FlyweightSerializer; +import org.apache.cassandra.service.accord.AccordJournalValueSerializers.HistoricalTransactionsSerializer; +import org.apache.cassandra.service.accord.AccordJournalValueSerializers.RedundantBeforeSerializer; import org.apache.cassandra.utils.ByteArrayUtil; +import static org.apache.cassandra.db.TypeSizes.BYTE_SIZE; import static org.apache.cassandra.db.TypeSizes.INT_SIZE; import static org.apache.cassandra.db.TypeSizes.LONG_SIZE; import static org.apache.cassandra.db.TypeSizes.SHORT_SIZE; +import static org.apache.cassandra.service.accord.AccordJournalValueSerializers.RangesForEpochSerializer; +import static org.apache.cassandra.service.accord.AccordJournalValueSerializers.SafeToReadSerializer; public final class JournalKey { + final Type type; public final Timestamp timestamp; - // TODO: command store id _before_ timestamp public final int commandStoreId; - JournalKey(Timestamp timestamp) + public JournalKey(Timestamp timestamp, Type type, int commandStoreId) { - this(timestamp, -1); - } - - JournalKey(Timestamp timestamp, int commandStoreId) - { - if (timestamp == null) throw new NullPointerException("Null timestamp"); + Invariants.nonNull(type); + Invariants.nonNull(timestamp); + this.type = type; this.timestamp = timestamp; this.commandStoreId = commandStoreId; } @@ -65,7 +72,8 @@ public final class JournalKey private static final int HLC_OFFSET = 0; private static final int EPOCH_AND_FLAGS_OFFSET = HLC_OFFSET + LONG_SIZE; private static final int NODE_OFFSET = EPOCH_AND_FLAGS_OFFSET + LONG_SIZE; - private static final int CS_ID_OFFSET = NODE_OFFSET + INT_SIZE; + private static final int TYPE_OFFSET = NODE_OFFSET + INT_SIZE; + private static final int CS_ID_OFFSET = TYPE_OFFSET + BYTE_SIZE; @Override public int serializedSize(int userVersion) @@ -74,6 +82,7 @@ public final class JournalKey + 6 // timestamp.epoch() + 2 // timestamp.flags() + INT_SIZE // timestamp.node + + BYTE_SIZE // type + SHORT_SIZE; // commandStoreId } @@ -81,29 +90,33 @@ public final class JournalKey public void serialize(JournalKey key, DataOutputPlus out, int userVersion) throws IOException { serializeTimestamp(key.timestamp, out); + out.writeByte(key.type.id); out.writeShort(key.commandStoreId); } private void serialize(JournalKey key, byte[] out) { serializeTimestamp(key.timestamp, out); - ByteArrayUtil.putShort(out, 20, (short) key.commandStoreId); + out[20] = (byte) (key.type.id & 0xFF); + ByteArrayUtil.putShort(out, 21, (short) key.commandStoreId); } @Override public JournalKey deserialize(DataInputPlus in, int userVersion) throws IOException { Timestamp timestamp = deserializeTimestamp(in); - int commandStoreId = in.readShort(); - return new JournalKey(timestamp, commandStoreId); + int type = in.readByte(); + int commandStoreId = in.readShort(); + return new JournalKey(timestamp, Type.fromId(type), commandStoreId); } @Override public JournalKey deserialize(ByteBuffer buffer, int position, int userVersion) { Timestamp timestamp = deserializeTimestamp(buffer, position); + int type = buffer.get(position + TYPE_OFFSET); int commandStoreId = buffer.getShort(position + CS_ID_OFFSET); - return new JournalKey(timestamp, commandStoreId); + return new JournalKey(timestamp, Type.fromId(type), commandStoreId); } private void serializeTimestamp(Timestamp timestamp, DataOutputPlus out) throws IOException @@ -118,7 +131,7 @@ public final class JournalKey long hlc = in.readLong(); long epochAndFlags = in.readLong(); int nodeId = in.readInt(); - return Timestamp.fromValues(epoch(epochAndFlags), hlc, flags(epochAndFlags), new Node.Id(nodeId)); + return Timestamp.fromValues(epoch(epochAndFlags), hlc, flags(epochAndFlags), new Id(nodeId)); } private void serializeTimestamp(Timestamp timestamp, byte[] out) @@ -133,7 +146,7 @@ public final class JournalKey long hlc = buffer.getLong(position + HLC_OFFSET); long epochAndFlags = buffer.getLong(position + EPOCH_AND_FLAGS_OFFSET); int nodeId = buffer.getInt(position + NODE_OFFSET); - return Timestamp.fromValues(epoch(epochAndFlags), hlc, flags(epochAndFlags), new Node.Id(nodeId)); + return Timestamp.fromValues(epoch(epochAndFlags), hlc, flags(epochAndFlags), new Id(nodeId)); } @Override @@ -150,6 +163,10 @@ public final class JournalKey int cmp = compareWithTimestampAt(k.timestamp, buffer, position); if (cmp != 0) return cmp; + byte type = buffer.get(position + TYPE_OFFSET); + cmp = Byte.compare((byte) k.type.id, type); + if (cmp != 0) return cmp; + short commandStoreId = buffer.getShort(position + CS_ID_OFFSET); cmp = Short.compare((byte) k.commandStoreId, commandStoreId); return cmp; @@ -174,6 +191,7 @@ public final class JournalKey public int compare(JournalKey k1, JournalKey k2) { int cmp = compare(k1.timestamp, k2.timestamp); + if (cmp == 0) cmp = Byte.compare((byte) k1.type.id, (byte) k2.type.id); if (cmp == 0) cmp = Short.compare((short) k1.commandStoreId, (short) k2.commandStoreId); return cmp; } @@ -213,20 +231,75 @@ public final class JournalKey boolean equals(JournalKey other) { return this.timestamp.equals(other.timestamp) && + this.type == other.type && this.commandStoreId == other.commandStoreId; } @Override public int hashCode() { - return Objects.hash(timestamp, commandStoreId); + return Objects.hash(timestamp, type, commandStoreId); } public String toString() { return "Key{" + "timestamp=" + timestamp + + "type=" + type + ", commandStoreId=" + commandStoreId + '}'; } + + public enum Type + { + COMMAND_DIFF (0, new CommandDiffSerializer()), + REDUNDANT_BEFORE (1, new RedundantBeforeSerializer()), + DURABLE_BEFORE (2, new DurableBeforeSerializer()), + SAFE_TO_READ (3, new SafeToReadSerializer()), + BOOTSTRAP_BEGAN_AT (4, new BootstrapBeganAtSerializer()), + RANGES_FOR_EPOCH (5, new RangesForEpochSerializer()), + HISTORICAL_TRANSACTIONS (6, new HistoricalTransactionsSerializer()) + ; + + final int id; + final FlyweightSerializer serializer; + + Type(int id, FlyweightSerializer serializer) + { + this.id = id; + this.serializer = serializer; + } + + private static final Type[] idToTypeMapping; + + static + { + Type[] types = values(); + + int maxId = -1; + for (Type type : types) + maxId = Math.max(type.id, maxId); + + Type[] idToType = new Type[maxId + 1]; + for (Type type : types) + { + if (null != idToType[type.id]) + throw new IllegalStateException("Duplicate Type id " + type.id); + idToType[type.id] = type; + } + idToTypeMapping = idToType; + } + + static Type fromId(int id) + { + if (id < 0 || id >= idToTypeMapping.length) + throw new IllegalArgumentException("Out or range Type id " + id); + Type type = idToTypeMapping[id]; + if (null == type) + throw new IllegalArgumentException("Unknown Type id " + id); + return type; + } + } + + } diff --git a/src/java/org/apache/cassandra/service/accord/SavedCommand.java b/src/java/org/apache/cassandra/service/accord/SavedCommand.java index bef75a70ef..a22467a9f3 100644 --- a/src/java/org/apache/cassandra/service/accord/SavedCommand.java +++ b/src/java/org/apache/cassandra/service/accord/SavedCommand.java @@ -21,7 +21,6 @@ package org.apache.cassandra.service.accord; import java.io.IOException; import java.nio.ByteBuffer; import java.util.function.Function; - import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; @@ -29,13 +28,12 @@ import com.google.common.annotations.VisibleForTesting; import accord.api.Result; import accord.local.Command; import accord.local.CommonAttributes; -import accord.local.SaveStatus; -import accord.local.Status; +import accord.local.StoreParticipants; import accord.primitives.Ballot; import accord.primitives.PartialDeps; import accord.primitives.PartialTxn; -import accord.primitives.Route; -import accord.primitives.Seekables; +import accord.primitives.SaveStatus; +import accord.primitives.Status; import accord.primitives.Timestamp; import accord.primitives.TxnId; import accord.primitives.Writes; @@ -44,10 +42,12 @@ import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.journal.Journal; import org.apache.cassandra.service.accord.serializers.CommandSerializers; import org.apache.cassandra.service.accord.serializers.DepsSerializer; -import org.apache.cassandra.service.accord.serializers.KeySerializers; import org.apache.cassandra.service.accord.serializers.WaitingOnSerializer; import org.apache.cassandra.utils.Throwables; +import static accord.primitives.Known.KnownDeps.DepsErased; +import static accord.primitives.Known.KnownDeps.DepsUnknown; +import static accord.primitives.Known.KnownDeps.NoDeps; import static accord.utils.Invariants.illegalState; public class SavedCommand @@ -62,31 +62,28 @@ public class SavedCommand DURABILITY, ACCEPTED, PROMISED, - ROUTE, + PARTICIPANTS, PARTIAL_TXN, PARTIAL_DEPS, - ADDITIONAL_KEYS, WAITING_ON, WRITES, } - public interface Writer extends Journal.Writer - { - void write(DataOutputPlus out, int userVersion) throws IOException; - K key(); - } - - public static class DiffWriter implements Writer + // TODO: maybe rename this and enclosing classes? + public static class DiffWriter implements Journal.Writer { private final Command before; private final Command after; private final TxnId txnId; + // TODO: improve encapsulationd + @VisibleForTesting public DiffWriter(Command before, Command after) { this(after.txnId(), before, after); } + @VisibleForTesting public DiffWriter(TxnId txnId, Command before, Command after) { this.txnId = txnId; @@ -118,21 +115,29 @@ public class SavedCommand } @Nullable - public static Writer diff(Command original, Command current) + public static DiffWriter diff(Command original, Command current) { if (original == current || current == null - || current.saveStatus() == SaveStatus.Uninitialised) + || current.saveStatus() == SaveStatus.Uninitialised + || !anyFieldChanged(original, current)) return null; return new SavedCommand.DiffWriter(original, current); } - - public static Writer diffWriter(Command before, Command after) + // TODO (required): this is very inefficient + private static boolean anyFieldChanged(Command before, Command after) { - return new DiffWriter(before, after); - } + int flags = getFlags(before, after); + for (Fields field : Fields.values()) + { + if (getFieldChanged(field, flags)) + return true; + } + return false; + } + public static void serialize(Command before, Command after, DataOutputPlus out, int userVersion) throws IOException { int flags = getFlags(before, after); @@ -157,14 +162,12 @@ public class SavedCommand if (getFieldChanged(Fields.PROMISED, flags) && after.promised() != null) CommandSerializers.ballot.serialize(after.promised(), out, userVersion); - if (getFieldChanged(Fields.ROUTE, flags) && after.route() != null) - AccordKeyspace.LocalVersionedSerializers.route.serialize(after.route(), out); // TODO (required): user version + if (getFieldChanged(Fields.PARTICIPANTS, flags) && after.participants() != null) + CommandSerializers.participants.serialize(after.participants(), out, userVersion); if (getFieldChanged(Fields.PARTIAL_TXN, flags) && after.partialTxn() != null) CommandSerializers.partialTxn.serialize(after.partialTxn(), out, userVersion); if (getFieldChanged(Fields.PARTIAL_DEPS, flags) && after.partialDeps() != null) DepsSerializer.partialDeps.serialize(after.partialDeps(), out, userVersion); - if (getFieldChanged(Fields.ADDITIONAL_KEYS, flags) && after.additionalKeysOrRanges() != null) - KeySerializers.seekables.serialize(after.additionalKeysOrRanges(), out, userVersion); Command.WaitingOn waitingOn = getWaitingOn(after); if (getFieldChanged(Fields.WAITING_ON, flags) && waitingOn != null) @@ -193,10 +196,9 @@ public class SavedCommand flags = collectFlags(before, after, Command::acceptedOrCommitted, false, Fields.ACCEPTED, flags); flags = collectFlags(before, after, Command::promised, false, Fields.PROMISED, flags); - flags = collectFlags(before, after, Command::route, true, Fields.ROUTE, flags); + flags = collectFlags(before, after, Command::participants, true, Fields.PARTICIPANTS, flags); flags = collectFlags(before, after, Command::partialTxn, false, Fields.PARTIAL_TXN, flags); flags = collectFlags(before, after, Command::partialDeps, false, Fields.PARTIAL_DEPS, flags); - flags = collectFlags(before, after, Command::additionalKeysOrRanges, false, Fields.ADDITIONAL_KEYS, flags); flags = collectFlags(before, after, SavedCommand::getWaitingOn, false, Fields.WAITING_ON, flags); @@ -259,8 +261,15 @@ public class SavedCommand return oldFlags | (1 << field.ordinal()); } + private static int unsetFieldIsNull(Fields field, int oldFlags) + { + return oldFlags & ~(1 << field.ordinal()); + } + public static class Builder { + int flags; + TxnId txnId; Timestamp executeAt; @@ -271,11 +280,11 @@ public class SavedCommand Ballot acceptedOrCommitted; Ballot promised; - Route route; + StoreParticipants participants; PartialTxn partialTxn; PartialDeps partialDeps; - Seekables additionalKeysOrRanges; + byte[] waitingOnBytes; SavedCommand.WaitingOnProvider waitingOn; Writes writes; Result result; @@ -298,6 +307,11 @@ public class SavedCommand return executeAt; } + public Timestamp executeAtLeast() + { + return executeAtLeast; + } + public SaveStatus saveStatus() { return saveStatus; @@ -318,9 +332,9 @@ public class SavedCommand return promised; } - public Route route() + public StoreParticipants participants() { - return route; + return participants; } public PartialTxn partialTxn() @@ -333,11 +347,6 @@ public class SavedCommand return partialDeps; } - public Seekables additionalKeysOrRanges() - { - return additionalKeysOrRanges; - } - public SavedCommand.WaitingOnProvider waitingOn() { return waitingOn; @@ -355,6 +364,8 @@ public class SavedCommand public void clear() { + flags = 0; + txnId = null; executeAt = null; @@ -364,10 +375,9 @@ public class SavedCommand acceptedOrCommitted = Ballot.ZERO; promised = null; - route = null; + participants = null; partialTxn = null; partialDeps = null; - additionalKeysOrRanges = null; waitingOn = (txn, deps) -> null; writes = null; @@ -387,13 +397,65 @@ public class SavedCommand return count; } + public void serialize(DataOutputPlus out, int userVersion) throws IOException + { + out.writeInt(flags); + + // We encode all changed fields unless their value is null + if (getFieldChanged(Fields.TXN_ID, flags) && !getFieldIsNull(Fields.TXN_ID, flags)) + CommandSerializers.txnId.serialize(txnId(), out, userVersion); + if (getFieldChanged(Fields.EXECUTE_AT, flags) && !getFieldIsNull(Fields.EXECUTE_AT, flags)) + CommandSerializers.timestamp.serialize(executeAt(), out, userVersion); + // TODO (desired): check if this can fold into executeAt + if (getFieldChanged(Fields.EXECUTES_AT_LEAST, flags) && !getFieldIsNull(Fields.EXECUTES_AT_LEAST, flags)) + CommandSerializers.timestamp.serialize(executeAtLeast(), out, userVersion); + if (getFieldChanged(Fields.SAVE_STATUS, flags) && !getFieldIsNull(Fields.SAVE_STATUS, flags)) + out.writeInt(saveStatus().ordinal()); + if (getFieldChanged(Fields.DURABILITY, flags) && !getFieldIsNull(Fields.DURABILITY, flags)) + out.writeInt(durability().ordinal()); + + if (getFieldChanged(Fields.ACCEPTED, flags) && !getFieldIsNull(Fields.ACCEPTED, flags)) + CommandSerializers.ballot.serialize(acceptedOrCommitted(), out, userVersion); + if (getFieldChanged(Fields.PROMISED, flags) && !getFieldIsNull(Fields.PROMISED, flags)) + CommandSerializers.ballot.serialize(promised(), out, userVersion); + + if (getFieldChanged(Fields.PARTICIPANTS, flags) && !getFieldIsNull(Fields.PARTICIPANTS, flags)) + CommandSerializers.participants.serialize(participants(), out, userVersion); + if (getFieldChanged(Fields.PARTIAL_TXN, flags) && !getFieldIsNull(Fields.PARTIAL_TXN, flags)) + CommandSerializers.partialTxn.serialize(partialTxn(), out, userVersion); + if (getFieldChanged(Fields.PARTIAL_DEPS, flags) && !getFieldIsNull(Fields.PARTIAL_DEPS, flags)) + DepsSerializer.partialDeps.serialize(partialDeps(), out, userVersion); + + if (getFieldChanged(Fields.WAITING_ON, flags) && !getFieldIsNull(Fields.WAITING_ON, flags)) + { + out.writeInt(waitingOnBytes.length); + out.write(waitingOnBytes); + } + + if (getFieldChanged(Fields.WRITES, flags) && !getFieldIsNull(Fields.WRITES, flags)) + CommandSerializers.writes.serialize(writes(), out, userVersion); + } + + + // TODO: we seem to be writing some form of empty transaction @SuppressWarnings({ "rawtypes", "unchecked" }) public void deserializeNext(DataInputPlus in, int userVersion) throws IOException { + final int flags = in.readInt(); nextCalled = true; count++; - final int flags = in.readInt(); + for (Fields field : Fields.values()) + { + if (getFieldChanged(field, flags)) + { + this.flags = setFieldChanged(field, this.flags); + if (getFieldIsNull(field, flags)) + this.flags = setFieldIsNull(field, this.flags); + else + this.flags = unsetFieldIsNull(field, this.flags); + } + } if (getFieldChanged(Fields.TXN_ID, flags)) { @@ -450,12 +512,12 @@ public class SavedCommand promised = CommandSerializers.ballot.deserialize(in, userVersion); } - if (getFieldChanged(Fields.ROUTE, flags)) + if (getFieldChanged(Fields.PARTICIPANTS, flags)) { - if (getFieldIsNull(Fields.ROUTE, flags)) - route = null; + if (getFieldIsNull(Fields.PARTICIPANTS, flags)) + participants = null; else - route = AccordKeyspace.LocalVersionedSerializers.route.deserialize(in); + participants = CommandSerializers.participants.deserialize(in, userVersion); } if (getFieldChanged(Fields.PARTIAL_TXN, flags)) @@ -474,14 +536,6 @@ public class SavedCommand partialDeps = DepsSerializer.partialDeps.deserialize(in, userVersion); } - if (getFieldChanged(Fields.ADDITIONAL_KEYS, flags)) - { - if (getFieldIsNull(Fields.ADDITIONAL_KEYS, flags)) - additionalKeysOrRanges = null; - else - additionalKeysOrRanges = KeySerializers.seekables.deserialize(in, userVersion); - } - if (getFieldChanged(Fields.WAITING_ON, flags)) { if (getFieldIsNull(Fields.WAITING_ON, flags)) @@ -491,9 +545,9 @@ public class SavedCommand else { int size = in.readInt(); - byte[] bytes = new byte[size]; - in.readFully(bytes); - ByteBuffer buffer = ByteBuffer.wrap(bytes); + waitingOnBytes = new byte[size]; + in.readFully(waitingOnBytes); + ByteBuffer buffer = ByteBuffer.wrap(waitingOnBytes); waitingOn = (localTxnId, deps) -> { try { @@ -514,6 +568,7 @@ public class SavedCommand else writes = CommandSerializers.writes.deserialize(in, userVersion); } + } public void forceResult(Result newValue) @@ -521,7 +576,7 @@ public class SavedCommand this.result = newValue; } - public Command construct() throws IOException + public Command construct() { if (!nextCalled) return null; @@ -531,15 +586,13 @@ public class SavedCommand attrs.partialTxn(partialTxn); if (durability != null) attrs.durability(durability); - if (route != null) - attrs.route(route); + if (participants != null) + attrs.setParticipants(participants); if (partialDeps != null && - (saveStatus.known.deps != Status.KnownDeps.NoDeps && - saveStatus.known.deps != Status.KnownDeps.DepsErased && - saveStatus.known.deps != Status.KnownDeps.DepsUnknown)) + (saveStatus.known.deps != NoDeps && + saveStatus.known.deps != DepsErased && + saveStatus.known.deps != DepsUnknown)) attrs.partialDeps(partialDeps); - if (additionalKeysOrRanges != null) - attrs.additionalKeysOrRanges(additionalKeysOrRanges); Command.WaitingOn waitingOn = null; if (this.waitingOn != null) @@ -553,14 +606,12 @@ public class SavedCommand case PreAccepted: return Command.PreAccepted.preAccepted(attrs, executeAt, promised); case AcceptedInvalidate: - if (saveStatus == SaveStatus.AcceptedInvalidateWithDefinition) - return Command.Accepted.accepted(attrs, saveStatus, executeAt, promised, acceptedOrCommitted); - else - return Command.AcceptedInvalidateWithoutDefinition.acceptedInvalidate(attrs, promised, acceptedOrCommitted); - case Accepted: case PreCommitted: - return Command.Accepted.accepted(attrs, saveStatus, executeAt, promised, acceptedOrCommitted); + if (saveStatus == SaveStatus.AcceptedInvalidate) + return Command.AcceptedInvalidateWithoutDefinition.acceptedInvalidate(attrs, promised, acceptedOrCommitted); + else + return Command.Accepted.accepted(attrs, saveStatus, executeAt, promised, acceptedOrCommitted); case Committed: case Stable: return Command.Committed.committed(attrs, saveStatus, executeAt, promised, acceptedOrCommitted, waitingOn); @@ -587,10 +638,10 @@ public class SavedCommand if (attrs.txnId().kind().awaitsOnlyDeps()) return Command.Truncated.truncatedApply(attrs, status, executeAt, writes, result, executesAtLeast); return Command.Truncated.truncatedApply(attrs, status, executeAt, writes, result, null); - case ErasedOrInvalidOrVestigial: - return Command.Truncated.erasedOrInvalidOrVestigial(attrs.txnId(), attrs.durability(), attrs.route()); + case ErasedOrVestigial: + return Command.Truncated.erasedOrInvalidOrVestigial(attrs.txnId(), attrs.durability(), attrs.participants()); case Erased: - return Command.Truncated.erased(attrs.txnId(), attrs.durability(), attrs.route()); + return Command.Truncated.erased(attrs.txnId(), attrs.durability(), attrs.participants()); case Invalidated: return Command.Truncated.invalidated(attrs.txnId()); } @@ -605,10 +656,9 @@ public class SavedCommand ", durability=" + durability + ", acceptedOrCommitted=" + acceptedOrCommitted + ", promised=" + promised + - ", route=" + route + + ", participants=" + participants + ", partialTxn=" + partialTxn + ", partialDeps=" + partialDeps + - ", additionalKeysOrRanges=" + additionalKeysOrRanges + ", waitingOn=" + waitingOn + ", writes=" + writes + '}'; 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 11d7dd01cf..d1af6f803c 100644 --- a/src/java/org/apache/cassandra/service/accord/api/AccordAgent.java +++ b/src/java/org/apache/cassandra/service/accord/api/AccordAgent.java @@ -19,7 +19,6 @@ package org.apache.cassandra.service.accord.api; import java.util.concurrent.TimeUnit; -import javax.annotation.Nonnull; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; @@ -35,7 +34,9 @@ import accord.local.Node; import accord.local.SafeCommand; import accord.local.SafeCommandStore; import accord.messages.ReplyContext; +import accord.primitives.Keys; import accord.primitives.Ranges; +import accord.primitives.Routable; import accord.primitives.Seekables; import accord.primitives.Timestamp; import accord.primitives.Txn; @@ -52,7 +53,6 @@ import org.apache.cassandra.net.ResponseContext; import org.apache.cassandra.service.accord.AccordService; import org.apache.cassandra.service.accord.txn.TxnQuery; import org.apache.cassandra.service.accord.txn.TxnRead; -import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.utils.Clock; import org.apache.cassandra.utils.JVMStabilityInspector; @@ -62,7 +62,6 @@ 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.getReadRpcTimeout; -import static org.apache.cassandra.service.consensus.migration.ConsensusKeyMigrationState.maybeSaveAccordKeyMigrationLocally; // TODO (expected): merge with AccordService public class AccordAgent implements Agent @@ -100,6 +99,11 @@ public class AccordAgent implements Agent throw error; } + public void onSuccessfulBarrier(TxnId id, Seekables keysOrRanges) + { + + } + public void onFailedBarrier(TxnId id, Seekables keysOrRanges, Throwable cause) { @@ -112,16 +116,6 @@ public class AccordAgent implements Agent AccordService.instance().scheduler().once(retry, retryBootstrapDelayMicros, MICROSECONDS); } - @Override - public void onLocalBarrier(@Nonnull Seekables keysOrRanges, @Nonnull TxnId txnId) - { - if (keysOrRanges.domain() == Key) - { - PartitionKey key = (PartitionKey)keysOrRanges.get(0); - maybeSaveAccordKeyMigrationLocally(key, Epoch.create(txnId.epoch())); - } - } - @Override public void onStale(Timestamp staleSince, Ranges ranges) { @@ -136,9 +130,10 @@ public class AccordAgent implements Agent } @Override - public void onHandledException(Throwable t) + public void onHandledException(Throwable t, String context) { - // TODO: this + logger.warn(context, t); + JVMStabilityInspector.uncaughtException(Thread.currentThread(), t); } @Override @@ -179,9 +174,9 @@ public class AccordAgent implements Agent * for tests since it skips validation done by regular transactions. */ @Override - public Txn emptySystemTxn(Kind kind, Seekables seekables) + public Txn emptySystemTxn(Kind kind, Routable.Domain domain) { - return new Txn.InMemory(kind, seekables, TxnRead.EMPTY, TxnQuery.UNSAFE_EMPTY, null); + return new Txn.InMemory(kind, domain == Key ? Keys.EMPTY : Ranges.EMPTY, TxnRead.EMPTY, TxnQuery.UNSAFE_EMPTY, null); } @Override diff --git a/src/java/org/apache/cassandra/service/accord/api/AccordRoutingKey.java b/src/java/org/apache/cassandra/service/accord/api/AccordRoutingKey.java index 3c8672ea93..1f29c16d96 100644 --- a/src/java/org/apache/cassandra/service/accord/api/AccordRoutingKey.java +++ b/src/java/org/apache/cassandra/service/accord/api/AccordRoutingKey.java @@ -34,6 +34,7 @@ import accord.primitives.Range; import accord.primitives.RangeFactory; import accord.primitives.Ranges; import org.apache.cassandra.db.TypeSizes; +import org.apache.cassandra.db.marshal.ByteBufferAccessor; import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.Token; import org.apache.cassandra.io.IVersionedSerializer; @@ -79,6 +80,18 @@ public abstract class AccordRoutingKey extends AccordRoutableKey implements Rout return (TokenKey) this; } + @Override + public RoutingKey toUnseekable() + { + return this; + } + + @Override + public RoutingKey asRoutingKey() + { + return asTokenKey(); + } + public static AccordRoutingKey of(Key key) { return (AccordRoutingKey) key; @@ -266,6 +279,25 @@ public abstract class AccordRoutingKey extends AccordRoutableKey implements Rout return new TokenKey(table, token); } + public TokenKey fromBytes(ByteBuffer bytes, IPartitioner partitioner) + { + TableId tableId = TableId.deserialize(bytes, ByteBufferAccessor.instance, 0); + bytes.position(tableId.serializedSize()); + Token token = Token.compactSerializer.deserialize(bytes, partitioner); + return new TokenKey(tableId, token); + } + + public ByteBuffer toBytes(TokenKey tokenKey) + { + int size = (int) (tokenKey.table.serializedSize() + Token.compactSerializer.serializedSize(tokenKey.token)); + ByteBuffer out = ByteBuffer.allocate(size); + int position = tokenKey.table.serialize(out, ByteBufferAccessor.instance, 0); + out.position(position); + Token.compactSerializer.serialize(tokenKey.token, out); + out.flip(); + return out; + } + @Override public long serializedSize(TokenKey key, int version) { diff --git a/src/java/org/apache/cassandra/service/accord/async/AsyncLoader.java b/src/java/org/apache/cassandra/service/accord/async/AsyncLoader.java index 2b1cfa55cc..4c71bdcb42 100644 --- a/src/java/org/apache/cassandra/service/accord/async/AsyncLoader.java +++ b/src/java/org/apache/cassandra/service/accord/async/AsyncLoader.java @@ -17,7 +17,7 @@ */ package org.apache.cassandra.service.accord.async; -import accord.api.Key; +import accord.api.RoutingKey; import accord.local.cfk.CommandsForKey; import accord.local.KeyHistory; import accord.local.PreLoadContext; @@ -32,7 +32,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import org.apache.cassandra.service.accord.*; import org.apache.cassandra.service.accord.api.AccordRoutingKey; -import org.apache.cassandra.service.accord.api.PartitionKey; +import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; import org.apache.cassandra.utils.NoSpamLogger; import org.apache.cassandra.utils.Pair; @@ -61,12 +61,12 @@ public class AsyncLoader private final AccordCommandStore commandStore; private final Iterable txnIds; - private final Seekables keysOrRanges; + private final Unseekables keysOrRanges; private final KeyHistory keyHistory; protected AsyncResult readResult; - public AsyncLoader(AccordCommandStore commandStore, Iterable txnIds, Seekables keysOrRanges, KeyHistory keyHistory) + public AsyncLoader(AccordCommandStore commandStore, Iterable txnIds, Unseekables keysOrRanges, KeyHistory keyHistory) { this.commandStore = commandStore; this.txnIds = txnIds; @@ -116,7 +116,7 @@ public class AsyncLoader } } - private void referenceAndAssembleReadsForKey(Key key, + private void referenceAndAssembleReadsForKey(RoutingKey key, AsyncOperation.Context context, List> listenChains) { @@ -151,7 +151,7 @@ public class AsyncLoader switch (keysOrRanges.domain()) { case Key: - AbstractKeys keys = (AbstractKeys) keysOrRanges; + AbstractKeys keys = (AbstractKeys) keysOrRanges; keys.forEach(key -> referenceAndAssembleReadsForKey(key, context, chains)); break; case Range: @@ -166,20 +166,20 @@ public class AsyncLoader private AsyncChain referenceAndDispatchReadsForRange(AsyncOperation.Context context) { - Ranges ranges = (Ranges) keysOrRanges; + Ranges ranges = ((AbstractRanges) keysOrRanges).toRanges(); List> root = new ArrayList<>(ranges.size() + 1); - class Watcher implements AccordStateCache.Listener + class Watcher implements AccordStateCache.Listener { - private final Set cached = commandStore.commandsForKeyCache().stream() - .map(n -> (PartitionKey) n.key()) + private final Set cached = commandStore.commandsForKeyCache().stream() + .map(n -> (TokenKey) n.key()) .filter(ranges::contains) .collect(Collectors.toSet()); @Override - public void onAdd(AccordCachingState state) + public void onAdd(AccordCachingState state) { - PartitionKey pk = (PartitionKey) state.key(); + TokenKey pk = (TokenKey) state.key(); if (ranges.contains(pk)) cached.add(pk); } @@ -190,7 +190,7 @@ public class AsyncLoader commandStore.commandsForKeyCache().unregister(watcher); if (keys.isEmpty() && watcher.cached.isEmpty()) return AsyncChains.success(null); - Set set = ImmutableSet.builder().addAll(watcher.cached).addAll(keys).build(); + Set set = ImmutableSet.builder().addAll(watcher.cached).addAll(keys).build(); List> chains = new ArrayList<>(); set.forEach(key -> referenceAndAssembleReadsForKey(key, context, chains)); return chains.isEmpty() ? AsyncChains.success(null) : AsyncChains.reduce(chains, (a, b) -> null); @@ -203,7 +203,7 @@ public class AsyncLoader return AsyncChains.all(root); } - private AsyncChain> findOverlappingKeys(Ranges ranges) + private AsyncChain> findOverlappingKeys(Ranges ranges) { if (ranges.isEmpty()) { @@ -211,16 +211,16 @@ public class AsyncLoader return AsyncChains.success(Collections.emptyList()); } - List>> chains = new ArrayList<>(ranges.size()); + List>> chains = new ArrayList<>(ranges.size()); for (Range range : ranges) chains.add(findOverlappingKeys(range)); - return AsyncChains.reduce(chains, (a, b) -> ImmutableList.builderWithExpectedSize(a.size() + b.size()).addAll(a).addAll(b).build()); + return AsyncChains.reduce(chains, (a, b) -> ImmutableList.builderWithExpectedSize(a.size() + b.size()).addAll(a).addAll(b).build()); } - private AsyncChain> findOverlappingKeys(Range range) + private AsyncChain> findOverlappingKeys(Range range) { // save to a variable as java gets confused when `.map` is called on the result of asChain - AsyncChain> map = Observable.asChain(callback -> + AsyncChain> map = Observable.asChain(callback -> AccordKeyspace.findAllKeysBetween(commandStore.id(), (AccordRoutingKey) range.start(), range.startInclusive(), (AccordRoutingKey) range.end(), range.endInclusive(), diff --git a/src/java/org/apache/cassandra/service/accord/async/AsyncOperation.java b/src/java/org/apache/cassandra/service/accord/async/AsyncOperation.java index 0b984446dc..cbcfe22f18 100644 --- a/src/java/org/apache/cassandra/service/accord/async/AsyncOperation.java +++ b/src/java/org/apache/cassandra/service/accord/async/AsyncOperation.java @@ -30,13 +30,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; -import accord.api.Key; +import accord.api.RoutingKey; import accord.local.Command; import accord.local.CommandStore; import accord.local.PreLoadContext; import accord.local.SafeCommandStore; -import accord.primitives.Seekables; import accord.primitives.TxnId; +import accord.primitives.Unseekables; import accord.utils.Invariants; import accord.utils.async.AsyncChains; import org.apache.cassandra.config.CassandraRelevantProperties; @@ -48,6 +48,7 @@ import org.apache.cassandra.service.accord.AccordSafeCommandsForRanges; import org.apache.cassandra.service.accord.AccordSafeState; import org.apache.cassandra.service.accord.AccordSafeTimestampsForKey; import org.apache.cassandra.service.accord.SavedCommand; +import org.apache.cassandra.utils.concurrent.Condition; import static org.apache.cassandra.service.accord.async.AsyncLoader.txnIds; import static org.apache.cassandra.service.accord.async.AsyncOperation.State.COMPLETING; @@ -71,8 +72,8 @@ public abstract class AsyncOperation extends AsyncChains.Head implements R static class Context { final HashMap commands = new HashMap<>(); - final TreeMap timestampsForKey = new TreeMap<>(); - final TreeMap commandsForKey = new TreeMap<>(); + final TreeMap timestampsForKey = new TreeMap<>(); + final TreeMap commandsForKey = new TreeMap<>(); @Nullable AccordSafeCommandsForRanges commandsForRanges = null; @@ -189,7 +190,7 @@ public abstract class AsyncOperation extends AsyncChains.Head implements R } @SuppressWarnings("unchecked") - Seekables keys() + Unseekables keys() { return preLoadContext.keys(); } @@ -251,10 +252,10 @@ public abstract class AsyncOperation extends AsyncChains.Head implements R result = apply(safeStore); // TODO (required): currently, we are not very efficient about ensuring that we persist the absolute minimum amount of state. Improve that. - List> diffs = null; + List diffs = null; for (AccordSafeCommand commandState : context.commands.values()) { - SavedCommand.Writer diff = commandState.diff(); + SavedCommand.DiffWriter diff = commandState.diff(); if (diff == null) continue; if (diffs == null) @@ -269,11 +270,22 @@ public abstract class AsyncOperation extends AsyncChains.Head implements R } commandStore.completeOperation(safeStore); + context.releaseResources(commandStore); state(COMPLETING); - if (diffs != null) + if (diffs != null || safeStore.fieldUpdates() != null) { - this.commandStore.appendCommands(diffs, sanityCheck, () -> finish(result, null)); + Runnable onFlush = () -> finish(result, null); + if (safeStore.fieldUpdates() != null) + { + if (diffs != null) + appendCommands(diffs, null); + commandStore.persistFieldUpdates(safeStore.fieldUpdates(), onFlush); + } + else + { + appendCommands(diffs, onFlush); + } return false; } case COMPLETING: @@ -286,6 +298,26 @@ public abstract class AsyncOperation extends AsyncChains.Head implements R return false; } + private void appendCommands(List diffs, Runnable onFlush) + { + if (sanityCheck != null) + { + Invariants.checkState(CassandraRelevantProperties.DTEST_ACCORD_JOURNAL_SANITY_CHECK_ENABLED.getBoolean()); + Condition condition = Condition.newOneTimeCondition(); + this.commandStore.appendCommands(diffs, condition::signal); + condition.awaitUninterruptibly(); + + for (Command check : sanityCheck) + this.commandStore.sanityCheckCommand(check); + + if (onFlush != null) onFlush.run(); + } + else + { + this.commandStore.appendCommands(diffs, onFlush); + } + } + @Override public void run() { diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java index bac98fd003..ed8341a234 100644 --- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java +++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java @@ -30,6 +30,7 @@ import accord.local.Node; import accord.messages.Apply; import accord.primitives.Deps; import accord.primitives.FullRoute; +import accord.primitives.Participants; import accord.primitives.Timestamp; import accord.primitives.Txn; import accord.primitives.TxnId; @@ -84,8 +85,9 @@ public class AccordInteropAdapter extends AbstractTxnAdapter } @Override - public void persist(Node node, Topologies all, FullRoute route, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, BiConsumer callback) + public void persist(Node node, Topologies all, FullRoute route, Participants sendTo, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, BiConsumer callback) { + // TODO (required): we aren't using sendTo if (applyKind == Minimal && doInteropPersist(node, all, route, txnId, txn, executeAt, deps, writes, result, callback)) return; diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropApply.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropApply.java index 8e3758a381..45832e8711 100644 --- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropApply.java +++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropApply.java @@ -27,19 +27,19 @@ import accord.local.Command; import accord.local.Node.Id; import accord.local.SafeCommand; import accord.local.SafeCommandStore; -import accord.local.Status; +import accord.local.StoreParticipants; import accord.messages.Apply; import accord.messages.MessageType; import accord.primitives.Deps; import accord.primitives.FullRoute; -import accord.primitives.Keys; import accord.primitives.PartialDeps; import accord.primitives.PartialTxn; import accord.primitives.Route; -import accord.primitives.Seekables; +import accord.primitives.Status; import accord.primitives.Timestamp; import accord.primitives.Txn; import accord.primitives.TxnId; +import accord.primitives.Unseekables; import accord.primitives.Writes; import accord.topology.Topologies; import org.apache.cassandra.db.ConsistencyLevel; @@ -77,9 +77,9 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL public static final IVersionedSerializer serializer = new ApplySerializer() { @Override - protected AccordInteropApply deserializeApply(TxnId txnId, Route scope, long waitForEpoch, Apply.Kind kind, Seekables keys, Timestamp executeAt, PartialDeps deps, PartialTxn txn, @Nullable FullRoute fullRoute, Writes writes, Result result) + protected AccordInteropApply deserializeApply(TxnId txnId, Route scope, long waitForEpoch, Apply.Kind kind, Timestamp executeAt, PartialDeps deps, PartialTxn txn, @Nullable FullRoute fullRoute, Writes writes, Result result) { - return new AccordInteropApply(kind, txnId, scope, waitForEpoch, keys, executeAt, deps, txn, fullRoute, writes, result); + return new AccordInteropApply(kind, txnId, scope, waitForEpoch, executeAt, deps, txn, fullRoute, writes, result); } }; @@ -87,9 +87,9 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL transient int waitingOnCount; final MpscChunkedArrayQueue listeners = new MpscChunkedArrayQueue<>(4, 1 << 30); - private AccordInteropApply(Kind kind, TxnId txnId, Route route, long waitForEpoch, Seekables keys, Timestamp executeAt, PartialDeps deps, @Nullable PartialTxn txn, @Nullable FullRoute fullRoute, Writes writes, Result result) + private AccordInteropApply(Kind kind, TxnId txnId, Route route, long waitForEpoch, Timestamp executeAt, PartialDeps deps, @Nullable PartialTxn txn, @Nullable FullRoute fullRoute, Writes writes, Result result) { - super(kind, txnId, route, waitForEpoch, keys, executeAt, deps, txn, fullRoute, writes, result); + super(kind, txnId, route, waitForEpoch, executeAt, deps, txn, fullRoute, writes, result); } private AccordInteropApply(Kind kind, Id to, Topologies participates, TxnId txnId, FullRoute route, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result) @@ -106,7 +106,7 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL @Override - public ApplyReply apply(SafeCommandStore safeStore) + public ApplyReply apply(SafeCommandStore safeStore, StoreParticipants participants) { ApplyReply reply = super.apply(safeStore); checkState(reply == ApplyReply.Redundant || reply == ApplyReply.Applied || reply == ApplyReply.Insufficient, "Unexpected ApplyReply"); @@ -118,7 +118,7 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL // once the coordinator sends a maximal commit // Applied doesn't actually mean the command is in the Applied state so we still need to check and maybe install // the listener - SafeCommand safeCommand = safeStore.get(txnId, executeAt, scope); + SafeCommand safeCommand = safeStore.get(txnId, participants); Command current = safeCommand.current(); // Don't actually think it is possible for this to reach applied while we are stll running, but just to be safe // check anyways @@ -201,10 +201,9 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL } @Override - public Seekables keys() + public Unseekables keys() { - if (txn == null) return Keys.EMPTY; - return txn.keys(); + return scope; } @Override diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropCommit.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropCommit.java index d1a26d22b4..8b94515330 100644 --- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropCommit.java +++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropCommit.java @@ -31,7 +31,6 @@ import accord.primitives.FullRoute; import accord.primitives.PartialDeps; import accord.primitives.PartialTxn; import accord.primitives.Route; -import accord.primitives.Seekables; import accord.primitives.Timestamp; import accord.primitives.Txn; import accord.primitives.TxnId; @@ -43,18 +42,18 @@ import org.apache.cassandra.service.accord.serializers.CommitSerializers.CommitS public class AccordInteropCommit extends Commit { - public static final IVersionedSerializer serializer = new CommitSerializer(AccordInteropRead.class, AccordInteropRead.requestSerializer) + public static final IVersionedSerializer serializer = new CommitSerializer<>(AccordInteropRead.class, AccordInteropRead.requestSerializer) { @Override - protected AccordInteropCommit deserializeCommit(TxnId txnId, Route scope, long waitForEpoch, Kind kind, Ballot ballot, Timestamp executeAt, Seekables keys, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute fullRoute, @Nullable ReadData read) + protected AccordInteropCommit deserializeCommit(TxnId txnId, Route scope, long waitForEpoch, long minEpoch, Kind kind, Ballot ballot, Timestamp executeAt, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute fullRoute, @Nullable ReadData read) { - return new AccordInteropCommit(kind, txnId, scope, waitForEpoch, ballot, executeAt, keys, partialTxn, partialDeps, fullRoute, read); + return new AccordInteropCommit(kind, txnId, scope, waitForEpoch, minEpoch, ballot, executeAt, partialTxn, partialDeps, fullRoute, read); } }; - public AccordInteropCommit(Kind kind, TxnId txnId, Route scope, long waitForEpoch, Ballot ballot, Timestamp executeAt, Seekables keys, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute fullRoute, @Nonnull ReadData readData) + public AccordInteropCommit(Kind kind, TxnId txnId, Route scope, long waitForEpoch, long minEpoch, Ballot ballot, Timestamp executeAt, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute fullRoute, @Nonnull ReadData readData) { - super(kind, txnId, scope, waitForEpoch, ballot, executeAt, keys, partialTxn, partialDeps, fullRoute, readData); + super(kind, txnId, scope, waitForEpoch, minEpoch, ballot, executeAt, partialTxn, partialDeps, fullRoute, readData); } public AccordInteropCommit(Kind kind, Node.Id to, Topology coordinateTopology, Topologies topologies, TxnId txnId, Txn txn, FullRoute route, Timestamp executeAt, Deps deps, AccordInteropRead read) diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropRead.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropRead.java index 8e2ec02a9b..d86f724ed5 100644 --- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropRead.java +++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropRead.java @@ -51,8 +51,8 @@ import org.apache.cassandra.service.accord.serializers.KeySerializers; import org.apache.cassandra.service.accord.serializers.ReadDataSerializers; import org.apache.cassandra.service.accord.serializers.ReadDataSerializers.ReadDataSerializer; -import static accord.local.SaveStatus.PreApplied; -import static accord.local.SaveStatus.ReadyToExecute; +import static accord.primitives.SaveStatus.PreApplied; +import static accord.primitives.SaveStatus.ReadyToExecute; public class AccordInteropRead extends ReadData { diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropReadRepair.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropReadRepair.java index 8e4ec2da26..c714c7857d 100644 --- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropReadRepair.java +++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropReadRepair.java @@ -24,12 +24,12 @@ import javax.annotation.Nullable; import accord.api.Data; import accord.local.Node; import accord.local.SafeCommandStore; -import accord.local.SaveStatus; import accord.messages.ReadData; import accord.messages.MessageType; import accord.primitives.PartialTxn; import accord.primitives.Participants; import accord.primitives.Ranges; +import accord.primitives.SaveStatus; import accord.primitives.Timestamp; import accord.primitives.TxnId; import accord.topology.Topologies; diff --git a/src/java/org/apache/cassandra/service/accord/repair/RepairSyncPointAdapter.java b/src/java/org/apache/cassandra/service/accord/repair/RepairSyncPointAdapter.java index 76e29adbc5..58c9f4b65f 100644 --- a/src/java/org/apache/cassandra/service/accord/repair/RepairSyncPointAdapter.java +++ b/src/java/org/apache/cassandra/service/accord/repair/RepairSyncPointAdapter.java @@ -30,11 +30,11 @@ import accord.coordinate.ExecuteSyncPoint; import accord.local.Node; import accord.primitives.Deps; import accord.primitives.FullRoute; -import accord.primitives.Seekables; import accord.primitives.SyncPoint; import accord.primitives.Timestamp; import accord.primitives.Txn; import accord.primitives.TxnId; +import accord.primitives.Unseekable; import accord.primitives.Writes; import accord.topology.Topologies; @@ -48,7 +48,7 @@ import accord.topology.Topologies; * adapter requires responses from all of the supplied endpoints before completing. Note that shards only block on the * intersection of the provided replicas and their own endpoints. */ -public class RepairSyncPointAdapter> extends CoordinationAdapter.Adapters.AbstractSyncPointAdapter +public class RepairSyncPointAdapter extends CoordinationAdapter.Adapters.AbstractInclusiveSyncPointAdapter { private final ImmutableSet requiredResponses; @@ -58,21 +58,27 @@ public class RepairSyncPointAdapter> extends Coordinat } @Override - public void execute(Node node, Topologies all, FullRoute route, ExecutePath path, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer, Throwable> callback) + public void execute(Node node, Topologies all, FullRoute route, ExecutePath path, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer, Throwable> callback) { RequiredResponseTracker tracker = new RequiredResponseTracker(requiredResponses, all); - ExecuteSyncPoint.ExecuteBlocking execute = new ExecuteSyncPoint.ExecuteBlocking<>(node, tracker, new SyncPoint<>(txnId, deps, (S) txn.keys(), route), executeAt); + ExecuteSyncPoint.ExecuteBlocking execute = new ExecuteSyncPoint.ExecuteBlocking<>(node, new SyncPoint(txnId, deps, (FullRoute) route), tracker, executeAt); execute.addCallback(callback); execute.start(); } @Override - public void persist(Node node, Topologies all, FullRoute route, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, BiConsumer, Throwable> callback) + protected void addOrExecuteCallback(ExecuteSyncPoint.ExecuteBlocking execute, BiConsumer, Throwable> callback) + { + execute.addCallback(callback); + } + + @Override + public void persist(Node node, Topologies all, FullRoute route, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, BiConsumer, Throwable> callback) { throw new UnsupportedOperationException(); } - public static > CoordinationAdapter> create(Collection requiredResponses) + public static CoordinationAdapter> create(Collection requiredResponses) { return new RepairSyncPointAdapter<>(requiredResponses); } diff --git a/src/java/org/apache/cassandra/service/accord/serializers/AcceptSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/AcceptSerializers.java index 11733d3cc2..9c6052832e 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/AcceptSerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/AcceptSerializers.java @@ -22,8 +22,11 @@ import java.io.IOException; import accord.messages.Accept; import accord.messages.Accept.AcceptReply; +import accord.primitives.Ballot; import accord.primitives.Route; +import accord.primitives.Timestamp; import accord.primitives.TxnId; +import accord.utils.Invariants; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.io.IVersionedSerializer; import org.apache.cassandra.io.util.DataInputPlus; @@ -35,14 +38,13 @@ public class AcceptSerializers { private AcceptSerializers() {} - public static final IVersionedSerializer request = new TxnRequestSerializer.WithUnsyncedSerializer() + public static final IVersionedSerializer request = new TxnRequestSerializer.WithUnsyncedSerializer<>() { @Override public void serializeBody(Accept accept, DataOutputPlus out, int version) throws IOException { CommandSerializers.ballot.serialize(accept.ballot, out, version); CommandSerializers.timestamp.serialize(accept.executeAt, out, version); - KeySerializers.seekables.serialize(accept.keys, out, version); DepsSerializer.partialDeps.serialize(accept.partialDeps, out, version); } @@ -52,7 +54,6 @@ public class AcceptSerializers return create(txnId, scope, waitForEpoch, minEpoch, CommandSerializers.ballot.deserialize(in, version), CommandSerializers.timestamp.deserialize(in, version), - KeySerializers.seekables.deserialize(in, version), DepsSerializer.partialDeps.deserialize(in, version)); } @@ -61,7 +62,6 @@ public class AcceptSerializers { return CommandSerializers.ballot.serializedSize(accept.ballot, version) + CommandSerializers.timestamp.serializedSize(accept.executeAt, version) - + KeySerializers.seekables.serializedSize(accept.keys, version) + DepsSerializer.partialDeps.serializedSize(accept.partialDeps, version); } }; @@ -105,43 +105,50 @@ public class AcceptSerializers if (reply.deps != null) { out.writeByte(1); - DepsSerializer.partialDeps.serialize(reply.deps, out, version); + DepsSerializer.deps.serialize(reply.deps, out, version); } else { + Invariants.checkState(reply == AcceptReply.ACCEPT_INVALIDATE); out.writeByte(2); } break; - case Redundant: + case Truncated: out.writeByte(3); break; case RejectedBallot: out.writeByte(4); CommandSerializers.ballot.serialize(reply.supersededBy, out, version); break; - case Truncated: - out.writeByte(5); - break; + case Redundant: + int flags = 5 | (reply.supersededBy == null ? 0x8 : 0) | (reply.committedExecuteAt == null ? 0x10 : 0); + out.writeByte(flags); + if (reply.supersededBy != null) + CommandSerializers.ballot.serialize(reply.supersededBy, out, version); + if (reply.committedExecuteAt != null) + CommandSerializers.timestamp.serialize(reply.committedExecuteAt, out, version); } } @Override public AcceptReply deserialize(DataInputPlus in, int version) throws IOException { - int type = in.readByte(); - switch (type) + int flags = in.readByte(); + switch (flags & 0x7) { - default: throw new IllegalStateException("Unexpected AcceptNack type: " + type); + default: throw new IllegalStateException("Unexpected AcceptNack type: " + (flags & 0x7)); case 1: - return new AcceptReply(DepsSerializer.partialDeps.deserialize(in, version)); + return new AcceptReply(DepsSerializer.deps.deserialize(in, version)); case 2: return AcceptReply.ACCEPT_INVALIDATE; case 3: - return AcceptReply.REDUNDANT; + return AcceptReply.TRUNCATED; case 4: return new AcceptReply(CommandSerializers.ballot.deserialize(in, version)); case 5: - return AcceptReply.TRUNCATED; + Ballot supersededBy = (flags & 0x8) == 0 ? null : CommandSerializers.ballot.deserialize(in, version); + Timestamp committedExecuteAt = (flags & 0x10) == 0 ? null : CommandSerializers.timestamp.deserialize(in, version); + return new AcceptReply(supersededBy, committedExecuteAt); } } @@ -154,13 +161,16 @@ public class AcceptSerializers default: throw new AssertionError(); case Success: if (reply.deps != null) - size += DepsSerializer.partialDeps.serializedSize(reply.deps, version); + size += DepsSerializer.deps.serializedSize(reply.deps, version); break; - case Redundant: case Truncated: break; case RejectedBallot: size += CommandSerializers.ballot.serializedSize(reply.supersededBy, version); + break; + case Redundant: + if (reply.supersededBy != null) size += CommandSerializers.ballot.serializedSize(reply.supersededBy, version); + if (reply.committedExecuteAt != null) size += CommandSerializers.timestamp.serializedSize(reply.committedExecuteAt, version); } return size; } diff --git a/src/java/org/apache/cassandra/service/accord/serializers/AccordRoutingKeyByteSource.java b/src/java/org/apache/cassandra/service/accord/serializers/AccordRoutingKeyByteSource.java index 712d781e8d..625d7bf9fe 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/AccordRoutingKeyByteSource.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/AccordRoutingKeyByteSource.java @@ -21,7 +21,9 @@ package org.apache.cassandra.service.accord.serializers; import java.io.IOException; import java.util.Arrays; import java.util.UUID; -import java.util.function.Function; +import java.util.function.BiFunction; + +import javax.annotation.Nullable; import org.apache.cassandra.db.marshal.ByteArrayAccessor; import org.apache.cassandra.db.marshal.LongType; @@ -29,6 +31,7 @@ import org.apache.cassandra.db.marshal.ValueAccessor; import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.Token; import org.apache.cassandra.schema.TableId; +import org.apache.cassandra.service.accord.AccordKeyspace; import org.apache.cassandra.service.accord.api.AccordRoutingKey; import org.apache.cassandra.utils.ByteArrayUtil; import org.apache.cassandra.utils.bytecomparable.ByteComparable; @@ -39,6 +42,8 @@ import static org.apache.cassandra.service.accord.api.AccordRoutingKey.RoutingKe public class AccordRoutingKeyByteSource { + public static final ByteComparable.Version currentVersion = ByteComparable.Version.OSS50; + private static final byte[] MIN_ORDER = { -1 }; private static final byte[] TOKEN_ORDER = { 0 }; private static final byte[] MAX_ORDER = { 1 }; @@ -61,18 +66,18 @@ public class AccordRoutingKeyByteSource public static Serializer create(IPartitioner partitioner) { if (partitioner.isFixedLength()) - return new FixedLength(partitioner, ByteComparable.Version.OSS50); - return new VariableLength(partitioner, ByteComparable.Version.OSS50); + return new FixedLength(partitioner, currentVersion); + return new VariableLength(partitioner, currentVersion); } public static FixedLength fixedLength(IPartitioner partitioner) { - return new FixedLength(partitioner, ByteComparable.Version.OSS50); + return new FixedLength(partitioner, currentVersion); } public static VariableLength variableLength(IPartitioner partitioner) { - return new VariableLength(partitioner, ByteComparable.Version.OSS50); + return new VariableLength(partitioner, currentVersion); } public static abstract class Serializer @@ -148,6 +153,11 @@ public class AccordRoutingKeyByteSource } public AccordRoutingKey fromComparableBytes(ValueAccessor accessor, V data) throws IOException + { + return fromComparableBytes(accessor, data, version, partitioner); + } + + public static AccordRoutingKey fromComparableBytes(ValueAccessor accessor, V data, ByteComparable.Version version, @Nullable IPartitioner partitioner) { ByteSource.Peekable bs = ByteSource.peekable(ByteSource.fixedLength(accessor, data)); long[] uuidValues = new long[2]; @@ -160,47 +170,63 @@ public class AccordRoutingKeyByteSource uuidValues[i] = value; } TableId tableId = TableId.fromUUID(new UUID(uuidValues[0], uuidValues[1])); - return fromComparableBytes(bs, - isMin -> isMin ? AccordRoutingKey.SentinelKey.min(tableId) : AccordRoutingKey.SentinelKey.max(tableId), - token -> new AccordRoutingKey.TokenKey(tableId, token)); + return fromComparableBytes(bs, tableId, version, partitioner); } - private AccordRoutingKey fromComparableBytes(ByteSource.Peekable bs, - Function onSentinel, - Function onToken) throws IOException + public static AccordRoutingKey fromComparableBytes(ValueAccessor accessor, V data, TableId tableId, ByteComparable.Version version, @Nullable IPartitioner partitioner) + { + ByteSource.Peekable bs = ByteSource.peekable(ByteSource.fixedLength(accessor, data)); + return fromComparableBytes(bs, tableId, version, partitioner); + } + + public static AccordRoutingKey fromComparableBytes(ByteSource.Peekable bs, TableId tableId, ByteComparable.Version version, @Nullable IPartitioner partitioner) + { + if (partitioner == null) + partitioner = AccordKeyspace.partitioner(tableId); + return fromComparableBytes(bs, tableId, + (id, isMin) -> isMin ? AccordRoutingKey.SentinelKey.min(id) : AccordRoutingKey.SentinelKey.max(id), + AccordRoutingKey.TokenKey::new, + version, partitioner + ); + } + + public static AccordRoutingKey fromComparableBytes(ByteSource.Peekable bs, TableId tableId, + BiFunction onSentinel, + BiFunction onToken, + ByteComparable.Version version, IPartitioner partitioner) { if (bs.peek() == ByteSource.TERMINATOR) - throw new IOException("Unable to read prefix"); + throw new IllegalStateException("Unable to read prefix"); ByteSource.Peekable component = progress(bs); byte[] prefix = ByteSourceInverse.getOptionalSignedFixedLength(ByteArrayAccessor.instance, component, 1); if (prefix == null) - throw new IOException("Unable to read prefix; prefix was null"); + throw new IllegalStateException("Unable to read prefix; prefix was null"); if (Arrays.equals(TOKEN_ORDER, prefix)) { component = ByteSourceInverse.nextComponentSource(bs); if (component == null) - throw new IOException("Unable to read token; component was not found"); - return onToken.apply(partitioner.getTokenFactory().fromComparableBytes(component, version)); + throw new IllegalStateException("Unable to read token; component was not found"); + return onToken.apply(tableId, partitioner.getTokenFactory().fromComparableBytes(component, version)); } if (Arrays.equals(MIN_ORDER, prefix)) - return onSentinel.apply(true); + return onSentinel.apply(tableId, true); if (Arrays.equals(MAX_ORDER, prefix)) - return onSentinel.apply(false); + return onSentinel.apply(tableId, false); throw new AssertionError("Unknown prefix"); } - private static ByteSource.Peekable progress(ByteSource.Peekable bs) throws IOException + private static ByteSource.Peekable progress(ByteSource.Peekable bs) { ByteSource.Peekable component = ByteSourceInverse.nextComponentSource(bs); if (component == null) - throw new IOException("Unable to read prefix; component was not found"); + throw new IllegalStateException("Unable to read prefix; component was not found"); if (component.peek() == ByteSource.NEXT_COMPONENT) { // this came from (table, token_or_sentinel) component = ByteSourceInverse.nextComponentSource(bs); if (component == null) - throw new IOException("Unable to read prefix; component was not found"); + throw new IllegalStateException("Unable to read prefix; component was not found"); } return component; } diff --git a/src/java/org/apache/cassandra/service/accord/serializers/ApplySerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/ApplySerializers.java index 8370d59b80..a9091edea3 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/ApplySerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/ApplySerializers.java @@ -26,7 +26,6 @@ import accord.primitives.FullRoute; import accord.primitives.PartialDeps; import accord.primitives.PartialTxn; import accord.primitives.Route; -import accord.primitives.Seekables; import accord.primitives.Timestamp; import accord.primitives.TxnId; import accord.primitives.Writes; @@ -64,7 +63,6 @@ public class ApplySerializers public void serializeBody(A apply, DataOutputPlus out, int version) throws IOException { kind.serialize(apply.kind, out, version); - KeySerializers.seekables.serialize(apply.keys(), out, version); CommandSerializers.timestamp.serialize(apply.executeAt, out, version); DepsSerializer.partialDeps.serialize(apply.deps, out, version); CommandSerializers.nullablePartialTxn.serialize(apply.txn, out, version); @@ -72,7 +70,7 @@ public class ApplySerializers CommandSerializers.writes.serialize(apply.writes, out, version); } - protected abstract A deserializeApply(TxnId txnId, Route scope, long waitForEpoch, Apply.Kind kind, Seekables keys, + protected abstract A deserializeApply(TxnId txnId, Route scope, long waitForEpoch, Apply.Kind kind, Timestamp executeAt, PartialDeps deps, PartialTxn txn, FullRoute fullRoute, Writes writes, Result result); @Override @@ -80,7 +78,6 @@ public class ApplySerializers { return deserializeApply(txnId, scope, waitForEpoch, kind.deserialize(in, version), - KeySerializers.seekables.deserialize(in, version), CommandSerializers.timestamp.deserialize(in, version), DepsSerializer.partialDeps.deserialize(in, version), CommandSerializers.nullablePartialTxn.deserialize(in, version), @@ -93,7 +90,6 @@ public class ApplySerializers public long serializedBodySize(A apply, int version) { return kind.serializedSize(apply.kind, version) - + KeySerializers.seekables.serializedSize(apply.keys(), version) + CommandSerializers.timestamp.serializedSize(apply.executeAt, version) + DepsSerializer.partialDeps.serializedSize(apply.deps, version) + CommandSerializers.nullablePartialTxn.serializedSize(apply.txn, version) @@ -102,17 +98,17 @@ public class ApplySerializers } } - public static final IVersionedSerializer request = new ApplySerializer() + public static final IVersionedSerializer request = new ApplySerializer<>() { @Override - protected Apply deserializeApply(TxnId txnId, Route scope, long waitForEpoch, Apply.Kind kind, Seekables keys, + protected Apply deserializeApply(TxnId txnId, Route scope, long waitForEpoch, Apply.Kind kind, Timestamp executeAt, PartialDeps deps, PartialTxn txn, FullRoute fullRoute, Writes writes, Result result) { - return Apply.SerializationSupport.create(txnId, scope, waitForEpoch, kind, keys, executeAt, deps, txn, fullRoute, writes, result); + return Apply.SerializationSupport.create(txnId, scope, waitForEpoch, kind, executeAt, deps, txn, fullRoute, writes, result); } }; - public static final IVersionedSerializer reply = new IVersionedSerializer() + public static final IVersionedSerializer reply = new IVersionedSerializer<>() { private final Apply.ApplyReply[] replies = Apply.ApplyReply.values(); diff --git a/src/java/org/apache/cassandra/service/accord/serializers/AwaitSerializer.java b/src/java/org/apache/cassandra/service/accord/serializers/AwaitSerializer.java index f18b40f473..54b25a1c87 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/AwaitSerializer.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/AwaitSerializer.java @@ -21,12 +21,12 @@ package org.apache.cassandra.service.accord.serializers; import java.io.IOException; import accord.api.ProgressLog.BlockedUntil; -import accord.local.SaveStatus; import accord.messages.Await; import accord.messages.Await.AsyncAwaitComplete; import accord.messages.Await.AwaitOk; import accord.primitives.Participants; import accord.primitives.Route; +import accord.primitives.SaveStatus; import accord.primitives.TxnId; import accord.utils.Invariants; import org.apache.cassandra.db.TypeSizes; @@ -45,6 +45,7 @@ public class AwaitSerializer CommandSerializers.txnId.serialize(await.txnId, out, version); KeySerializers.participants.serialize(await.scope, out, version); out.writeByte(await.blockedUntil.ordinal()); + out.writeUnsignedVInt(await.awaitEpoch - await.txnId.epoch()); out.writeUnsignedVInt32(await.callbackId + 1); Invariants.checkState(await.callbackId >= -1); } @@ -55,9 +56,10 @@ public class AwaitSerializer TxnId txnId = CommandSerializers.txnId.deserialize(in, version); Participants scope = KeySerializers.participants.deserialize(in, version); BlockedUntil blockedUntil = BlockedUntil.forOrdinal(in.readByte()); + long awaitEpoch = in.readUnsignedVInt() + txnId.epoch(); int callbackId = in.readUnsignedVInt32() - 1; Invariants.checkState(callbackId >= -1); - return Await.SerializerSupport.create(txnId, scope, blockedUntil, callbackId); + return Await.SerializerSupport.create(txnId, scope, blockedUntil, awaitEpoch, callbackId); } @Override @@ -66,6 +68,7 @@ public class AwaitSerializer return CommandSerializers.txnId.serializedSize(await.txnId, version) + KeySerializers.participants.serializedSize(await.scope, version) + TypeSizes.BYTE_SIZE + + VIntCoding.computeUnsignedVIntSize(await.awaitEpoch - await.txnId.epoch()) + VIntCoding.computeUnsignedVIntSize(await.callbackId + 1); } }; diff --git a/src/java/org/apache/cassandra/service/accord/serializers/BeginInvalidationSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/BeginInvalidationSerializers.java index 94390bd905..676e689326 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/BeginInvalidationSerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/BeginInvalidationSerializers.java @@ -21,11 +21,12 @@ package org.apache.cassandra.service.accord.serializers; import java.io.IOException; import accord.api.RoutingKey; -import accord.local.SaveStatus; import accord.messages.BeginInvalidation; import accord.messages.BeginInvalidation.InvalidateReply; import accord.primitives.Ballot; +import accord.primitives.Participants; import accord.primitives.Route; +import accord.primitives.SaveStatus; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.io.IVersionedSerializer; import org.apache.cassandra.io.util.DataInputPlus; @@ -39,7 +40,7 @@ public class BeginInvalidationSerializers public void serialize(BeginInvalidation begin, DataOutputPlus out, int version) throws IOException { CommandSerializers.txnId.serialize(begin.txnId, out, version); - KeySerializers.unseekables.serialize(begin.someUnseekables, out, version); + KeySerializers.participants.serialize(begin.participants, out, version); CommandSerializers.ballot.serialize(begin.ballot, out, version); } @@ -47,7 +48,7 @@ public class BeginInvalidationSerializers public BeginInvalidation deserialize(DataInputPlus in, int version) throws IOException { return new BeginInvalidation(CommandSerializers.txnId.deserialize(in, version), - KeySerializers.unseekables.deserialize(in, version), + KeySerializers.participants.deserialize(in, version), CommandSerializers.ballot.deserialize(in, version)); } @@ -55,7 +56,7 @@ public class BeginInvalidationSerializers public long serializedSize(BeginInvalidation begin, int version) { return CommandSerializers.txnId.serializedSize(begin.txnId, version) - + KeySerializers.unseekables.serializedSize(begin.someUnseekables, version) + + KeySerializers.participants.serializedSize(begin.participants, version) + CommandSerializers.ballot.serializedSize(begin.ballot, version); } }; @@ -70,6 +71,7 @@ public class BeginInvalidationSerializers CommandSerializers.saveStatus.serialize(reply.maxStatus, out, version); CommandSerializers.saveStatus.serialize(reply.maxKnowledgeStatus, out, version); out.writeBoolean(reply.acceptedFastPath); + KeySerializers.nullableParticipants.serialize(reply.truncated, out, version); KeySerializers.nullableRoute.serialize(reply.route, out, version); KeySerializers.nullableRoutingKey.serialize(reply.homeKey, out, version); } @@ -77,14 +79,16 @@ public class BeginInvalidationSerializers @Override public InvalidateReply deserialize(DataInputPlus in, int version) throws IOException { + // TODO (expected): use headers instead of nullable+bool serializers Ballot supersededBy = CommandSerializers.nullableBallot.deserialize(in, version); Ballot accepted = CommandSerializers.ballot.deserialize(in, version); SaveStatus maxStatus = CommandSerializers.saveStatus.deserialize(in, version); SaveStatus maxKnowledgeStatus = CommandSerializers.saveStatus.deserialize(in, version); boolean acceptedFastPath = in.readBoolean(); + Participants truncated = KeySerializers.nullableParticipants.deserialize(in, version); Route route = KeySerializers.nullableRoute.deserialize(in, version); RoutingKey homeKey = KeySerializers.nullableRoutingKey.deserialize(in, version); - return new InvalidateReply(supersededBy, accepted, maxStatus, maxKnowledgeStatus, acceptedFastPath, route, homeKey); + return new InvalidateReply(supersededBy, accepted, maxStatus, maxKnowledgeStatus, acceptedFastPath, truncated, route, homeKey); } @Override @@ -95,6 +99,7 @@ public class BeginInvalidationSerializers + CommandSerializers.saveStatus.serializedSize(reply.maxStatus, version) + CommandSerializers.saveStatus.serializedSize(reply.maxKnowledgeStatus, version) + TypeSizes.BOOL_SIZE + + KeySerializers.nullableParticipants.serializedSize(reply.truncated, version) + KeySerializers.nullableRoute.serializedSize(reply.route, version) + KeySerializers.nullableRoutingKey.serializedSize(reply.homeKey, version); } diff --git a/src/java/org/apache/cassandra/service/accord/serializers/CalculateDepsSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/CalculateDepsSerializers.java index e10425a321..842f2bafde 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/CalculateDepsSerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/CalculateDepsSerializers.java @@ -23,7 +23,6 @@ import java.io.IOException; import accord.messages.CalculateDeps; import accord.messages.CalculateDeps.CalculateDepsOk; import accord.primitives.Route; -import accord.primitives.Seekables; import accord.primitives.Timestamp; import accord.primitives.TxnId; import org.apache.cassandra.io.IVersionedSerializer; @@ -32,28 +31,25 @@ import org.apache.cassandra.io.util.DataOutputPlus; public class CalculateDepsSerializers { - public static final IVersionedSerializer request = new TxnRequestSerializer.WithUnsyncedSerializer() + public static final IVersionedSerializer request = new TxnRequestSerializer.WithUnsyncedSerializer<>() { @Override public void serializeBody(CalculateDeps msg, DataOutputPlus out, int version) throws IOException { - KeySerializers.seekables.serialize(msg.keys, out, version); CommandSerializers.timestamp.serialize(msg.executeAt, out, version); } @Override public CalculateDeps deserializeBody(DataInputPlus in, int version, TxnId txnId, Route scope, long waitForEpoch, long minEpoch) throws IOException { - Seekables keys = KeySerializers.seekables.deserialize(in, version); Timestamp executeAt = CommandSerializers.timestamp.deserialize(in, version); - return CalculateDeps.SerializationSupport.create(txnId, scope, waitForEpoch, minEpoch, keys, executeAt); + return CalculateDeps.SerializationSupport.create(txnId, scope, waitForEpoch, minEpoch, executeAt); } @Override public long serializedBodySize(CalculateDeps msg, int version) { - return KeySerializers.seekables.serializedSize(msg.keys, version) - + CommandSerializers.timestamp.serializedSize(msg.executeAt, version); + return CommandSerializers.timestamp.serializedSize(msg.executeAt, version); } }; @@ -62,19 +58,19 @@ public class CalculateDepsSerializers @Override public void serialize(CalculateDepsOk reply, DataOutputPlus out, int version) throws IOException { - DepsSerializer.partialDeps.serialize(reply.deps, out, version); + DepsSerializer.deps.serialize(reply.deps, out, version); } @Override public CalculateDepsOk deserialize(DataInputPlus in, int version) throws IOException { - return new CalculateDepsOk(DepsSerializer.partialDeps.deserialize(in, version)); + return new CalculateDepsOk(DepsSerializer.deps.deserialize(in, version)); } @Override public long serializedSize(CalculateDepsOk reply, int version) { - return DepsSerializer.partialDeps.serializedSize(reply.deps, version); + return DepsSerializer.deps.serializedSize(reply.deps, version); } }; } diff --git a/src/java/org/apache/cassandra/service/accord/serializers/CheckStatusSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/CheckStatusSerializers.java index e506bbf85c..7a25f55c54 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/CheckStatusSerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/CheckStatusSerializers.java @@ -1,5 +1,5 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one + * Licensed to the Apache Software ation (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 @@ -23,99 +23,68 @@ import java.io.IOException; import accord.api.Result; import accord.api.RoutingKey; import accord.coordinate.Infer; -import accord.local.SaveStatus; -import accord.local.Status.Durability; -import accord.local.Status.Known; import accord.messages.CheckStatus; import accord.messages.CheckStatus.CheckStatusNack; import accord.messages.CheckStatus.CheckStatusOk; import accord.messages.CheckStatus.CheckStatusOkFull; import accord.messages.CheckStatus.CheckStatusReply; -import accord.messages.CheckStatus.FoundKnown; -import accord.messages.CheckStatus.FoundKnownMap; import accord.primitives.Ballot; +import accord.primitives.Known; +import accord.primitives.KnownMap; import accord.primitives.PartialDeps; import accord.primitives.PartialTxn; +import accord.primitives.Participants; import accord.primitives.Route; +import accord.primitives.SaveStatus; +import accord.primitives.Status.Durability; import accord.primitives.Timestamp; import accord.primitives.TxnId; -import accord.primitives.Unseekables; import accord.primitives.Writes; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.io.IVersionedSerializer; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; -import org.apache.cassandra.utils.NullableSerializer; import static accord.messages.CheckStatus.SerializationSupport.createOk; +import static org.apache.cassandra.service.accord.serializers.CommandSerializers.nullableKnown; public class CheckStatusSerializers { - public static final IVersionedSerializer foundKnown = new IVersionedSerializer<>() + public static final IVersionedSerializer knownMap = new IVersionedSerializer<>() { @Override - public void serialize(FoundKnown known, DataOutputPlus out, int version) throws IOException - { - CommandSerializers.known.serialize(known, out, version); - CommandSerializers.invalidIfNot.serialize(known.invalidIfNot, out, version); - CommandSerializers.isPreempted.serialize(known.isPreempted, out, version); - } - - @Override - public FoundKnown deserialize(DataInputPlus in, int version) throws IOException - { - Known known = CommandSerializers.known.deserialize(in, version); - Infer.InvalidIfNot invalidIfNot = CommandSerializers.invalidIfNot.deserialize(in, version); - Infer.IsPreempted isPreempted = CommandSerializers.isPreempted.deserialize(in, version); - return new FoundKnown(known, invalidIfNot, isPreempted); - } - - @Override - public long serializedSize(FoundKnown known, int version) - { - return CommandSerializers.known.serializedSize(known, version) - + CommandSerializers.invalidIfNot.serializedSize(known.invalidIfNot, version) - + CommandSerializers.isPreempted.serializedSize(known.isPreempted, version); - } - }; - - public static final IVersionedSerializer foundKnownNullable = NullableSerializer.wrap(foundKnown); - - public static final IVersionedSerializer foundKnownMap = new IVersionedSerializer<>() - { - @Override - public void serialize(FoundKnownMap knownMap, DataOutputPlus out, int version) throws IOException + public void serialize(KnownMap knownMap, DataOutputPlus out, int version) throws IOException { int size = knownMap.size(); out.writeUnsignedVInt32(size); for (int i = 0 ; i <= size ; ++i) KeySerializers.routingKey.serialize(knownMap.startAt(i), out, version); for (int i = 0 ; i < size ; ++i) - foundKnownNullable.serialize(knownMap.valueAt(i), out, version); + nullableKnown.serialize(knownMap.valueAt(i), out, version); } @Override - public FoundKnownMap deserialize(DataInputPlus in, int version) throws IOException + public KnownMap deserialize(DataInputPlus in, int version) throws IOException { int size = in.readUnsignedVInt32(); RoutingKey[] starts = new RoutingKey[size + 1]; for (int i = 0 ; i <= size ; ++i) starts[i] = KeySerializers.routingKey.deserialize(in, version); - FoundKnown[] values = new FoundKnown[size]; + Known[] values = new Known[size]; for (int i = 0 ; i < size ; ++i) - values[i] = foundKnownNullable.deserialize(in, version); - return FoundKnownMap.SerializerSupport.create(true, starts, values); + values[i] = nullableKnown.deserialize(in, version); + return KnownMap.SerializerSupport.create(true, starts, values); } @Override - public long serializedSize(FoundKnownMap knownMap, int version) + public long serializedSize(KnownMap knownMap, int version) { int size = knownMap.size(); long result = TypeSizes.sizeofUnsignedVInt(size); for (int i = 0 ; i <= size ; ++i) result += KeySerializers.routingKey.serializedSize(knownMap.startAt(i), version); for (int i = 0 ; i < size ; ++i) - result += foundKnownNullable.serializedSize(knownMap.valueAt(i), version); + result += nullableKnown.serializedSize(knownMap.valueAt(i), version); return result; } }; @@ -128,7 +97,7 @@ public class CheckStatusSerializers public void serialize(CheckStatus check, DataOutputPlus out, int version) throws IOException { CommandSerializers.txnId.serialize(check.txnId, out, version); - KeySerializers.unseekables.serialize(check.query, out, version); + KeySerializers.participants.serialize(check.query, out, version); out.writeUnsignedVInt(check.sourceEpoch); out.writeByte(check.includeInfo.ordinal()); } @@ -137,7 +106,7 @@ public class CheckStatusSerializers public CheckStatus deserialize(DataInputPlus in, int version) throws IOException { TxnId txnId = CommandSerializers.txnId.deserialize(in, version); - Unseekables query = KeySerializers.unseekables.deserialize(in, version); + Participants query = KeySerializers.participants.deserialize(in, version); long sourceEpoch = in.readUnsignedVInt(); CheckStatus.IncludeInfo info = infos[in.readByte()]; return new CheckStatus(txnId, query, sourceEpoch, info); @@ -147,7 +116,7 @@ public class CheckStatusSerializers public long serializedSize(CheckStatus check, int version) { return CommandSerializers.txnId.serializedSize(check.txnId, version) - + KeySerializers.unseekables.serializedSize(check.query, version) + + KeySerializers.participants.serializedSize(check.query, version) + TypeSizes.sizeofUnsignedVInt(check.sourceEpoch) + TypeSizes.BYTE_SIZE; } @@ -170,7 +139,7 @@ public class CheckStatusSerializers CheckStatusOk ok = (CheckStatusOk) reply; out.write(reply instanceof CheckStatusOkFull ? FULL : OK); - foundKnownMap.serialize(ok.map, out, version); + knownMap.serialize(ok.map, out, version); CommandSerializers.saveStatus.serialize(ok.maxKnowledgeSaveStatus, out, version); CommandSerializers.saveStatus.serialize(ok.maxSaveStatus, out, version); CommandSerializers.ballot.serialize(ok.maxPromised, out, version); @@ -181,6 +150,7 @@ public class CheckStatusSerializers CommandSerializers.durability.serialize(ok.durability, out, version); KeySerializers.nullableRoute.serialize(ok.route, out, version); KeySerializers.nullableRoutingKey.serialize(ok.homeKey, out, version); + CommandSerializers.invalidIf.serialize(ok.invalidIf, out, version); if (!(reply instanceof CheckStatusOkFull)) return; @@ -202,7 +172,7 @@ public class CheckStatusSerializers return CheckStatusNack.NotOwned; case OK: case FULL: - FoundKnownMap map = foundKnownMap.deserialize(in, version); + KnownMap map = knownMap.deserialize(in, version); SaveStatus maxKnowledgeStatus = CommandSerializers.saveStatus.deserialize(in, version); SaveStatus maxStatus = CommandSerializers.saveStatus.deserialize(in, version); Ballot maxPromised = CommandSerializers.ballot.deserialize(in, version); @@ -213,10 +183,11 @@ public class CheckStatusSerializers Durability durability = CommandSerializers.durability.deserialize(in, version); Route route = KeySerializers.nullableRoute.deserialize(in, version); RoutingKey homeKey = KeySerializers.nullableRoutingKey.deserialize(in, version); + Infer.InvalidIf invalidIf = CommandSerializers.invalidIf.deserialize(in, version); if (kind == OK) return createOk(map, maxKnowledgeStatus, maxStatus, maxPromised, maxAcceptedOrCommitted, acceptedOrCommitted, executeAt, - isCoordinating, durability, route, homeKey); + isCoordinating, durability, route, homeKey, invalidIf); PartialTxn partialTxn = CommandSerializers.nullablePartialTxn.deserialize(in, version); PartialDeps committedDeps = DepsSerializer.nullablePartialDeps.deserialize(in, version); @@ -227,7 +198,7 @@ public class CheckStatusSerializers result = CommandSerializers.APPLIED; return createOk(map, maxKnowledgeStatus, maxStatus, maxPromised, maxAcceptedOrCommitted, acceptedOrCommitted, executeAt, - isCoordinating, durability, route, homeKey, partialTxn, committedDeps, writes, result); + isCoordinating, durability, route, homeKey, invalidIf, partialTxn, committedDeps, writes, result); } } @@ -240,7 +211,7 @@ public class CheckStatusSerializers return size; CheckStatusOk ok = (CheckStatusOk) reply; - size += foundKnownMap.serializedSize(ok.map, version); + size += knownMap.serializedSize(ok.map, version); size += CommandSerializers.saveStatus.serializedSize(ok.maxKnowledgeSaveStatus, version); size += CommandSerializers.saveStatus.serializedSize(ok.maxSaveStatus, version); size += CommandSerializers.ballot.serializedSize(ok.maxPromised, version); @@ -249,8 +220,9 @@ public class CheckStatusSerializers size += CommandSerializers.nullableTimestamp.serializedSize(ok.executeAt, version); size += TypeSizes.BOOL_SIZE; size += CommandSerializers.durability.serializedSize(ok.durability, version); - size += KeySerializers.nullableRoutingKey.serializedSize(ok.homeKey, version); size += KeySerializers.nullableRoute.serializedSize(ok.route, version); + size += KeySerializers.nullableRoutingKey.serializedSize(ok.homeKey, version); + size += CommandSerializers.invalidIf.serializedSize(ok.invalidIf, version); if (!(reply instanceof CheckStatusOkFull)) return size; diff --git a/src/java/org/apache/cassandra/service/accord/serializers/CommandSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/CommandSerializers.java index cd76550262..16acd2ab0b 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/CommandSerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/CommandSerializers.java @@ -29,10 +29,18 @@ import accord.api.Result; import accord.api.Update; import accord.coordinate.Infer; import accord.local.Node; -import accord.local.SaveStatus; -import accord.local.Status; -import accord.local.Status.Durability; -import accord.local.Status.Known; +import accord.local.StoreParticipants; +import accord.primitives.Known.Definition; +import accord.primitives.Known.KnownDeps; +import accord.primitives.Known.KnownExecuteAt; +import accord.primitives.Known.KnownRoute; +import accord.primitives.Known.Outcome; +import accord.primitives.Participants; +import accord.primitives.Route; +import accord.primitives.SaveStatus; +import accord.primitives.Status; +import accord.primitives.Status.Durability; +import accord.primitives.Known; import accord.primitives.Ballot; import accord.primitives.PartialTxn; import accord.primitives.ProgressToken; @@ -47,7 +55,6 @@ import org.apache.cassandra.io.IVersionedSerializer; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.service.accord.serializers.IVersionedWithKeysSerializer.AbstractWithKeysSerializer; -import org.apache.cassandra.service.accord.serializers.IVersionedWithKeysSerializer.NullableWithKeysSerializer; import org.apache.cassandra.service.accord.serializers.SmallEnumSerializer.NullableSmallEnumSerializer; import org.apache.cassandra.service.accord.txn.AccordUpdate; import org.apache.cassandra.service.accord.txn.TxnQuery; @@ -71,11 +78,74 @@ public class CommandSerializers }; public static final TimestampSerializer txnId = new TimestampSerializer<>(TxnId::fromBits); + public static final IVersionedSerializer nullableTxnId = NullableSerializer.wrap(txnId); public static final TimestampSerializer timestamp = new TimestampSerializer<>(Timestamp::fromBits); public static final IVersionedSerializer nullableTimestamp = NullableSerializer.wrap(timestamp); public static final TimestampSerializer ballot = new TimestampSerializer<>(Ballot::fromBits); public static final IVersionedSerializer nullableBallot = NullableSerializer.wrap(ballot); public static final EnumSerializer kind = new EnumSerializer<>(Txn.Kind.class); + public static final StoreParticipantsSerializer participants = new StoreParticipantsSerializer(); + + // TODO (expected): optimise using subset serializers (but be careful for range txns, e.g. some collections have differently sliced sub ranges) + public static class StoreParticipantsSerializer implements IVersionedSerializer + { + static final int HAS_ROUTE = 0x1; + static final int HAS_TOUCHED_EQUALS_ROUTE = 0x2; + static final int TOUCHES_EQUALS_HAS_TOUCHED = 0x4; + static final int OWNS_EQUALS_TOUCHES = 0x8; + @Override + public void serialize(StoreParticipants t, DataOutputPlus out, int version) throws IOException + { + boolean hasRoute = t.route() != null; + boolean hasTouchedEqualsRoute = t.route() == t.hasTouched(); + boolean touchesEqualsHasTouched = t.touches() == t.hasTouched(); + boolean ownsEqualsTouches = t.owns() == t.touches(); + out.writeByte((hasRoute ? HAS_ROUTE : 0) + | (hasTouchedEqualsRoute ? HAS_TOUCHED_EQUALS_ROUTE : 0) + | (touchesEqualsHasTouched ? TOUCHES_EQUALS_HAS_TOUCHED : 0) + | (ownsEqualsTouches ? OWNS_EQUALS_TOUCHES : 0) + ); + if (hasRoute) KeySerializers.route.serialize(t.route(), out, version); + if (!hasTouchedEqualsRoute) KeySerializers.participants.serialize(t.hasTouched(), out, version); + if (!touchesEqualsHasTouched) KeySerializers.participants.serialize(t.touches(), out, version); + if (!ownsEqualsTouches) KeySerializers.participants.serialize(t.owns(), out, version); + } + + @Override + public StoreParticipants deserialize(DataInputPlus in, int version) throws IOException + { + int flags = in.readByte(); + Route route = 0 == (flags & HAS_ROUTE) ? null : KeySerializers.route.deserialize(in, version); + Participants hasTouched = 0 != (flags & HAS_TOUCHED_EQUALS_ROUTE) ? route : KeySerializers.participants.deserialize(in, version); + Participants touches = 0 != (flags & TOUCHES_EQUALS_HAS_TOUCHED) ? hasTouched : KeySerializers.participants.deserialize(in, version); + Participants owns = 0 != (flags & OWNS_EQUALS_TOUCHES) ? touches : KeySerializers.participants.deserialize(in, version); + return StoreParticipants.SerializationSupport.create(route, owns, touches, hasTouched); + } + + public Route deserializeRouteOnly(DataInputPlus in, int version) throws IOException + { + int flags = in.readByte(); + if (0 == (flags & HAS_ROUTE)) + return null; + + return KeySerializers.route.deserialize(in, version); + } + + @Override + public long serializedSize(StoreParticipants t, int version) + { + boolean hasRoute = t.route() != null; + boolean hasTouchedEqualsRoute = t.route() == t.hasTouched(); + boolean touchesEqualsHasTouched = t.touches() == t.hasTouched(); + boolean ownsEqualsTouches = t.owns() == t.touches(); + long size = 1; + if (hasRoute) size += KeySerializers.route.serializedSize(t.route(), version); + if (!hasTouchedEqualsRoute) size += KeySerializers.participants.serializedSize(t.hasTouched(), version); + if (!touchesEqualsHasTouched) size += KeySerializers.participants.serializedSize(t.touches(), version); + if (!ownsEqualsTouches) size += KeySerializers.participants.serializedSize(t.owns(), version); + return size; + } + } public static class TimestampSerializer implements IVersionedSerializer { @@ -173,7 +243,7 @@ public class CommandSerializers } } - public static class PartialTxnSerializer extends AbstractWithKeysSerializer implements IVersionedWithKeysSerializer, PartialTxn> + public static class PartialTxnSerializer extends AbstractWithKeysSerializer implements IVersionedSerializer { private final IVersionedSerializer readSerializer; private final IVersionedSerializer querySerializer; @@ -208,28 +278,6 @@ public class CommandSerializers return size; } - @Override - public void serialize(Seekables superset, PartialTxn txn, DataOutputPlus out, int version) throws IOException - { - serializeSubset(txn.keys(), superset, out); - serializeWithoutKeys(txn, out, version); - } - - @Override - public PartialTxn deserialize(Seekables superset, DataInputPlus in, int version) throws IOException - { - Seekables keys = deserializeSubset(superset, in); - return deserializeWithoutKeys(keys, in, version); - } - - @Override - public long serializedSize(Seekables superset, PartialTxn txn, int version) - { - long size = serializedSubsetSize(txn.keys(), superset); - size += serializedSizeWithoutKeys(txn, version); - return size; - } - private void serializeWithoutKeys(PartialTxn txn, DataOutputPlus out, int version) throws IOException { CommandSerializers.kind.serialize(txn.kind(), out, version); @@ -249,7 +297,6 @@ public class CommandSerializers return new PartialTxn.InMemory(kind, keys, read, query, update); } - private long serializedSizeWithoutKeys(PartialTxn txn, int version) { long size = CommandSerializers.kind.serializedSize(txn.kind(), version); @@ -266,14 +313,14 @@ public class CommandSerializers private static final IVersionedSerializer query = new CastingSerializer<>(TxnQuery.class, TxnQuery.serializer); private static final IVersionedSerializer update = new CastingSerializer<>(AccordUpdate.class, AccordUpdate.serializer); - public static final IVersionedWithKeysSerializer, PartialTxn> partialTxn = new PartialTxnSerializer(read, query, update); - public static final IVersionedWithKeysSerializer, PartialTxn> nullablePartialTxn = new NullableWithKeysSerializer<>(partialTxn); + public static final IVersionedSerializer partialTxn = new PartialTxnSerializer(read, query, update); + public static final IVersionedSerializer nullablePartialTxn = NullableSerializer.wrap(partialTxn); public static final EnumSerializer saveStatus = new EnumSerializer<>(SaveStatus.class); public static final EnumSerializer status = new EnumSerializer<>(Status.class); public static final EnumSerializer durability = new EnumSerializer<>(Durability.class); - public static final IVersionedSerializer writes = new IVersionedSerializer() + public static final IVersionedSerializer writes = new IVersionedSerializer<>() { @Override public void serialize(Writes writes, DataOutputPlus out, int version) throws IOException @@ -311,14 +358,13 @@ public class CommandSerializers public static final IVersionedSerializer nullableWrites = NullableSerializer.wrap(writes); - public static final SmallEnumSerializer knownRoute = new SmallEnumSerializer<>(Status.KnownRoute.class); - public static final SmallEnumSerializer definition = new SmallEnumSerializer<>(Status.Definition.class); - public static final SmallEnumSerializer knownExecuteAt = new SmallEnumSerializer<>(Status.KnownExecuteAt.class); - public static final SmallEnumSerializer knownDeps = new SmallEnumSerializer<>(Status.KnownDeps.class); - public static final NullableSmallEnumSerializer nullableKnownDeps = new NullableSmallEnumSerializer<>(knownDeps); - public static final SmallEnumSerializer outcome = new SmallEnumSerializer<>(Status.Outcome.class); - public static final SmallEnumSerializer invalidIfNot = new SmallEnumSerializer<>(Infer.InvalidIfNot.class); - public static final SmallEnumSerializer isPreempted = new SmallEnumSerializer<>(Infer.IsPreempted.class); + public static final SmallEnumSerializer knownRoute = new SmallEnumSerializer<>(KnownRoute.class); + public static final SmallEnumSerializer definition = new SmallEnumSerializer<>(Definition.class); + public static final SmallEnumSerializer knownExecuteAt = new SmallEnumSerializer<>(KnownExecuteAt.class); + public static final SmallEnumSerializer knownDeps = new SmallEnumSerializer<>(KnownDeps.class); + public static final NullableSmallEnumSerializer nullableKnownDeps = new NullableSmallEnumSerializer<>(knownDeps); + public static final SmallEnumSerializer outcome = new SmallEnumSerializer<>(Outcome.class); + public static final SmallEnumSerializer invalidIf = new SmallEnumSerializer<>(Infer.InvalidIf.class); public static final IVersionedSerializer known = new IVersionedSerializer<>() { @@ -352,4 +398,6 @@ public class CommandSerializers + outcome.serializedSize(known.outcome, version); } }; + + public static final IVersionedSerializer nullableKnown = NullableSerializer.wrap(known); } diff --git a/src/java/org/apache/cassandra/service/accord/serializers/CommandStoreSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/CommandStoreSerializers.java index 5af4b53a36..bca3b763c1 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/CommandStoreSerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/CommandStoreSerializers.java @@ -26,6 +26,7 @@ import java.util.function.IntFunction; import accord.api.RoutingKey; import accord.local.DurableBefore; import accord.local.RedundantBefore; +import accord.local.RejectBefore; import accord.primitives.Range; import accord.primitives.Ranges; import accord.primitives.Timestamp; @@ -61,29 +62,31 @@ public class CommandStoreSerializers public void serialize(R map, DataOutputPlus out, int version) throws IOException { out.writeBoolean(map.inclusiveEnds()); - int size = map.size(); - out.writeUnsignedVInt32(size); + int mapSize = map.size(); + out.writeUnsignedVInt32(mapSize); - for (int i=0; i 0) + KeySerializers.routingKey.serialize(map.startAt(mapSize), out, version); } public R deserialize(DataInputPlus in, int version) throws IOException { boolean inclusiveEnds = in.readBoolean(); - int size = in.readUnsignedVInt32(); - RoutingKey[] keys = new RoutingKey[size + 1]; - T[] values = newValueArray.apply(size); - for (int i=0; i 0) + keys[mapSize] = KeySerializers.routingKey.deserialize(in, version); return constructor.apply(inclusiveEnds, keys, values); } @@ -97,14 +100,15 @@ public class CommandStoreSerializers size += KeySerializers.routingKey.serializedSize(map.startAt(i), version); size += valueSerializer.serializedSize(map.valueAt(i), version); } - size += KeySerializers.routingKey.serializedSize(map.startAt(mapSize), version); + if (mapSize > 0) + size += KeySerializers.routingKey.serializedSize(map.startAt(mapSize), version); return size; } } - public static IVersionedSerializer> rejectBefore = new ReducingRangeMapSerializer<>(CommandSerializers.nullableTimestamp, Timestamp[]::new, ReducingRangeMap.SerializerSupport::create); - public static IVersionedSerializer durableBefore = new ReducingRangeMapSerializer<>(NullableSerializer.wrap(new IVersionedSerializer() + public static IVersionedSerializer rejectBefore = new ReducingRangeMapSerializer<>(CommandSerializers.nullableTxnId, TxnId[]::new, RejectBefore.SerializerSupport::create); + public static IVersionedSerializer durableBefore = new ReducingRangeMapSerializer<>(NullableSerializer.wrap(new IVersionedSerializer<>() { @Override public void serialize(DurableBefore.Entry t, DataOutputPlus out, int version) throws IOException @@ -135,12 +139,15 @@ public class CommandStoreSerializers public void serialize(RedundantBefore.Entry t, DataOutputPlus out, int version) throws IOException { TokenRange.serializer.serialize((TokenRange) t.range, out, version); - Invariants.checkState(t.startEpoch <= t.endEpoch); - out.writeUnsignedVInt(t.startEpoch); - if (t.endEpoch == Long.MAX_VALUE) out.writeUnsignedVInt(0L); - else out.writeUnsignedVInt(1 + t.endEpoch - t.startEpoch); + Invariants.checkState(t.startOwnershipEpoch <= t.endOwnershipEpoch); + out.writeUnsignedVInt(t.startOwnershipEpoch); + if (t.endOwnershipEpoch == Long.MAX_VALUE) out.writeUnsignedVInt(0L); + else out.writeUnsignedVInt(1 + t.endOwnershipEpoch - t.startOwnershipEpoch); CommandSerializers.txnId.serialize(t.locallyAppliedOrInvalidatedBefore, out, version); + CommandSerializers.txnId.serialize(t.locallyDecidedAndAppliedOrInvalidatedBefore, out, version); CommandSerializers.txnId.serialize(t.shardAppliedOrInvalidatedBefore, out, version); + CommandSerializers.txnId.serialize(t.shardOnlyAppliedOrInvalidatedBefore, out, version); + CommandSerializers.txnId.serialize(t.gcBefore, out, version); CommandSerializers.txnId.serialize(t.bootstrappedAt, out, version); CommandSerializers.nullableTimestamp.serialize(t.staleUntilAtLeast, out, version); } @@ -154,20 +161,26 @@ public class CommandStoreSerializers if (endEpoch == 0) endEpoch = Long.MAX_VALUE; else endEpoch = endEpoch - 1 + startEpoch; TxnId locallyAppliedOrInvalidatedBefore = CommandSerializers.txnId.deserialize(in, version); + TxnId locallyDecidedAndAppliedOrInvalidatedBefore = CommandSerializers.txnId.deserialize(in, version); TxnId shardAppliedOrInvalidatedBefore = CommandSerializers.txnId.deserialize(in, version); + TxnId shardOnlyAppliedOrInvalidatedBefore = CommandSerializers.txnId.deserialize(in, version); + TxnId gcBefore = CommandSerializers.txnId.deserialize(in, version); TxnId bootstrappedAt = CommandSerializers.txnId.deserialize(in, version); Timestamp staleUntilAtLeast = CommandSerializers.nullableTimestamp.deserialize(in, version); - return new RedundantBefore.Entry(range, startEpoch, endEpoch, locallyAppliedOrInvalidatedBefore, shardAppliedOrInvalidatedBefore, bootstrappedAt, staleUntilAtLeast); + return new RedundantBefore.Entry(range, startEpoch, endEpoch, locallyAppliedOrInvalidatedBefore, locallyDecidedAndAppliedOrInvalidatedBefore, shardAppliedOrInvalidatedBefore, shardOnlyAppliedOrInvalidatedBefore, gcBefore, bootstrappedAt, staleUntilAtLeast); } @Override public long serializedSize(RedundantBefore.Entry t, int version) { long size = TokenRange.serializer.serializedSize((TokenRange) t.range, version); - size += TypeSizes.sizeofUnsignedVInt(t.startEpoch); - size += TypeSizes.sizeofUnsignedVInt(t.endEpoch == Long.MAX_VALUE ? 0 : 1 + t.endEpoch - t.startEpoch); + size += TypeSizes.sizeofUnsignedVInt(t.startOwnershipEpoch); + size += TypeSizes.sizeofUnsignedVInt(t.endOwnershipEpoch == Long.MAX_VALUE ? 0 : 1 + t.endOwnershipEpoch - t.startOwnershipEpoch); size += CommandSerializers.txnId.serializedSize(t.locallyAppliedOrInvalidatedBefore, version); + size += CommandSerializers.txnId.serializedSize(t.locallyDecidedAndAppliedOrInvalidatedBefore, version); size += CommandSerializers.txnId.serializedSize(t.shardAppliedOrInvalidatedBefore, version); + size += CommandSerializers.txnId.serializedSize(t.shardOnlyAppliedOrInvalidatedBefore, version); + size += CommandSerializers.txnId.serializedSize(t.gcBefore, version); size += CommandSerializers.txnId.serializedSize(t.bootstrappedAt, version); size += CommandSerializers.nullableTimestamp.serializedSize(t.staleUntilAtLeast, version); return size; diff --git a/src/java/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializer.java b/src/java/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializer.java index 4dc9ed1805..a12b17098e 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializer.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializer.java @@ -21,9 +21,12 @@ package org.apache.cassandra.service.accord.serializers; import java.nio.ByteBuffer; import java.util.Arrays; +import javax.annotation.Nonnull; + import com.google.common.primitives.Ints; -import accord.api.Key; +import accord.api.RoutingKey; +import accord.local.RedundantBefore; import accord.local.cfk.CommandsForKey; import accord.local.cfk.CommandsForKey.TxnInfo; import accord.local.cfk.CommandsForKey.InternalStatus; @@ -40,18 +43,16 @@ import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.db.marshal.ByteBufferAccessor; import org.apache.cassandra.utils.vint.VIntCoding; +import static accord.local.cfk.CommandsForKey.NO_BOUNDS_INFO; import static accord.local.cfk.CommandsForKey.NO_PENDING_UNMANAGED; +import static accord.primitives.Txn.Kind.ExclusiveSyncPoint; import static accord.primitives.TxnId.NO_TXNIDS; import static accord.primitives.Txn.Kind.Read; import static accord.primitives.Txn.Kind.SyncPoint; import static accord.primitives.Txn.Kind.Write; import static accord.utils.ArrayBuffers.cachedInts; import static accord.utils.ArrayBuffers.cachedTxnIds; -import static org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer.TxnIdFlags.EXTENDED; -import static org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer.TxnIdFlags.EXTENDED_BITS; -import static org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer.TxnIdFlags.RAW; import static org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer.TxnIdFlags.RAW_BITS; -import static org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer.TxnIdFlags.STANDARD; import static org.apache.cassandra.utils.ByteBufferUtil.readLeastSignificantBytes; import static org.apache.cassandra.utils.ByteBufferUtil.writeLeastSignificantBytes; import static org.apache.cassandra.utils.ByteBufferUtil.writeMostSignificantBytes; @@ -63,7 +64,8 @@ public class CommandsForKeySerializer private static final int HAS_MISSING_DEPS_HEADER_BIT = 0x1; private static final int HAS_EXECUTE_AT_HEADER_BIT = 0x2; private static final int HAS_BALLOT_HEADER_BIT = 0x4; - private static final int HAS_NON_STANDARD_FLAGS = 0x8; + private static final int HAS_STATUS_OVERRIDES = 0x8; + private static final int HAS_NON_STANDARD_FLAGS = 0x10; /** * We read/write a fixed number of intial bytes for each command, with an initial flexible number of flag bits @@ -77,8 +79,9 @@ public class CommandsForKeySerializer * bit 0 is set if there are any missing ids; * bit 1 is set if there are any executeAt specified * bit 2 is set if there are any ballots specified - * bit 3 is set if there are any queries present besides reads/writes - * bits 4-5 number of header bytes to read for each command + * bit 3 is set if there are any non-standard TxnId.Kind present + * bit 4 is set if there are any queries with override flags + * bits 6-7 number of header bytes to read for each command * bits 8-9: level 0 extra hlc bytes to read * bits 10-11: level 1 extra hlc bytes to read (+ 1 + level 0) * bits 12-13: level 2 extra hlc bytes to read (+ 1 + level 1) @@ -86,9 +89,10 @@ public class CommandsForKeySerializer * * In order, for each command, we consume: * 3 bits for the InternalStatus of the command + * 1 optional bit: if any command has override flags; 2 bits more to read if this bit is set * 1 optional bit: if the status encodes an executeAt, indicating if the executeAt is not the TxnId * 1 optional bit: if the status encodes any dependencies and there are non-zero missing ids, indicating if there are any missing for this command - * 1 or 2 bits for the kind of the TxnId: 0=key read, 1=key write, 2=exclusive sync point,3=read 16 bits + * 2 or 3 bits for the kind of the TxnId * 1 bit encoding if the epoch has changed * 2 optional bits: if the prior bit is set, indicating how many bits should be read for the epoch increment: 0=none (increment by 1); 1=4, 2=8, 3=32 * 4 option bits: if prior bits=01, epoch delta @@ -124,7 +128,7 @@ public class CommandsForKeySerializer // whether we have any missing transactions to encode, any executeAt that are not equal to their TxnId // and whether there are any non-standard flag bits to encode boolean hasNonStandardFlags = false; - int nodeIdCount, missingIdCount = 0, executeAtCount = 0, ballotCount = 0; + int nodeIdCount, missingIdCount = 0, executeAtCount = 0, ballotCount = 0, overrideCount = 0; int bitsPerExecuteAtEpoch = 0, bitsPerExecuteAtFlags = 0, bitsPerExecuteAtHlc = 1; // to permit us to use full 64 bits and encode in 5 bits we force at least one hlc bit { nodeIds[0] = cfk.redundantBefore().node.id; @@ -139,13 +143,13 @@ public class CommandsForKeySerializer } TxnInfo txn = cfk.get(i); - - hasNonStandardFlags |= txnIdFlags(txn) != STANDARD; + overrideCount += txn.statusOverrides() > 0 ? 1 : 0; + hasNonStandardFlags |= hasNonStandardFlags(txn); nodeIds[nodeIdCount++] = txn.node.id; if (txn.executeAt != txn) { - Invariants.checkState(txn.status.hasExecuteAtOrDeps); + Invariants.checkState(txn.status().hasExecuteAtOrDeps); nodeIds[nodeIdCount++] = txn.executeAt.node.id; bitsPerExecuteAtEpoch = Math.max(bitsPerExecuteAtEpoch, numberOfBitsToRepresent(txn.executeAt.epoch() - txn.epoch())); bitsPerExecuteAtHlc = Math.max(bitsPerExecuteAtHlc, numberOfBitsToRepresent(txn.executeAt.hlc() - txn.hlc())); @@ -159,7 +163,7 @@ public class CommandsForKeySerializer missingIdCount += extra.missing.length; if (extra.ballot != Ballot.ZERO) { - Invariants.checkArgument(txn.status.hasBallot); + Invariants.checkArgument(txn.status().hasBallot); nodeIds[nodeIdCount++] = extra.ballot.node.id; ballotCount += 1; } @@ -172,7 +176,7 @@ public class CommandsForKeySerializer // We can now use this information to calculate the fixed header size, compute the amount // of additional space we'll need to store the TxnId and its basic info int bitsPerNodeId = numberOfBitsToRepresent(nodeIdCount); - int minHeaderBits = 7 + bitsPerNodeId + (hasNonStandardFlags ? 1 : 0); + int minHeaderBits = 8 + bitsPerNodeId + (hasNonStandardFlags ? 1 : 0) + (overrideCount > 0 ? 1 : 0); int infoHeaderBits = (executeAtCount > 0 ? 1 : 0) + (missingIdCount > 0 ? 1 : 0); int ballotHeaderBits = (ballotCount > 0 ? 1 : 0); int maxHeaderBits = minHeaderBits; @@ -216,14 +220,16 @@ public class CommandsForKeySerializer prevHlc = hlc; } - if (hasNonStandardFlags && txnIdFlags(txnId) == RAW) + if (txnIdFlagsBits(txnId, hasNonStandardFlags) == RAW_BITS) totalBytes += 2; TxnInfo info = cfk.get(i); - if (info.status.hasExecuteAtOrDeps) + if (info.status().hasExecuteAtOrDeps) headerBits += infoHeaderBits; - if (info.status.hasBallot) + if (info.status().hasBallot) headerBits += ballotHeaderBits; + if (info.statusOverrides() != 0) + headerBits += 2; maxHeaderBits = Math.max(headerBits, maxHeaderBits); int basicBytes = (headerBits + payloadBits + 7)/8; bytesHistogram[basicBytes]++; @@ -242,10 +248,11 @@ public class CommandsForKeySerializer int flags = (missingIdCount > 0 ? HAS_MISSING_DEPS_HEADER_BIT : 0) | (executeAtCount > 0 ? HAS_EXECUTE_AT_HEADER_BIT : 0) | (ballotCount > 0 ? HAS_BALLOT_HEADER_BIT : 0) - | (hasNonStandardFlags ? HAS_NON_STANDARD_FLAGS : 0); + | (hasNonStandardFlags ? HAS_NON_STANDARD_FLAGS : 0) + | (overrideCount > 0 ? HAS_STATUS_OVERRIDES : 0); int headerBytes = (maxHeaderBits+7)/8; - flags |= Invariants.checkArgument(headerBytes - 1, headerBytes <= 4) << 4; + flags |= Invariants.checkArgument(headerBytes - 1, headerBytes <= 4) << 6; int hlcBytesLookup; { // 2bits per size, first value may be zero and remainder may be increments of 1-4; @@ -281,7 +288,14 @@ public class CommandsForKeySerializer prevEpoch = cfk.redundantBefore().epoch(); prevHlc = cfk.redundantBefore().hlc(); - totalBytes += TypeSizes.sizeofUnsignedVInt(prevEpoch); + { + RedundantBefore.Entry boundsInfo = cfk.boundsInfo(); + long start = boundsInfo.startOwnershipEpoch; + long end = boundsInfo.endOwnershipEpoch; + totalBytes += VIntCoding.computeUnsignedVIntSize(start); + totalBytes += VIntCoding.computeUnsignedVIntSize(end == Long.MAX_VALUE ? 0 : (1 + end - start)); + totalBytes += VIntCoding.computeVIntSize(prevEpoch - start); + } totalBytes += TypeSizes.sizeofUnsignedVInt(prevHlc); totalBytes += TypeSizes.sizeofUnsignedVInt(cfk.redundantBefore().flags()); totalBytes += TypeSizes.sizeofUnsignedVInt(Arrays.binarySearch(nodeIds, 0, nodeIdCount, cfk.redundantBefore().node.id)); @@ -297,7 +311,7 @@ public class CommandsForKeySerializer { TxnInfo txn = cfk.get(i); if (txn.getClass() != TxnInfoExtra.class) continue; - if (!txn.status.hasBallot) continue; + if (!txn.status().hasBallot) continue; TxnInfoExtra extra = (TxnInfoExtra) txn; if (extra.ballot == Ballot.ZERO) continue; if (prevBallot != null) @@ -353,7 +367,14 @@ public class CommandsForKeySerializer out.putShort((short)flags); - VIntCoding.writeUnsignedVInt(prevEpoch, out); + { + RedundantBefore.Entry boundsInfo = cfk.boundsInfo(); + long start = boundsInfo.startOwnershipEpoch; + long end = boundsInfo.endOwnershipEpoch; + VIntCoding.writeUnsignedVInt(start, out); + VIntCoding.writeUnsignedVInt(end == Long.MAX_VALUE ? 0 : (1 + end - start), out); + VIntCoding.writeVInt(prevEpoch - start, out); + } VIntCoding.writeUnsignedVInt(prevHlc, out); VIntCoding.writeUnsignedVInt32(cfk.redundantBefore().flags(), out); VIntCoding.writeUnsignedVInt32(Arrays.binarySearch(nodeIds, 0, nodeIdCount, cfk.redundantBefore().node.id), out); @@ -362,32 +383,37 @@ public class CommandsForKeySerializer int executeAtMask = executeAtCount > 0 ? 1 : 0; int missingDepsMask = missingIdCount > 0 ? 1 : 0; int ballotMask = ballotCount > 0 ? 1 : 0; - int flagsIncrement = hasNonStandardFlags ? 2 : 1; + int noOverrideIncrement = overrideCount > 0 ? 1 : 0; + int flagsIncrement = hasNonStandardFlags ? 3 : 2; // TODO (desired): check this loop compiles correctly to only branch on epoch case, for binarySearch and flushing for (int i = 0 ; i < commandCount ; ++i) { - TxnId txnId = cfk.txnId(i); - TxnInfo info = cfk.get(i); - InternalStatus status = info.status; + TxnInfo txn = cfk.get(i); + InternalStatus status = txn.status(); long bits = status.ordinal(); int bitIndex = 3; int statusHasInfo = status.hasExecuteAtOrDeps ? 1 : 0; int statusHasBallot = status.hasBallot ? 1 : 0; - long hasExecuteAt = info.executeAt != txnId ? 1 : 0; + long hasExecuteAt = txn.executeAt != txn ? 1 : 0; bits |= hasExecuteAt << bitIndex; bitIndex += statusHasInfo & executeAtMask; - long hasMissingIds = info.getClass() == TxnInfoExtra.class && ((TxnInfoExtra)info).missing != NO_TXNIDS ? 1 : 0; + long hasMissingIds = txn.getClass() == TxnInfoExtra.class && ((TxnInfoExtra)txn).missing != NO_TXNIDS ? 1 : 0; bits |= hasMissingIds << bitIndex; bitIndex += statusHasInfo & missingDepsMask; - long hasBallot = info.getClass() == TxnInfoExtra.class && ((TxnInfoExtra)info).ballot != Ballot.ZERO ? 1 : 0; + long hasBallot = txn.getClass() == TxnInfoExtra.class && ((TxnInfoExtra)txn).ballot != Ballot.ZERO ? 1 : 0; bits |= hasBallot << bitIndex; bitIndex += statusHasBallot & ballotMask; - long flagBits = txnIdFlagsBits(txnId); + long statusOverrides = (long) txn.statusOverrides() << 1; + statusOverrides |= statusOverrides != 0 ? 1 : 0; + bits |= statusOverrides << bitIndex; + bitIndex += statusOverrides != 0 ? 3 : noOverrideIncrement; + + long flagBits = txnIdFlagsBits(txn, hasNonStandardFlags); boolean writeFullFlags = flagBits == RAW_BITS; bits |= flagBits << bitIndex; bitIndex += flagsIncrement; @@ -395,9 +421,9 @@ public class CommandsForKeySerializer long hlcBits; int extraEpochDeltaBytes = 0; { - long epoch = txnId.epoch(); + long epoch = txn.epoch(); long delta = epoch - prevEpoch; - long hlc = txnId.hlc(); + long hlc = txn.hlc(); hlcBits = hlc - prevHlc; if (delta == 0) { @@ -432,7 +458,7 @@ public class CommandsForKeySerializer prevHlc = hlc; } - bits |= ((long)Arrays.binarySearch(nodeIds, 0, nodeIdCount, txnId.node.id)) << bitIndex; + bits |= ((long)Arrays.binarySearch(nodeIds, 0, nodeIdCount, txn.node.id)) << bitIndex; bitIndex += bitsPerNodeId; bits |= hlcBits << (bitIndex + 2); @@ -444,7 +470,7 @@ public class CommandsForKeySerializer writeLeastSignificantBytes(hlcBits, getHlcBytes(hlcBytesLookup, hlcFlag), out); if (writeFullFlags) - out.putShort((short)txnId.flags()); + out.putShort((short)txn.flags()); if (extraEpochDeltaBytes > 0) { @@ -608,7 +634,7 @@ public class CommandsForKeySerializer } } - public static CommandsForKey fromBytes(Key key, ByteBuffer in) + public static CommandsForKey fromBytes(RoutingKey key, ByteBuffer in) { if (!in.hasRemaining()) return null; @@ -632,19 +658,26 @@ public class CommandsForKeySerializer nodeIds[i] = new Node.Id(prev += VIntCoding.readUnsignedVInt32(in)); } - int missingDepsMasks, executeAtMasks, ballotMasks, txnIdFlagsMask; + int missingDepsMasks, executeAtMasks, ballotMasks, txnIdFlagsMask, overrideMask; int headerByteCount, hlcBytesLookup; { int flags = in.getShort(); missingDepsMasks = 0 != (flags & HAS_MISSING_DEPS_HEADER_BIT) ? 1 : 0; executeAtMasks = 0 != (flags & HAS_EXECUTE_AT_HEADER_BIT) ? 1 : 0; ballotMasks = 0 != (flags & HAS_BALLOT_HEADER_BIT) ? 1 : 0; - txnIdFlagsMask = 0 != (flags & HAS_NON_STANDARD_FLAGS) ? 3 : 1; - headerByteCount = 1 + ((flags >>> 4) & 0x3); + overrideMask = 0 != (flags & HAS_STATUS_OVERRIDES) ? 1 : 0; + txnIdFlagsMask = 0 != (flags & HAS_NON_STANDARD_FLAGS) ? 7 : 3; + headerByteCount = 1 + ((flags >>> 6) & 0x3); hlcBytesLookup = setHlcByteDeltas((flags >>> 8) & 0x3, (flags >>> 10) & 0x3, (flags >>> 12) & 0x3, (flags >>> 14) & 0x3); } - long prevEpoch = VIntCoding.readUnsignedVInt(in); + long minEpoch = VIntCoding.readUnsignedVInt(in); + long maxEpoch; { + long offset = VIntCoding.readUnsignedVInt(in); + maxEpoch = offset == 0 ? Long.MAX_VALUE : minEpoch + offset - 1; + } + RedundantBefore.Entry boundsInfo = NO_BOUNDS_INFO.withEpochs(minEpoch, maxEpoch); + long prevEpoch = minEpoch + VIntCoding.readVInt(in); long prevHlc = VIntCoding.readUnsignedVInt(in); TxnId redundantBefore; { @@ -661,7 +694,7 @@ public class CommandsForKeySerializer int commandDecodeFlags = (int)(header & 0x7); InternalStatus status = InternalStatus.get(commandDecodeFlags); header >>>= 3; - commandDecodeFlags <<= 3; + commandDecodeFlags <<= 6; { int infoMask = status.hasExecuteAtOrDeps ? 1 : 0; @@ -669,15 +702,21 @@ public class CommandsForKeySerializer int missingDepsMask = infoMask & missingDepsMasks; commandDecodeFlags |= ((int)header & executeAtMask) << 1; header >>>= executeAtMask; - commandDecodeFlags |= (int)header & missingDepsMask; + commandDecodeFlags |= ((int)header & missingDepsMask); header >>>= missingDepsMask; int ballotMask = status.hasBallot ? ballotMasks : 0; commandDecodeFlags |= ((int)header & ballotMask) << 2; header >>>= ballotMask; + commandDecodeFlags |= (header & 0x7) << 3; + header >>= (header & overrideMask) == 0 ? overrideMask : 3; decodeFlags[i] = commandDecodeFlags; } - Txn.Kind kind = TXN_ID_FLAG_BITS_KIND_LOOKUP[((int)header & txnIdFlagsMask)]; + Txn.Kind kind; Domain domain; { + int flags = (int)header & txnIdFlagsMask; + kind = kindLookup(flags); + domain = domainLookup(flags); + } header >>>= Integer.bitCount(txnIdFlagsMask); boolean hlcIsNegative = false; @@ -725,7 +764,7 @@ public class CommandsForKeySerializer if (readEpochBytes > 0) epoch += readEpochBytes == 1 ? (in.get() & 0xff) : in.getInt(); - txnIds[i] = kind != null ? new TxnId(epoch, hlc, kind, Domain.Key, node) + txnIds[i] = kind != null ? new TxnId(epoch, hlc, kind, domain, node) : TxnId.fromValues(epoch, hlc, flags, node); prevEpoch = epoch; @@ -880,7 +919,9 @@ public class CommandsForKeySerializer prevBallot = ballot; } - txns[i] = TxnInfo.create(txnId, InternalStatus.get(commandDecodeFlags >>> 3), executeAt, missing, ballot); + InternalStatus status = InternalStatus.get(commandDecodeFlags >>> 6); + int statusOverrides = ((commandDecodeFlags >>> 3) & overrideMask) == 0 ? 0 : commandDecodeFlags >>> 4; + txns[i] = create(boundsInfo, txnId, status, statusOverrides, executeAt, missing, ballot); } cachedTxnIds().forceDiscard(missingIdBuffer, maxIdBufferCount); @@ -888,13 +929,25 @@ public class CommandsForKeySerializer else { for (int i = 0 ; i < commandCount ; ++i) - txns[i] = TxnInfo.create(txnIds[i], InternalStatus.get(decodeFlags[i] >>> 3), txnIds[i], Ballot.ZERO); + { + int commandDecodeFlags = decodeFlags[i]; + InternalStatus status = InternalStatus.get(commandDecodeFlags >>> 6); + int statusOverrides = ((commandDecodeFlags >>> 3) & overrideMask) == 0 ? 0 : commandDecodeFlags >>> 4; + txns[i] = create(boundsInfo, txnIds[i], status, statusOverrides, txnIds[i], NO_TXNIDS, Ballot.ZERO); + } } cachedTxnIds().forceDiscard(txnIds, commandCount); return CommandsForKey.SerializerSupport.create(key, txns, unmanageds, redundantBefore, prunedBeforeIndex == -1 ? TxnId.NONE : txns[prunedBeforeIndex]); } + private static TxnInfo create(RedundantBefore.Entry boundsInfo, @Nonnull TxnId txnId, InternalStatus status, int statusOverrides, @Nonnull Timestamp executeAt, @Nonnull TxnId[] missing, @Nonnull Ballot ballot) + { + boolean mayExecute = status.isCommittedToExecute() ? CommandsForKey.executes(boundsInfo, txnId, executeAt) + : CommandsForKey.mayExecute(boundsInfo, txnId); + return TxnInfo.create(txnId, status, mayExecute, statusOverrides, executeAt, missing, ballot); + } + private static int getHlcBytes(int lookup, int index) { return (lookup >>> (index * 4)) & 0xf; @@ -998,40 +1051,48 @@ public class CommandsForKeySerializer enum TxnIdFlags { STANDARD, EXTENDED, RAW; - static final int EXTENDED_BITS = 0x2; - static final int RAW_BITS = 0x3; + static final int RAW_BITS = 0; } - private static TxnIdFlags txnIdFlags(TxnId txnId) + private static boolean hasNonStandardFlags(TxnId txnId) { - if (txnId.flags() > Timestamp.IDENTITY_FLAGS || txnId.domain() != Domain.Key) - return RAW; - switch (txnId.kind()) + if (txnId.flags() > Timestamp.IDENTITY_FLAGS) + return false; + + int flagBits = txnIdFlagsBits(txnId, true); + return flagBits > 3; + } + + private static int txnIdFlagsBits(TxnId txnId, boolean permitNonStandardFlags) + { + Txn.Kind kind = txnId.kind(); + Domain domain = txnId.domain(); + if (!permitNonStandardFlags && domain == Domain.Range) + return 0; + + int offset = domain == Domain.Range ? 3 : 0; + switch (kind) { - default: throw new AssertionError("Unhandled Kind: " + txnId.kind()); - case Read: - case Write: - return STANDARD; - case SyncPoint: - return EXTENDED; + case Read: return offset + 1; + case Write: return offset + 2; + case SyncPoint: return offset + 3; case ExclusiveSyncPoint: - case LocalOnly: - case EphemeralRead: - return RAW; + if (domain == Domain.Range) + return 7; + default: + return 0; } } - private static long txnIdFlagsBits(TxnId txnId) + private static Domain domainLookup(int flags) { - switch (txnIdFlags(txnId)) - { - default: throw new AssertionError("Unhandled TxnIdFlag: " + txnIdFlags(txnId)); - case RAW: return RAW_BITS; - case EXTENDED: return EXTENDED_BITS; - case STANDARD: - return txnId.kind() == Read ? 0 : 1; - } + return flags <= 4 ? Domain.Key : Domain.Range; } - private static final Txn.Kind[] TXN_ID_FLAG_BITS_KIND_LOOKUP = new Txn.Kind[] { Read, Write, SyncPoint, null }; + private static Txn.Kind kindLookup(int flags) + { + return TXN_ID_FLAG_BITS_KIND_LOOKUP[flags]; + } + + private static final Txn.Kind[] TXN_ID_FLAG_BITS_KIND_LOOKUP = new Txn.Kind[] { null, Read, Write, SyncPoint, Read, Write, SyncPoint, ExclusiveSyncPoint }; } diff --git a/src/java/org/apache/cassandra/service/accord/serializers/CommitSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/CommitSerializers.java index 9e439233ae..c286cd330a 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/CommitSerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/CommitSerializers.java @@ -27,11 +27,10 @@ import accord.primitives.Ballot; import accord.primitives.FullRoute; import accord.primitives.PartialDeps; import accord.primitives.PartialTxn; +import accord.primitives.Participants; import accord.primitives.Route; -import accord.primitives.Seekables; import accord.primitives.Timestamp; import accord.primitives.TxnId; -import accord.primitives.Unseekables; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.io.IVersionedSerializer; import org.apache.cassandra.io.util.DataInputPlus; @@ -46,7 +45,7 @@ public class CommitSerializers { private static final IVersionedSerializer kind = new EnumSerializer<>(Commit.Kind.class); - public abstract static class CommitSerializer extends TxnRequestSerializer + public abstract static class CommitSerializer extends TxnRequestSerializer.WithUnsyncedSerializer { private final IVersionedSerializer read; @@ -61,30 +60,28 @@ public class CommitSerializers kind.serialize(msg.kind, out, version); CommandSerializers.ballot.serialize(msg.ballot, out, version); CommandSerializers.timestamp.serialize(msg.executeAt, out, version); - KeySerializers.seekables.serialize(msg.keys, out, version); - CommandSerializers.nullablePartialTxn.serialize(msg.keys, msg.partialTxn, out, version); - DepsSerializer.partialDeps.serialize(msg.keys, msg.partialDeps, out, version); + CommandSerializers.nullablePartialTxn.serialize(msg.partialTxn, out, version); + DepsSerializer.partialDeps.serialize(msg.scope, msg.partialDeps, out, version); serializeNullable(msg.route, out, version, KeySerializers.fullRoute); serializeNullable(msg.readData, out, version, read); } - protected abstract C deserializeCommit(TxnId txnId, Route scope, long waitForEpoch, Commit.Kind kind, + protected abstract C deserializeCommit(TxnId txnId, Route scope, long waitForEpoch, long minEpoch, Commit.Kind kind, Ballot ballot, Timestamp executeAt, - Seekables keys, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, + @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute fullRoute, @Nullable ReadData read); @Override - public C deserializeBody(DataInputPlus in, int version, TxnId txnId, Route scope, long waitForEpoch) throws IOException + public C deserializeBody(DataInputPlus in, int version, TxnId txnId, Route scope, long waitForEpoch, long minEpoch) throws IOException { Commit.Kind kind = CommitSerializers.kind.deserialize(in, version); Ballot ballot = CommandSerializers.ballot.deserialize(in, version); Timestamp executeAt = CommandSerializers.timestamp.deserialize(in, version); - Seekables keys = KeySerializers.seekables.deserialize(in, version); - PartialTxn txn = CommandSerializers.nullablePartialTxn.deserialize(keys, in, version); - PartialDeps deps = DepsSerializer.partialDeps.deserialize(keys, in, version); + PartialTxn txn = CommandSerializers.nullablePartialTxn.deserialize(in, version); + PartialDeps deps = DepsSerializer.partialDeps.deserialize(scope, in, version); FullRoute route = deserializeNullable(in, version, KeySerializers.fullRoute); ReadData read = deserializeNullable(in, version, this.read); - return deserializeCommit(txnId, scope, waitForEpoch, kind, ballot, executeAt, keys, txn, deps, route, read); + return deserializeCommit(txnId, scope, waitForEpoch, minEpoch, kind, ballot, executeAt, txn, deps, route, read); } @Override @@ -93,9 +90,8 @@ public class CommitSerializers return kind.serializedSize(msg.kind, version) + CommandSerializers.ballot.serializedSize(msg.ballot, version) + CommandSerializers.timestamp.serializedSize(msg.executeAt, version) - + KeySerializers.seekables.serializedSize(msg.keys, version) - + CommandSerializers.nullablePartialTxn.serializedSize(msg.keys, msg.partialTxn, version) - + DepsSerializer.partialDeps.serializedSize(msg.keys, msg.partialDeps, version) + + CommandSerializers.nullablePartialTxn.serializedSize(msg.partialTxn, version) + + DepsSerializer.partialDeps.serializedSize(msg.scope, msg.partialDeps, version) + serializedNullableSize(msg.route, version, KeySerializers.fullRoute) + serializedNullableSize(msg.readData, version, read); } @@ -104,9 +100,9 @@ public class CommitSerializers public static final IVersionedSerializer request = new CommitSerializer(ReadData.class, ReadDataSerializers.readData) { @Override - protected Commit deserializeCommit(TxnId txnId, Route scope, long waitForEpoch, Commit.Kind kind, Ballot ballot, Timestamp executeAt, Seekables keys, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute fullRoute, @Nullable ReadData read) + protected Commit deserializeCommit(TxnId txnId, Route scope, long waitForEpoch, long minEpoch, Commit.Kind kind, Ballot ballot, Timestamp executeAt, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute fullRoute, @Nullable ReadData read) { - return Commit.SerializerSupport.create(txnId, scope, waitForEpoch, kind, ballot, executeAt, keys, partialTxn, partialDeps, fullRoute, read); + return Commit.SerializerSupport.create(txnId, scope, waitForEpoch, minEpoch, kind, ballot, executeAt, partialTxn, partialDeps, fullRoute, read); } }; @@ -116,7 +112,7 @@ public class CommitSerializers public void serialize(Commit.Invalidate invalidate, DataOutputPlus out, int version) throws IOException { CommandSerializers.txnId.serialize(invalidate.txnId, out, version); - KeySerializers.unseekables.serialize(invalidate.scope, out, version); + KeySerializers.participants.serialize(invalidate.scope, out, version); out.writeUnsignedVInt(invalidate.waitForEpoch); out.writeUnsignedVInt(invalidate.invalidateUntilEpoch - invalidate.waitForEpoch); } @@ -125,7 +121,7 @@ public class CommitSerializers public Commit.Invalidate deserialize(DataInputPlus in, int version) throws IOException { TxnId txnId = CommandSerializers.txnId.deserialize(in, version); - Unseekables scope = KeySerializers.unseekables.deserialize(in, version); + Participants scope = KeySerializers.participants.deserialize(in, version); long waitForEpoch = in.readUnsignedVInt(); long invalidateUntilEpoch = in.readUnsignedVInt() + waitForEpoch; return Commit.Invalidate.SerializerSupport.create(txnId, scope, waitForEpoch, invalidateUntilEpoch); @@ -135,7 +131,7 @@ public class CommitSerializers public long serializedSize(Commit.Invalidate invalidate, int version) { return CommandSerializers.txnId.serializedSize(invalidate.txnId, version) - + KeySerializers.unseekables.serializedSize(invalidate.scope, version) + + KeySerializers.participants.serializedSize(invalidate.scope, version) + TypeSizes.sizeofUnsignedVInt(invalidate.waitForEpoch) + TypeSizes.sizeofUnsignedVInt(invalidate.invalidateUntilEpoch - invalidate.waitForEpoch); } diff --git a/src/java/org/apache/cassandra/service/accord/serializers/DepsSerializer.java b/src/java/org/apache/cassandra/service/accord/serializers/DepsSerializer.java index 841d89882e..72dfc5f969 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/DepsSerializer.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/DepsSerializer.java @@ -21,15 +21,16 @@ import java.io.IOException; import com.google.common.primitives.Ints; +import accord.primitives.AbstractUnseekableKeys; import accord.primitives.Deps; import accord.primitives.KeyDeps; -import accord.primitives.Keys; import accord.primitives.PartialDeps; import accord.primitives.Participants; import accord.primitives.Range; import accord.primitives.RangeDeps; -import accord.primitives.Seekables; +import accord.primitives.RoutingKeys; import accord.primitives.TxnId; +import accord.primitives.Unseekables; import org.apache.cassandra.io.IVersionedSerializer; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; @@ -43,7 +44,7 @@ import static accord.primitives.RangeDeps.SerializerSupport.rangesToTxnIdsCount; import static accord.primitives.Routable.Domain.Key; import static org.apache.cassandra.db.TypeSizes.sizeofUnsignedVInt; -public abstract class DepsSerializer extends IVersionedWithKeysSerializer.AbstractWithKeysSerializer implements IVersionedWithKeysSerializer, D> +public abstract class DepsSerializer extends IVersionedWithKeysSerializer.AbstractWithKeysSerializer implements IVersionedWithKeysSerializer, D> { public static final DepsSerializer deps = new DepsSerializer<>() { @@ -72,7 +73,7 @@ public abstract class DepsSerializer extends IVersionedWithKeysS } @Override - public void serialize(Seekables superset, PartialDeps partialDeps, DataOutputPlus out, int version) throws IOException + public void serialize(Unseekables superset, PartialDeps partialDeps, DataOutputPlus out, int version) throws IOException { super.serialize(superset, partialDeps, out, version); KeySerializers.participants.serialize(partialDeps.covering, out, version); @@ -86,7 +87,7 @@ public abstract class DepsSerializer extends IVersionedWithKeysS } @Override - public long serializedSize(Seekables keys, PartialDeps partialDeps, int version) + public long serializedSize(Unseekables keys, PartialDeps partialDeps, int version) { return super.serializedSize(keys, partialDeps, version) + KeySerializers.participants.serializedSize(partialDeps.covering, version); @@ -100,48 +101,48 @@ public abstract class DepsSerializer extends IVersionedWithKeysS @Override public void serialize(D deps, DataOutputPlus out, int version) throws IOException { - KeySerializers.keys.serialize(deps.keyDeps.keys(), out, version); + KeySerializers.routingKeys.serialize(deps.keyDeps.keys(), out, version); serializeWithoutKeys(deps, out, version); } @Override - public void serialize(Seekables superset, D deps, DataOutputPlus out, int version) throws IOException + public void serialize(Unseekables superset, D deps, DataOutputPlus out, int version) throws IOException { if (superset.domain() == Key) serializeSubset(deps.keyDeps.keys(), superset, out); - else KeySerializers.keys.serialize(deps.keyDeps.keys(), out, version); + else KeySerializers.routingKeys.serialize(deps.keyDeps.keys(), out, version); serializeWithoutKeys(deps, out, version); } @Override public D deserialize(DataInputPlus in, int version) throws IOException { - Keys keys = KeySerializers.keys.deserialize(in, version); + RoutingKeys keys = KeySerializers.routingKeys.deserialize(in, version); return deserializeWithoutKeys(keys, in, version); } @Override - public D deserialize(Seekables superset, DataInputPlus in, int version) throws IOException + public D deserialize(Unseekables superset, DataInputPlus in, int version) throws IOException { - Keys keys; - if (superset.domain() == Key) keys = (Keys)deserializeSubset(superset, in); - else keys = KeySerializers.keys.deserialize(in, version); + RoutingKeys keys; + if (superset.domain() == Key) keys = ((AbstractUnseekableKeys)deserializeSubset(superset, in)).toParticipants(); + else keys = KeySerializers.routingKeys.deserialize(in, version); return deserializeWithoutKeys(keys, in, version); } @Override public long serializedSize(D deps, int version) { - long size = KeySerializers.keys.serializedSize(deps.keyDeps.keys(), version); + long size = KeySerializers.routingKeys.serializedSize(deps.keyDeps.keys(), version); size += serializedSizeWithoutKeys(deps, version); return size; } @Override - public long serializedSize(Seekables keys, D deps, int version) + public long serializedSize(Unseekables keys, D deps, int version) { long size; if (keys.domain() == Key) size = serializedSubsetSize(deps.keyDeps.keys(), keys); - else size = KeySerializers.keys.serializedSize(deps.keyDeps.keys(), version); + else size = KeySerializers.routingKeys.serializedSize(deps.keyDeps.keys(), version); size += serializedSizeWithoutKeys(deps, version); return size; } @@ -169,11 +170,11 @@ public abstract class DepsSerializer extends IVersionedWithKeysS } { - Keys keys = deps.directKeyDeps.keys(); + RoutingKeys keys = deps.directKeyDeps.keys(); boolean isSubset = isSubset(keys, deps.keyDeps.keys()); out.writeBoolean(isSubset); if (isSubset) serializeSubset(keys, deps.keyDeps.keys(), out); - else KeySerializers.keys.serialize(keys, out, version); + else KeySerializers.routingKeys.serialize(keys, out, version); serializeKeyDepsWithoutKeys(deps.directKeyDeps, out, version); } @@ -192,7 +193,7 @@ public abstract class DepsSerializer extends IVersionedWithKeysS out.writeUnsignedVInt32(keysToTxnIds(keyDeps, i)); } - private D deserializeWithoutKeys(Keys keys, DataInputPlus in, int version) throws IOException + private D deserializeWithoutKeys(RoutingKeys keys, DataInputPlus in, int version) throws IOException { KeyDeps keyDeps = deserializeKeyDeps(keys, in, version); @@ -219,14 +220,14 @@ public abstract class DepsSerializer extends IVersionedWithKeysS KeyDeps directKeyDeps; { boolean isSubset = in.readBoolean(); - Keys directKeys = isSubset ? (Keys)deserializeSubset(keys, in) : KeySerializers.keys.deserialize(in, version); + RoutingKeys directKeys = isSubset ? (RoutingKeys)deserializeSubset(keys, in) : KeySerializers.routingKeys.deserialize(in, version); directKeyDeps = deserializeKeyDeps(directKeys, in, version); } return deserialize(keyDeps, rangeDeps, directKeyDeps, in, version); } - private static KeyDeps deserializeKeyDeps(Keys keys, DataInputPlus in, int version) throws IOException + private static KeyDeps deserializeKeyDeps(RoutingKeys keys, DataInputPlus in, int version) throws IOException { int txnIdCount = in.readUnsignedVInt32(); TxnId[] txnIds = new TxnId[txnIdCount]; @@ -266,7 +267,7 @@ public abstract class DepsSerializer extends IVersionedWithKeysS { boolean isSubset = isSubset(deps.directKeyDeps.keys(), deps.keyDeps.keys()); size += 1; - size += isSubset ? serializedSubsetSize(deps.directKeyDeps.keys(), deps.keyDeps.keys()) : KeySerializers.keys.serializedSize(deps.directKeyDeps.keys(), version); + size += isSubset ? serializedSubsetSize(deps.directKeyDeps.keys(), deps.keyDeps.keys()) : KeySerializers.routingKeys.serializedSize(deps.directKeyDeps.keys(), version); size += serializedSizeOfKeyDepsWithoutKeys(deps.directKeyDeps, version); } return size; @@ -286,7 +287,7 @@ public abstract class DepsSerializer extends IVersionedWithKeysS return size; } - private static boolean isSubset(Keys test, Keys superset) + private static boolean isSubset(RoutingKeys test, RoutingKeys superset) { return test.foldl(superset, (k, p, v, i) -> v + 1, 0, 0, 0) == test.size(); } diff --git a/src/java/org/apache/cassandra/service/accord/serializers/GetEphmrlReadDepsSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/GetEphmrlReadDepsSerializers.java index 7fe67842de..c716b2afd5 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/GetEphmrlReadDepsSerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/GetEphmrlReadDepsSerializers.java @@ -22,9 +22,8 @@ import java.io.IOException; import accord.messages.GetEphemeralReadDeps; import accord.messages.GetEphemeralReadDeps.GetEphemeralReadDepsOk; -import accord.primitives.PartialDeps; +import accord.primitives.Deps; import accord.primitives.Route; -import accord.primitives.Seekables; import accord.primitives.TxnId; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.io.IVersionedSerializer; @@ -38,23 +37,20 @@ public class GetEphmrlReadDepsSerializers @Override public void serializeBody(GetEphemeralReadDeps msg, DataOutputPlus out, int version) throws IOException { - KeySerializers.seekables.serialize(msg.keys, out, version); out.writeUnsignedVInt(msg.executionEpoch); } @Override public GetEphemeralReadDeps deserializeBody(DataInputPlus in, int version, TxnId txnId, Route scope, long waitForEpoch, long minEpoch) throws IOException { - Seekables keys = KeySerializers.seekables.deserialize(in, version); long executionEpoch = in.readUnsignedVInt(); - return GetEphemeralReadDeps.SerializationSupport.create(txnId, scope, waitForEpoch, minEpoch, keys, executionEpoch); + return GetEphemeralReadDeps.SerializationSupport.create(txnId, scope, waitForEpoch, minEpoch, executionEpoch); } @Override public long serializedBodySize(GetEphemeralReadDeps msg, int version) { - return KeySerializers.seekables.serializedSize(msg.keys, version) - + TypeSizes.sizeofUnsignedVInt(msg.executionEpoch); + return TypeSizes.sizeofUnsignedVInt(msg.executionEpoch); } }; @@ -63,14 +59,14 @@ public class GetEphmrlReadDepsSerializers @Override public void serialize(GetEphemeralReadDepsOk reply, DataOutputPlus out, int version) throws IOException { - DepsSerializer.partialDeps.serialize(reply.deps, out, version); + DepsSerializer.deps.serialize(reply.deps, out, version); out.writeUnsignedVInt(reply.latestEpoch); } @Override public GetEphemeralReadDepsOk deserialize(DataInputPlus in, int version) throws IOException { - PartialDeps deps = DepsSerializer.partialDeps.deserialize(in, version); + Deps deps = DepsSerializer.deps.deserialize(in, version); long latestEpoch = in.readUnsignedVInt(); return new GetEphemeralReadDepsOk(deps, latestEpoch); } @@ -78,7 +74,7 @@ public class GetEphmrlReadDepsSerializers @Override public long serializedSize(GetEphemeralReadDepsOk reply, int version) { - return DepsSerializer.partialDeps.serializedSize(reply.deps, version) + return DepsSerializer.deps.serializedSize(reply.deps, version) + TypeSizes.sizeofUnsignedVInt(reply.latestEpoch); } }; diff --git a/src/java/org/apache/cassandra/service/accord/serializers/GetMaxConflictSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/GetMaxConflictSerializers.java index ad8af3ba88..bb580690c4 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/GetMaxConflictSerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/GetMaxConflictSerializers.java @@ -23,7 +23,6 @@ import java.io.IOException; import accord.messages.GetMaxConflict; import accord.messages.GetMaxConflict.GetMaxConflictOk; import accord.primitives.Route; -import accord.primitives.Seekables; import accord.primitives.Timestamp; import accord.primitives.TxnId; import org.apache.cassandra.db.TypeSizes; @@ -38,23 +37,20 @@ public class GetMaxConflictSerializers @Override public void serializeBody(GetMaxConflict msg, DataOutputPlus out, int version) throws IOException { - KeySerializers.seekables.serialize(msg.keys, out, version); out.writeUnsignedVInt(msg.executionEpoch); } @Override public GetMaxConflict deserializeBody(DataInputPlus in, int version, TxnId txnId, Route scope, long waitForEpoch, long minEpoch) throws IOException { - Seekables keys = KeySerializers.seekables.deserialize(in, version); long executionEpoch = in.readUnsignedVInt(); - return GetMaxConflict.SerializationSupport.create(scope, waitForEpoch, minEpoch, keys, executionEpoch); + return GetMaxConflict.SerializationSupport.create(scope, waitForEpoch, minEpoch, executionEpoch); } @Override public long serializedBodySize(GetMaxConflict msg, int version) { - return KeySerializers.seekables.serializedSize(msg.keys, version) - + TypeSizes.sizeofUnsignedVInt(msg.executionEpoch); + return TypeSizes.sizeofUnsignedVInt(msg.executionEpoch); } }; diff --git a/src/java/org/apache/cassandra/service/accord/serializers/IVersionedWithKeysSerializer.java b/src/java/org/apache/cassandra/service/accord/serializers/IVersionedWithKeysSerializer.java index ef4b6d4202..3b4acb00a5 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/IVersionedWithKeysSerializer.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/IVersionedWithKeysSerializer.java @@ -20,14 +20,16 @@ package org.apache.cassandra.service.accord.serializers; import java.io.IOException; -import accord.api.Key; +import accord.api.RoutingKey; import accord.primitives.AbstractKeys; -import accord.primitives.Keys; +import accord.primitives.AbstractRanges; +import accord.primitives.AbstractUnseekableKeys; import accord.primitives.Range; import accord.primitives.Ranges; import accord.primitives.RoutableKey; import accord.primitives.Routables; -import accord.primitives.Seekables; +import accord.primitives.RoutingKeys; +import accord.primitives.Unseekables; import net.nicoulaj.compilecommand.annotations.DontInline; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.io.IVersionedSerializer; @@ -123,7 +125,7 @@ public interface IVersionedWithKeysSerializer, T> extends * If both ends have a pre-shared superset of the columns we are serializing, we can send them much * more efficiently. Both ends must provide the identically same set of columns. */ - protected void serializeSubset(Seekables serialize, Seekables superset, DataOutputPlus out) throws IOException + protected void serializeSubset(Routables serialize, Routables superset, DataOutputPlus out) throws IOException { /** * We weight this towards small sets, and sets where the majority of items are present, since @@ -149,10 +151,10 @@ public interface IVersionedWithKeysSerializer, T> extends { default: throw new AssertionError("Unhandled domain: " + serialize.domain()); case Key: - out.writeUnsignedVInt(encodeBitmap((Keys)serialize, (Keys)superset, supersetCount)); + out.writeUnsignedVInt(encodeBitmap((AbstractUnseekableKeys)serialize, (AbstractUnseekableKeys)superset, supersetCount)); break; case Range: - out.writeUnsignedVInt(encodeBitmap((Ranges)serialize, (Ranges)superset, supersetCount)); + out.writeUnsignedVInt(encodeBitmap((AbstractRanges)serialize, (AbstractRanges)superset, supersetCount)); break; } } @@ -162,16 +164,16 @@ public interface IVersionedWithKeysSerializer, T> extends { default: throw new AssertionError("Unhandled domain: " + serialize.domain()); case Key: - serializeLargeSubset((Keys)serialize, serializeCount, (Keys)superset, supersetCount, out); + serializeLargeSubset((AbstractUnseekableKeys)serialize, serializeCount, (AbstractUnseekableKeys)superset, supersetCount, out); break; case Range: - serializeLargeSubset((Ranges)serialize, serializeCount, (Ranges)superset, supersetCount, out); + serializeLargeSubset((AbstractRanges)serialize, serializeCount, (AbstractRanges)superset, supersetCount, out); break; } } } - public long serializedSubsetSize(Seekables serialize, Seekables superset) + public long serializedSubsetSize(Routables serialize, Routables superset) { int columnCount = serialize.size(); int supersetCount = superset.size(); @@ -185,9 +187,9 @@ public interface IVersionedWithKeysSerializer, T> extends { default: throw new AssertionError("Unhandled domain: " + serialize.domain()); case Key: - return TypeSizes.sizeofUnsignedVInt(encodeBitmap((Keys)serialize, (Keys)superset, supersetCount)); + return TypeSizes.sizeofUnsignedVInt(encodeBitmap((AbstractUnseekableKeys)serialize, (AbstractUnseekableKeys)superset, supersetCount)); case Range: - return TypeSizes.sizeofUnsignedVInt(encodeBitmap((Ranges)serialize, (Ranges)superset, supersetCount)); + return TypeSizes.sizeofUnsignedVInt(encodeBitmap((AbstractRanges)serialize, (AbstractRanges)superset, supersetCount)); } } else @@ -196,14 +198,14 @@ public interface IVersionedWithKeysSerializer, T> extends { default: throw new AssertionError("Unhandled domain: " + serialize.domain()); case Key: - return serializeLargeSubsetSize((Keys)serialize, columnCount, (Keys)superset, supersetCount); + return serializeLargeSubsetSize((AbstractUnseekableKeys)serialize, columnCount, (AbstractUnseekableKeys)superset, supersetCount); case Range: - return serializeLargeSubsetSize((Ranges)serialize, columnCount, (Ranges)superset, supersetCount); + return serializeLargeSubsetSize((AbstractRanges)serialize, columnCount, (AbstractRanges)superset, supersetCount); } } } - public Seekables deserializeSubset(Seekables superset, DataInputPlus in) throws IOException + public Unseekables deserializeSubset(Unseekables superset, DataInputPlus in) throws IOException { long encoded = in.readUnsignedVInt(); int supersetCount = superset.size(); @@ -224,8 +226,8 @@ public interface IVersionedWithKeysSerializer, T> extends default: throw new AssertionError("Unhandled domain: " + superset.domain()); case Key: { - Keys keys = (Keys)superset; - Key[] out = new Key[deserializeCount]; + AbstractUnseekableKeys keys = (AbstractUnseekableKeys) superset; + RoutingKey[] out = new RoutingKey[deserializeCount]; int count = 0; while (encoded != 0) { @@ -233,11 +235,11 @@ public interface IVersionedWithKeysSerializer, T> extends out[count++] = keys.get(Long.numberOfTrailingZeros(lowestBit)); encoded ^= lowestBit; } - return Keys.ofSortedUnique(out); + return RoutingKeys.ofSortedUnique(out); } case Range: { - Ranges ranges = (Ranges)superset; + AbstractRanges ranges = (AbstractRanges)superset; Range[] out = new Range[deserializeCount]; int count = 0; while (encoded != 0) @@ -264,7 +266,7 @@ public interface IVersionedWithKeysSerializer, T> extends return bitmap; } - private static long encodeBitmap(Ranges serialize, Ranges superset, int supersetCount) + private static long encodeBitmap(AbstractRanges serialize, AbstractRanges superset, int supersetCount) { // the index we would encounter next if all columns are present long bitmap = superset.foldl(serialize, (k, p1, v, i) -> { @@ -299,7 +301,7 @@ public interface IVersionedWithKeysSerializer, T> extends } @DontInline - private void serializeLargeSubset(Ranges serialize, int serializeCount, Ranges superset, int supersetCount, DataOutputPlus out) throws IOException + private void serializeLargeSubset(AbstractRanges serialize, int serializeCount, AbstractRanges superset, int supersetCount, DataOutputPlus out) throws IOException { out.writeUnsignedVInt32(supersetCount - serializeCount); int serializeIndex = 0, supersetIndex = 0; @@ -323,7 +325,7 @@ public interface IVersionedWithKeysSerializer, T> extends } @DontInline - private Seekables deserializeLargeSubset(DataInputPlus in, Seekables superset, int supersetCount, int delta) throws IOException + private Unseekables deserializeLargeSubset(DataInputPlus in, Unseekables superset, int supersetCount, int delta) throws IOException { int deserializeCount = supersetCount - delta; switch (superset.domain()) @@ -331,8 +333,8 @@ public interface IVersionedWithKeysSerializer, T> extends default: throw new AssertionError("Unhandled domain: " + superset.domain()); case Key: { - Keys keys = (Keys)superset; - Key[] out = new Key[deserializeCount]; + RoutingKeys keys = (RoutingKeys) superset; + RoutingKey[] out = new RoutingKey[deserializeCount]; int supersetIndex = 0; int count = 0; while (count < deserializeCount) @@ -341,11 +343,11 @@ public interface IVersionedWithKeysSerializer, T> extends while (takeCount-- > 0) out[count++] = keys.get(supersetIndex++); supersetIndex += in.readUnsignedVInt32(); } - return Keys.ofSortedUnique(out); + return RoutingKeys.ofSortedUnique(out); } case Range: { - Ranges ranges = (Ranges)superset; + AbstractRanges ranges = (AbstractRanges)superset; Range[] out = new Range[deserializeCount]; int supersetIndex = 0; int count = 0; @@ -386,7 +388,7 @@ public interface IVersionedWithKeysSerializer, T> extends } @DontInline - private long serializeLargeSubsetSize(Ranges serialize, int serializeCount, Ranges superset, int supersetCount) + private long serializeLargeSubsetSize(AbstractRanges serialize, int serializeCount, AbstractRanges superset, int supersetCount) { long size = TypeSizes.sizeofUnsignedVInt(supersetCount - serializeCount); int serializeIndex = 0, supersetIndex = 0; diff --git a/src/java/org/apache/cassandra/service/accord/serializers/InformDurableSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/InformDurableSerializers.java index d23e6c99b9..59c2d461b6 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/InformDurableSerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/InformDurableSerializers.java @@ -20,9 +20,9 @@ package org.apache.cassandra.service.accord.serializers; import java.io.IOException; -import accord.local.Status; import accord.messages.InformDurable; import accord.primitives.Route; +import accord.primitives.Status; import accord.primitives.Timestamp; import accord.primitives.TxnId; import org.apache.cassandra.io.IVersionedSerializer; diff --git a/src/java/org/apache/cassandra/service/accord/serializers/KeySerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/KeySerializers.java index 1e105d8192..f2f9f8ac1a 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/KeySerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/KeySerializers.java @@ -203,6 +203,8 @@ public class KeySerializers EnumSet.allOf(UnseekablesKind.class) ); + public static final IVersionedSerializer> nullableParticipants = NullableSerializer.wrap(participants); + static class AbstractRoutablesSerializer> implements IVersionedSerializer { final EnumSet permitted; @@ -337,6 +339,7 @@ public class KeySerializers }; public static final IVersionedSerializer> nullableSeekables = NullableSerializer.wrap(seekables); + public static final IVersionedSerializer> nullableUnseekables = NullableSerializer.wrap(unseekables); public static abstract class AbstractKeysSerializer> implements IVersionedSerializer { diff --git a/src/java/org/apache/cassandra/service/accord/serializers/PreacceptSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/PreacceptSerializers.java index 89f4abdec1..a030ba63b4 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/PreacceptSerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/PreacceptSerializers.java @@ -42,14 +42,14 @@ public class PreacceptSerializers { private PreacceptSerializers() {} - public static final IVersionedSerializer request = new WithUnsyncedSerializer() + public static final IVersionedSerializer request = new WithUnsyncedSerializer<>() { @Override public void serializeBody(PreAccept msg, DataOutputPlus out, int version) throws IOException { CommandSerializers.partialTxn.serialize(msg.partialTxn, out, version); serializeNullable(msg.route, out, version, KeySerializers.fullRoute); - out.writeUnsignedVInt(msg.maxEpoch - msg.minEpoch); + out.writeUnsignedVInt(msg.acceptEpoch - msg.minEpoch); } @Override @@ -57,9 +57,9 @@ public class PreacceptSerializers { PartialTxn partialTxn = CommandSerializers.partialTxn.deserialize(in, version); @Nullable FullRoute fullRoute = deserializeNullable(in, version, KeySerializers.fullRoute); - long maxEpoch = in.readUnsignedVInt() + minEpoch; + long acceptEpoch = in.readUnsignedVInt() + minEpoch; return PreAccept.SerializerSupport.create(txnId, scope, waitForEpoch, minEpoch, - maxEpoch, partialTxn, fullRoute); + acceptEpoch, partialTxn, fullRoute); } @Override @@ -67,11 +67,11 @@ public class PreacceptSerializers { return CommandSerializers.partialTxn.serializedSize(msg.partialTxn, version) + serializedNullableSize(msg.route, version, KeySerializers.fullRoute) - + TypeSizes.sizeofUnsignedVInt(msg.maxEpoch - msg.minEpoch); + + TypeSizes.sizeofUnsignedVInt(msg.acceptEpoch - msg.minEpoch); } }; - public static final IVersionedSerializer reply = new IVersionedSerializer() + public static final IVersionedSerializer reply = new IVersionedSerializer<>() { @Override public void serialize(PreAcceptReply reply, DataOutputPlus out, int version) throws IOException @@ -83,7 +83,7 @@ public class PreacceptSerializers PreAcceptOk preAcceptOk = (PreAcceptOk) reply; CommandSerializers.txnId.serialize(preAcceptOk.txnId, out, version); CommandSerializers.timestamp.serialize(preAcceptOk.witnessedAt, out, version); - DepsSerializer.partialDeps.serialize(preAcceptOk.deps, out, version); + DepsSerializer.deps.serialize(preAcceptOk.deps, out, version); } @Override @@ -94,7 +94,7 @@ public class PreacceptSerializers return new PreAcceptOk(CommandSerializers.txnId.deserialize(in, version), CommandSerializers.timestamp.deserialize(in, version), - DepsSerializer.partialDeps.deserialize(in, version)); + DepsSerializer.deps.deserialize(in, version)); } @Override @@ -107,7 +107,7 @@ public class PreacceptSerializers PreAcceptOk preAcceptOk = (PreAcceptOk) reply; size += CommandSerializers.txnId.serializedSize(preAcceptOk.txnId, version); size += CommandSerializers.timestamp.serializedSize(preAcceptOk.witnessedAt, version); - size += DepsSerializer.partialDeps.serializedSize(preAcceptOk.deps, version); + size += DepsSerializer.deps.serializedSize(preAcceptOk.deps, version); return size; } diff --git a/src/java/org/apache/cassandra/service/accord/serializers/ReadDataSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/ReadDataSerializers.java index 00728a68f9..6ef51b957d 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/ReadDataSerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/ReadDataSerializers.java @@ -87,7 +87,6 @@ public class ReadDataSerializers DepsSerializer.partialDeps.serialize(msg.deps, out, version); CommandSerializers.writes.serialize(msg.writes, out, version); TxnResult.serializer.serialize((TxnResult) msg.result, out, version); - KeySerializers.nullableSeekables.serialize(msg.notify, out, version); } @Override @@ -101,8 +100,7 @@ public class ReadDataSerializers CommandSerializers.partialTxn.deserialize(in, version), DepsSerializer.partialDeps.deserialize(in, version), CommandSerializers.writes.deserialize(in, version), - TxnResult.serializer.deserialize(in, version), - KeySerializers.nullableSeekables.deserialize(in, version)); + TxnResult.serializer.deserialize(in, version)); } @Override @@ -115,8 +113,7 @@ public class ReadDataSerializers + CommandSerializers.partialTxn.serializedSize(msg.txn, version) + DepsSerializer.partialDeps.serializedSize(msg.deps, version) + CommandSerializers.writes.serializedSize(msg.writes, version) - + TxnResult.serializer.serializedSize((TxnData)msg.result, version) - + KeySerializers.nullableSeekables.serializedSize(msg.notify, version); + + TxnResult.serializer.serializedSize((TxnData)msg.result, version); } } diff --git a/src/java/org/apache/cassandra/service/accord/serializers/RecoverySerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/RecoverySerializers.java index 5caab8fb2b..05f7d42c90 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/RecoverySerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/RecoverySerializers.java @@ -24,7 +24,6 @@ import javax.annotation.Nullable; import accord.api.Result; import accord.api.RoutingKey; -import accord.local.Status; import accord.messages.BeginRecovery; import accord.messages.BeginRecovery.RecoverNack; import accord.messages.BeginRecovery.RecoverOk; @@ -32,9 +31,11 @@ import accord.messages.BeginRecovery.RecoverReply; import accord.primitives.Ballot; import accord.primitives.Deps; import accord.primitives.FullRoute; +import accord.primitives.Known.KnownDeps; import accord.primitives.LatestDeps; import accord.primitives.PartialTxn; import accord.primitives.Route; +import accord.primitives.Status; import accord.primitives.Timestamp; import accord.primitives.TxnId; import accord.primitives.Writes; @@ -42,6 +43,7 @@ import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.io.IVersionedSerializer; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; +import org.apache.cassandra.service.accord.serializers.TxnRequestSerializer.WithUnsyncedSerializer; import static org.apache.cassandra.utils.NullableSerializer.deserializeNullable; import static org.apache.cassandra.utils.NullableSerializer.serializeNullable; @@ -49,7 +51,7 @@ import static org.apache.cassandra.utils.NullableSerializer.serializedNullableSi public class RecoverySerializers { - public static final IVersionedSerializer request = new TxnRequestSerializer() + public static final IVersionedSerializer request = new WithUnsyncedSerializer() { @Override public void serializeBody(BeginRecovery recover, DataOutputPlus out, int version) throws IOException @@ -57,15 +59,17 @@ public class RecoverySerializers CommandSerializers.partialTxn.serialize(recover.partialTxn, out, version); CommandSerializers.ballot.serialize(recover.ballot, out, version); serializeNullable(recover.route, out, version, KeySerializers.fullRoute); + out.writeUnsignedVInt(recover.executeAtOrTxnIdEpoch - recover.txnId.epoch()); } @Override - public BeginRecovery deserializeBody(DataInputPlus in, int version, TxnId txnId, Route scope, long waitForEpoch) throws IOException + public BeginRecovery deserializeBody(DataInputPlus in, int version, TxnId txnId, Route scope, long waitForEpoch, long minEpoch) throws IOException { PartialTxn partialTxn = CommandSerializers.partialTxn.deserialize(in, version); Ballot ballot = CommandSerializers.ballot.deserialize(in, version); @Nullable FullRoute route = deserializeNullable(in, version, KeySerializers.fullRoute); - return BeginRecovery.SerializationSupport.create(txnId, scope, waitForEpoch, partialTxn, ballot, route); + long executeAtOrTxnIdEpoch = in.readUnsignedVInt32() + txnId.epoch(); + return BeginRecovery.SerializationSupport.create(txnId, scope, waitForEpoch, minEpoch, partialTxn, ballot, route, executeAtOrTxnIdEpoch); } @Override @@ -73,7 +77,8 @@ public class RecoverySerializers { return CommandSerializers.partialTxn.serializedSize(recover.partialTxn, version) + CommandSerializers.ballot.serializedSize(recover.ballot, version) - + serializedNullableSize(recover.route, version, KeySerializers.fullRoute); + + serializedNullableSize(recover.route, version, KeySerializers.fullRoute) + + TypeSizes.sizeofUnsignedVInt(recover.executeAtOrTxnIdEpoch - recover.txnId.epoch()); } }; @@ -93,6 +98,7 @@ public class RecoverySerializers latestDeps.serialize(recoverOk.deps, out, version); DepsSerializer.deps.serialize(recoverOk.earlierCommittedWitness, out, version); DepsSerializer.deps.serialize(recoverOk.earlierAcceptedNoWitness, out, version); + out.writeBoolean(recoverOk.acceptsFastPath); out.writeBoolean(recoverOk.rejectsFastPath); CommandSerializers.nullableWrites.serialize(recoverOk.writes, out, version); } @@ -112,9 +118,9 @@ public class RecoverySerializers return new RecoverNack(supersededBy); } - RecoverOk deserializeOk(TxnId txnId, Status status, Ballot accepted, Timestamp executeAt, @Nonnull LatestDeps deps, Deps earlierCommittedWitness, Deps earlierAcceptedNoWitness, boolean rejectsFastPath, Writes writes, Result result, DataInputPlus in, int version) + RecoverOk deserializeOk(TxnId txnId, Status status, Ballot accepted, Timestamp executeAt, @Nonnull LatestDeps deps, Deps earlierCommittedWitness, Deps earlierAcceptedNoWitness, boolean acceptsFastPath, boolean rejectsFastPath, Writes writes, Result result, DataInputPlus in, int version) { - return new RecoverOk(txnId, status, accepted, executeAt, deps, earlierCommittedWitness, earlierAcceptedNoWitness, rejectsFastPath, writes, result); + return new RecoverOk(txnId, status, accepted, executeAt, deps, earlierCommittedWitness, earlierAcceptedNoWitness, acceptsFastPath, rejectsFastPath, writes, result); } @Override @@ -139,6 +145,7 @@ public class RecoverySerializers DepsSerializer.deps.deserialize(in, version), DepsSerializer.deps.deserialize(in, version), in.readBoolean(), + in.readBoolean(), CommandSerializers.nullableWrites.deserialize(in, version), result, in, @@ -159,6 +166,7 @@ public class RecoverySerializers size += latestDeps.serializedSize(recoverOk.deps, version); size += DepsSerializer.deps.serializedSize(recoverOk.earlierCommittedWitness, version); size += DepsSerializer.deps.serializedSize(recoverOk.earlierAcceptedNoWitness, version); + size += TypeSizes.sizeof(recoverOk.acceptsFastPath); size += TypeSizes.sizeof(recoverOk.rejectsFastPath); size += CommandSerializers.nullableWrites.serializedSize(recoverOk.writes, version); return size; @@ -207,7 +215,7 @@ public class RecoverySerializers for (int i = 0 ; i < size ; ++i) { starts[i] = KeySerializers.routingKey.deserialize(in, version); - Status.KnownDeps knownDeps = CommandSerializers.nullableKnownDeps.deserialize(in, version); + KnownDeps knownDeps = CommandSerializers.nullableKnownDeps.deserialize(in, version); if (knownDeps == null) continue; diff --git a/src/java/org/apache/cassandra/service/accord/serializers/SetDurableSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/SetDurableSerializers.java index f42ff86870..c1cd9b7eb7 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/SetDurableSerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/SetDurableSerializers.java @@ -19,11 +19,10 @@ package org.apache.cassandra.service.accord.serializers; import java.io.IOException; -import accord.api.RoutingKey; import accord.messages.SetGloballyDurable; import accord.messages.SetShardDurable; import accord.primitives.Deps; -import accord.primitives.Seekables; +import accord.primitives.FullRoute; import accord.primitives.SyncPoint; import accord.primitives.TxnId; import org.apache.cassandra.io.IVersionedSerializer; @@ -58,21 +57,19 @@ public class SetDurableSerializers @Override public void serialize(SetGloballyDurable msg, DataOutputPlus out, int version) throws IOException { - CommandSerializers.txnId.serialize(msg.txnId, out, version); CommandStoreSerializers.durableBefore.serialize(msg.durableBefore, out, version); } @Override public SetGloballyDurable deserialize(DataInputPlus in, int version) throws IOException { - return new SetGloballyDurable(CommandSerializers.txnId.deserialize(in, version), CommandStoreSerializers.durableBefore.deserialize(in, version)); + return new SetGloballyDurable(CommandStoreSerializers.durableBefore.deserialize(in, version)); } @Override public long serializedSize(SetGloballyDurable msg, int version) { - return CommandSerializers.txnId.serializedSize(msg.txnId, version) - + CommandStoreSerializers.durableBefore.serializedSize(msg.durableBefore, version); + return CommandStoreSerializers.durableBefore.serializedSize(msg.durableBefore, version); } }; @@ -83,8 +80,7 @@ public class SetDurableSerializers { CommandSerializers.txnId.serialize(sp.syncId, out, version); DepsSerializer.deps.serialize(sp.waitFor, out, version); - KeySerializers.seekables.serialize(sp.keysOrRanges, out, version); - KeySerializers.routingKey.serialize(sp.homeKey, out, version); + KeySerializers.fullRoute.serialize(sp.route, out, version); } @Override @@ -92,9 +88,8 @@ public class SetDurableSerializers { TxnId syncId = CommandSerializers.txnId.deserialize(in, version); Deps waitFor = DepsSerializer.deps.deserialize(in, version); - Seekables keysOrRanges = KeySerializers.seekables.deserialize(in, version); - RoutingKey homeKey = KeySerializers.routingKey.deserialize(in, version); - return SyncPoint.SerializationSupport.construct(syncId, waitFor, keysOrRanges, homeKey); + FullRoute route = KeySerializers.fullRoute.deserialize(in, version); + return SyncPoint.SerializationSupport.construct(syncId, waitFor, route); } @Override @@ -102,8 +97,7 @@ public class SetDurableSerializers { return CommandSerializers.txnId.serializedSize(sp.syncId, version) + DepsSerializer.deps.serializedSize(sp.waitFor, version) - + KeySerializers.seekables.serializedSize(sp.keysOrRanges, version) - + KeySerializers.routingKey.serializedSize(sp.homeKey, version); + + KeySerializers.fullRoute.serializedSize(sp.route, version); } }; } diff --git a/src/java/org/apache/cassandra/service/accord/serializers/TopologySerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/TopologySerializers.java index 73708c125f..4d5fbad524 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/TopologySerializers.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/TopologySerializers.java @@ -152,7 +152,7 @@ public class TopologySerializers } }; - public static final IVersionedSerializer topology = new IVersionedSerializer() + public static final IVersionedSerializer topology = new IVersionedSerializer<>() { @Override public void serialize(Topology topology, DataOutputPlus out, int version) throws IOException @@ -167,7 +167,7 @@ public class TopologySerializers { long epoch = in.readLong(); Shard[] shards = ArraySerializers.deserializeArray(in, version, shard, Shard[]::new); - Set staleIds = CollectionSerializers.deserializeSet(in, version, TopologySerializers.nodeId); + SortedArrayList staleIds = CollectionSerializers.deserializeSortedArrayList(in, version, TopologySerializers.nodeId, Node.Id[]::new); return new Topology(epoch, staleIds, shards); } diff --git a/src/java/org/apache/cassandra/service/accord/serializers/WaitingOnSerializer.java b/src/java/org/apache/cassandra/service/accord/serializers/WaitingOnSerializer.java index fab09f2354..021f01ec75 100644 --- a/src/java/org/apache/cassandra/service/accord/serializers/WaitingOnSerializer.java +++ b/src/java/org/apache/cassandra/service/accord/serializers/WaitingOnSerializer.java @@ -23,9 +23,9 @@ import java.nio.ByteBuffer; import accord.local.Command.WaitingOn; import accord.primitives.KeyDeps; -import accord.primitives.Keys; import accord.primitives.RangeDeps; import accord.primitives.Routable; +import accord.primitives.RoutingKeys; import accord.primitives.TxnId; import accord.utils.ImmutableBitSet; import accord.utils.Invariants; @@ -84,7 +84,7 @@ public class WaitingOnSerializer out.putLong(bits[i]); } - public static WaitingOn deserialize(TxnId txnId, Keys keys, RangeDeps directRangeDeps, KeyDeps directKeyDeps, ByteBuffer in) throws IOException + public static WaitingOn deserialize(TxnId txnId, RoutingKeys keys, RangeDeps directRangeDeps, KeyDeps directKeyDeps, ByteBuffer in) throws IOException { int txnIdCount = directRangeDeps.txnIdCount() + directKeyDeps.txnIdCount(); int waitingOnLength = (txnIdCount + keys.size() + 63) / 64; 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 f7081927eb..cc005bf6e1 100644 --- a/src/java/org/apache/cassandra/service/accord/txn/TxnWrite.java +++ b/src/java/org/apache/cassandra/service/accord/txn/TxnWrite.java @@ -45,6 +45,7 @@ import accord.primitives.PartialTxn; import accord.primitives.RoutableKey; import accord.primitives.Seekable; import accord.primitives.Timestamp; +import accord.primitives.TxnId; import accord.primitives.Writes; import accord.utils.async.AsyncChain; import accord.utils.async.AsyncChains; @@ -144,7 +145,7 @@ public class TxnWrite extends AbstractKeySorted implements Writ 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); + return AsyncChains.ofRunnable(Stage.MUTATION.executor(), mutation::applyUnsafe); } @Override @@ -373,12 +374,12 @@ public class TxnWrite extends AbstractKeySorted implements Writ } @Override - public AsyncChain apply(Seekable key, SafeCommandStore safeStore, Timestamp executeAt, DataStore store, PartialTxn txn) + public AsyncChain apply(Seekable key, SafeCommandStore safeStore, TxnId txnId, Timestamp executeAt, DataStore store, PartialTxn txn) { // TODO (expected, efficiency): 99.9999% of the time we can just use executeAt.hlc(), so can avoid bringing // cfk into memory by retaining at all times in memory key ranges that are dirty and must use this logic; // any that aren't can just use executeAt.hlc - TimestampsForKey cfk = TimestampsForKeys.updateLastExecutionTimestamps((AbstractSafeCommandStore) safeStore, (Key) key, executeAt, true); + TimestampsForKey cfk = TimestampsForKeys.updateLastExecutionTimestamps((AbstractSafeCommandStore) safeStore, ((Key) key).toUnseekable(), txnId, executeAt, true); long timestamp = AccordSafeTimestampsForKey.timestampMicrosFor(cfk, executeAt, true); // TODO (low priority - do we need to compute nowInSeconds, or can we just use executeAt?) int nowInSeconds = AccordSafeTimestampsForKey.nowInSecondsFor(cfk, executeAt, true); diff --git a/src/java/org/apache/cassandra/tcm/membership/Directory.java b/src/java/org/apache/cassandra/tcm/membership/Directory.java index a4dab0bc21..e2ba275cda 100644 --- a/src/java/org/apache/cassandra/tcm/membership/Directory.java +++ b/src/java/org/apache/cassandra/tcm/membership/Directory.java @@ -27,7 +27,6 @@ import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; @@ -360,9 +359,9 @@ public class Directory implements MetadataValue return ImmutableList.copyOf(peers.values()); } - public ImmutableSet peerIds() + public NavigableSet peerIds() { - return ImmutableSet.copyOf(peers.keySet()); + return peers.keySet(); } public NodeAddresses getNodeAddresses(NodeId id) diff --git a/src/java/org/apache/cassandra/utils/FBUtilities.java b/src/java/org/apache/cassandra/utils/FBUtilities.java index 7417f757bc..a4493f30a2 100644 --- a/src/java/org/apache/cassandra/utils/FBUtilities.java +++ b/src/java/org/apache/cassandra/utils/FBUtilities.java @@ -65,11 +65,13 @@ import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; -import com.vdurmont.semver4j.Semver; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import accord.utils.async.AsyncResult; +import accord.utils.async.AsyncResults; +import com.vdurmont.semver4j.Semver; import org.apache.cassandra.audit.IAuditLogger; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.DecoratedKey; @@ -1458,4 +1460,16 @@ public class FBUtilities if (rc == 0) return Order.EQ; return Order.GT; } + + public static AsyncResult futureToAsyncResult(org.apache.cassandra.utils.concurrent.Future future) + { + AsyncResult.Settable adapter = AsyncResults.settable(); + future.addCallback((value, failure) -> { + if (failure != null) + adapter.tryFailure(failure); + else + adapter.trySuccess(value); + }); + return adapter; + } } \ No newline at end of file diff --git a/src/java/org/apache/cassandra/utils/btree/AbstractBTreeMap.java b/src/java/org/apache/cassandra/utils/btree/AbstractBTreeMap.java index 3d0baf08f1..3abef3bca0 100644 --- a/src/java/org/apache/cassandra/utils/btree/AbstractBTreeMap.java +++ b/src/java/org/apache/cassandra/utils/btree/AbstractBTreeMap.java @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.Map; +import java.util.NavigableSet; import java.util.Set; import com.google.common.collect.Iterators; @@ -98,9 +99,9 @@ public abstract class AbstractBTreeMap extends AbstractMap return null; } - private Set keySet = null; + private NavigableSet keySet = null; @Override - public Set keySet() + public NavigableSet keySet() { if (keySet == null) keySet = BTreeSet.wrap(BTree.transformAndFilter(tree, (entry) -> ((Map.Entry)entry).getKey()), comparator.keyComparator); diff --git a/src/java/org/apache/cassandra/utils/vint/VIntCoding.java b/src/java/org/apache/cassandra/utils/vint/VIntCoding.java index dc873f0210..9a0e2c5cb7 100644 --- a/src/java/org/apache/cassandra/utils/vint/VIntCoding.java +++ b/src/java/org/apache/cassandra/utils/vint/VIntCoding.java @@ -331,6 +331,11 @@ public class VIntCoding return decodeZigZag64(readUnsignedVInt(input)); } + public static long readVInt(ByteBuffer input) + { + return decodeZigZag64(readUnsignedVInt(input)); + } + /** * Read up to a signed 32-bit integer back. * diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordBootstrapTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordBootstrapTest.java index 14ae7d1cde..483f7e4f37 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordBootstrapTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordBootstrapTest.java @@ -453,7 +453,7 @@ public class AccordBootstrapTest extends TestBaseImpl PartitionKey partitionKey = new PartitionKey(tableId, dk); - awaitUninterruptiblyAndRethrow(service().node().commandStores().forEach(PreLoadContext.contextFor(partitionKey), + awaitUninterruptiblyAndRethrow(service().node().commandStores().forEach(PreLoadContext.contextFor(partitionKey.toUnseekable()), partitionKey.toUnseekable(), moveMax, moveMax, safeStore -> { if (!safeStore.ranges().allAt(preMove).contains(partitionKey)) diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordDropTableBase.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordDropTableBase.java index 732fe303cb..09a445b30a 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordDropTableBase.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordDropTableBase.java @@ -22,7 +22,7 @@ import java.util.UUID; import com.google.common.base.Throwables; -import accord.api.Key; +import accord.api.RoutingKey; import accord.local.CommandStores; import accord.local.KeyHistory; import accord.local.PreLoadContext; @@ -45,8 +45,6 @@ import org.apache.cassandra.service.accord.AccordService; import org.apache.cassandra.service.accord.TokenRange; import org.assertj.core.api.Assertions; -import static org.apache.cassandra.service.accord.AccordTestUtils.wrapInTxn; - public class AccordDropTableBase extends TestBaseImpl { protected static void addChaos(Cluster cluster, int example) @@ -137,7 +135,7 @@ public class AccordDropTableBase extends TestBaseImpl AccordCommandStore store = (AccordCommandStore) stores.forId(storeId); AsyncChains.getUnchecked(store.submit(ctx, input -> { AccordSafeCommandStore safe = (AccordSafeCommandStore) input; - for (Key key : safe.commandsForKeysKeys()) + for (RoutingKey key : safe.commandsForKeysKeys()) { AccordSafeCommandsForKey safeCFK = safe.maybeCommandsForKey(key); if (safeCFK == null) // we read and found a key, but its null at load time... so ignore it 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 e30706a47e..c563650d72 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordIncrementalRepairTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordIncrementalRepairTest.java @@ -40,9 +40,11 @@ import accord.impl.progresslog.DefaultProgressLogs; import accord.local.Node; import accord.local.PreLoadContext; import accord.local.SafeCommand; -import accord.local.Status; +import accord.local.StoreParticipants; import accord.local.cfk.CommandsForKey; +import accord.local.cfk.SafeCommandsForKey; import accord.primitives.Seekables; +import accord.primitives.Status; import accord.primitives.Timestamp; import accord.primitives.TxnId; import accord.utils.async.AsyncChains; @@ -61,13 +63,14 @@ import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.accord.AccordSafeCommandStore; import org.apache.cassandra.service.accord.AccordService; import org.apache.cassandra.service.accord.api.AccordAgent; -import org.apache.cassandra.service.accord.api.PartitionKey; +import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; import org.apache.cassandra.service.consensus.TransactionalMode; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.Clock; import org.apache.cassandra.utils.concurrent.Future; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; +import static accord.local.KeyHistory.COMMANDS; import static java.lang.String.format; public class AccordIncrementalRepairTest extends AccordTestBase @@ -100,9 +103,9 @@ public class AccordIncrementalRepairTest extends AccordTestBase private final List barriers = new ArrayList<>(); @Override - public void onLocalBarrier(@Nonnull Seekables keysOrRanges, @Nonnull TxnId txnId) + public void onSuccessfulBarrier(@Nonnull TxnId txnId, @Nonnull Seekables keysOrRanges) { - super.onLocalBarrier(keysOrRanges, txnId); + super.onSuccessfulBarrier(txnId, keysOrRanges); synchronized (barriers) { barriers.add(new ExecutedBarrier(keysOrRanges, txnId)); @@ -215,19 +218,27 @@ public class AccordIncrementalRepairTest extends AccordTestBase return getUninterruptibly(future, 1, TimeUnit.MINUTES); } - private static TxnId awaitLocalApplyOnKey(PartitionKey key) + private static TxnId awaitLocalApplyOnKey(TableMetadata metadata, int k) + { + return awaitLocalApplyOnKey(new TokenKey(metadata.id, metadata.partitioner.decorateKey(ByteBufferUtil.bytes(k)).getToken())); + } + + private static TxnId awaitLocalApplyOnKey(TokenKey key) { Node node = accordService().node(); AtomicReference waitFor = new AtomicReference<>(null); - AsyncChains.awaitUninterruptibly(node.commandStores().ifLocal(PreLoadContext.contextFor(key), key.toUnseekable(), 0, Long.MAX_VALUE, safeStore -> { + AsyncChains.awaitUninterruptibly(node.commandStores().ifLocal(PreLoadContext.contextFor(key, COMMANDS), key.toUnseekable(), 0, Long.MAX_VALUE, safeStore -> { AccordSafeCommandStore store = (AccordSafeCommandStore) safeStore; - CommandsForKey commands = store.maybeCommandsForKey(key).current(); - int size = commands.size(); + SafeCommandsForKey safeCfk = store.maybeCommandsForKey(key); + if (safeCfk == null) + return; + CommandsForKey cfk = safeCfk.current(); + int size = cfk.size(); if (size < 1) return; // if txnId is an instance of CommandsForKey.TxnInfo, copying it into a // new txnId instance will prevent any issues related to TxnInfo#hashCode - waitFor.set(new TxnId(commands.txnId(size - 1))); + waitFor.set(new TxnId(cfk.txnId(size - 1))); })); Assert.assertNotNull(waitFor.get()); TxnId txnId = waitFor.get(); @@ -239,7 +250,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase if (now - start > TimeUnit.MINUTES.toMillis(1)) throw new AssertionError("Timeout"); AsyncChains.awaitUninterruptibly(node.commandStores().ifLocal(PreLoadContext.contextFor(txnId), key.toUnseekable(), 0, Long.MAX_VALUE, safeStore -> { - SafeCommand command = safeStore.get(txnId, key.toUnseekable()); + SafeCommand command = safeStore.get(txnId, StoreParticipants.empty(txnId)); Assert.assertNotNull(command.current()); if (command.current().status().hasBeen(Status.Applied)) applied.set(true); @@ -267,7 +278,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase SHARED_CLUSTER.get(1, 2).forEach(instance -> instance.runOnInstance(() -> { TableMetadata metadata = Schema.instance.getTableMetadata(keyspace, table); - awaitLocalApplyOnKey(new PartitionKey(metadata.id, metadata.partitioner.decorateKey(ByteBufferUtil.bytes(1)))); + awaitLocalApplyOnKey(metadata, 1); })); SHARED_CLUSTER.forEach(instance -> instance.runOnInstance(() -> agent().reset())); @@ -299,14 +310,12 @@ public class AccordIncrementalRepairTest extends AccordTestBase awaitEndpointUp(SHARED_CLUSTER.get(1), SHARED_CLUSTER.get(3)); 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); - Assert.assertFalse(cfs.getLiveSSTables().isEmpty()); - cfs.getLiveSSTables().forEach(sstable -> { - Assert.assertTrue(sstable.isRepaired() || sstable.isPendingRepair()); - }); + SHARED_CLUSTER.get(1).runOnInstance(() -> { + Assert.assertFalse( agent().executedBarriers().isEmpty()); + ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table); + Assert.assertFalse(cfs.getLiveSSTables().isEmpty()); + cfs.getLiveSSTables().forEach(sstable -> { + Assert.assertTrue(sstable.isRepaired() || sstable.isPendingRepair()); }); }); } @@ -351,7 +360,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase })); nodetool(SHARED_CLUSTER.get(1), "repair", KEYSPACE); - SHARED_CLUSTER.forEach(instance -> instance.runOnInstance(() -> { + SHARED_CLUSTER.get(1).runOnInstance(() -> { Assert.assertFalse( agent().executedBarriers().isEmpty()); ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table); Assert.assertFalse(cfs.getLiveSSTables().isEmpty()); @@ -364,7 +373,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase UntypedResultSet.Row row = Iterables.getOnlyElement(result); Assert.assertEquals(1, row.getInt("k")); Assert.assertEquals(2, row.getInt("v")); - })); + }); } /** @@ -402,7 +411,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase SHARED_CLUSTER.get(1, 2).forEach(instance -> instance.runOnInstance(() -> { TableMetadata metadata = Schema.instance.getTableMetadata(keyspace, table); - awaitLocalApplyOnKey(new PartitionKey(metadata.id, metadata.partitioner.decorateKey(ByteBufferUtil.bytes(1)))); + awaitLocalApplyOnKey(metadata, 1); })); SHARED_CLUSTER.forEach(instance -> instance.runOnInstance(() -> agent().reset())); @@ -411,11 +420,8 @@ public class AccordIncrementalRepairTest extends AccordTestBase awaitEndpointUp(SHARED_CLUSTER.get(1), SHARED_CLUSTER.get(3)); nodetool(SHARED_CLUSTER.get(1), "repair", "--accord-only", KEYSPACE); - SHARED_CLUSTER.forEach(instance -> { - logger().info("checking instance {}", instance.broadcastAddress()); - instance.runOnInstance(() -> { - Assert.assertFalse( agent().executedBarriers().isEmpty()); - }); + SHARED_CLUSTER.get(1).runOnInstance(() -> { + Assert.assertFalse( agent().executedBarriers().isEmpty()); }); } } diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordJournalIntegrationTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordJournalIntegrationTest.java index 80d48b6091..66d677080e 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordJournalIntegrationTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordJournalIntegrationTest.java @@ -19,16 +19,20 @@ package org.apache.cassandra.distributed.test.accord; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Assert; import org.junit.Test; import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.api.ConsistencyLevel; +import org.apache.cassandra.distributed.shared.ClusterUtils; import org.apache.cassandra.distributed.shared.WithProperties; import org.apache.cassandra.distributed.test.TestBaseImpl; +import org.apache.cassandra.service.accord.AccordService; import org.apache.cassandra.utils.concurrent.CountDownLatch; public class AccordJournalIntegrationTest extends TestBaseImpl @@ -36,14 +40,9 @@ public class AccordJournalIntegrationTest extends TestBaseImpl @Test public void saveLoadSanityCheck() throws Throwable { - String timeout = "10s"; try (WithProperties wp = new WithProperties().set(CassandraRelevantProperties.DTEST_ACCORD_JOURNAL_SANITY_CHECK_ENABLED, "true"); Cluster cluster = init(Cluster.build(1) .withoutVNodes() - .withConfig(c -> c - .set("read_request_timeout", timeout) - .set("transaction_timeout", timeout) - ) .start())) { final String TABLE = KEYSPACE + ".test_table"; @@ -86,4 +85,41 @@ public class AccordJournalIntegrationTest extends TestBaseImpl cluster.coordinator(1).execute("SELECT * FROM " + TABLE + " WHERE k = ?;", ConsistencyLevel.SERIAL, 1); } } -} + + @Test + public void memtableStateReloadingTest() throws Throwable + { + try (Cluster cluster = Cluster.build(1) + .withoutVNodes() + .start()) + { + cluster.schemaChange("CREATE KEYSPACE " + KEYSPACE + " WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};"); + final String TABLE = KEYSPACE + ".test_table"; + cluster.schemaChange("CREATE TABLE " + TABLE + " (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'"); + + for (int j = 0; j < 1_000; j++) + { + cluster.coordinator(1).execute("BEGIN TRANSACTION\n" + + "INSERT INTO " + TABLE + "(k, c, v) VALUES (?, ?, ?);\n" + + "COMMIT TRANSACTION", + ConsistencyLevel.ALL, + j, j, 1 + ); + } + + Object[][] before = cluster.coordinator(1).execute("SELECT * FROM " + TABLE + " WHERE k = ?;", ConsistencyLevel.SERIAL, 1); + + cluster.get(1).runOnInstance(() -> { + ((AccordService) AccordService.instance()).journal().closeCurrentSegmentForTesting(); + }); + ClusterUtils.stopUnchecked(cluster.get(1)); + cluster.get(1).startup(); + + Object[][] after = cluster.coordinator(1).execute("SELECT * FROM " + TABLE + " WHERE k = ?;", ConsistencyLevel.SERIAL, 1); + for (int i = 0; i < before.length; i++) + { + Assert.assertTrue(Arrays.equals(before[i], after[i])); + } + } + } +} \ No newline at end of file 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 318b922d29..92e81c73f3 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordTestBase.java +++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordTestBase.java @@ -337,7 +337,8 @@ public abstract class AccordTestBase extends TestBaseImpl .withConfig(c -> c.with(Feature.GOSSIP) .set("write_request_timeout", "10s") .set("transaction_timeout", "15s") - .set("native_transport_timeout", "30s")) + .set("native_transport_timeout", "30s") + .set("accord.shard_count", "2")) .withInstanceInitializer(EnforceUpdateDoesNotPerformRead::install); builder = options.apply(builder); return init(builder.start()); diff --git a/test/distributed/org/apache/cassandra/journal/AccordJournalCompactionTest.java b/test/distributed/org/apache/cassandra/journal/AccordJournalCompactionTest.java deleted file mode 100644 index a2161c4386..0000000000 --- a/test/distributed/org/apache/cassandra/journal/AccordJournalCompactionTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.cassandra.journal; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; - -import org.apache.cassandra.ServerTestUtils; -import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.io.util.File; -import org.apache.cassandra.service.accord.AccordJournalTable; -import org.apache.cassandra.service.accord.AccordSegmentCompactor; -import org.apache.cassandra.utils.ByteBufferUtil; -import org.apache.cassandra.utils.TimeUUID; - -import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUID; - -public class AccordJournalCompactionTest -{ - private static final Set SENTINEL_HOSTS = Collections.singleton(0); - - @BeforeClass - public static void setUp() - { - DatabaseDescriptor.daemonInitialization(); - ServerTestUtils.prepareServer(); - } - - @Test - public void segmentMergeTest() throws IOException - { - File directory = new File(Files.createTempDirectory(null)); - directory.deleteOnExit(); - - Journal journal = journal(directory); - AccordJournalTable journalTable = new AccordJournalTable<>(journal, journal.keySupport, journal.params.userVersion()); - journal.start(); - - Map> uuids = new HashMap<>(); - - int count = 0; - for (int i = 0; i < 1024 * 5; i++) - { - TimeUUID uuid = nextTimeUUID(); - for (long j = 0; j < 5; j++) - { - ByteBuffer buf = ByteBuffer.allocate(1024); - for (int k = 0; k < 1024; k++) - buf.put((byte) count); - count++; - buf.rewind(); - uuids.computeIfAbsent(uuid, (k) -> new ArrayList<>()) - .add(buf); - journal.asyncWrite(uuid, buf, SENTINEL_HOSTS); - } - } - - journal.closeCurrentSegmentForTesting(); - Runnable checkAll = () -> { - for (Map.Entry> e : uuids.entrySet()) - { - List expected = e.getValue(); - - List actual = new ArrayList<>(); - journalTable.readAll(e.getKey(), (key, in, userVersion) -> actual.add(journal.valueSerializer.deserialize(key, in, userVersion))); - Assert.assertEquals(actual.size(), expected.size()); - for (int i = 0; i < actual.size(); i++) - { - if (!actual.get(i).equals(expected.get(i))) - { - StringBuilder sb = new StringBuilder(); - sb.append("Actual:\n"); - for (ByteBuffer bb : actual) - sb.append(ByteBufferUtil.bytesToHex(bb)).append('\n'); - sb.append("Expected:\n"); - for (ByteBuffer bb : expected) - sb.append(ByteBufferUtil.bytesToHex(bb)).append('\n'); - throw new AssertionError(sb.toString()); - } - } - } - }; - - checkAll.run(); - journal.runCompactorForTesting(); - checkAll.run(); - journal.shutdown(); - } - - private static Journal journal(File directory) - { - return new Journal<>("TestJournal", directory, - new TestParams() { - @Override - public int segmentSize() - { - return 1024 * 1024; - } - - @Override - public boolean enableCompaction() - { - return false; - } - }, - TimeUUIDKeySupport.INSTANCE, - JournalTest.ByteBufferSerializer.INSTANCE, - new AccordSegmentCompactor<>()); - } -} diff --git a/test/distributed/org/apache/cassandra/service/accord/AccordJournalCompactionTest.java b/test/distributed/org/apache/cassandra/service/accord/AccordJournalCompactionTest.java new file mode 100644 index 0000000000..545d9c8b9f --- /dev/null +++ b/test/distributed/org/apache/cassandra/service/accord/AccordJournalCompactionTest.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.cassandra.service.accord; + +import java.nio.file.Files; +import java.util.NavigableMap; +import java.util.concurrent.atomic.AtomicInteger; + +import com.google.common.collect.ImmutableSortedMap; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import accord.local.DurableBefore; +import accord.local.RedundantBefore; +import accord.primitives.Deps; +import accord.primitives.KeyDeps; +import accord.primitives.Ranges; +import accord.primitives.Timestamp; +import accord.primitives.TxnId; +import accord.utils.AccordGens; +import accord.utils.DefaultRandom; +import accord.utils.Gen; +import accord.utils.RandomSource; +import org.apache.cassandra.ServerTestUtils; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.dht.Murmur3Partitioner; +import org.apache.cassandra.io.util.File; +import org.apache.cassandra.journal.TestParams; +import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.service.accord.AccordJournalValueSerializers.HistoricalTransactionsAccumulator; +import org.apache.cassandra.utils.AccordGenerators; +import org.apache.cassandra.utils.concurrent.Condition; + +import static accord.local.CommandStores.RangesForEpoch; +import static org.apache.cassandra.service.accord.AccordJournalValueSerializers.DurableBeforeAccumulator; +import static org.apache.cassandra.service.accord.AccordJournalValueSerializers.IdentityAccumulator; +import static org.apache.cassandra.service.accord.AccordJournalValueSerializers.RedundantBeforeAccumulator; + + +public class AccordJournalCompactionTest +{ + @BeforeClass + public static void setUp() throws Throwable + { + ServerTestUtils.daemonInitialization(); + StorageService.instance.registerMBeans(); + StorageService.instance.setPartitionerUnsafe(Murmur3Partitioner.instance); + ServerTestUtils.prepareServerNoRegister(); + + StorageService.instance.initServer(); + Keyspace.setInitialized(); + } + + private AtomicInteger counter = new AtomicInteger(); + @Before + public void beforeTest() throws Throwable + { + File directory = new File(Files.createTempDirectory(Integer.toString(counter.incrementAndGet()))); + directory.deleteRecursiveOnExit(); + DatabaseDescriptor.setAccordJournalDirectory(directory.path()); + } + + @Test + public void segmentMergeTest() throws InterruptedException + { + RedundantBeforeAccumulator redundantBeforeAccumulator = new RedundantBeforeAccumulator(); + DurableBeforeAccumulator durableBeforeAccumulator = new DurableBeforeAccumulator(); + IdentityAccumulator> bootstrapBeganAtAccumulator = new IdentityAccumulator<>(ImmutableSortedMap.of(TxnId.NONE, Ranges.EMPTY)); + IdentityAccumulator> safeToReadAccumulator = new IdentityAccumulator<>(ImmutableSortedMap.of(Timestamp.NONE, Ranges.EMPTY)); + IdentityAccumulator rangesForEpochAccumulator = new IdentityAccumulator<>(null); + HistoricalTransactionsAccumulator historicalTransactionsAccumulator = new HistoricalTransactionsAccumulator(); + + Gen basicRedundantBeforeGen = AccordGenerators.redundantBefore(DatabaseDescriptor.getPartitioner()); + Gen redundantBeforeGen = rs -> { + // TODO: find a better way to generate consecutive redundant befores + while (true) + { + RedundantBefore next = basicRedundantBeforeGen.next(rs); + try + { + RedundantBefore.merge(redundantBeforeAccumulator.get(), next); + return next; + } + catch (Throwable t) + { + // retry; + } + } + }; + Gen durableBeforeGen = AccordGenerators.durableBeforeGen(DatabaseDescriptor.getPartitioner()); + Gen> safeToReadGen = AccordGenerators.safeToReadGen(DatabaseDescriptor.getPartitioner()); + Gen rangesForEpochGen = AccordGenerators.rangesForEpoch(DatabaseDescriptor.getPartitioner()); + Gen historicalTransactionsGen = depsGen(); + + AccordJournal journal = new AccordJournal(new TestParams() + { + @Override + public int segmentSize() + { + return 1024 * 1024; + } + + @Override + public boolean enableCompaction() + { + return false; + } + }); + try + { + journal.start(null); + Timestamp timestamp = Timestamp.NONE; + + RandomSource rs = new DefaultRandom(); + + int count = 1_000; + Condition condition = Condition.newOneTimeCondition(); + for (int i = 0; i <= count; i++) + { + timestamp = timestamp.next(); + AccordSafeCommandStore.FieldUpdates updates = new AccordSafeCommandStore.FieldUpdates(); + updates.durableBefore = durableBeforeGen.next(rs); + updates.redundantBefore = redundantBeforeGen.next(rs); + updates.safeToRead = safeToReadGen.next(rs); + updates.rangesForEpoch = rangesForEpochGen.next(rs); + updates.historicalTransactions = historicalTransactionsGen.next(rs); + + if (i == count) + journal.persistStoreState(1, updates, condition::signal); + else + journal.persistStoreState(1, updates, null); + + redundantBeforeAccumulator.update(updates.redundantBefore); + durableBeforeAccumulator.update(updates.durableBefore); + if (updates.bootstrapBeganAt != null) + bootstrapBeganAtAccumulator.update(updates.bootstrapBeganAt); + safeToReadAccumulator.update(updates.safeToRead); + rangesForEpochAccumulator.update(updates.rangesForEpoch); + historicalTransactionsAccumulator.update(updates.historicalTransactions); + } + + condition.await(); + + journal.closeCurrentSegmentForTesting(); + journal.runCompactorForTesting(); + + Assert.assertEquals(redundantBeforeAccumulator.get(), journal.loadRedundantBefore(1)); + Assert.assertEquals(durableBeforeAccumulator.get(), journal.loadDurableBefore(1)); + Assert.assertEquals(bootstrapBeganAtAccumulator.get(), journal.loadBootstrapBeganAt(1)); + Assert.assertEquals(safeToReadAccumulator.get(), journal.loadSafeToRead(1)); + Assert.assertEquals(rangesForEpochAccumulator.get(), journal.loadRangesForEpoch(1)); + Assert.assertEquals(historicalTransactionsAccumulator.get(), journal.loadHistoricalTransactions(1)); + } + finally + { + journal.shutdown(); + } + } + + public static Gen depsGen() + { + Gen keyDepsGen = AccordGenerators.keyDepsGen(DatabaseDescriptor.getPartitioner()); + return AccordGens.deps(keyDepsGen::next, + (rs) -> Deps.NONE.rangeDeps, + (rs) -> Deps.NONE.directKeyDeps); + } +} diff --git a/test/simulator/test/org/apache/cassandra/simulator/test/AccordJournalSimulationTest.java b/test/simulator/test/org/apache/cassandra/simulator/test/AccordJournalSimulationTest.java index 8ff8938645..4c339ebcfe 100644 --- a/test/simulator/test/org/apache/cassandra/simulator/test/AccordJournalSimulationTest.java +++ b/test/simulator/test/org/apache/cassandra/simulator/test/AccordJournalSimulationTest.java @@ -64,7 +64,7 @@ public class AccordJournalSimulationTest extends SimulationTestBase ListenableFileSystem fs = new ListenableFileSystem(Jimfs.newFileSystem()); File.unsafeSetFilesystem(fs); DatabaseDescriptor.daemonInitialization(); - DatabaseDescriptor.setCommitLogCompression(new ParameterizedClass("LZ4Compressor", ImmutableMap.of())); // + DatabaseDescriptor.setCommitLogCompression(new ParameterizedClass("LZ4Compressor", ImmutableMap.of())); DatabaseDescriptor.setCommitLogWriteDiskAccessMode(Config.DiskAccessMode.standard); DatabaseDescriptor.initializeCommitLogDiskAccessMode(); DatabaseDescriptor.setPartitionerUnsafe(Murmur3Partitioner.instance); @@ -130,12 +130,6 @@ public class AccordJournalSimulationTest extends SimulationTestBase @Isolated public static class IdentityValueSerializer implements ValueSerializer { - @Override - public int serializedSize(String key, String value, int userVersion) - { - return TypeSizes.INT_SIZE + key.length(); - } - @Override public void serialize(String key, String value, DataOutputPlus out, int userVersion) throws IOException { diff --git a/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java b/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java index 368687b5af..ad8f1ba08c 100644 --- a/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java +++ b/test/unit/org/apache/cassandra/cql3/conditions/ColumnConditionTest.java @@ -28,11 +28,6 @@ import org.junit.Assert; import org.junit.Test; import org.apache.cassandra.cql3.*; -import org.apache.cassandra.cql3.terms.Constants; -import org.apache.cassandra.cql3.terms.MultiElements; -import org.apache.cassandra.cql3.terms.Sets; -import org.apache.cassandra.cql3.terms.Term; -import org.apache.cassandra.cql3.terms.Terms; import org.apache.cassandra.db.Clustering; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.Int32Type; diff --git a/test/unit/org/apache/cassandra/db/compaction/CompactionAccordIteratorsTest.java b/test/unit/org/apache/cassandra/db/compaction/CompactionAccordIteratorsTest.java index e69bffb8e1..181d2c7ca6 100644 --- a/test/unit/org/apache/cassandra/db/compaction/CompactionAccordIteratorsTest.java +++ b/test/unit/org/apache/cassandra/db/compaction/CompactionAccordIteratorsTest.java @@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; import accord.local.CommandStores; +import accord.local.StoreParticipants; import accord.primitives.Route; import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.distributed.shared.WithProperties; @@ -51,9 +52,9 @@ import accord.local.Command; import accord.local.CommandStore; import accord.local.DurableBefore; import accord.local.RedundantBefore; -import accord.local.SaveStatus; -import accord.local.Status; -import accord.local.Status.Durability; +import accord.primitives.SaveStatus; +import accord.primitives.Status; +import accord.primitives.Status.Durability; import accord.primitives.Ballot; import accord.primitives.Deps; import accord.primitives.FullRoute; @@ -85,7 +86,7 @@ import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.StorageService; -import org.apache.cassandra.service.accord.api.PartitionKey; +import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; import org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.Pair; @@ -187,10 +188,10 @@ public class CompactionAccordIteratorsTest this.singleCompaction = singleCompaction; // Null redudnant before should make no change since we have no information on this CommandStore testAccordCommandsPurger(null, DurableBefore.EMPTY, expectAccordCommandsNoChange()); - // Universally durable (and global to boot) should be erased since literally everyone knows about it - // The way Commands.shouldCleanup was implemented (when this was written) it doesn't check redundantBefore - // at all for this - testAccordCommandsPurger(redundantBefore(LT_TXN_ID), durableBefore(UNIVERSAL), expectAccordCommandsErase()); + // Universally and locally durable (and global to boot) should be erased since literally everyone knows about it + testAccordCommandsPurger(redundantBefore(GT_TXN_ID), durableBefore(UNIVERSAL), expectAccordCommandsErase()); + // Universally durable but not locally; we're stale, but shouldn't erase + testAccordCommandsPurger(redundantBefore(LT_TXN_ID), durableBefore(UNIVERSAL), expectAccordCommandsNoChange()); // With redundantBefore at the txnId there should be no change because it is < not <= testAccordCommandsPurger(redundantBefore(TXN_ID), durableBefore(MAJORITY), expectAccordCommandsNoChange()); testAccordCommandsPurger(redundantBefore(LT_TXN_ID), durableBefore(MAJORITY), expectAccordCommandsNoChange()); @@ -262,7 +263,7 @@ public class CompactionAccordIteratorsTest return partitions -> { assertEquals(1, partitions.size()); Partition partition = partitions.get(0); - PartitionKey partitionKey = new PartitionKey(partition.metadata().id, partition.partitionKey()); + TokenKey partitionKey = new TokenKey(partition.metadata().id, partition.partitionKey().getToken()); CommandsForKey cfk = CommandsForKeysAccessor.getCommandsForKey(partitionKey, ((Row) partition.unfilteredIterator().next())); assertEquals(TXN_IDS.length, cfk.size()); for (int i = 0; i < TXN_IDS.length; ++i) @@ -384,7 +385,7 @@ public class CompactionAccordIteratorsTest { Ranges ranges = AccordTestUtils.fullRange(AccordTestUtils.keys(table, 42)); txnId = txnId.as(Kind.Read, Range); - return RedundantBefore.create(ranges, Long.MIN_VALUE, Long.MAX_VALUE, txnId, txnId, LT_TXN_ID.as(Range)); + return RedundantBefore.create(ranges, Long.MIN_VALUE, Long.MAX_VALUE, txnId, txnId, txnId, LT_TXN_ID.as(Range)); } enum DurableBeforeType @@ -469,19 +470,19 @@ public class CompactionAccordIteratorsTest PartialDeps partialDeps = Deps.NONE.intersecting(AccordTestUtils.fullRange(txn)); PartialTxn partialTxn = txn.slice(commandStore.unsafeRangesForEpoch().currentRanges(), true); Route partialRoute = route.slice(commandStore.unsafeRangesForEpoch().currentRanges()); - getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys(), COMMANDS), safe -> { + getUninterruptibly(commandStore.execute(contextFor(txnId, route, COMMANDS), safe -> { CheckedCommands.preaccept(safe, txnId, partialTxn, route, appendDiffToKeyspace(commandStore)); }).beginAsResult()); flush(commandStore); - getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys(), COMMANDS), safe -> { - CheckedCommands.accept(safe, txnId, Ballot.ZERO, partialRoute, partialTxn.keys(), txnId, partialDeps, appendDiffToKeyspace(commandStore)); + getUninterruptibly(commandStore.execute(contextFor(txnId, route, COMMANDS), safe -> { + CheckedCommands.accept(safe, txnId, Ballot.ZERO, partialRoute, txnId, partialDeps, appendDiffToKeyspace(commandStore)); }).beginAsResult()); flush(commandStore); - getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys(), COMMANDS), safe -> { + getUninterruptibly(commandStore.execute(contextFor(txnId, route, COMMANDS), safe -> { CheckedCommands.commit(safe, SaveStatus.Stable, Ballot.ZERO, txnId, route, partialTxn, txnId, partialDeps, appendDiffToKeyspace(commandStore)); }).beginAsResult()); flush(commandStore); - getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys(), COMMANDS), safe -> { + getUninterruptibly(commandStore.execute(contextFor(txnId, route, COMMANDS), safe -> { Pair result = AccordTestUtils.processTxnResultDirect(safe, txnId, partialTxn, txnId); CheckedCommands.apply(safe, txnId, route, txnId, partialDeps, partialTxn, result.left, result.right, appendDiffToKeyspace(commandStore)); }).beginAsResult()); @@ -489,8 +490,9 @@ public class CompactionAccordIteratorsTest // The apply chain is asychronous, so it is easiest to just spin until it is applied // in order to have the updated state in the system table spinAssertEquals(true, 5, () -> { - return getUninterruptibly(commandStore.submit(contextFor(txnId, txn.keys(), COMMANDS), safe -> { - Command command = safe.get(txnId, route.homeKey()).current(); + return getUninterruptibly(commandStore.submit(contextFor(txnId, route, COMMANDS), safe -> { + StoreParticipants participants = StoreParticipants.all(route); + Command command = safe.get(txnId, participants).current(); appendDiffToKeyspace(commandStore).accept(null, command); return command.hasBeen(Status.Applied); }).beginAsResult()); @@ -506,7 +508,7 @@ public class CompactionAccordIteratorsTest UntypedResultSet commandsForKeyTable = QueryProcessor.executeInternal("SELECT * FROM " + ACCORD_KEYSPACE_NAME + "." + COMMANDS_FOR_KEY + ";"); logger.info(commandsForKeyTable.toStringUnsafe()); assertEquals(1, commandsForKeyTable.size()); - CommandsForKey cfk = CommandsForKeySerializer.fromBytes((Key) key, commandsForKeyTable.iterator().next().getBytes("data")); + CommandsForKey cfk = CommandsForKeySerializer.fromBytes(((Key) key).toUnseekable(), commandsForKeyTable.iterator().next().getBytes("data")); assertEquals(txnIds.length, cfk.size()); for (int i = 0; i < txnIds.length; ++i) assertEquals(txnIds[i], cfk.txnId(i)); diff --git a/test/unit/org/apache/cassandra/db/virtual/AccordVirtualTablesTest.java b/test/unit/org/apache/cassandra/db/virtual/AccordVirtualTablesTest.java index 59eae7ab0c..7277edfa15 100644 --- a/test/unit/org/apache/cassandra/db/virtual/AccordVirtualTablesTest.java +++ b/test/unit/org/apache/cassandra/db/virtual/AccordVirtualTablesTest.java @@ -31,7 +31,7 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import accord.local.SaveStatus; +import accord.primitives.SaveStatus; import accord.messages.TxnRequest; import accord.primitives.Routable; import accord.primitives.Txn; diff --git a/test/unit/org/apache/cassandra/index/accord/AccordIndexStressTest.java b/test/unit/org/apache/cassandra/index/accord/AccordIndexStressTest.java index 59a7cf437d..fb278581df 100644 --- a/test/unit/org/apache/cassandra/index/accord/AccordIndexStressTest.java +++ b/test/unit/org/apache/cassandra/index/accord/AccordIndexStressTest.java @@ -41,8 +41,9 @@ import org.slf4j.LoggerFactory; import accord.api.RoutingKey; import accord.local.Node; -import accord.local.SaveStatus; -import accord.local.Status; +import accord.local.StoreParticipants; +import accord.primitives.SaveStatus; +import accord.primitives.Status; import accord.primitives.FullKeyRoute; import accord.primitives.Range; import accord.primitives.Ranges; @@ -399,7 +400,7 @@ public class AccordIndexStressTest extends CQLTester int minToken, int maxToken, int numRecords) { - var cql = "INSERT INTO system_accord.commands (store_id, domain, txn_id, status, route, durability) VALUES (?, ?, ?, ?, ?, ?)"; + var cql = "INSERT INTO system_accord.commands (store_id, domain, txn_id, status, participants, durability) VALUES (?, ?, ?, ?, ?, ?)"; for (int i = 0; i < numRecords; i++) { int store = rs.nextInt(0, numStores); @@ -409,8 +410,8 @@ public class AccordIndexStressTest extends CQLTester ByteBuffer routeBB; try { - Route route = createRoute(rs, numRecords, i, rs.nextInt(1, 20), tables, minToken, maxToken); - for (var u : route) + StoreParticipants participants = StoreParticipants.all(createRoute(rs, numRecords, i, rs.nextInt(1, 20), tables, minToken, maxToken)); + for (var u : participants.route()) { switch (u.domain()) { @@ -439,7 +440,7 @@ public class AccordIndexStressTest extends CQLTester throw new AssertionError("Unexpected domain: " + u.domain()); } } - routeBB = AccordKeyspace.serializeRoute(route); + routeBB = AccordKeyspace.serializeParticipants(participants); } catch (IOException e) { diff --git a/test/unit/org/apache/cassandra/index/accord/RouteIndexTest.java b/test/unit/org/apache/cassandra/index/accord/RouteIndexTest.java index edc7f2c517..bd5bc5eb93 100644 --- a/test/unit/org/apache/cassandra/index/accord/RouteIndexTest.java +++ b/test/unit/org/apache/cassandra/index/accord/RouteIndexTest.java @@ -36,8 +36,9 @@ import org.junit.Test; import accord.api.RoutingKey; import accord.local.Node; -import accord.local.SaveStatus; -import accord.local.Status.Durability; +import accord.local.StoreParticipants; +import accord.primitives.SaveStatus; +import accord.primitives.Status.Durability; import accord.primitives.FullKeyRoute; import accord.primitives.Range; import accord.primitives.Ranges; @@ -250,12 +251,12 @@ public class RouteIndexTest extends CQLTester.InMemory private class InsertTxn implements UnitCommand { - private static final String cql = "INSERT INTO system_accord.commands (store_id, domain, txn_id, status, route, durability) VALUES (?, ?, ?, ?, ?, ?)"; + private static final String cql = "INSERT INTO system_accord.commands (store_id, domain, txn_id, status, participants, durability) VALUES (?, ?, ?, ?, ?, ?)"; private final int storeId; private final TxnId txnId; private final SaveStatus saveStatus; private final Durability durability; - private final Route route; + private final StoreParticipants participants; private InsertTxn(int storeId, TxnId txnId, SaveStatus saveStatus, Durability durability, Route route) { @@ -263,13 +264,13 @@ public class RouteIndexTest extends CQLTester.InMemory this.txnId = txnId; this.saveStatus = saveStatus; this.durability = durability; - this.route = route; + this.participants = StoreParticipants.all(route); } @Override public void applyUnit(State state) { - for (var u : route) + for (var u : participants.route()) { switch (u.domain()) { @@ -302,7 +303,7 @@ public class RouteIndexTest extends CQLTester.InMemory @Override public void runUnit(ColumnFamilyStore sut) throws Throwable { - execute(cql, storeId, txnId.domain().ordinal(), AccordKeyspace.serializeTimestamp(txnId), saveStatus.ordinal(), AccordKeyspace.serializeRoute(route), durability.ordinal()); + execute(cql, storeId, txnId.domain().ordinal(), AccordKeyspace.serializeTimestamp(txnId), saveStatus.ordinal(), AccordKeyspace.serializeParticipants(participants), durability.ordinal()); } @Override @@ -313,7 +314,7 @@ public class RouteIndexTest extends CQLTester.InMemory ", txnId=" + txnId + ", saveStatus=" + saveStatus + ", durability=" + durability + - ", route=" + route + + ", participants=" + participants + '}'; } } diff --git a/test/unit/org/apache/cassandra/journal/JournalTest.java b/test/unit/org/apache/cassandra/journal/JournalTest.java index bab37ca150..30952a96d8 100644 --- a/test/unit/org/apache/cassandra/journal/JournalTest.java +++ b/test/unit/org/apache/cassandra/journal/JournalTest.java @@ -18,10 +18,8 @@ package org.apache.cassandra.journal; import java.io.IOException; -import java.nio.ByteBuffer; import java.nio.file.Files; import java.util.Collections; -import java.util.Set; import org.junit.BeforeClass; import org.junit.Test; @@ -38,8 +36,6 @@ import static org.junit.Assert.assertEquals; public class JournalTest { - private static final Set SENTINEL_HOSTS = Collections.singleton(0); - @BeforeClass public static void setUp() { @@ -87,29 +83,6 @@ public class JournalTest journal.shutdown(); } - static class ByteBufferSerializer implements ValueSerializer - { - static final ByteBufferSerializer INSTANCE = new ByteBufferSerializer(); - - public int serializedSize(TimeUUID key, ByteBuffer value, int userVersion) - { - return Integer.BYTES + value.capacity(); - } - - public void serialize(TimeUUID key, ByteBuffer value, DataOutputPlus out, int userVersion) throws IOException - { - out.writeInt(value.capacity()); - out.write(value); - } - - public ByteBuffer deserialize(TimeUUID key, DataInputPlus in, int userVersion) throws IOException - { - byte[] bytes = new byte[in.readInt()]; - in.readFully(bytes); - return ByteBuffer.wrap(bytes); - } - } - static class LongSerializer implements ValueSerializer { static final LongSerializer INSTANCE = new LongSerializer(); diff --git a/test/unit/org/apache/cassandra/service/StorageServiceTest.java b/test/unit/org/apache/cassandra/service/StorageServiceTest.java index 0b7ef7b1c4..8742cdac23 100644 --- a/test/unit/org/apache/cassandra/service/StorageServiceTest.java +++ b/test/unit/org/apache/cassandra/service/StorageServiceTest.java @@ -68,6 +68,7 @@ public class StorageServiceTest extends TestBaseImpl ServerTestUtils.prepareServerNoRegister(); DatabaseDescriptor.daemonInitialization(); DatabaseDescriptor.setTransientReplicationEnabledUnsafe(true); + DatabaseDescriptor.setAccordTransactionsEnabled(false); ClusterMetadataService.instance().commit(new Register(NodeAddresses.current(), SimpleLocationProvider.LOCATION, diff --git a/test/unit/org/apache/cassandra/service/accord/AccordCommandStoreTest.java b/test/unit/org/apache/cassandra/service/accord/AccordCommandStoreTest.java index 036e513ae3..b6fca2e9cc 100644 --- a/test/unit/org/apache/cassandra/service/accord/AccordCommandStoreTest.java +++ b/test/unit/org/apache/cassandra/service/accord/AccordCommandStoreTest.java @@ -34,9 +34,10 @@ import accord.api.Result; import accord.impl.TimestampsForKey; import accord.impl.TimestampsForKeys; import accord.local.Command; +import accord.local.StoreParticipants; import accord.local.cfk.CommandsForKey; import accord.local.CommonAttributes; -import accord.local.SaveStatus; +import accord.primitives.SaveStatus; import accord.primitives.Ballot; import accord.primitives.PartialDeps; import accord.primitives.PartialTxn; @@ -61,12 +62,13 @@ import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; import org.apache.cassandra.service.accord.api.PartitionKey; import org.apache.cassandra.service.accord.serializers.CommandSerializers; import org.apache.cassandra.service.consensus.TransactionalMode; import org.apache.cassandra.utils.Pair; -import static accord.local.Status.Durability.Majority; +import static accord.primitives.Status.Durability.Majority; import static com.google.common.collect.Iterables.getOnlyElement; import static org.apache.cassandra.cql3.statements.schema.CreateTableStatement.parse; import static org.apache.cassandra.service.accord.AccordTestUtils.Commands.preaccepted; @@ -125,7 +127,7 @@ public class AccordCommandStoreTest PartialTxn txn = createPartialTxn(0); Route route = RoutingKeys.of(key.toUnseekable()).toRoute(key.toUnseekable()); attrs.partialTxn(txn); - attrs.route(route); + attrs.setParticipants(StoreParticipants.all(route)); attrs.durability(Majority); attrs.partialTxn(txn); Ballot promised = ballot(1, clock.incrementAndGet(), 1); @@ -160,7 +162,7 @@ public class AccordCommandStoreTest Timestamp maxTimestamp = timestamp(1, clock.incrementAndGet(), 1); PartialTxn txn = createPartialTxn(1); - PartitionKey key = (PartitionKey) getOnlyElement(txn.keys()); + TokenKey key = ((PartitionKey) getOnlyElement(txn.keys())).toUnseekable(); TxnId txnId1 = txnId(1, clock.incrementAndGet(), 1); TxnId txnId2 = txnId(1, clock.incrementAndGet(), 1); @@ -170,10 +172,10 @@ public class AccordCommandStoreTest AccordSafeTimestampsForKey tfk = new AccordSafeTimestampsForKey(loaded(key, null)); tfk.initialize(); - TimestampsForKeys.updateLastExecutionTimestamps(commandStore, tfk, txnId1, true); + TimestampsForKeys.updateLastExecutionTimestamps(commandStore, tfk, txnId1, txnId1, true); Assert.assertEquals(txnId1.hlc(), AccordSafeTimestampsForKey.timestampMicrosFor(tfk.current(), txnId1, true)); - TimestampsForKeys.updateLastExecutionTimestamps(commandStore, tfk, txnId2, true); + TimestampsForKeys.updateLastExecutionTimestamps(commandStore, tfk, txnId2, txnId2, true); Assert.assertEquals(txnId2.hlc(), AccordSafeTimestampsForKey.timestampMicrosFor(tfk.current(), txnId2, true)); Assert.assertEquals(txnId2, tfk.current().lastExecutedTimestamp()); @@ -200,7 +202,7 @@ public class AccordCommandStoreTest AccordCommandStore commandStore = createAccordCommandStore(clock::incrementAndGet, "ks", "tbl"); PartialTxn txn = createPartialTxn(1); - PartitionKey key = (PartitionKey) getOnlyElement(txn.keys()); + TokenKey key = ((PartitionKey) getOnlyElement(txn.keys())).toUnseekable(); TxnId txnId1 = txnId(1, clock.incrementAndGet(), 1); TxnId txnId2 = txnId(1, clock.incrementAndGet(), 1); diff --git a/test/unit/org/apache/cassandra/service/accord/AccordCommandTest.java b/test/unit/org/apache/cassandra/service/accord/AccordCommandTest.java index 8f01b8f685..28f54ce688 100644 --- a/test/unit/org/apache/cassandra/service/accord/AccordCommandTest.java +++ b/test/unit/org/apache/cassandra/service/accord/AccordCommandTest.java @@ -26,6 +26,7 @@ import org.junit.Test; import accord.api.Key; import accord.api.RoutingKey; +import accord.local.StoreParticipants; import accord.local.cfk.CommandsForKey; import accord.local.Command; import accord.local.KeyHistory; @@ -33,7 +34,7 @@ import accord.local.Node; import accord.local.PreLoadContext; import accord.local.SafeCommand; import accord.local.SafeCommandStore; -import accord.local.Status; +import accord.primitives.Status; import accord.messages.Accept; import accord.messages.Commit; import accord.messages.PreAccept; @@ -105,7 +106,7 @@ public class AccordCommandTest // Check preaccept getUninterruptibly(commandStore.execute(preAccept, safeStore -> { - SafeCommand safeCommand = safeStore.get(txnId, txnId, route); + SafeCommand safeCommand = safeStore.get(txnId, StoreParticipants.all(route)); Command before = safeCommand.current(); PreAccept.PreAcceptReply reply = preAccept.apply(safeStore); Command after = safeCommand.current(); @@ -120,12 +121,12 @@ public class AccordCommandTest getUninterruptibly(commandStore.execute(preAccept, safeStore -> { Command before = safeStore.ifInitialised(txnId).current(); - SafeCommand safeCommand = safeStore.get(txnId, txnId, route); + SafeCommand safeCommand = safeStore.get(txnId, StoreParticipants.all(route)); Assert.assertEquals(txnId, before.executeAt()); Assert.assertEquals(Status.PreAccepted, before.status()); Assert.assertTrue(before.partialDeps() == null || before.partialDeps().isEmpty()); - CommandsForKey cfk = safeStore.get(key(1)).current(); + CommandsForKey cfk = safeStore.get(key(1).toUnseekable()).current(); Assert.assertTrue(cfk.indexOf(txnId) >= 0); Command after = safeCommand.current(); AccordTestUtils.appendCommandsBlocking(commandStore, before, after); @@ -137,10 +138,10 @@ public class AccordCommandTest PartialDeps deps; try (PartialDeps.Builder builder = PartialDeps.builder(route)) { - builder.add(key, txnId2); + builder.add(key.toUnseekable(), txnId2); deps = builder.build(); } - Accept accept = Accept.SerializerSupport.create(txnId, route, 1, 1, Ballot.ZERO, executeAt, partialTxn.keys(), deps); + Accept accept = Accept.SerializerSupport.create(txnId, route, 1, 1, Ballot.ZERO, executeAt, deps); getUninterruptibly(commandStore.execute(accept, safeStore -> { Command before = safeStore.ifInitialised(txnId).current(); @@ -157,23 +158,23 @@ public class AccordCommandTest Assert.assertEquals(Status.Accepted, before.status()); Assert.assertEquals(deps, before.partialDeps()); - CommandsForKey cfk = safeStore.get(key(1)).current(); + CommandsForKey cfk = safeStore.get(key(1).toUnseekable()).current(); Assert.assertTrue(cfk.indexOf(txnId) >= 0); Command after = safeStore.ifInitialised(txnId).current(); AccordTestUtils.appendCommandsBlocking(commandStore, before, after); })); // check commit - Commit commit = Commit.SerializerSupport.create(txnId, route, 1, Commit.Kind.StableWithTxnAndDeps, Ballot.ZERO, executeAt, partialTxn.keys(), partialTxn, deps, fullRoute, null); + Commit commit = Commit.SerializerSupport.create(txnId, route, 1, 1, Commit.Kind.StableWithTxnAndDeps, Ballot.ZERO, executeAt, partialTxn, deps, fullRoute, null); getUninterruptibly(commandStore.execute(commit, commit::apply)); - getUninterruptibly(commandStore.execute(PreLoadContext.contextFor(txnId, Keys.of(key), KeyHistory.COMMANDS), safeStore -> { + getUninterruptibly(commandStore.execute(PreLoadContext.contextFor(txnId, Keys.of(key).toParticipants(), KeyHistory.COMMANDS), safeStore -> { Command before = safeStore.ifInitialised(txnId).current(); Assert.assertEquals(commit.executeAt, before.executeAt()); Assert.assertTrue(before.hasBeen(Status.Committed)); Assert.assertEquals(commit.partialDeps, before.partialDeps()); - CommandsForKey cfk = safeStore.get(key(1)).current(); + CommandsForKey cfk = safeStore.get(key(1).toUnseekable()).current(); Assert.assertTrue(cfk.indexOf(txnId) >= 0); Command after = safeStore.ifInitialised(txnId).current(); AccordTestUtils.appendCommandsBlocking(commandStore, before, after); @@ -216,7 +217,7 @@ public class AccordCommandTest private static void persistDiff(AccordCommandStore commandStore, SafeCommandStore safeStore, TxnId txnId, Route route, Runnable runnable) { - SafeCommand safeCommand = safeStore.get(txnId, txnId, route); + SafeCommand safeCommand = safeStore.get(txnId, StoreParticipants.all(route)); Command before = safeCommand.current(); runnable.run(); Command after = safeCommand.current(); diff --git a/test/unit/org/apache/cassandra/service/accord/AccordJournalOrderTest.java b/test/unit/org/apache/cassandra/service/accord/AccordJournalOrderTest.java index 2c6dc9221c..34a270f544 100644 --- a/test/unit/org/apache/cassandra/service/accord/AccordJournalOrderTest.java +++ b/test/unit/org/apache/cassandra/service/accord/AccordJournalOrderTest.java @@ -18,7 +18,6 @@ package org.apache.cassandra.service.accord; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Random; @@ -64,7 +63,7 @@ public class AccordJournalOrderTest { if (new File(DatabaseDescriptor.getAccordJournalDirectory()).exists()) ServerTestUtils.cleanupDirectory(DatabaseDescriptor.getAccordJournalDirectory()); - AccordJournal accordJournal = new AccordJournal(SimpleAccordEndpointMapper.INSTANCE, TestParams.INSTANCE); + AccordJournal accordJournal = new AccordJournal(TestParams.INSTANCE); accordJournal.start(null); RandomSource randomSource = RandomSource.wrap(new Random()); TxnId id1 = AccordGens.txnIds().next(randomSource); @@ -74,11 +73,10 @@ public class AccordJournalOrderTest for (int i = 0; i < 10_000; i++) { TxnId txnId = randomSource.nextBoolean() ? id1 : id2; - JournalKey key = new JournalKey(txnId, randomSource.nextInt(5)); + JournalKey key = new JournalKey(txnId, JournalKey.Type.COMMAND_DIFF, randomSource.nextInt(5)); res.compute(key, (k, prev) -> prev == null ? 1 : prev + 1); accordJournal.appendCommand(key.commandStoreId, - Collections.singletonList(new SavedCommand.DiffWriter(txnId, null, null)), - null, + new SavedCommand.DiffWriter(txnId, null, null), () -> {}); } diff --git a/test/unit/org/apache/cassandra/service/accord/AccordJournalTest.java b/test/unit/org/apache/cassandra/service/accord/AccordJournalTest.java index 75a07196e2..c7b9a05c8c 100644 --- a/test/unit/org/apache/cassandra/service/accord/AccordJournalTest.java +++ b/test/unit/org/apache/cassandra/service/accord/AccordJournalTest.java @@ -20,6 +20,7 @@ package org.apache.cassandra.service.accord; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.ByteBuffer; +import java.nio.file.Files; import java.util.ArrayList; import java.util.List; @@ -30,9 +31,15 @@ import accord.primitives.TxnId; import accord.utils.AccordGens; import accord.utils.Gen; import accord.utils.Gens; +import org.apache.cassandra.ServerTestUtils; import org.apache.cassandra.config.CassandraRelevantProperties; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.dht.Murmur3Partitioner; import org.apache.cassandra.io.util.DataInputBuffer; import org.apache.cassandra.io.util.DataOutputBuffer; +import org.apache.cassandra.io.util.File; +import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.AsymmetricOrdering; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities.Order; @@ -45,9 +52,20 @@ import static org.assertj.core.api.Assertions.assertThat; public class AccordJournalTest { @BeforeClass - public static void setCompatibilityMode() + public static void setCompatibilityMode() throws IOException { CassandraRelevantProperties.TEST_STORAGE_COMPATIBILITY_MODE.setEnum(StorageCompatibilityMode.NONE); + + ServerTestUtils.daemonInitialization(); + StorageService.instance.registerMBeans(); + StorageService.instance.setPartitionerUnsafe(Murmur3Partitioner.instance); + ServerTestUtils.prepareServerNoRegister(); + + File directory = new File(Files.createTempDirectory(null)); + directory.deleteRecursiveOnExit(); + DatabaseDescriptor.setAccordJournalDirectory(directory.path()); + StorageService.instance.initServer(); + Keyspace.setInitialized(); } @Test @@ -122,6 +140,6 @@ public class AccordJournalTest private Gen keyGen() { Gen txnIdGen = AccordGens.txnIds(); - return rs -> new JournalKey(txnIdGen.next(rs)); + return rs -> new JournalKey(txnIdGen.next(rs), JournalKey.Type.COMMAND_DIFF, -1); } } diff --git a/test/unit/org/apache/cassandra/service/accord/AccordKeyspaceTest.java b/test/unit/org/apache/cassandra/service/accord/AccordKeyspaceTest.java index 3939371ad9..82e8f25afb 100644 --- a/test/unit/org/apache/cassandra/service/accord/AccordKeyspaceTest.java +++ b/test/unit/org/apache/cassandra/service/accord/AccordKeyspaceTest.java @@ -35,8 +35,7 @@ import accord.api.RoutingKey; import accord.local.Command; import accord.local.CommonAttributes; import accord.local.Node; -import accord.local.SaveStatus; -import accord.local.Status; +import accord.local.StoreParticipants; import accord.primitives.Ballot; import accord.primitives.Deps; import accord.primitives.FullRoute; @@ -46,6 +45,8 @@ import accord.primitives.PartialTxn; import accord.primitives.RangeDeps; import accord.primitives.Ranges; import accord.primitives.Routable; +import accord.primitives.SaveStatus; +import accord.primitives.Status; import accord.primitives.Timestamp; import accord.primitives.Txn; import accord.primitives.TxnId; @@ -67,7 +68,7 @@ import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.SchemaProvider; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.service.accord.api.AccordRoutingKey; -import org.apache.cassandra.service.accord.api.PartitionKey; +import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; import org.apache.cassandra.utils.CassandraGenerators; import org.assertj.core.api.Assertions; import org.mockito.Mockito; @@ -109,11 +110,12 @@ public class AccordKeyspaceTest extends CQLTester.InMemory PartialTxn partialTxn = txn.slice(scope, true); RoutingKey routingKey = partialTxn.keys().get(0).asKey().toUnseekable(); FullRoute route = partialTxn.keys().toRoute(routingKey); - Deps deps = new Deps(KeyDeps.none((Keys) txn.keys()), RangeDeps.NONE, KeyDeps.NONE); + StoreParticipants participants = StoreParticipants.all(route); + Deps deps = new Deps(KeyDeps.none(((Keys) txn.keys()).toParticipants()), RangeDeps.NONE, KeyDeps.NONE); CommonAttributes.Mutable common = new CommonAttributes.Mutable(id); common.partialTxn(partialTxn); - common.route(route); + common.setParticipants(participants); common.partialDeps(deps.intersecting(scope)); common.durability(Status.Durability.NotDurable); Command.WaitingOn waitingOn = null; @@ -171,13 +173,13 @@ public class AccordKeyspaceTest extends CQLTester.InMemory int numStores = rs.nextInt(1, 3); // The model of the DB - TreeMap> storesToKeys = new TreeMap<>(); + TreeMap> storesToKeys = new TreeMap<>(); // write to the table and the model for (int i = 0, numKeys = rs.nextInt(10, 20); i < numKeys; i++) { int store = rs.nextInt(0, numStores); var keys = storesToKeys.computeIfAbsent(store, ignore -> new TreeSet<>()); - PartitionKey pk = null; + TokenKey pk = null; // LocalPartitioner may have a type with a very small domain (boolean, vector, etc.), so need to bound the attempts // else this will loop forever... for (int attempt = 0; attempt < 10; attempt++) @@ -191,7 +193,7 @@ public class AccordKeyspaceTest extends CQLTester.InMemory data = fromQT(getTypeSupport(partitioner.getTokenValidator()).bytesGen()).next(rs); else data = Int32Type.instance.decompose(rs.nextInt()); - PartitionKey key = new PartitionKey(tableId, tables.get(tableId).decorateKey(data)); + TokenKey key = new TokenKey(tableId, tables.get(tableId).decorateKey(data).getToken()); if (keys.add(key)) { pk = key; @@ -206,8 +208,8 @@ public class AccordKeyspaceTest extends CQLTester.InMemory // The memtable will allow the write, but it will be dropped when writing to the SSTable... //TODO (now, correctness): since we store the user token + user key, if a key is close to the PK limits then we could tip over and loose our CFK // new Mutation(AccordKeyspace.getCommandsForKeyPartitionUpdate(store, pk, 42, ByteBufferUtil.EMPTY_BYTE_BUFFER)).apply(); - execute("INSERT INTO system_accord.commands_for_key (store_id, table_id, key_token, key) VALUES (?, ?, ?, ?)", - store, pk.table().asUUID(), AccordKeyspace.serializeRoutingKeyNoTable(pk.toUnseekable()), pk.partitionKey().getKey()); + execute("INSERT INTO system_accord.commands_for_key (store_id, table_id, key_token) VALUES (?, ?, ?)", + store, pk.table().asUUID(), AccordKeyspace.serializeRoutingKeyNoTable(pk)); } catch (IllegalArgumentException | InvalidRequestException e) { @@ -241,10 +243,10 @@ public class AccordKeyspaceTest extends CQLTester.InMemory for (var e : storesToKeys.entrySet()) { int store = e.getKey(); - SortedSet keys = e.getValue(); + SortedSet keys = e.getValue(); if (keys.isEmpty()) continue; - expectedCqlStoresToKeys.put(store, new TreeSet<>(keys.stream().map(p -> AccordKeyspace.serializeRoutingKeyNoTable(p.toUnseekable())).collect(Collectors.toList()))); + expectedCqlStoresToKeys.put(store, new TreeSet<>(keys.stream().map(AccordKeyspace::serializeRoutingKeyNoTable).collect(Collectors.toList()))); } // make sure no data loss... when this test was written sstable had all the rows but the sstable didn't... this @@ -255,7 +257,6 @@ public class AccordKeyspaceTest extends CQLTester.InMemory { int storeId = row.getInt("store_id"); ByteBuffer bb = row.getBytes("key_token"); - // FIXME: include table_id cqlStoresToKeys.computeIfAbsent(storeId, ignore -> new TreeSet<>()).add(bb); } Assertions.assertThat(cqlStoresToKeys).isEqualTo(expectedCqlStoresToKeys); @@ -280,12 +281,12 @@ public class AccordKeyspaceTest extends CQLTester.InMemory offset = rs.nextInt(0, keysForStore.size()); offsetEnd = rs.nextInt(offset, keysForStore.size()) + 1; } - List expected = keysForStore.subList(offset, offsetEnd); - PartitionKey start = expected.get(0); - PartitionKey end = expected.get(expected.size() - 1); + List expected = keysForStore.subList(offset, offsetEnd); + TokenKey start = expected.get(0); + TokenKey end = expected.get(expected.size() - 1); - AsyncChain> map = Observable.asChain(callback -> AccordKeyspace.findAllKeysBetween(store, start.toUnseekable(), true, end.toUnseekable(), true, callback)); - List actual = AsyncChains.getUnchecked(map); + AsyncChain> map = Observable.asChain(callback -> AccordKeyspace.findAllKeysBetween(store, start, true, end, true, callback)); + List actual = AsyncChains.getUnchecked(map); Assertions.assertThat(actual).isEqualTo(expected); } diff --git a/test/unit/org/apache/cassandra/service/accord/AccordSyncPropagatorTest.java b/test/unit/org/apache/cassandra/service/accord/AccordSyncPropagatorTest.java index 5969770e2e..8682d1df81 100644 --- a/test/unit/org/apache/cassandra/service/accord/AccordSyncPropagatorTest.java +++ b/test/unit/org/apache/cassandra/service/accord/AccordSyncPropagatorTest.java @@ -44,7 +44,7 @@ import accord.api.Agent; import accord.impl.AbstractConfigurationService; import accord.impl.TestAgent; import accord.impl.basic.PendingQueue; -import accord.impl.basic.PropagatingPendingQueue; +import accord.impl.basic.MonitoredPendingQueue; import accord.impl.basic.RandomDelayQueue; import accord.impl.basic.SimulatedDelayedExecutorService; import accord.local.Node; @@ -103,7 +103,7 @@ public class AccordSyncPropagatorTest List failures = new ArrayList<>(); RandomDelayQueue delayQueue = new RandomDelayQueue.Factory(rs).get(); - PendingQueue queue = new PropagatingPendingQueue(failures, delayQueue); + PendingQueue queue = new MonitoredPendingQueue(failures, delayQueue); Agent agent = new TestAgent.RethrowAgent(); SimulatedDelayedExecutorService globalExecutor = new SimulatedDelayedExecutorService(queue, agent); ScheduledExecutorPlus scheduler = new AdaptingScheduledExecutorPlus(globalExecutor); diff --git a/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java b/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java index 80a7b41769..57006fdbf2 100644 --- a/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java +++ b/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java @@ -32,20 +32,15 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import com.google.common.collect.Sets; - -import accord.api.LocalListeners; -import accord.api.ProgressLog.NoOpProgressLog; -import accord.api.RemoteListeners; -import accord.impl.DefaultLocalListeners; -import accord.utils.SortedArrays.SortedArrayList; -import org.apache.cassandra.ServerTestUtils; -import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.io.util.File; import org.junit.Assert; import accord.api.Data; +import accord.api.LocalListeners; +import accord.api.ProgressLog.NoOpProgressLog; +import accord.api.RemoteListeners; import accord.api.Result; import accord.api.RoutingKey; +import accord.impl.DefaultLocalListeners; import accord.impl.InMemoryCommandStore; import accord.local.Command; import accord.local.CommandStore; @@ -57,7 +52,8 @@ import accord.local.NodeTimeService; import accord.local.PreLoadContext; import accord.local.SafeCommand; import accord.local.SafeCommandStore; -import accord.local.SaveStatus; +import accord.local.StoreParticipants; +import accord.primitives.SaveStatus; import accord.primitives.Ballot; import accord.primitives.FullKeyRoute; import accord.primitives.FullRoute; @@ -74,12 +70,15 @@ import accord.primitives.TxnId; import accord.primitives.Writes; import accord.topology.Shard; import accord.topology.Topology; +import accord.utils.SortedArrays.SortedArrayList; import accord.utils.async.AsyncChains; +import org.apache.cassandra.ServerTestUtils; import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.concurrent.ImmediateExecutor; import org.apache.cassandra.concurrent.ManualExecutor; import org.apache.cassandra.concurrent.Stage; import org.apache.cassandra.config.AccordSpec; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.statements.TransactionStatement; @@ -88,6 +87,7 @@ import org.apache.cassandra.db.marshal.Int32Type; import org.apache.cassandra.dht.Murmur3Partitioner; import org.apache.cassandra.dht.Range; import org.apache.cassandra.dht.Token; +import org.apache.cassandra.io.util.File; import org.apache.cassandra.metrics.AccordStateCacheMetrics; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.TableId; @@ -124,7 +124,7 @@ public class AccordTestUtils { CommonAttributes.Mutable attrs = new CommonAttributes.Mutable(txnId); attrs.partialTxn(txn); - attrs.route(route(txn)); + attrs.setParticipants(StoreParticipants.all(route(txn))); return Command.SerializerSupport.preaccepted(attrs, executeAt, Ballot.ZERO); } @@ -132,7 +132,7 @@ public class AccordTestUtils { CommonAttributes.Mutable attrs = new CommonAttributes.Mutable(txnId).partialDeps(PartialDeps.NONE); attrs.partialTxn(txn); - attrs.route(route(txn)); + attrs.setParticipants(StoreParticipants.all(route(txn))); return Command.SerializerSupport.committed(attrs, SaveStatus.Committed, executeAt, @@ -145,7 +145,7 @@ public class AccordTestUtils { CommonAttributes.Mutable attrs = new CommonAttributes.Mutable(txnId).partialDeps(PartialDeps.NONE); attrs.partialTxn(txn); - attrs.route(route(txn)); + attrs.setParticipants(StoreParticipants.all(route(txn))); return Command.SerializerSupport.committed(attrs, SaveStatus.Stable, executeAt, @@ -229,7 +229,7 @@ public class AccordTestUtils public static Pair processTxnResult(AccordCommandStore commandStore, TxnId txnId, PartialTxn txn, Timestamp executeAt) throws Throwable { AtomicReference> result = new AtomicReference<>(); - getUninterruptibly(commandStore.execute(PreLoadContext.contextFor(txn.keys()), + getUninterruptibly(commandStore.execute(PreLoadContext.contextFor(txn.keys().toParticipants()), safeStore -> result.set(processTxnResultDirect(safeStore, txnId, txn, executeAt)))); return result.get(); } @@ -398,7 +398,7 @@ public class AccordTestUtils if (new File(DatabaseDescriptor.getAccordJournalDirectory()).exists()) ServerTestUtils.cleanupDirectory(DatabaseDescriptor.getAccordJournalDirectory()); - AccordJournal journal = new AccordJournal(SimpleAccordEndpointMapper.INSTANCE, new AccordSpec.JournalSpec()); + AccordJournal journal = new AccordJournal(new AccordSpec.JournalSpec()); journal.start(null); SingleEpochRanges holder = new SingleEpochRanges(topology.rangesForNode(node)); @@ -418,7 +418,11 @@ public class AccordTestUtils saveExecutor, new AccordStateCacheMetrics(AccordCommandStores.ACCORD_STATE_CACHE + System.currentTimeMillis())); holder.set(result); - result.updateRangesForEpoch(); + + // TODO: CompactionAccordIteratorsTest relies on this + result.execute(PreLoadContext.empty(), + result::updateRangesForEpoch) + .beginAsResult(); return result; } @@ -508,10 +512,10 @@ public class AccordTestUtils public static void appendCommandsBlocking(AccordCommandStore commandStore, Command before, Command after) { - SavedCommand.Writer diff = SavedCommand.diff(before, after); + SavedCommand.DiffWriter diff = SavedCommand.diff(before, after); if (diff == null) return; Condition condition = Condition.newOneTimeCondition(); - commandStore.appendCommands(Collections.singletonList(diff), null, condition::signal); + commandStore.appendCommands(Collections.singletonList(diff), condition::signal); condition.awaitUninterruptibly(30, TimeUnit.SECONDS); } } diff --git a/test/unit/org/apache/cassandra/service/accord/CommandsForRangesTest.java b/test/unit/org/apache/cassandra/service/accord/CommandsForRangesTest.java index 5993d1d133..93e35bc92c 100644 --- a/test/unit/org/apache/cassandra/service/accord/CommandsForRangesTest.java +++ b/test/unit/org/apache/cassandra/service/accord/CommandsForRangesTest.java @@ -27,7 +27,7 @@ import org.junit.Test; import accord.api.RoutingKey; import accord.impl.IntKey; -import accord.local.SaveStatus; +import accord.primitives.SaveStatus; import accord.primitives.Range; import accord.primitives.Ranges; import accord.primitives.TxnId; diff --git a/test/unit/org/apache/cassandra/service/accord/MockJournal.java b/test/unit/org/apache/cassandra/service/accord/MockJournal.java index a20ee61a08..9f6827f1b9 100644 --- a/test/unit/org/apache/cassandra/service/accord/MockJournal.java +++ b/test/unit/org/apache/cassandra/service/accord/MockJournal.java @@ -18,39 +18,60 @@ package org.apache.cassandra.service.accord; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.NavigableMap; import java.util.function.Function; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSortedMap; import accord.api.Result; import accord.local.Command; +import accord.local.CommandStores; import accord.local.CommonAttributes; -import accord.local.SaveStatus; -import accord.local.Status; +import accord.local.DurableBefore; +import accord.local.RedundantBefore; +import accord.local.StoreParticipants; +import accord.primitives.Known; +import accord.primitives.SaveStatus; +import accord.primitives.Status; import accord.primitives.Ballot; +import accord.primitives.Deps; import accord.primitives.PartialDeps; import accord.primitives.PartialTxn; -import accord.primitives.Route; -import accord.primitives.Seekables; +import accord.primitives.Ranges; import accord.primitives.Timestamp; import accord.primitives.TxnId; import accord.primitives.Writes; import accord.utils.Invariants; +import org.apache.cassandra.service.accord.AccordJournalValueSerializers.DurableBeforeAccumulator; +import org.apache.cassandra.service.accord.AccordJournalValueSerializers.HistoricalTransactionsAccumulator; +import org.apache.cassandra.service.accord.AccordJournalValueSerializers.IdentityAccumulator; +import org.apache.cassandra.service.accord.AccordJournalValueSerializers.RedundantBeforeAccumulator; import org.apache.cassandra.service.accord.serializers.CommandSerializers; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - public class MockJournal implements IJournal { private final Map> commands = new HashMap<>(); - @Override - public Command loadCommand(int commandStoreId, TxnId txnId) + private static class FieldUpdates { - JournalKey key = new JournalKey(txnId, commandStoreId); + final RedundantBeforeAccumulator redundantBeforeAccumulator = new RedundantBeforeAccumulator(); + final DurableBeforeAccumulator durableBeforeAccumulator = new DurableBeforeAccumulator(); + final IdentityAccumulator> bootstrapBeganAtAccumulator = new IdentityAccumulator<>(ImmutableSortedMap.of(TxnId.NONE, Ranges.EMPTY)); + final IdentityAccumulator> safeToReadAccumulator = new IdentityAccumulator<>(ImmutableSortedMap.of(Timestamp.NONE, Ranges.EMPTY)); + final IdentityAccumulator rangesForEpochAccumulator = new IdentityAccumulator<>(null); + final HistoricalTransactionsAccumulator historicalTransactionsAccumulator = new HistoricalTransactionsAccumulator(); + } + + private final Map fieldUpdates = new HashMap<>(); + @Override + public Command loadCommand(int store, TxnId txnId) + { + JournalKey key = new JournalKey(txnId, JournalKey.Type.COMMAND_DIFF, store); List saved = commands.get(key); if (saved == null) return null; @@ -58,16 +79,77 @@ public class MockJournal implements IJournal } @Override - public void appendCommand(int commandStoreId, List> diffs, List sanityCheck, Runnable onFlush) + public RedundantBefore loadRedundantBefore(int store) { - for (SavedCommand.Writer diff : diffs) - { - SavedCommand.DiffWriter writer = (SavedCommand.DiffWriter) diff; + return fieldUpdates(store).redundantBeforeAccumulator.get(); + } - JournalKey key = new JournalKey(diff.key(), commandStoreId); - commands.computeIfAbsent(key, (ignore_) -> new ArrayList<>()) - .add(diff(writer.before(), writer.after())); + @Override + public DurableBefore loadDurableBefore(int store) + { + return fieldUpdates(store).durableBeforeAccumulator.get(); + } + + @Override + public NavigableMap loadBootstrapBeganAt(int store) + { + return fieldUpdates(store).bootstrapBeganAtAccumulator.get(); + } + + @Override + public NavigableMap loadSafeToRead(int store) + { + return fieldUpdates(store).safeToReadAccumulator.get(); + } + + @Override + public CommandStores.RangesForEpoch.Snapshot loadRangesForEpoch(int store) + { + return fieldUpdates(store).rangesForEpochAccumulator.get(); + } + + @Override + public List loadHistoricalTransactions(int store) + { + return fieldUpdates(store).historicalTransactionsAccumulator.get(); + } + + @Override + public void appendCommand(int store, SavedCommand.DiffWriter diff, Runnable onFlush) + { + if (diff != null) + { + commands.computeIfAbsent(new JournalKey(diff.after().txnId(), JournalKey.Type.COMMAND_DIFF, store), + (ignore_) -> new ArrayList<>()) + .add(diff(diff.before(), diff.after())); } + + if (onFlush != null) + onFlush.run(); + } + + private FieldUpdates fieldUpdates(int store) + { + return fieldUpdates.computeIfAbsent(store, (o) -> new FieldUpdates()); + } + + @Override + public void persistStoreState(int store, AccordSafeCommandStore.FieldUpdates fieldUpdates, Runnable onFlush) + { + FieldUpdates updates = fieldUpdates(store); + if (fieldUpdates.redundantBefore != null) + updates.redundantBeforeAccumulator.update(fieldUpdates.redundantBefore); + if (fieldUpdates.durableBefore != null) + updates.durableBeforeAccumulator.update(fieldUpdates.durableBefore); + if (fieldUpdates.bootstrapBeganAt != null) + updates.bootstrapBeganAtAccumulator.update(fieldUpdates.bootstrapBeganAt); + if (fieldUpdates.safeToRead != null) + updates.safeToReadAccumulator.update(fieldUpdates.safeToRead); + if (fieldUpdates.rangesForEpoch != null) + updates.rangesForEpochAccumulator.update(fieldUpdates.rangesForEpoch); + if (fieldUpdates.historicalTransactions != null) + updates.historicalTransactionsAccumulator.update(fieldUpdates.historicalTransactions); + onFlush.run(); } @@ -90,10 +172,9 @@ public class MockJournal implements IJournal ifNotEqual(before, after, Command::acceptedOrCommitted, false), ifNotEqual(before, after, Command::promised, false), - ifNotEqual(before, after, Command::route, true), + ifNotEqual(before, after, Command::participants, false), ifNotEqual(before, after, Command::partialTxn, false), ifNotEqual(before, after, Command::partialDeps, false), - ifNotEqual(before, after, Command::additionalKeysOrRanges, false), new NewValue<>((k, deps) -> waitingOn), ifNotEqual(before, after, Command::writes, false)); @@ -122,10 +203,9 @@ public class MockJournal implements IJournal Ballot acceptedOrCommitted = Ballot.ZERO; Ballot promised = null; - Route route = null; + StoreParticipants participants = null; PartialTxn partialTxn = null; PartialDeps partialDeps = null; - Seekables additionalKeysOrRanges = null; SavedCommand.WaitingOnProvider waitingOnProvider = null; Writes writes = null; @@ -146,14 +226,12 @@ public class MockJournal implements IJournal if (diff.promised != null) promised = diff.promised.get(); - if (diff.route != null) - route = diff.route.get(); + if (diff.participants != null) + participants = diff.participants.get(); if (diff.partialTxn != null) partialTxn = diff.partialTxn.get(); if (diff.partialDeps != null) partialDeps = diff.partialDeps.get(); - if (diff.additionalKeysOrRanges != null) - additionalKeysOrRanges = diff.additionalKeysOrRanges.get(); if (diff.waitingOn != null) waitingOnProvider = diff.waitingOn.get(); @@ -166,15 +244,13 @@ public class MockJournal implements IJournal attrs.partialTxn(partialTxn); if (durability != null) attrs.durability(durability); - if (route != null) - attrs.route(route); + if (participants != null) + attrs.setParticipants(participants); if (partialDeps != null && - (saveStatus.known.deps != Status.KnownDeps.NoDeps && - saveStatus.known.deps != Status.KnownDeps.DepsErased && - saveStatus.known.deps != Status.KnownDeps.DepsUnknown)) + (saveStatus.known.deps != Known.KnownDeps.NoDeps && + saveStatus.known.deps != Known.KnownDeps.DepsErased && + saveStatus.known.deps != Known.KnownDeps.DepsUnknown)) attrs.partialDeps(partialDeps); - if (additionalKeysOrRanges != null) - attrs.additionalKeysOrRanges(additionalKeysOrRanges); Command.WaitingOn waitingOn = null; if (waitingOnProvider != null) @@ -279,10 +355,9 @@ public class MockJournal implements IJournal public final NewValue acceptedOrCommitted; public final NewValue promised; - public final NewValue> route; + public final NewValue participants; public final NewValue partialTxn; public final NewValue partialDeps; - public final NewValue> additionalKeysOrRanges; public final NewValue writes; public final NewValue waitingOn; @@ -295,10 +370,9 @@ public class MockJournal implements IJournal NewValue acceptedOrCommitted, NewValue promised, - NewValue> route, + NewValue participants, NewValue partialTxn, NewValue partialDeps, - NewValue> additionalKeysOrRanges, NewValue waitingOn, NewValue writes) @@ -311,10 +385,9 @@ public class MockJournal implements IJournal this.acceptedOrCommitted = acceptedOrCommitted; this.promised = promised; - this.route = route; + this.participants = participants; this.partialTxn = partialTxn; this.partialDeps = partialDeps; - this.additionalKeysOrRanges = additionalKeysOrRanges; this.writes = writes; diff --git a/test/unit/org/apache/cassandra/service/accord/SavedCommandTest.java b/test/unit/org/apache/cassandra/service/accord/SavedCommandTest.java index 5135bde35f..1d86856922 100644 --- a/test/unit/org/apache/cassandra/service/accord/SavedCommandTest.java +++ b/test/unit/org/apache/cassandra/service/accord/SavedCommandTest.java @@ -27,7 +27,7 @@ import org.junit.BeforeClass; import org.junit.Test; import accord.local.Command; -import accord.local.SaveStatus; +import accord.primitives.SaveStatus; import accord.primitives.TxnId; import accord.utils.Gen; import accord.utils.LazyToString; @@ -74,7 +74,7 @@ public class SavedCommandTest public void simpleNullChangeCheck() { int flags = getFlags(null, Command.NotDefined.uninitialised(TxnId.NONE)); - EnumSet has = EnumSet.of(Fields.TXN_ID, Fields.SAVE_STATUS, Fields.DURABILITY, Fields.PROMISED, + EnumSet has = EnumSet.of(Fields.TXN_ID, Fields.SAVE_STATUS, Fields.PARTICIPANTS, Fields.DURABILITY, Fields.PROMISED, Fields.ACCEPTED /* this is Zero... which kinda means null... */); Set missing = Sets.difference(ALL, has); assertHas(flags, has); @@ -87,7 +87,7 @@ public class SavedCommandTest Gen gen = AccordGenerators.commandsBuilder(); try (DataOutputBuffer out = new DataOutputBuffer()) { - qt().forAll(gen).check(cmdBuilder -> { + qt().forAll(gen).withSeed(3447978952908153749L).check(cmdBuilder -> { int userVersion = 1; //TODO (maintance): where can we fetch all supported versions? SoftAssertions checks = new SoftAssertions(); for (SaveStatus saveStatus : SaveStatus.values()) diff --git a/test/unit/org/apache/cassandra/service/accord/SimpleSimulatedAccordCommandStoreTest.java b/test/unit/org/apache/cassandra/service/accord/SimpleSimulatedAccordCommandStoreTest.java index 3d54d3af9d..dd8678c1ef 100644 --- a/test/unit/org/apache/cassandra/service/accord/SimpleSimulatedAccordCommandStoreTest.java +++ b/test/unit/org/apache/cassandra/service/accord/SimpleSimulatedAccordCommandStoreTest.java @@ -21,8 +21,8 @@ package org.apache.cassandra.service.accord; import org.junit.Test; import accord.local.PreLoadContext; -import accord.local.SaveStatus; -import accord.primitives.Ranges; +import accord.local.StoreParticipants; +import accord.primitives.SaveStatus; import accord.primitives.TxnId; import accord.utils.AccordGens; import org.assertj.core.api.Assertions; @@ -42,7 +42,7 @@ public class SimpleSimulatedAccordCommandStoreTest extends SimulatedAccordComman { TxnId id = AccordGens.txnIds().next(rs); instance.process(PreLoadContext.contextFor(id), (safe) -> { - var safeCommand = safe.get(id, id, Ranges.EMPTY); + var safeCommand = safe.get(id, StoreParticipants.empty(id)); var command = safeCommand.current(); Assertions.assertThat(command.saveStatus()).isEqualTo(SaveStatus.Uninitialised); return null; diff --git a/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStore.java b/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStore.java index 68eadd0d1b..06469b0bb2 100644 --- a/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStore.java +++ b/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStore.java @@ -31,6 +31,7 @@ import java.util.function.ToLongFunction; import accord.api.LocalListeners; import accord.api.ProgressLog; import accord.api.RemoteListeners; +import accord.api.RoutingKey; import accord.impl.DefaultLocalListeners; import accord.impl.SizeOfIntersectionSorter; import accord.impl.TestAgent; @@ -45,17 +46,18 @@ import accord.local.SafeCommandStore; import accord.messages.BeginRecovery; import accord.messages.PreAccept; import accord.messages.TxnRequest; +import accord.primitives.AbstractUnseekableKeys; import accord.primitives.Ballot; import accord.primitives.FullRoute; -import accord.primitives.Keys; import accord.primitives.Ranges; import accord.primitives.Routable; import accord.primitives.RoutableKey; import accord.primitives.Routables; -import accord.primitives.Seekables; +import accord.primitives.RoutingKeys; import accord.primitives.Timestamp; import accord.primitives.Txn; import accord.primitives.TxnId; +import accord.primitives.Unseekables; import accord.topology.Topologies; import accord.topology.Topology; import accord.utils.Gens; @@ -227,22 +229,22 @@ public class SimulatedAccordCommandStore implements AutoCloseable return new TxnId(timeService.epoch(), timeService.now(), kind, domain, nodeId); } - public void maybeCacheEvict(Seekables keysOrRanges) + public void maybeCacheEvict(Unseekables keysOrRanges) { switch (keysOrRanges.domain()) { case Key: - maybeCacheEvict((Keys) keysOrRanges, Ranges.EMPTY); + maybeCacheEvict((AbstractUnseekableKeys) keysOrRanges, Ranges.EMPTY); break; case Range: - maybeCacheEvict(Keys.EMPTY, (Ranges) keysOrRanges); + maybeCacheEvict(RoutingKeys.EMPTY, (Ranges) keysOrRanges); break; default: throw new UnsupportedOperationException("Unknown domain: " + keysOrRanges.domain()); } } - public void maybeCacheEvict(Keys keys, Ranges ranges) + public void maybeCacheEvict(Unseekables keys, Ranges ranges) { AccordStateCache cache = store.cache(); cache.forEach(state -> { @@ -294,14 +296,14 @@ public class SimulatedAccordCommandStore implements AutoCloseable } } - private static boolean intersects(ColumnFamilyStore store, Memtable memtable, Keys keys, Ranges ranges) + private static boolean intersects(ColumnFamilyStore store, Memtable memtable, Unseekables keys, Ranges ranges) { if (keys.isEmpty() && ranges.isEmpty()) // shouldn't happen, but just in case... return false; switch (store.name) { case "commands_for_key": - // pk = (store_id, key_token, key) + // pk = (store_id, routing_key) // since this is simulating a single store, store_id is a constant, so check key try (var it = memtable.partitionIterator(ColumnFilter.NONE, DataRange.allData(store.getPartitioner()), null)) { @@ -365,7 +367,7 @@ public class SimulatedAccordCommandStore implements AutoCloseable { TxnId txnId = nextTxnId(txn.kind(), txn.keys().domain()); Ballot ballot = Ballot.fromValues(timeService.epoch(), timeService.now(), nodeId); - BeginRecovery br = new BeginRecovery(nodeId, topologies, txnId, txn, route, ballot); + BeginRecovery br = new BeginRecovery(nodeId, topologies, txnId, null, txn, route, ballot); return Pair.create(txnId, processAsync(br, safe -> { var reply = br.apply(safe); diff --git a/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStoreTestBase.java b/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStoreTestBase.java index 1c05c0a0ad..f9f09f5c5c 100644 --- a/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStoreTestBase.java +++ b/test/unit/org/apache/cassandra/service/accord/SimulatedAccordCommandStoreTestBase.java @@ -30,7 +30,7 @@ import com.google.common.collect.Maps; import org.junit.Before; import org.junit.BeforeClass; -import accord.api.Key; +import accord.api.RoutingKey; import accord.impl.SizeOfIntersectionSorter; import accord.local.Node; import accord.messages.BeginRecovery; @@ -44,8 +44,10 @@ import accord.primitives.LatestDeps; import accord.primitives.Range; import accord.primitives.Ranges; import accord.primitives.Routable; +import accord.primitives.RoutingKeys; import accord.primitives.Txn; import accord.primitives.TxnId; +import accord.primitives.Unseekables; import accord.topology.Topologies; import accord.utils.Gen; import accord.utils.Gens; @@ -148,11 +150,11 @@ public abstract class SimulatedAccordCommandStoreTestBase extends CQLTester return new AccordRoutingKey.TokenKey(id, new Murmur3Partitioner.LongToken(token)); } - protected static Map> keyConflicts(List list, Keys keys) + protected static Map> keyConflicts(List list, Unseekables keys) { if (list.isEmpty()) return Collections.emptyMap(); - Map> kc = Maps.newHashMapWithExpectedSize(keys.size()); - for (Key key : keys) + Map> kc = Maps.newHashMapWithExpectedSize(keys.size()); + for (RoutingKey key : keys) kc.put(key, list); return kc; } @@ -169,7 +171,7 @@ public abstract class SimulatedAccordCommandStoreTestBase extends CQLTester protected static TxnId assertDepsMessage(SimulatedAccordCommandStore instance, DepsMessage messageType, Txn txn, FullRoute route, - Map> keyConflicts) throws ExecutionException, InterruptedException + Map> keyConflicts) throws ExecutionException, InterruptedException { return assertDepsMessage(instance, messageType, txn, route, keyConflicts, Collections.emptyMap()); } @@ -177,7 +179,7 @@ public abstract class SimulatedAccordCommandStoreTestBase extends CQLTester protected static TxnId assertDepsMessage(SimulatedAccordCommandStore instance, DepsMessage messageType, Txn txn, FullRoute route, - Map> keyConflicts, + Map> keyConflicts, Map> rangeConflicts) throws ExecutionException, InterruptedException { var pair = assertDepsMessageAsync(instance, messageType, txn, route, keyConflicts, rangeConflicts); @@ -190,7 +192,7 @@ public abstract class SimulatedAccordCommandStoreTestBase extends CQLTester protected static Pair> assertDepsMessageAsync(SimulatedAccordCommandStore instance, DepsMessage messageType, Txn txn, FullRoute route, - Map> keyConflicts, + Map> keyConflicts, Map> rangeConflicts) { switch (messageType) @@ -208,10 +210,10 @@ public abstract class SimulatedAccordCommandStoreTestBase extends CQLTester protected static Pair> assertPreAcceptAsync(SimulatedAccordCommandStore instance, Txn txn, FullRoute route, - Map> keyConflicts, + Map> keyConflicts, Map> rangeConflicts) { - Map> cloneKeyConflicts = keyConflicts.entrySet().stream() + Map> cloneKeyConflicts = keyConflicts.entrySet().stream() .filter(e -> !e.getValue().isEmpty()) .collect(Collectors.toMap(e -> e.getKey(), e -> new ArrayList(e.getValue()))); Map> cloneRangeConflicts = rangeConflicts.entrySet().stream() @@ -226,10 +228,10 @@ public abstract class SimulatedAccordCommandStoreTestBase extends CQLTester protected static Pair> assertBeginRecoveryAsync(SimulatedAccordCommandStore instance, Txn txn, FullRoute route, - Map> keyConflicts, + Map> keyConflicts, Map> rangeConflicts) { - Map> cloneKeyConflicts = keyConflicts.entrySet().stream() + Map> cloneKeyConflicts = keyConflicts.entrySet().stream() .filter(e -> !e.getValue().isEmpty()) .collect(Collectors.toMap(e -> e.getKey(), e -> new ArrayList(e.getValue()))); Map> cloneRangeConflicts = rangeConflicts.entrySet().stream() @@ -245,10 +247,10 @@ public abstract class SimulatedAccordCommandStoreTestBase extends CQLTester protected static Pair> assertBeginRecoveryAfterPreAcceptAsync(SimulatedAccordCommandStore instance, Txn txn, FullRoute route, - Map> keyConflicts, + Map> keyConflicts, Map> rangeConflicts) { - Map> cloneKeyConflicts = keyConflicts.entrySet().stream() + Map> cloneKeyConflicts = keyConflicts.entrySet().stream() .filter(e -> !e.getValue().isEmpty()) .collect(Collectors.toMap(e -> e.getKey(), e -> new ArrayList(e.getValue()))); Map> cloneRangeConflicts = rangeConflicts.entrySet().stream() @@ -267,7 +269,7 @@ public abstract class SimulatedAccordCommandStoreTestBase extends CQLTester }); var delay = preAcceptAsync.flatMap(ignore -> AsyncChains.ofCallable(instance.unorderedScheduled, () -> { Ballot ballot = Ballot.fromValues(instance.timeService.epoch(), instance.timeService.now(), nodeId); - return new BeginRecovery(nodeId, new Topologies.Single(SizeOfIntersectionSorter.SUPPLIER, instance.topology), txnId, txn, route, ballot); + return new BeginRecovery(nodeId, new Topologies.Single(SizeOfIntersectionSorter.SUPPLIER, instance.topology), txnId, null, txn, route, ballot); })); var recoverAsync = delay.flatMap(br -> instance.processAsync(br, safe -> { var reply = br.apply(safe); @@ -282,7 +284,7 @@ public abstract class SimulatedAccordCommandStoreTestBase extends CQLTester } protected static void assertDeps(TxnId txnId, Deps deps, - Map> keyConflicts, + Map> keyConflicts, Map> rangeConflicts) { if (rangeConflicts.isEmpty()) @@ -291,7 +293,7 @@ public abstract class SimulatedAccordCommandStoreTestBase extends CQLTester } else { - List actualRanges = IntStream.range(0, deps.rangeDeps.rangeCount()).mapToObj(i -> deps.rangeDeps.range(i)).collect(Collectors.toList()); + List actualRanges = IntStream.range(0, deps.rangeDeps.rangeCount()).mapToObj(deps.rangeDeps::range).collect(Collectors.toList()); // Assertions.assertThat(deps.rangeDeps.rangeCount()).describedAs("Txn %s Expected ranges size; %s", txnId, deps.rangeDeps).isEqualTo(rangeConflicts.size()); Assertions.assertThat(Ranges.of(actualRanges.toArray(Range[]::new))) .describedAs("Txn %s had different ranges than expected", txnId) @@ -324,7 +326,7 @@ public abstract class SimulatedAccordCommandStoreTestBase extends CQLTester } else { - Assertions.assertThat(deps.keyDeps.keys()).describedAs("Txn %s Keys", txnId).isEqualTo(Keys.of(keyConflicts.keySet())); + Assertions.assertThat(deps.keyDeps.keys()).describedAs("Txn %s Keys", txnId).isEqualTo(RoutingKeys.of(keyConflicts.keySet())); for (var key : keyConflicts.keySet()) Assertions.assertThat(deps.keyDeps.txnIds(key)).describedAs("Txn %s for key %s", txnId, key).isEqualTo(keyConflicts.get(key)); } diff --git a/test/unit/org/apache/cassandra/service/accord/SimulatedDepsTest.java b/test/unit/org/apache/cassandra/service/accord/SimulatedDepsTest.java index 9fda5cd16f..e3526e30b9 100644 --- a/test/unit/org/apache/cassandra/service/accord/SimulatedDepsTest.java +++ b/test/unit/org/apache/cassandra/service/accord/SimulatedDepsTest.java @@ -28,7 +28,7 @@ import java.util.Map; import org.junit.Test; -import accord.api.Key; +import accord.api.RoutingKey; import accord.primitives.FullKeyRoute; import accord.primitives.FullRangeRoute; import accord.primitives.FullRoute; @@ -40,6 +40,7 @@ import accord.primitives.TxnId; import org.apache.cassandra.db.marshal.Int32Type; import org.apache.cassandra.dht.Murmur3Partitioner.LongToken; import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; import org.apache.cassandra.service.accord.api.PartitionKey; import static accord.utils.Property.qt; @@ -65,8 +66,8 @@ public class SimulatedDepsTest extends SimulatedAccordCommandStoreTestBase List conflicts = new ArrayList<>(numSamples); for (int i = 0; i < numSamples; i++) { - instance.maybeCacheEvict(keys, Ranges.EMPTY); - conflicts.add(assertDepsMessage(instance, rs.pick(DepsMessage.values()), txn, route, keyConflicts(conflicts, keys))); + instance.maybeCacheEvict(route, Ranges.EMPTY); + conflicts.add(assertDepsMessage(instance, rs.pick(DepsMessage.values()), txn, route, keyConflicts(conflicts, route))); } } }); @@ -89,8 +90,8 @@ public class SimulatedDepsTest extends SimulatedAccordCommandStoreTestBase long outOfRangeToken = token - 10; if (outOfRangeToken == Long.MIN_VALUE) // if this wraps around that is fine, just can't be min outOfRangeToken++; - Key key = new PartitionKey(tbl.id, tbl.partitioner.decorateKey(LongToken.keyForToken(token))); - Key outOfRangeKey = new PartitionKey(tbl.id, tbl.partitioner.decorateKey(LongToken.keyForToken(outOfRangeToken))); + RoutingKey key = new TokenKey(tbl.id, new LongToken(token)); + RoutingKey outOfRangeKey = new TokenKey(tbl.id, new LongToken(outOfRangeToken)); Txn keyTxn = createTxn(wrapInTxn("INSERT INTO " + tbl + "(pk, value) VALUES (?, ?)", "INSERT INTO " + tbl + "(pk, value) VALUES (?, ?)"), Arrays.asList(LongToken.keyForToken(token), 42, @@ -111,7 +112,7 @@ public class SimulatedDepsTest extends SimulatedAccordCommandStoreTestBase List rangeConflicts = new ArrayList<>(numSamples); for (int i = 0; i < numSamples; i++) { - instance.maybeCacheEvict((Keys) keyTxn.keys(), partialRange); + instance.maybeCacheEvict(((Keys) keyTxn.keys()).toParticipants(), partialRange); for (int j = 0; j < numConflictKeyTxns; j++) outOfRangeKeyConflicts.add(assertDepsMessage(instance, rs.pick(DepsMessage.values()), conflictingKeyTxn, conflictingRoute, Map.of(outOfRangeKey, outOfRangeKeyConflicts))); @@ -153,9 +154,9 @@ public class SimulatedDepsTest extends SimulatedAccordCommandStoreTestBase List rangeConflicts = new ArrayList<>(numSamples); for (int i = 0; i < numSamples; i++) { - instance.maybeCacheEvict(keys, ranges); - keyConflicts.add(assertDepsMessage(instance, rs.pick(DepsMessage.values()), keyTxn, keyRoute, keyConflicts(keyConflicts, keys))); - rangeConflicts.add(assertDepsMessage(instance, rs.pick(DepsMessage.values()), rangeTxn, rangeRoute, keyConflicts(keyConflicts, keys), rangeConflicts(rangeConflicts, instance.slice(ranges)))); + instance.maybeCacheEvict(keyRoute, ranges); + keyConflicts.add(assertDepsMessage(instance, rs.pick(DepsMessage.values()), keyTxn, keyRoute, keyConflicts(keyConflicts, keyRoute))); + rangeConflicts.add(assertDepsMessage(instance, rs.pick(DepsMessage.values()), rangeTxn, rangeRoute, keyConflicts(keyConflicts, keyRoute), rangeConflicts(rangeConflicts, instance.slice(ranges)))); } } }); @@ -187,9 +188,9 @@ public class SimulatedDepsTest extends SimulatedAccordCommandStoreTestBase Txn rangeTxn = createTxn(Txn.Kind.ExclusiveSyncPoint, partialRange); try { - instance.maybeCacheEvict(keys, partialRange); - keyConflicts.add(assertDepsMessage(instance, rs.pick(DepsMessage.values()), keyTxn, keyRoute, keyConflicts(keyConflicts, keys))); - rangeConflicts.put(partialRange.get(0), Collections.singletonList(assertDepsMessage(instance, rs.pick(DepsMessage.values()), rangeTxn, rangeRoute, keyConflicts(keyConflicts, keys), rangeConflicts))); + instance.maybeCacheEvict(keyRoute, partialRange); + keyConflicts.add(assertDepsMessage(instance, rs.pick(DepsMessage.values()), keyTxn, keyRoute, keyConflicts(keyConflicts, keyRoute))); + rangeConflicts.put(partialRange.get(0), Collections.singletonList(assertDepsMessage(instance, rs.pick(DepsMessage.values()), rangeTxn, rangeRoute, keyConflicts(keyConflicts, keyRoute), rangeConflicts))); } catch (Throwable t) { @@ -231,12 +232,12 @@ public class SimulatedDepsTest extends SimulatedAccordCommandStoreTestBase Ranges partialRange = Ranges.of(rs.nextBoolean() ? left : right); try { - instance.maybeCacheEvict(keys, partialRange); - keyConflicts.add(assertDepsMessage(instance, rs.pick(DepsMessage.values()), keyTxn, keyRoute, keyConflicts(keyConflicts, keys))); + instance.maybeCacheEvict(keyRoute, partialRange); + keyConflicts.add(assertDepsMessage(instance, rs.pick(DepsMessage.values()), keyTxn, keyRoute, keyConflicts(keyConflicts, keyRoute))); FullRangeRoute rangeRoute = partialRange.toRoute(pk.toUnseekable()); Txn rangeTxn = createTxn(Txn.Kind.ExclusiveSyncPoint, partialRange); - rangeConflicts.get(partialRange.get(0)).add(assertDepsMessage(instance, rs.pick(DepsMessage.values()), rangeTxn, rangeRoute, keyConflicts(keyConflicts, keys), rangeConflicts)); + rangeConflicts.get(partialRange.get(0)).add(assertDepsMessage(instance, rs.pick(DepsMessage.values()), rangeTxn, rangeRoute, keyConflicts(keyConflicts, keyRoute), rangeConflicts)); } catch (Throwable t) { diff --git a/test/unit/org/apache/cassandra/service/accord/SimulatedMultiKeyAndRangeTest.java b/test/unit/org/apache/cassandra/service/accord/SimulatedMultiKeyAndRangeTest.java index 11497133b3..feaddeff8c 100644 --- a/test/unit/org/apache/cassandra/service/accord/SimulatedMultiKeyAndRangeTest.java +++ b/test/unit/org/apache/cassandra/service/accord/SimulatedMultiKeyAndRangeTest.java @@ -73,7 +73,7 @@ public class SimulatedMultiKeyAndRangeTest extends SimulatedAccordCommandStoreTe Gen.LongGen tokenGen = tokenDistribution.next(rs); Gen domainGen = domainDistribution.next(rs); Gen msgGen = msgDistribution.next(rs); - Map> keyConflicts = new HashMap<>(); + Map> keyConflicts = new HashMap<>(); RangeTree rangeConflicts = RTree.create(RangeTreeRangeAccessor.instance); Gen.IntGen keyCountGen = keyDistribution.next(rs); @@ -97,13 +97,13 @@ public class SimulatedMultiKeyAndRangeTest extends SimulatedAccordCommandStoreTe binds.add(42); }); Txn txn = createTxn(wrapInTxn(inserts), binds); - FullRoute route = keys.toRoute(keys.get(0).toUnseekable()); + FullRoute route = keys.toRoute(keys.get(0).toUnseekable()); - Map> expectedConflicts = new HashMap<>(); - keys.forEach(k -> expectedConflicts.put(k, keyConflicts.computeIfAbsent(k, ignore -> new ArrayList<>()))); + Map> expectedConflicts = new HashMap<>(); + route.forEach(k -> expectedConflicts.put(k, keyConflicts.computeIfAbsent(k, ignore -> new ArrayList<>()))); TxnId id = assertDepsMessage(instance, msgGen.next(rs), txn, route, expectedConflicts, Collections.emptyMap()); - keys.forEach(k -> keyConflicts.get(k).add(id)); + route.forEach(k -> keyConflicts.get(k).add(id)); } break; case Range: @@ -133,7 +133,7 @@ public class SimulatedMultiKeyAndRangeTest extends SimulatedAccordCommandStoreTe FullRangeRoute route = ranges.toRoute(ranges.get(0).end()); Txn txn = createTxn(Txn.Kind.ExclusiveSyncPoint, ranges); - Map> expectedKeyConflicts = keyConflicts.entrySet().stream() + Map> expectedKeyConflicts = keyConflicts.entrySet().stream() .filter(e -> ranges.contains(e.getKey())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); Map> expectedRangeConflicts = new HashMap<>(); diff --git a/test/unit/org/apache/cassandra/service/accord/SimulatedRandomKeysWithRangeConflictTest.java b/test/unit/org/apache/cassandra/service/accord/SimulatedRandomKeysWithRangeConflictTest.java index 31880d3297..9e9ac1fad7 100644 --- a/test/unit/org/apache/cassandra/service/accord/SimulatedRandomKeysWithRangeConflictTest.java +++ b/test/unit/org/apache/cassandra/service/accord/SimulatedRandomKeysWithRangeConflictTest.java @@ -27,14 +27,15 @@ import java.util.Map; import org.junit.Test; -import accord.api.Key; +import accord.api.RoutingKey; import accord.primitives.FullRangeRoute; import accord.primitives.FullRoute; import accord.primitives.Keys; import accord.primitives.Ranges; import accord.primitives.Txn; import accord.primitives.TxnId; -import org.apache.cassandra.service.accord.api.PartitionKey; +import org.apache.cassandra.dht.Murmur3Partitioner.LongToken; +import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; import static accord.utils.Property.qt; import static org.apache.cassandra.dht.Murmur3Partitioner.LongToken.keyForToken; @@ -55,18 +56,18 @@ public class SimulatedRandomKeysWithRangeConflictTest extends SimulatedAccordCom AccordKeyspace.unsafeClear(); try (var instance = new SimulatedAccordCommandStore(rs)) { - Map> keyConflicts = new HashMap<>(); + Map> keyConflicts = new HashMap<>(); List rangeConflicts = new ArrayList<>(numSamples); for (int i = 0; i < numSamples; i++) { long token = rs.nextLong(Long.MIN_VALUE + 1, Long.MAX_VALUE); - Key key = new PartitionKey(tbl.id, tbl.partitioner.decorateKey(keyForToken(token))); + RoutingKey key = new TokenKey(tbl.id, new LongToken(token)); Txn keyTxn = createTxn(wrapInTxn("INSERT INTO " + tbl + "(pk, value) VALUES (?, ?)"), Arrays.asList(keyForToken(token), 42)); Keys keys = (Keys) keyTxn.keys(); - FullRoute keyRoute = keys.toRoute(keys.get(0).toUnseekable()); + FullRoute keyRoute = keys.toRoute(keys.get(0).toUnseekable()); - instance.maybeCacheEvict((Keys) keyTxn.keys(), wholeRange); + instance.maybeCacheEvict(keyRoute, wholeRange); // the full range is (-Inf, +Inf] but the store could be [(-Inf, Number], (Number, +Inf]], so need to slice to the store to get a matching range Ranges wholeRangeSlicedShard = instance.slice(wholeRange); diff --git a/test/unit/org/apache/cassandra/service/accord/async/AsyncLoaderTest.java b/test/unit/org/apache/cassandra/service/accord/async/AsyncLoaderTest.java index 6ef6a9cde8..3a29e63e99 100644 --- a/test/unit/org/apache/cassandra/service/accord/async/AsyncLoaderTest.java +++ b/test/unit/org/apache/cassandra/service/accord/async/AsyncLoaderTest.java @@ -31,18 +31,20 @@ import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; -import accord.api.Key; +import accord.api.RoutingKey; import accord.local.cfk.CommandsForKey; import accord.impl.TimestampsForKey; import accord.local.Command; import accord.local.KeyHistory; -import accord.primitives.Keys; import accord.primitives.PartialTxn; +import accord.primitives.RoutingKeys; import accord.primitives.TxnId; import accord.utils.async.AsyncChains; import accord.utils.async.AsyncResult; import accord.utils.async.AsyncResults; import org.apache.cassandra.SchemaLoader; +import org.apache.cassandra.concurrent.ExecutorFactory; +import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.concurrent.ManualExecutor; import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.service.StorageService; @@ -53,9 +55,11 @@ import org.apache.cassandra.service.accord.AccordSafeCommand; import org.apache.cassandra.service.accord.AccordSafeState; import org.apache.cassandra.service.accord.AccordSafeTimestampsForKey; import org.apache.cassandra.service.accord.AccordStateCache; +import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; import org.apache.cassandra.service.accord.api.PartitionKey; import org.apache.cassandra.service.accord.async.AsyncOperation.Context; import org.apache.cassandra.utils.concurrent.AsyncPromise; +import org.apache.cassandra.utils.concurrent.Condition; import static accord.local.KeyHistory.COMMANDS; import static accord.local.KeyHistory.TIMESTAMPS; @@ -95,10 +99,10 @@ public class AsyncLoaderTest AccordStateCache.Instance commandCache = commandStore.commandCache(); commandStore.executeBlocking(() -> commandStore.setCapacity(1024)); - AccordStateCache.Instance timestampsCache = commandStore.timestampsForKeyCache(); + AccordStateCache.Instance timestampsCache = commandStore.timestampsForKeyCache(); TxnId txnId = txnId(1, clock.incrementAndGet(), 1); PartialTxn txn = createPartialTxn(0); - PartitionKey key = (PartitionKey) Iterables.getOnlyElement(txn.keys()); + TokenKey key = ((PartitionKey) Iterables.getOnlyElement(txn.keys())).toUnseekable(); // acquire / release @@ -108,13 +112,13 @@ public class AsyncLoaderTest AccordCachingState safeCommandGlobal = safeCommand.global(); commandCache.release(safeCommand); - timestampsCache.unsafeSetLoadFunction(k -> new TimestampsForKey((PartitionKey) k)); + timestampsCache.unsafeSetLoadFunction(k -> new TimestampsForKey((TokenKey) k)); AccordSafeTimestampsForKey safeTimestamps = timestampsCache.acquire(key); testLoad(executor, safeTimestamps, new TimestampsForKey(key)); - AccordCachingState safeTimestampsGlobal = safeTimestamps.global(); + AccordCachingState safeTimestampsGlobal = safeTimestamps.global(); timestampsCache.release(safeTimestamps); - AsyncLoader loader = new AsyncLoader(commandStore, singleton(txnId), Keys.of(key), TIMESTAMPS); + AsyncLoader loader = new AsyncLoader(commandStore, singleton(txnId), RoutingKeys.of(key), TIMESTAMPS); // everything is cached, so the loader should return immediately commandStore.executeBlocking(() -> { @@ -139,7 +143,7 @@ public class AsyncLoaderTest AccordCommandStore commandStore = createAccordCommandStore(clock::incrementAndGet, "ks", "tbl"); TxnId txnId = txnId(1, clock.incrementAndGet(), 1); PartialTxn txn = createPartialTxn(0); - PartitionKey key = (PartitionKey) Iterables.getOnlyElement(txn.keys()); + TokenKey key = ((PartitionKey) Iterables.getOnlyElement(txn.keys())).toUnseekable(); // create / persist AccordSafeCommand safeCommand = new AccordSafeCommand(loaded(txnId, null)); @@ -154,7 +158,7 @@ public class AsyncLoaderTest AccordKeyspace.getTimestampsForKeyMutation(commandStore.id(), null, timestamps.current(), commandStore.nextSystemTimestampMicros()).apply(); // resources are on disk only, so the loader should suspend... - AsyncLoader loader = new AsyncLoader(commandStore, singleton(txnId), Keys.of(key), TIMESTAMPS); + AsyncLoader loader = new AsyncLoader(commandStore, singleton(txnId), RoutingKeys.of(key), TIMESTAMPS); AsyncPromise cbFired = new AsyncPromise<>(); Context context = new Context(); commandStore.executeBlocking(() -> { @@ -191,7 +195,7 @@ public class AsyncLoaderTest AccordStateCache.Instance commandCache = commandStore.commandCache(); TxnId txnId = txnId(1, clock.incrementAndGet(), 1); PartialTxn txn = createPartialTxn(0); - PartitionKey key = (PartitionKey) Iterables.getOnlyElement(txn.keys()); + TokenKey key = ((PartitionKey) Iterables.getOnlyElement(txn.keys())).toUnseekable(); // acquire /release, create / persist commandCache.unsafeSetLoadFunction(id -> notDefined(id, txn)); @@ -202,7 +206,7 @@ public class AsyncLoaderTest AccordKeyspace.getTimestampsForKeyMutation(commandStore.id(), null, new TimestampsForKey(key), commandStore.nextSystemTimestampMicros()).apply(); // resources are on disk only, so the loader should suspend... - AsyncLoader loader = new AsyncLoader(commandStore, singleton(txnId), Keys.of(key), TIMESTAMPS); + AsyncLoader loader = new AsyncLoader(commandStore, singleton(txnId), RoutingKeys.of(key), TIMESTAMPS); AsyncPromise cbFired = new AsyncPromise<>(); Context context = new Context(); commandStore.executeBlocking(() -> { @@ -242,7 +246,7 @@ public class AsyncLoaderTest AccordStateCache.Instance commandCache = commandStore.commandCache(); TxnId txnId = txnId(1, clock.incrementAndGet(), 1); PartialTxn txn = createPartialTxn(0); - PartitionKey key = (PartitionKey) Iterables.getOnlyElement(txn.keys()); + TokenKey key = ((PartitionKey) Iterables.getOnlyElement(txn.keys())).toUnseekable(); commandCache.unsafeSetLoadFunction(id -> { Assert.assertEquals(txnId, id); return notDefined(id, txn); }); AccordSafeCommand safeCommand = commandCache.acquire(txnId); @@ -250,7 +254,7 @@ public class AsyncLoaderTest Assert.assertTrue(commandCache.isReferenced(txnId)); Assert.assertFalse(commandCache.isLoaded(txnId)); - AsyncLoader loader = new AsyncLoader(commandStore, singleton(txnId), Keys.of(key), KeyHistory.NONE); + AsyncLoader loader = new AsyncLoader(commandStore, singleton(txnId), RoutingKeys.of(key), KeyHistory.NONE); // since there's a read future associated with the txnId, we'll wait for it to load AsyncPromise cbFired = new AsyncPromise<>(); @@ -284,7 +288,9 @@ public class AsyncLoaderTest public void failedLoadTest() throws Throwable { AtomicLong clock = new AtomicLong(0); - AccordCommandStore commandStore = createAccordCommandStore(clock::incrementAndGet, "ks", "tbl"); + ExecutorPlus executor = ExecutorFactory.Global.executorFactory().sequential("GlobalLogFollower"); + AccordCommandStore commandStore = createAccordCommandStore(clock::incrementAndGet, "ks", "tbl", executor, executor); + TxnId txnId1 = txnId(1, clock.incrementAndGet(), 1); TxnId txnId2 = txnId(1, clock.incrementAndGet(), 1); @@ -292,27 +298,38 @@ public class AsyncLoaderTest AsyncResult.Settable callback = AsyncResults.settable(); RuntimeException failure = new RuntimeException(); + Condition startResponding = Condition.newOneTimeCondition(); + Condition loadedAll = Condition.newOneTimeCondition(); execute(commandStore, () -> { AtomicInteger loadCalls = new AtomicInteger(); commandStore.commandCache().unsafeSetLoadFunction(txnId -> { + startResponding.awaitUninterruptibly(); loadCalls.incrementAndGet(); - if (txnId.equals(txnId1)) + + if (!txnId.equals(txnId1) && !txnId.equals(txnId2)) + throw new AssertionError("Unknown txnId: " + txnId); + + if (loadCalls.get() == 2) + { + loadedAll.signal(); throw failure; - else if (txnId.equals(txnId2)) - return notDefined(txnId, null); - throw new AssertionError("Unknown txnId: " + txnId); + } + + return notDefined(txnId, null); }); - AsyncLoader loader = new AsyncLoader(commandStore, ImmutableList.of(txnId1, txnId2), Keys.EMPTY, KeyHistory.COMMANDS); + AsyncLoader loader = new AsyncLoader(commandStore, ImmutableList.of(txnId1, txnId2), RoutingKeys.EMPTY, KeyHistory.COMMANDS); - boolean result = loader.load(new Context(), (u, t) -> { + boolean result = loader.load(new Context(), (u, t) -> { Assert.assertFalse(callback.isDone()); Assert.assertNull(u); Assert.assertEquals(failure, t); callback.trySuccess(null); }); + startResponding.signal(); + loadedAll.awaitUninterruptibly(); Assert.assertFalse(result); Assert.assertEquals(2, loadCalls.get()); }); @@ -348,7 +365,7 @@ public class AsyncLoaderTest Assert.assertEquals(AccordCachingState.Status.SAVING, commandCache.getUnsafe(txnId).status()); // since the command is still saving, the loader shouldn't be able to acquire a reference - AsyncLoader loader = new AsyncLoader(commandStore, singleton(txnId), Keys.of(), KeyHistory.NONE); + AsyncLoader loader = new AsyncLoader(commandStore, singleton(txnId), RoutingKeys.of(), KeyHistory.NONE); AsyncPromise cbFired = new AsyncPromise<>(); Context context = new Context(); commandStore.executeBlocking(() -> { @@ -382,10 +399,10 @@ public class AsyncLoaderTest @Test public void inProgressTFKSaveTest() { - inProgressCFKSaveTest(TIMESTAMPS, AccordCommandStore::timestampsForKeyCache, context -> context.timestampsForKey, TimestampsForKey::new, (tfk, c) -> new TimestampsForKey(tfk.key(), c.executeAt(), c.executeAt().hlc(), c.executeAt())); + inProgressCFKSaveTest(TIMESTAMPS, AccordCommandStore::timestampsForKeyCache, context -> context.timestampsForKey, TimestampsForKey::new, (tfk, c) -> new TimestampsForKey(tfk.key(), c.executeAt(), c.executeAt().hlc(), c.txnId(), c.executeAt())); } - private , C extends AccordStateCache.Instance> void inProgressCFKSaveTest(KeyHistory history, Function getter, Function> inContext, Function initialiser, BiFunction update) + private , C extends AccordStateCache.Instance> void inProgressCFKSaveTest(KeyHistory history, Function getter, Function> inContext, Function initialiser, BiFunction update) { AtomicLong clock = new AtomicLong(0); ManualExecutor executor = new ManualExecutor(); @@ -397,11 +414,11 @@ public class AsyncLoaderTest TxnId txnId = txnId(1, clock.incrementAndGet(), 1); PartialTxn txn = createPartialTxn(0); - PartitionKey key = (PartitionKey) Iterables.getOnlyElement(txn.keys()); + TokenKey key = ((PartitionKey) Iterables.getOnlyElement(txn.keys())).toUnseekable(); Command preaccepted = preaccepted(txnId, txn, txnId); // acquire / release - T2 safe = cache.acquireOrInitialize(key, k -> initialiser.apply((Key)k)); + T2 safe = cache.acquireOrInitialize(key, k -> initialiser.apply(k)); safe.preExecute(); safe.set(update.apply(safe.current(), preaccepted)); cache.release(safe); @@ -411,7 +428,7 @@ public class AsyncLoaderTest Assert.assertEquals(AccordCachingState.Status.SAVING, cache.getUnsafe(key).status()); // since the command is still saving, the loader shouldn't be able to acquire a reference - AsyncLoader loader = new AsyncLoader(commandStore, emptyList(), Keys.of(key), history); + AsyncLoader loader = new AsyncLoader(commandStore, emptyList(), RoutingKeys.of(key), history); AsyncPromise cbFired = new AsyncPromise<>(); Context context = new Context(); commandStore.executeBlocking(() -> { diff --git a/test/unit/org/apache/cassandra/service/accord/async/AsyncOperationTest.java b/test/unit/org/apache/cassandra/service/accord/async/AsyncOperationTest.java index aca8702454..31988224b4 100644 --- a/test/unit/org/apache/cassandra/service/accord/async/AsyncOperationTest.java +++ b/test/unit/org/apache/cassandra/service/accord/async/AsyncOperationTest.java @@ -27,6 +27,8 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiConsumer; import java.util.function.Consumer; +import accord.local.StoreParticipants; +import accord.primitives.Participants; import accord.primitives.Route; import accord.utils.DefaultRandom; import com.google.common.collect.Iterables; @@ -48,7 +50,7 @@ import accord.local.Command; import accord.local.PreLoadContext; import accord.local.SafeCommand; import accord.local.SafeCommandStore; -import accord.local.SaveStatus; +import accord.primitives.SaveStatus; import accord.primitives.Ballot; import accord.primitives.FullRoute; import accord.primitives.Keys; @@ -78,6 +80,7 @@ import org.apache.cassandra.service.accord.AccordSafeCommand; import org.apache.cassandra.service.accord.AccordSafeCommandStore; import org.apache.cassandra.service.accord.AccordStateCache; import org.apache.cassandra.service.accord.AccordTestUtils; +import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; import org.apache.cassandra.service.accord.api.PartitionKey; import org.apache.cassandra.utils.AssertionUtils; import org.apache.cassandra.utils.FBUtilities; @@ -150,7 +153,8 @@ public class AsyncOperationTest TxnId txnId = txnId(1, clock.incrementAndGet(), 1); getUninterruptibly(commandStore.execute(contextFor(txnId), safe -> { - SafeCommand command = safe.get(txnId, txnId, safe.ranges().currentRanges()); + StoreParticipants participants = StoreParticipants.empty(txnId); + SafeCommand command = safe.get(txnId, participants); Assert.assertNotNull(command); })); @@ -163,7 +167,7 @@ public class AsyncOperationTest { AccordCommandStore commandStore = createAccordCommandStore(clock::incrementAndGet, "ks", "tbl"); Txn txn = AccordTestUtils.createWriteTxn((int)clock.incrementAndGet()); - PartitionKey key = (PartitionKey) Iterables.getOnlyElement(txn.keys()); + TokenKey key = ((PartitionKey) Iterables.getOnlyElement(txn.keys())).toUnseekable(); getUninterruptibly(commandStore.execute(contextFor(key), instance -> { SafeCommandsForKey cfk = ((AccordSafeCommandStore) instance).maybeCommandsForKey(key); @@ -211,7 +215,7 @@ public class AsyncOperationTest try { - Command command = getUninterruptibly(commandStore.submit(contextFor(txnId, partialTxn.keys(), COMMANDS), safe -> { + Command command = getUninterruptibly(commandStore.submit(contextFor(txnId, route, COMMANDS), safe -> { CheckedCommands.preaccept(safe, txnId, partialTxn, route, appendDiffToLog(commandStore)); CheckedCommands.commit(safe, SaveStatus.Stable, Ballot.ZERO, txnId, route, partialTxn, executeAt, deps, appendDiffToLog(commandStore)); return safe.ifInitialised(txnId).current(); @@ -258,9 +262,9 @@ public class AsyncOperationTest try { - Command command = getUninterruptibly(commandStore.submit(contextFor(txnId, partialTxn.keys(), COMMANDS), safe -> { + Command command = getUninterruptibly(commandStore.submit(contextFor(txnId, route, COMMANDS), safe -> { CheckedCommands.preaccept(safe, txnId, partialTxn, route, appendDiffToLog(commandStore)); - CheckedCommands.accept(safe, txnId, Ballot.ZERO, partialRoute, partialTxn.keys(), executeAt, deps, appendDiffToLog(commandStore)); + CheckedCommands.accept(safe, txnId, Ballot.ZERO, partialRoute, executeAt, deps, appendDiffToLog(commandStore)); CheckedCommands.commit(safe, SaveStatus.Committed, Ballot.ZERO, txnId, route, partialTxn, executeAt, deps, appendDiffToLog(commandStore)); CheckedCommands.commit(safe, SaveStatus.Stable, Ballot.ZERO, txnId, route, partialTxn, executeAt, deps, appendDiffToLog(commandStore)); return safe.ifInitialised(txnId).current(); @@ -370,13 +374,13 @@ public class AsyncOperationTest .check((rs, ids) -> { before(); // truncate tables - - assertNoReferences(commandStore, ids, keys); + Participants participants = keys.toParticipants(); + assertNoReferences(commandStore, ids, participants); createCommand(commandStore, rs, ids); - awaitDone(commandStore, ids, keys); - assertNoReferences(commandStore, ids, keys); + awaitDone(commandStore, ids, participants); + assertNoReferences(commandStore, ids, participants); - PreLoadContext ctx = contextFor(null, ids, keys, COMMANDS); + PreLoadContext ctx = contextFor(null, ids, participants, COMMANDS); Consumer consumer = Mockito.mock(Consumer.class); Map failed = selectFailedTxn(rs, ids); @@ -396,10 +400,10 @@ public class AsyncOperationTest Mockito.verifyNoInteractions(consumer); - assertNoReferences(commandStore, ids, keys); + assertNoReferences(commandStore, ids, participants); // the first failed load causes the whole operation to fail, so some ids may still be pending // to make sure the next operation does not see a PENDING that will fail, wait for all loads to complete - awaitDone(commandStore, ids, keys); + awaitDone(commandStore, ids, participants); // can we recover? commandStore.commandCache().unsafeSetLoadFunction(txnId -> { @@ -412,8 +416,8 @@ public class AsyncOperationTest }); }); getUninterruptibly(o2); - awaitDone(commandStore, ids, keys); - assertNoReferences(commandStore, ids, keys); + awaitDone(commandStore, ids, participants); + assertNoReferences(commandStore, ids, participants); }); } @@ -432,10 +436,11 @@ public class AsyncOperationTest logger.info("Test #{}", counter.incrementAndGet()); before(); // truncate tables - assertNoReferences(commandStore, ids, keys); + Participants participants = keys.toParticipants(); + assertNoReferences(commandStore, ids, participants); createCommand(commandStore, rs, ids); - PreLoadContext ctx = contextFor(null, ids, keys, COMMANDS); + PreLoadContext ctx = contextFor(null, ids, participants, COMMANDS); Consumer consumer = Mockito.mock(Consumer.class); String errorMsg = "txn_ids " + ids; @@ -449,7 +454,7 @@ public class AsyncOperationTest .hasMessage(errorMsg) .hasNoSuppressedExceptions(); - assertNoReferences(commandStore, ids, keys); + assertNoReferences(commandStore, ids, participants); }); } @@ -482,7 +487,7 @@ public class AsyncOperationTest return failed; } - private static void assertNoReferences(AccordCommandStore commandStore, List ids, Keys keys) + private static void assertNoReferences(AccordCommandStore commandStore, List ids, Participants keys) { AssertionError error = null; try @@ -533,7 +538,7 @@ public class AsyncOperationTest if (error != null) throw error; } - private static void awaitDone(AccordCommandStore commandStore, List ids, Keys keys) + private static void awaitDone(AccordCommandStore commandStore, List ids, Participants keys) { awaitDone(commandStore.commandCache(), ids); awaitDone(commandStore.commandsForKeyCache(), keys); diff --git a/test/unit/org/apache/cassandra/service/accord/async/SimulatedAsyncOperationTest.java b/test/unit/org/apache/cassandra/service/accord/async/SimulatedAsyncOperationTest.java index 9ade467954..39b12a862a 100644 --- a/test/unit/org/apache/cassandra/service/accord/async/SimulatedAsyncOperationTest.java +++ b/test/unit/org/apache/cassandra/service/accord/async/SimulatedAsyncOperationTest.java @@ -26,18 +26,18 @@ import java.util.function.BooleanSupplier; import org.junit.Before; import org.junit.Test; -import accord.api.Key; +import accord.api.RoutingKey; import accord.impl.basic.SimulatedFault; import accord.local.PreLoadContext; import accord.local.SafeCommandStore; import accord.messages.PreAccept; import accord.primitives.FullRoute; -import accord.primitives.Keys; import accord.primitives.Range; import accord.primitives.Ranges; -import accord.primitives.Seekables; +import accord.primitives.RoutingKeys; import accord.primitives.Txn; import accord.primitives.TxnId; +import accord.primitives.Unseekables; import accord.utils.Gen; import accord.utils.Gens; import accord.utils.RandomSource; @@ -51,7 +51,6 @@ import org.apache.cassandra.service.accord.SimulatedAccordCommandStore; import org.apache.cassandra.service.accord.SimulatedAccordCommandStoreTestBase; import org.apache.cassandra.service.accord.TokenRange; import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; -import org.apache.cassandra.service.accord.api.PartitionKey; import org.apache.cassandra.utils.Pair; import org.assertj.core.api.Assertions; @@ -90,10 +89,10 @@ public class SimulatedAsyncOperationTest extends SimulatedAccordCommandStoreTest long minToken = 0; long maxToken = numKeys; - Gen keyGen = Gens.longs().between(minToken + 1, maxToken).map(t -> new PartitionKey(tbl.id, tbl.partitioner.decorateKey(LongToken.keyForToken(t)))); - Gen keysGen = Gens.lists(keyGen).unique().ofSizeBetween(1, 10).map(l -> Keys.of(l)); + Gen keyGen = Gens.longs().between(minToken + 1, maxToken).map(t -> new TokenKey(tbl.id, new LongToken(t))); + Gen keysGen = Gens.lists(keyGen).unique().ofSizeBetween(1, 10).map(l -> RoutingKeys.of(l)); Gen rangesGen = Gens.lists(rangeInsideRange(tbl.id, minToken, maxToken)).uniqueBestEffort().ofSizeBetween(1, 10).map(l -> Ranges.of(l.toArray(Range[]::new))); - Gen> seekablesGen = Gens.oneOf(keysGen, rangesGen); + Gen> unseekablesGen = Gens.oneOf(keysGen, rangesGen); Gen>> txnGen = randomTxn(mixedDomainGen.next(rs), mixedTokenGen.next(rs)); try (var instance = new SimulatedAccordCommandStore(rs)) @@ -107,7 +106,7 @@ public class SimulatedAsyncOperationTest extends SimulatedAccordCommandStoreTest { case Task: { - PreLoadContext ctx = PreLoadContext.contextFor(seekablesGen.next(rs)); + PreLoadContext ctx = PreLoadContext.contextFor(unseekablesGen.next(rs)); instance.maybeCacheEvict(ctx.keys()); operation(instance, ctx, actionGen.next(rs), rs::nextBoolean).begin(counter); } @@ -129,7 +128,7 @@ public class SimulatedAsyncOperationTest extends SimulatedAccordCommandStoreTest return result; } }; - instance.maybeCacheEvict(txn.keys()); + instance.maybeCacheEvict(txn.keys().toParticipants()); instance.processAsync(preAccept).begin(counter); } break; diff --git a/test/unit/org/apache/cassandra/service/accord/serializers/CheckStatusSerializersTest.java b/test/unit/org/apache/cassandra/service/accord/serializers/CheckStatusSerializersTest.java index d966757e95..d62bd1b42d 100644 --- a/test/unit/org/apache/cassandra/service/accord/serializers/CheckStatusSerializersTest.java +++ b/test/unit/org/apache/cassandra/service/accord/serializers/CheckStatusSerializersTest.java @@ -25,9 +25,8 @@ import java.util.List; import org.junit.Test; import accord.api.RoutingKey; -import accord.coordinate.Infer; -import accord.local.SaveStatus; -import accord.messages.CheckStatus.FoundKnownMap; +import accord.primitives.SaveStatus; +import accord.primitives.KnownMap; import accord.primitives.Ballot; import accord.primitives.FullKeyRoute; import accord.primitives.Routable; @@ -61,7 +60,7 @@ public class CheckStatusSerializersTest public void serde() { DataOutputBuffer buffer = new DataOutputBuffer(); - qt().forAll(foundKnownMap()).check(map -> Assertions.assertThat(serde(CheckStatusSerializers.foundKnownMap, MessagingService.Version.CURRENT.value, buffer, map)).isEqualTo(map)); + qt().forAll(foundKnownMap()).check(map -> Assertions.assertThat(serde(CheckStatusSerializers.knownMap, MessagingService.Version.CURRENT.value, buffer, map)).isEqualTo(map)); } private static T serde(IVersionedSerializer serializer, int version, DataOutputBuffer buffer, T value) throws IOException @@ -76,11 +75,10 @@ public class CheckStatusSerializersTest } } - private static Gen foundKnownMap() + private static Gen foundKnownMap() { return rs -> { SaveStatus saveStatus = Gens.pick(SaveStatus.values()).next(rs); - Infer.InvalidIfNot invalidIfNot = Gens.pick(Infer.InvalidIfNot.values()).next(rs); Ballot promised = AccordGens.ballot().next(rs); Routable.Domain domain = Gens.pick(Routable.Domain.values()).next(rs); Unseekables keysOrRanges; @@ -101,7 +99,7 @@ public class CheckStatusSerializersTest default: throw new AssertionError("Unknown domain"); } - return FoundKnownMap.create(keysOrRanges, saveStatus, invalidIfNot, promised); + return KnownMap.create(keysOrRanges, saveStatus); }; } } \ No newline at end of file diff --git a/test/unit/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializerTest.java b/test/unit/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializerTest.java index 93d66eb1ec..6f41f00830 100644 --- a/test/unit/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializerTest.java +++ b/test/unit/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializerTest.java @@ -39,6 +39,8 @@ import org.junit.BeforeClass; import org.junit.Test; import accord.api.Key; +import accord.api.RoutingKey; +import accord.local.StoreParticipants; import accord.local.cfk.CommandsForKey; import accord.local.cfk.CommandsForKey.InternalStatus; import accord.local.Command; @@ -47,8 +49,8 @@ import accord.local.cfk.CommandsForKey.Unmanaged; import accord.local.CommonAttributes; import accord.local.CommonAttributes.Mutable; import accord.local.Node; -import accord.local.SaveStatus; -import accord.local.Status; +import accord.primitives.SaveStatus; +import accord.primitives.Status; import accord.primitives.Ballot; import accord.primitives.KeyDeps; import accord.primitives.PartialDeps; @@ -66,27 +68,28 @@ import accord.utils.RandomSource; import accord.utils.SortedArrays; import org.agrona.collections.Int2ObjectHashMap; import org.apache.cassandra.SchemaLoader; -import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.dht.Murmur3Partitioner; +import org.apache.cassandra.dht.Token; import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.accord.AccordTestUtils; -import org.apache.cassandra.service.accord.api.PartitionKey; +import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey; import org.apache.cassandra.service.accord.txn.TxnData; import org.apache.cassandra.service.accord.txn.TxnWrite; import org.apache.cassandra.simulator.RandomSource.Choices; import org.apache.cassandra.utils.AccordGenerators; import org.apache.cassandra.utils.CassandraGenerators; -import static accord.local.Status.Durability.NotDurable; -import static accord.local.Status.KnownExecuteAt.ExecuteAtErased; -import static accord.local.Status.KnownExecuteAt.ExecuteAtUnknown; +import static accord.primitives.Status.Durability.NotDurable; +import static accord.primitives.Known.KnownExecuteAt.ExecuteAtErased; +import static accord.primitives.Known.KnownExecuteAt.ExecuteAtUnknown; import static accord.utils.Property.qt; import static accord.utils.SortedArrays.Search.FAST; import static org.apache.cassandra.cql3.statements.schema.CreateTableStatement.parse; import static org.apache.cassandra.service.accord.AccordTestUtils.createPartialTxn; +// TODO (required): test statusOverrides public class CommandsForKeySerializerTest { @BeforeClass @@ -125,14 +128,14 @@ public class CommandsForKeySerializerTest if (saveStatus.known.isDefinitionKnown()) mutable.partialTxn(txn); - mutable.route(txn.keys().toRoute(txn.keys().get(0).someIntersectingRoutingKey(null))); + mutable.setParticipants(StoreParticipants.all(txn.keys().toRoute(txn.keys().get(0).someIntersectingRoutingKey(null)))); mutable.durability(NotDurable); if (saveStatus.known.deps.hasProposedOrDecidedDeps()) { try (KeyDeps.Builder builder = KeyDeps.builder();) { for (TxnId id : deps) - builder.add((Key)txn.keys().get(0), id); + builder.add(((Key)txn.keys().get(0)).toUnseekable(), id); mutable.partialDeps(new PartialDeps(AccordTestUtils.fullRange(txn), builder.build(), RangeDeps.NONE, KeyDeps.NONE)); } } @@ -183,7 +186,7 @@ public class CommandsForKeySerializerTest else return Command.SerializerSupport.truncatedApply(attributes(), saveStatus, executeAt, new Writes(txnId, executeAt, txn.keys(), new TxnWrite(Collections.emptyList(), true)), new TxnData()); case Erased: - case ErasedOrInvalidOrVestigial: + case ErasedOrVestigial: case Invalidated: return Command.SerializerSupport.invalidated(txnId); } @@ -356,7 +359,7 @@ public class CommandsForKeySerializerTest @Test public void serde() { - testOne(-8928257345122888710L); + testOne(3466420662549679178L); Random random = new Random(); for (int i = 0 ; i < 10000 ; ++i) { @@ -446,7 +449,7 @@ public class CommandsForKeySerializerTest } PartialTxn txn = createPartialTxn(0); - Key key = (Key) txn.keys().get(0); + RoutingKey key = ((Key) txn.keys().get(0)).toUnseekable(); ObjectGraph graph = generateObjectGraph(source.nextInt(0, 100), () -> txnIdSupplier.apply(null), saveStatusSupplier, ignore -> txn, executeAtSupplier, ballotSupplier, missingCountSupplier, source); List commands = graph.toCommands(); CommandsForKey cfk = new CommandsForKey(key); @@ -471,7 +474,7 @@ public class CommandsForKeySerializerTest if (expectStatus == null) expectStatus = InternalStatus.TRANSITIVELY_KNOWN; if (expectStatus.hasExecuteAtOrDeps) Assert.assertEquals(cmd.executeAt, info.executeAt); - Assert.assertEquals(expectStatus, info.status); + Assert.assertEquals(expectStatus, info.status()); Assert.assertArrayEquals(cmd.missing.toArray(TxnId[]::new), info.missing()); if (expectStatus.hasBallot) Assert.assertEquals(cmd.ballot, info.ballot()); @@ -495,7 +498,7 @@ public class CommandsForKeySerializerTest var txnIdGen = AccordGens.txnIds(rs -> rs.nextLong(0, 100), rs -> rs.nextLong(100), rs -> rs.nextInt(10)); qt().check(rs -> { TableId table = tableGen.next(rs); - PartitionKey pk = new PartitionKey(table, Murmur3Partitioner.instance.decorateKey(Murmur3Partitioner.LongToken.keyForToken(rs.nextLong()))); + TokenKey pk = new TokenKey(table, new Murmur3Partitioner.LongToken(rs.nextLong())); var redudentBefore = txnIdGen.next(rs); TxnId[] ids = Gens.arrays(TxnId.class, rs0 -> { TxnId next = txnIdGen.next(rs0); @@ -508,7 +511,7 @@ public class CommandsForKeySerializerTest for (int i = 0; i < info.length; i++) { InternalStatus status = rs.pick(InternalStatus.values()); - info[i] = TxnInfo.create(ids[i], status, ids[i], TxnId.NO_TXNIDS, Ballot.ZERO); + info[i] = TxnInfo.create(ids[i], status, true, ids[i], TxnId.NO_TXNIDS, Ballot.ZERO); } Gen pendingGen = Gens.enums().allMixedDistribution(Unmanaged.Pending.class).next(rs); @@ -527,7 +530,7 @@ public class CommandsForKeySerializerTest { int idx = Arrays.binarySearch(ids, u.txnId); if (idx < 0) - missing.add(TxnInfo.create(u.txnId, InternalStatus.TRANSITIVELY_KNOWN)); + missing.add(TxnInfo.create(u.txnId, InternalStatus.TRANSITIVELY_KNOWN, true, u.txnId, Ballot.ZERO)); } if (!missing.isEmpty()) { @@ -549,11 +552,11 @@ public class CommandsForKeySerializerTest public void thereAndBackAgain() { long tokenValue = -2311778975040348869L; - DecoratedKey key = Murmur3Partitioner.instance.decorateKey(Murmur3Partitioner.LongToken.keyForToken(tokenValue)); - PartitionKey pk = new PartitionKey(TableId.fromString("1b255f4d-ef25-40a6-0000-000000000009"), key); + Token token = new Murmur3Partitioner.LongToken(tokenValue); + TokenKey pk = new TokenKey(TableId.fromString("1b255f4d-ef25-40a6-0000-000000000009"), token); TxnId txnId = TxnId.fromValues(11,34052499,2,1); CommandsForKey expected = CommandsForKey.SerializerSupport.create(pk, - new TxnInfo[] { TxnInfo.create(txnId, InternalStatus.PREACCEPTED_OR_ACCEPTED_INVALIDATE, txnId, TxnId.NO_TXNIDS, Ballot.ZERO) }, + new TxnInfo[] { TxnInfo.create(txnId, InternalStatus.PREACCEPTED_OR_ACCEPTED_INVALIDATE, true, txnId, TxnId.NO_TXNIDS, Ballot.ZERO) }, CommandsForKey.NO_PENDING_UNMANAGED, TxnId.NONE, TxnId.NONE); ByteBuffer buffer = CommandsForKeySerializer.toBytesWithoutKey(expected); diff --git a/test/unit/org/apache/cassandra/utils/AccordGenerators.java b/test/unit/org/apache/cassandra/utils/AccordGenerators.java index cf23494d84..28ae575fb2 100644 --- a/test/unit/org/apache/cassandra/utils/AccordGenerators.java +++ b/test/unit/org/apache/cassandra/utils/AccordGenerators.java @@ -21,16 +21,23 @@ package org.apache.cassandra.utils; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.NavigableMap; import java.util.Set; import java.util.function.BiFunction; import java.util.stream.Stream; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedMap; + import accord.local.Command; import accord.local.CommonAttributes; +import accord.local.DurableBefore; import accord.local.RedundantBefore; -import accord.local.SaveStatus; +import accord.local.StoreParticipants; +import accord.primitives.SaveStatus; import accord.primitives.Ballot; import accord.primitives.Deps; import accord.primitives.FullRoute; @@ -50,6 +57,7 @@ import accord.utils.AccordGens; import accord.utils.Gen; import accord.utils.Gens; import accord.utils.RandomSource; +import accord.utils.ReducingRangeMap; import accord.utils.TriFunction; import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.dht.AccordSplitter; @@ -64,7 +72,8 @@ import org.apache.cassandra.service.accord.txn.TxnData; import org.apache.cassandra.service.accord.txn.TxnWrite; import org.quicktheories.impl.JavaRandom; -import static accord.local.Status.Durability.NotDurable; +import static accord.local.CommandStores.RangesForEpoch; +import static accord.primitives.Status.Durability.NotDurable; import static org.apache.cassandra.service.accord.AccordTestUtils.TABLE_ID1; import static org.apache.cassandra.service.accord.AccordTestUtils.createPartialTxn; @@ -210,7 +219,7 @@ public class AccordGenerators if (saveStatus.known.deps.hasProposedOrDecidedDeps()) mutable.partialDeps(partialDeps); - mutable.route(route); + mutable.setParticipants(StoreParticipants.all(route)); mutable.durability(NotDurable); return mutable; @@ -261,7 +270,7 @@ public class AccordGenerators else return Command.SerializerSupport.truncatedApply(attributes(saveStatus), saveStatus, executeAt, new Writes(txnId, executeAt, keysOrRanges, new TxnWrite(Collections.emptyList(), true)), new TxnData()); case Erased: - case ErasedOrInvalidOrVestigial: + case ErasedOrVestigial: case Invalidated: return Command.SerializerSupport.invalidated(txnId); } @@ -285,6 +294,18 @@ public class AccordGenerators return rs -> new PartitionKey(tableIdGen.next(rs), key.next(rs)); } + public static Gen routingKeys() + { + return routingKeyGen(fromQT(CassandraGenerators.TABLE_ID_GEN), + fromQT(CassandraGenerators.token())); + } + + public static Gen routingKeys(IPartitioner partitioner) + { + return routingKeyGen(fromQT(CassandraGenerators.TABLE_ID_GEN), + fromQT(CassandraGenerators.token(partitioner))); + } + public static Gen routingKeyGen(Gen tableIdGen, Gen tokenGen) { return routingKeyGen(tableIdGen, Gens.enums().all(AccordRoutingKey.RoutingKeyKind.class), tokenGen); @@ -382,22 +403,22 @@ public class AccordGenerators public static Gen keyDepsGen() { - return AccordGens.keyDeps(AccordGenerators.keys()); + return AccordGens.keyDeps(AccordGenerators.routingKeys()); } public static Gen keyDepsGen(IPartitioner partitioner) { - return AccordGens.keyDeps(AccordGenerators.keys(partitioner)); + return AccordGens.keyDeps(AccordGenerators.routingKeys(partitioner)); } public static Gen directKeyDepsGen() { - return AccordGens.directKeyDeps(AccordGenerators.keys()); + return AccordGens.directKeyDeps(AccordGenerators.routingKeys()); } public static Gen directKeyDepsGen(IPartitioner partitioner) { - return AccordGens.directKeyDeps(AccordGenerators.keys(partitioner)); + return AccordGens.directKeyDeps(AccordGenerators.routingKeys(partitioner)); } public static Gen rangeDepsGen() @@ -430,14 +451,17 @@ public class AccordGenerators return rs -> { Range range = rangeGen.next(rs); TxnId locallyAppliedOrInvalidatedBefore = emptyGen.next(rs) ? TxnId.NONE : txnIdGen.next(rs); // emptyable or range + TxnId locallyDecidedAndAppliedOrInvalidatedBefore = locallyAppliedOrInvalidatedBefore; TxnId shardAppliedOrInvalidatedBefore = emptyGen.next(rs) ? TxnId.NONE : txnIdGen.next(rs); // emptyable or range + TxnId shardOnlyAppliedOrInvalidatedBefore = shardAppliedOrInvalidatedBefore; + TxnId gcBefore = shardAppliedOrInvalidatedBefore; TxnId bootstrappedAt = txnIdGen.next(rs); Timestamp staleUntilAtLeast = emptyGen.next(rs) ? null : txnIdGen.next(rs); // nullable long maxEpoch = Stream.of(locallyAppliedOrInvalidatedBefore, shardAppliedOrInvalidatedBefore, bootstrappedAt, staleUntilAtLeast).filter(t -> t != null).mapToLong(Timestamp::epoch).max().getAsLong(); long startEpoch = rs.nextLong(maxEpoch); long endEpoch = emptyGen.next(rs) ? Long.MAX_VALUE : 1 + rs.nextLong(startEpoch, Long.MAX_VALUE); - return new RedundantBefore.Entry(range, startEpoch, endEpoch, locallyAppliedOrInvalidatedBefore, shardAppliedOrInvalidatedBefore, bootstrappedAt, staleUntilAtLeast); + return new RedundantBefore.Entry(range, startEpoch, endEpoch, locallyAppliedOrInvalidatedBefore, locallyDecidedAndAppliedOrInvalidatedBefore, shardAppliedOrInvalidatedBefore, shardOnlyAppliedOrInvalidatedBefore, gcBefore, bootstrappedAt, staleUntilAtLeast); }; } @@ -449,6 +473,65 @@ public class AccordGenerators return AccordGens.redundantBefore(rangeGen, entryGen); } + public static Gen durableBeforeGen(IPartitioner partitioner) + { + Gen rangeGen = rangesArbitrary(partitioner); + Gen txnIdGen = AccordGens.txnIds(Gens.pick(Txn.Kind.SyncPoint, Txn.Kind.ExclusiveSyncPoint), ignore -> Routable.Domain.Range); + + return (rs) -> { + Ranges ranges = rangeGen.next(rs); + TxnId majority = txnIdGen.next(rs); + TxnId universal = majority; + return DurableBefore.create(ranges, majority, universal); + }; + } + + public static Gen> rejectBeforeGen(IPartitioner partitioner) + { + Gen rangeGen = rangesArbitrary(partitioner); + Gen timestampGen = AccordGens.timestamps(); + + return (rs) -> { + ReducingRangeMap initial = new ReducingRangeMap<>(); + int size = rs.nextInt(10); + for (int i = 0; i < size; i++) + initial = ReducingRangeMap.add(initial, rangeGen.next(rs), timestampGen.next(rs)); + + return initial; + }; + } + + public static Gen> safeToReadGen(IPartitioner partitioner) + { + Gen rangeGen = ranges(partitioner); + Gen timestampGen = AccordGens.timestamps(); + + return (rs) -> { + ImmutableMap.Builder initial = new ImmutableSortedMap.Builder<>(Comparator.comparing(o -> o)); + int size = rs.nextInt(10); + for (int i = 0; i < size; i++) + initial.put(timestampGen.next(rs), rangeGen.next(rs)); + + return (NavigableMap) initial.build(); + }; + } + + public static Gen rangesForEpoch(IPartitioner partitioner) + { + Gen rangesGen = ranges(partitioner); + + return rs -> { + int size = rs.nextInt(1, 5); + long[] epochs = new long[size]; + for (int i = 0; i < size; i++) + epochs[i] = rs.nextLong(1, 10_000); + Ranges[] ranges = new Ranges[size]; + for (int i = 0; i < size; i++) + ranges[i] = rangesGen.next(rs); + return new RangesForEpoch.Snapshot(epochs, ranges); + }; + } + public static Gen fromQT(org.quicktheories.core.Gen qt) { return rs -> {