You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by be...@apache.org on 2024/02/28 10:05:55 UTC
(cassandra) branch cep-15-accord updated: Refactor CommandsForKey for efficiency, and to support transitive dependency elision
This is an automated email from the ASF dual-hosted git repository.
benedict pushed a commit to branch cep-15-accord
in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/cep-15-accord by this push:
new 2fbaee2529 Refactor CommandsForKey for efficiency, and to support transitive dependency elision
2fbaee2529 is described below
commit 2fbaee2529e83406ac853385d252ba071ca36fa7
Author: Benedict Elliott Smith <be...@apache.org>
AuthorDate: Thu Feb 15 11:04:56 2024 +0000
Refactor CommandsForKey for efficiency, and to support transitive dependency elision
patch by Benedict; reviewed by Aleksey Yeshchenko for CASSANDRA-19310
---
modules/accord | 2 +-
.../org/apache/cassandra/config/AccordSpec.java | 5 +
.../cassandra/config/DatabaseDescriptor.java | 44 +-
.../apache/cassandra/db/DiskBoundaryManager.java | 2 +-
.../db/compaction/CompactionIterator.java | 40 +-
.../cassandra/dht/ByteOrderedPartitioner.java | 8 +-
.../org/apache/cassandra/dht/IPartitioner.java | 4 +-
.../apache/cassandra/dht/Murmur3Partitioner.java | 2 +-
.../cassandra/dht/OrderPreservingPartitioner.java | 2 +-
.../apache/cassandra/dht/RandomPartitioner.java | 2 +-
src/java/org/apache/cassandra/dht/Splitter.java | 14 +-
src/java/org/apache/cassandra/dht/Token.java | 7 +-
src/java/org/apache/cassandra/net/Verb.java | 5 +-
.../service/accord/AccordCachingState.java | 5 -
.../service/accord/AccordCommandStore.java | 108 +--
.../service/accord/AccordCommandsForKeys.java | 253 ------
.../service/accord/AccordConfigurationService.java | 2 +-
.../cassandra/service/accord/AccordKeyspace.java | 251 ++----
.../service/accord/AccordMessageSink.java | 2 +-
.../service/accord/AccordObjectSizes.java | 42 +-
.../service/accord/AccordSafeCommandStore.java | 253 ++----
.../service/accord/AccordSafeCommandsForKey.java | 5 +-
.../accord/AccordSafeCommandsForKeyUpdate.java | 122 ---
.../cassandra/service/accord/AccordService.java | 18 +-
.../cassandra/service/accord/AccordStateCache.java | 2 -
.../service/accord/CommandsForKeyUpdate.java | 101 ---
.../service/accord/CommandsForRanges.java | 221 +++--
.../cassandra/service/accord/TokenRange.java | 2 +-
.../service/accord/api/AccordRoutingKey.java | 4 +-
.../service/accord/async/AsyncLoader.java | 16 +-
.../service/accord/async/AsyncOperation.java | 17 +-
.../accord/interop/AccordInteropAdapter.java | 121 +++
.../service/accord/interop/AccordInteropApply.java | 14 +-
.../accord/interop/AccordInteropCommit.java | 9 +-
.../accord/interop/AccordInteropExecution.java | 35 +-
.../accord/interop/AccordInteropPersist.java | 38 +-
.../accord/serializers/ApplySerializers.java | 10 +-
.../accord/serializers/CommandSerializers.java | 61 +-
.../serializers/CommandsForKeySerializer.java | 892 ++++++++++++++++++---
.../accord/serializers/CommitSerializers.java | 37 +-
.../service/accord/serializers/DepsSerializer.java | 83 +-
.../accord/serializers/FetchSerializers.java | 7 +-
.../serializers/GetMaxConflictSerializers.java | 85 ++
.../serializers/IVersionedWithKeysSerializer.java | 414 ++++++++++
.../service/accord/serializers/KeySerializers.java | 4 +
.../accord/serializers/ListenerSerializers.java | 69 +-
.../accord/serializers/ReadDataSerializers.java | 46 +-
.../accord/serializers/SetDurableSerializers.java | 14 +-
.../cassandra/service/accord/txn/TxnWrite.java | 2 +-
.../org/apache/cassandra/utils/ByteBufferUtil.java | 99 +++
.../apache/cassandra/utils/vint/VIntCoding.java | 59 ++
.../test/accord/AccordMigrationTest.java | 2 +-
.../cassandra/db/DiskBoundaryManagerTest.java | 2 +-
.../db/compaction/CancelCompactionsTest.java | 2 +-
.../compaction/CompactionAccordIteratorsTest.java | 91 +--
.../db/compaction/CompactionsBytemanTest.java | 2 +-
.../compaction/UnifiedCompactionStrategyTest.java | 6 +-
.../apache/cassandra/dht/LengthPartitioner.java | 2 +-
.../org/apache/cassandra/dht/SplitterTest.java | 24 +-
.../cassandra/index/sai/cql/VectorTypeTest.java | 2 +-
.../sai/disk/v1/InvertedIndexSearcherTest.java | 2 +-
.../cassandra/index/sai/disk/v1/SegmentTest.java | 2 +-
.../org/apache/cassandra/repair/RepairJobTest.java | 2 +-
.../cassandra/repair/ValidationTaskTest.java | 2 +-
.../service/accord/AccordCommandStoreTest.java | 140 +---
.../service/accord/AccordCommandTest.java | 23 +-
.../service/accord/AccordKeyspaceTest.java | 2 +-
.../service/accord/AccordMessageSinkTest.java | 2 +-
.../cassandra/service/accord/AccordTestUtils.java | 8 -
.../service/accord/AccordTopologyTest.java | 8 +-
.../service/accord/api/AccordKeyTest.java | 2 +-
.../service/accord/async/AsyncLoaderTest.java | 77 +-
.../service/accord/async/AsyncOperationTest.java | 31 +-
.../serializers/CommandsForKeySerializerTest.java | 455 ++++++++++-
74 files changed, 2716 insertions(+), 1833 deletions(-)
diff --git a/modules/accord b/modules/accord
index 62bd2c6c8c..985ac15a82 160000
--- a/modules/accord
+++ b/modules/accord
@@ -1 +1 @@
-Subproject commit 62bd2c6c8cc98946ee9d41c2136339f68b12930d
+Subproject commit 985ac15a82593dbee203aaadcc1d5a2f7d54a393
diff --git a/src/java/org/apache/cassandra/config/AccordSpec.java b/src/java/org/apache/cassandra/config/AccordSpec.java
index d821c31e87..9eb8a1d0be 100644
--- a/src/java/org/apache/cassandra/config/AccordSpec.java
+++ b/src/java/org/apache/cassandra/config/AccordSpec.java
@@ -44,4 +44,9 @@ public class AccordSpec
public DurationSpec.IntMillisecondsBound range_barrier_timeout = new DurationSpec.IntMillisecondsBound("2m");
public volatile DurationSpec fast_path_update_delay = new DurationSpec.IntSecondsBound(5);
+
+ public volatile DurationSpec schedule_durability_frequency = new DurationSpec.IntSecondsBound(15);
+ public volatile DurationSpec durability_txnid_lag = new DurationSpec.IntSecondsBound(5);
+ public volatile DurationSpec shard_durability_cycle = new DurationSpec.IntMinutesBound(2);
+ public volatile DurationSpec global_durability_cycle = new DurationSpec.IntMinutesBound(10);
}
diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
index c75614081d..69bca06140 100644
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@ -4910,9 +4910,49 @@ public class DatabaseDescriptor
return conf.accord.fast_path_update_delay.to(TimeUnit.MILLISECONDS);
}
- public static void setAccordFastPathUpdateDelayMillis(long millis)
+ public static void setAccordFastPathUpdateDelaySeconds(long seconds)
{
- conf.accord.fast_path_update_delay = new DurationSpec.IntMillisecondsBound(millis);
+ conf.accord.fast_path_update_delay = new DurationSpec.IntSecondsBound(seconds);
+ }
+
+ public static long getAccordScheduleDurabilityFrequency(TimeUnit unit)
+ {
+ return conf.accord.schedule_durability_frequency.to(unit);
+ }
+
+ public static void setAccordScheduleDurabilityFrequencySeconds(long seconds)
+ {
+ conf.accord.schedule_durability_frequency = new DurationSpec.IntSecondsBound(seconds);
+ }
+
+ public static long getAccordScheduleDurabilityTxnIdLag(TimeUnit unit)
+ {
+ return conf.accord.durability_txnid_lag.to(unit);
+ }
+
+ public static void setAccordScheduleDurabilityTxnIdLagSeconds(long seconds)
+ {
+ conf.accord.durability_txnid_lag = new DurationSpec.IntSecondsBound(seconds);
+ }
+
+ public static long getAccordGlobalDurabilityCycle(TimeUnit unit)
+ {
+ return conf.accord.global_durability_cycle.to(unit);
+ }
+
+ public static void setAccordGlobalDurabilityCycleSeconds(long seconds)
+ {
+ conf.accord.global_durability_cycle = new DurationSpec.IntSecondsBound(seconds);
+ }
+
+ public static long getAccordShardDurabilityCycle(TimeUnit unit)
+ {
+ return conf.accord.shard_durability_cycle.to(unit);
+ }
+
+ public static void setAccordShardDurabilityCycleSeconds(long seconds)
+ {
+ conf.accord.shard_durability_cycle = new DurationSpec.IntSecondsBound(seconds);
}
public static boolean getForceNewPreparedStatementBehaviour()
diff --git a/src/java/org/apache/cassandra/db/DiskBoundaryManager.java b/src/java/org/apache/cassandra/db/DiskBoundaryManager.java
index 13d1585858..8e0cb56524 100644
--- a/src/java/org/apache/cassandra/db/DiskBoundaryManager.java
+++ b/src/java/org/apache/cassandra/db/DiskBoundaryManager.java
@@ -194,7 +194,7 @@ public class DiskBoundaryManager
List<PartitionPosition> diskBoundaries = new ArrayList<>();
for (int i = 0; i < boundaries.size() - 1; i++)
diskBoundaries.add(boundaries.get(i).maxKeyBound());
- diskBoundaries.add(partitioner.getMaximumToken().maxKeyBound());
+ diskBoundaries.add(partitioner.getMaximumTokenForSplitting().maxKeyBound());
return diskBoundaries;
}
}
diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionIterator.java b/src/java/org/apache/cassandra/db/compaction/CompactionIterator.java
index 2350937320..350420ca03 100644
--- a/src/java/org/apache/cassandra/db/compaction/CompactionIterator.java
+++ b/src/java/org/apache/cassandra/db/compaction/CompactionIterator.java
@@ -98,10 +98,10 @@ 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.maybeDropTruncatedCommandColumns;
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;
import static org.apache.cassandra.service.accord.AccordKeyspace.TimestampsForKeyColumns.last_executed_timestamp;
import static org.apache.cassandra.service.accord.AccordKeyspace.TimestampsForKeyColumns.last_write_timestamp;
-import static org.apache.cassandra.service.accord.AccordKeyspace.TimestampsForKeyColumns.max_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;
@@ -221,11 +221,8 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
if (isAccordTimestampsForKey(cfs))
return new AccordTimestampsForKeyPurger(accordService);
- if (isAccordDepsCommandsForKey(cfs))
- return new AccordCommandsForKeyPurger(AccordKeyspace.DepsCommandsForKeysAccessor, accordService);
-
- if (isAccordAllCommandsForKey(cfs))
- return new AccordCommandsForKeyPurger(AccordKeyspace.AllCommandsForKeysAccessor, accordService);
+ if (isAccordCommandsForKey(cfs))
+ return new AccordCommandsForKeyPurger(AccordKeyspace.CommandsForKeysAccessor, accordService);
throw new IllegalArgumentException("Unhandled accord table: " + cfs.keyspace.getName() + '.' + cfs.name);
}
@@ -936,22 +933,14 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
lastWriteCell = null;
}
- Cell<?> maxTimestampCell = row.getCell(max_timestamp);
- Timestamp max_timestamp = deserializeTimestampOrNull(maxTimestampCell);
- if (max_timestamp != null && max_timestamp.compareTo(redundantBeforeTxnId) < 0)
- {
- maxTimestampCell = null;
- }
-
// No need to emit a tombstone as earlier versions of the row will also be nulled out
// when compacted later or loaded into a commands for key
if (lastExecuteMicrosCell == null &&
lastExecuteCell == null &&
- lastWriteCell == null &&
- maxTimestampCell == null)
+ lastWriteCell == null)
return null;
- return truncateTimestampsForKeyRow(nowInSec, row, lastExecuteMicrosCell, lastExecuteCell, lastWriteCell, maxTimestampCell);
+ return truncateTimestampsForKeyRow(nowInSec, row, lastExecuteMicrosCell, lastExecuteCell, lastWriteCell);
}
@Override
@@ -997,11 +986,10 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
return row;
TxnId redundantBeforeTxnId = redundantBeforeEntry.shardRedundantBefore();
- Timestamp timestamp = accessor.getTimestamp(row);
- if (timestamp != null && timestamp.compareTo(redundantBeforeTxnId) < 0)
- return null;
+ if (redundantBeforeTxnId.equals(TxnId.NONE))
+ return row;
- return row;
+ return CommandsForKeysAccessor.withoutRedundantCommands(partitionKey, row, redundantBeforeTxnId);
}
@Override
@@ -1057,8 +1045,7 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
return cfs.getKeyspaceName().equals(SchemaConstants.ACCORD_KEYSPACE_NAME) &&
ImmutableSet.of(AccordKeyspace.COMMANDS,
AccordKeyspace.TIMESTAMPS_FOR_KEY,
- AccordKeyspace.DEPS_COMMANDS_FOR_KEY,
- AccordKeyspace.ALL_COMMANDS_FOR_KEY)
+ AccordKeyspace.COMMANDS_FOR_KEY)
.contains(cfs.getTableName());
}
@@ -1077,13 +1064,8 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
return isAccordTable(cfs, AccordKeyspace.TIMESTAMPS_FOR_KEY);
}
- private static boolean isAccordDepsCommandsForKey(ColumnFamilyStore cfs)
- {
- return isAccordTable(cfs, AccordKeyspace.DEPS_COMMANDS_FOR_KEY);
- }
-
- private static boolean isAccordAllCommandsForKey(ColumnFamilyStore cfs)
+ private static boolean isAccordCommandsForKey(ColumnFamilyStore cfs)
{
- return isAccordTable(cfs, AccordKeyspace.ALL_COMMANDS_FOR_KEY);
+ return isAccordTable(cfs, AccordKeyspace.COMMANDS_FOR_KEY);
}
}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/dht/ByteOrderedPartitioner.java b/src/java/org/apache/cassandra/dht/ByteOrderedPartitioner.java
index 7f5103dece..9cb95426cf 100644
--- a/src/java/org/apache/cassandra/dht/ByteOrderedPartitioner.java
+++ b/src/java/org/apache/cassandra/dht/ByteOrderedPartitioner.java
@@ -144,6 +144,12 @@ public class ByteOrderedPartitioner implements IPartitioner
@Override
public Token nextValidToken()
+ {
+ throw new UnsupportedOperationException(String.format("Token type %s does not support token allocation.",
+ getClass().getSimpleName()));
+ }
+
+ public Token increaseSlightly()
{
// find first byte we can increment
int i = token.length - 1;
@@ -180,8 +186,6 @@ public class ByteOrderedPartitioner implements IPartitioner
if (i == -1)
{
byte[] newToken = Arrays.copyOf(token, token.length - 1);
- if (newToken.length > 0)
- newToken[newToken.length - 1] = (byte)-1;
return new BytesToken(newToken);
}
diff --git a/src/java/org/apache/cassandra/dht/IPartitioner.java b/src/java/org/apache/cassandra/dht/IPartitioner.java
index bb4c96685d..e959e7b1ed 100644
--- a/src/java/org/apache/cassandra/dht/IPartitioner.java
+++ b/src/java/org/apache/cassandra/dht/IPartitioner.java
@@ -83,9 +83,9 @@ public interface IPartitioner
* The biggest token for this partitioner, unlike getMinimumToken, this token is actually used and users wanting to
* include all tokens need to do getMaximumToken().maxKeyBound()
*
- * Not implemented for the ordered partitioners
+ * THIS IS NOT SAFE FOR PURPOSES BESIDES SPLITTING/BALANCING
*/
- default Token getMaximumToken()
+ default Token getMaximumTokenForSplitting()
{
throw new UnsupportedOperationException("If you are using a splitting partitioner, getMaximumToken has to be implemented");
}
diff --git a/src/java/org/apache/cassandra/dht/Murmur3Partitioner.java b/src/java/org/apache/cassandra/dht/Murmur3Partitioner.java
index 9e636ff0c1..4d79018530 100644
--- a/src/java/org/apache/cassandra/dht/Murmur3Partitioner.java
+++ b/src/java/org/apache/cassandra/dht/Murmur3Partitioner.java
@@ -463,7 +463,7 @@ public class Murmur3Partitioner implements IPartitioner
return LongType.instance;
}
- public Token getMaximumToken()
+ public Token getMaximumTokenForSplitting()
{
return new LongToken(Long.MAX_VALUE);
}
diff --git a/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java b/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java
index 676cdd2e08..25442593d1 100644
--- a/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java
+++ b/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java
@@ -140,7 +140,7 @@ public class OrderPreservingPartitioner implements IPartitioner
return MINIMUM;
}
- public StringToken getMaximumToken()
+ public StringToken getMaximumTokenForSplitting()
{
return MAXIMUM;
}
diff --git a/src/java/org/apache/cassandra/dht/RandomPartitioner.java b/src/java/org/apache/cassandra/dht/RandomPartitioner.java
index 43517898f4..6882c8d574 100644
--- a/src/java/org/apache/cassandra/dht/RandomPartitioner.java
+++ b/src/java/org/apache/cassandra/dht/RandomPartitioner.java
@@ -369,7 +369,7 @@ public class RandomPartitioner implements IPartitioner
return ownerships;
}
- public Token getMaximumToken()
+ public Token getMaximumTokenForSplitting()
{
return new BigIntegerToken(MAXIMUM);
}
diff --git a/src/java/org/apache/cassandra/dht/Splitter.java b/src/java/org/apache/cassandra/dht/Splitter.java
index 3f9d663b7e..165a2ecf9b 100644
--- a/src/java/org/apache/cassandra/dht/Splitter.java
+++ b/src/java/org/apache/cassandra/dht/Splitter.java
@@ -50,7 +50,7 @@ public abstract class Splitter extends AccordSplitter
{
//full range case
if (range.left.equals(range.right))
- return tokensInRange(new Range(partitioner.getMinimumToken(), partitioner.getMaximumToken()));
+ return tokensInRange(new Range(partitioner.getMinimumToken(), partitioner.getMaximumTokenForSplitting()));
BigInteger totalTokens = BigInteger.ZERO;
for (Range<Token> unwrapped : range.unwrap())
@@ -95,7 +95,7 @@ public abstract class Splitter extends AccordSplitter
{
//full range case
if (range.left.equals(range.right))
- return positionInRange(token, new Range(partitioner.getMinimumToken(), partitioner.getMaximumToken()));
+ return positionInRange(token, new Range(partitioner.getMinimumToken(), partitioner.getMaximumTokenForSplitting()));
// leftmost token means we are on position 0.0
if (token.equals(range.left))
@@ -115,7 +115,7 @@ public abstract class Splitter extends AccordSplitter
public List<Token> splitOwnedRanges(int parts, List<WeightedRange> weightedRanges, boolean dontSplitRanges)
{
if (weightedRanges.isEmpty() || parts == 1)
- return Collections.singletonList(partitioner.getMaximumToken());
+ return Collections.singletonList(partitioner.getMaximumTokenForSplitting());
BigInteger totalTokens = BigInteger.ZERO;
for (WeightedRange weightedRange : weightedRanges)
@@ -126,7 +126,7 @@ public abstract class Splitter extends AccordSplitter
BigInteger perPart = totalTokens.divide(BigInteger.valueOf(parts));
// the range owned is so tiny we can't split it:
if (perPart.equals(BigInteger.ZERO))
- return Collections.singletonList(partitioner.getMaximumToken());
+ return Collections.singletonList(partitioner.getMaximumTokenForSplitting());
if (dontSplitRanges)
return splitOwnedRangesNoPartialRanges(weightedRanges, perPart, parts);
@@ -155,7 +155,7 @@ public abstract class Splitter extends AccordSplitter
}
sum = sum.add(currentRangeWidth);
}
- boundaries.set(boundaries.size() - 1, partitioner.getMaximumToken());
+ boundaries.set(boundaries.size() - 1, partitioner.getMaximumTokenForSplitting());
assert boundaries.size() == parts : boundaries.size() + "!=" + parts + " " + boundaries + ":" + weightedRanges;
return boundaries;
@@ -192,7 +192,7 @@ public abstract class Splitter extends AccordSplitter
}
i++;
}
- boundaries.add(partitioner.getMaximumToken());
+ boundaries.add(partitioner.getMaximumTokenForSplitting());
return boundaries;
}
@@ -202,7 +202,7 @@ public abstract class Splitter extends AccordSplitter
*/
private Token token(Token t)
{
- return t.equals(partitioner.getMinimumToken()) ? partitioner.getMaximumToken() : t;
+ return t.equals(partitioner.getMinimumToken()) ? partitioner.getMaximumTokenForSplitting() : t;
}
/**
diff --git a/src/java/org/apache/cassandra/dht/Token.java b/src/java/org/apache/cassandra/dht/Token.java
index 55e7143eb8..70fa65a227 100644
--- a/src/java/org/apache/cassandra/dht/Token.java
+++ b/src/java/org/apache/cassandra/dht/Token.java
@@ -249,7 +249,12 @@ public abstract class Token implements RingPosition<Token>, Serializable
abstract public Token nextValidToken();
/**
- * Returns a token that is slightly less than this.
+ * Returns a token that is slightly more than this. This is NOT guaranteed to be the directly following token.
+ */
+ public Token increaseSlightly() { return nextValidToken(); }
+
+ /**
+ * Returns a token that is slightly less than this. This is NOT guaranteed to be the directly preceding token.
*/
abstract public Token decreaseSlightly();
diff --git a/src/java/org/apache/cassandra/net/Verb.java b/src/java/org/apache/cassandra/net/Verb.java
index 7232ab4b1d..3db5a6e848 100644
--- a/src/java/org/apache/cassandra/net/Verb.java
+++ b/src/java/org/apache/cassandra/net/Verb.java
@@ -94,6 +94,7 @@ import org.apache.cassandra.service.accord.serializers.EnumSerializer;
import org.apache.cassandra.service.accord.serializers.FetchSerializers;
import org.apache.cassandra.service.accord.serializers.GetDepsSerializers;
import org.apache.cassandra.service.accord.serializers.GetEphmrlReadDepsSerializers;
+import org.apache.cassandra.service.accord.serializers.GetMaxConflictSerializers;
import org.apache.cassandra.service.accord.serializers.InformDurableSerializers;
import org.apache.cassandra.service.accord.serializers.InformHomeDurableSerializers;
import org.apache.cassandra.service.accord.serializers.InformOfTxnIdSerializers;
@@ -332,6 +333,8 @@ public enum Verb
ACCORD_GET_DEPS_REQ (144, P2, writeTimeout, IMMEDIATE, () -> GetDepsSerializers.request, AccordService::verbHandlerOrNoop, ACCORD_GET_DEPS_RSP ),
ACCORD_GET_EPHMRL_READ_DEPS_RSP (161, P2, writeTimeout, REQUEST_RESPONSE, () -> GetEphmrlReadDepsSerializers.reply, RESPONSE_HANDLER ),
ACCORD_GET_EPHMRL_READ_DEPS_REQ (162, P2, writeTimeout, IMMEDIATE, () -> GetEphmrlReadDepsSerializers.request, AccordService::verbHandlerOrNoop, ACCORD_GET_EPHMRL_READ_DEPS_RSP),
+ ACCORD_GET_MAX_CONFLICT_RSP (163, P2, writeTimeout, REQUEST_RESPONSE, () -> GetMaxConflictSerializers.reply, RESPONSE_HANDLER ),
+ ACCORD_GET_MAX_CONFLICT_REQ (164, P2, writeTimeout, IMMEDIATE, () -> GetMaxConflictSerializers.request, AccordService::verbHandlerOrNoop, ACCORD_GET_MAX_CONFLICT_RSP),
ACCORD_FETCH_DATA_RSP (145, P2, repairTimeout,REQUEST_RESPONSE, () -> FetchSerializers.reply, RESPONSE_HANDLER ),
ACCORD_FETCH_DATA_REQ (146, P2, repairTimeout,IMMEDIATE, () -> FetchSerializers.request, AccordService::verbHandlerOrNoop, ACCORD_FETCH_DATA_RSP ),
ACCORD_SET_SHARD_DURABLE_REQ (147, P2, writeTimeout, IMMEDIATE, () -> SetDurableSerializers.shardDurable, AccordService::verbHandlerOrNoop, ACCORD_SIMPLE_RSP ),
@@ -341,7 +344,7 @@ public enum Verb
ACCORD_SYNC_NOTIFY_REQ (151, P2, writeTimeout, IMMEDIATE, () -> Notification.listSerializer, () -> AccordSyncPropagator.verbHandler, ACCORD_SIMPLE_RSP ),
- ACCORD_APPLY_AND_WAIT_UNTIL_APPLIED_REQ(152, P2, writeTimeout, IMMEDIATE, () -> ReadDataSerializers.readData,() -> AccordSyncPropagator.verbHandler, ACCORD_READ_RSP),
+ ACCORD_APPLY_AND_WAIT_REQ (152, P2, writeTimeout, IMMEDIATE, () -> ReadDataSerializers.readData, () -> AccordService.instance().verbHandler(), ACCORD_READ_RSP),
CONSENSUS_KEY_MIGRATION (153, P1, writeTimeout, MUTATION, () -> ConsensusKeyMigrationFinished.serializer,() -> ConsensusKeyMigrationState.consensusKeyMigrationFinishedHandler),
diff --git a/src/java/org/apache/cassandra/service/accord/AccordCachingState.java b/src/java/org/apache/cassandra/service/accord/AccordCachingState.java
index 43e0a50c7a..5175e86c10 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordCachingState.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordCachingState.java
@@ -121,11 +121,6 @@ public class AccordCachingState<K, V> extends IntrusiveLinkedListNode
return status().isComplete();
}
- public boolean canEvict()
- {
- return true;
- }
-
int estimatedSizeOnHeap(ToLongFunction<V> estimator)
{
shouldUpdateSize = false;
diff --git a/src/java/org/apache/cassandra/service/accord/AccordCommandStore.java b/src/java/org/apache/cassandra/service/accord/AccordCommandStore.java
index c823a6d925..36b224b022 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordCommandStore.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordCommandStore.java
@@ -27,9 +27,9 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
-import java.util.function.Predicate;
import javax.annotation.Nullable;
@@ -43,8 +43,7 @@ import accord.api.DataStore;
import accord.api.Key;
import accord.api.ProgressLog;
import accord.impl.CommandsForKey;
-import accord.impl.DomainCommands;
-import accord.impl.DomainTimestamps;
+import accord.impl.CommandsSummary;
import accord.impl.TimestampsForKey;
import accord.local.Command;
import accord.local.CommandStore;
@@ -70,7 +69,6 @@ import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import accord.utils.Invariants;
import accord.utils.ReducingRangeMap;
-import accord.utils.TriFunction;
import accord.utils.async.AsyncChain;
import accord.utils.async.AsyncChains;
import accord.utils.async.Observable;
@@ -114,13 +112,10 @@ public class AccordCommandStore extends CommandStore implements CacheSize
private final AccordJournal journal;
private final ExecutorService executor;
private final ExecutionOrder executionOrder;
- private final AccordCommandsForKeys keyCoordinator;
private final AccordStateCache stateCache;
private final AccordStateCache.Instance<TxnId, Command, AccordSafeCommand> commandCache;
private final AccordStateCache.Instance<RoutableKey, TimestampsForKey, AccordSafeTimestampsForKey> timestampsForKeyCache;
- private final AccordStateCache.Instance<RoutableKey, CommandsForKey, AccordSafeCommandsForKey> depsCommandsForKeyCache;
- private final AccordStateCache.Instance<RoutableKey, CommandsForKey, AccordSafeCommandsForKey> allCommandsForKeyCache;
- private final AccordStateCache.Instance<RoutableKey, CommandsForKeyUpdate, AccordSafeCommandsForKeyUpdate> updatesForKeyCache;
+ private final AccordStateCache.Instance<RoutableKey, CommandsForKey, AccordSafeCommandsForKey> commandsForKeyCache;
private AsyncOperation<?> currentOperation = null;
private AccordSafeCommandStore current = null;
private long lastSystemTimestampMicros = Long.MIN_VALUE;
@@ -153,7 +148,6 @@ public class AccordCommandStore extends CommandStore implements CacheSize
super(id, time, agent, dataStore, progressLogFactory, epochUpdateHolder);
this.journal = journal;
loggingId = String.format("[%s]", id);
- keyCoordinator = new AccordCommandsForKeys(this);
executor = executorFactory().sequential(CommandStore.class.getSimpleName() + '[' + id + ']');
executionOrder = new ExecutionOrder();
threadId = getThreadId(executor);
@@ -174,33 +168,15 @@ public class AccordCommandStore extends CommandStore implements CacheSize
this::saveTimestampsForKey,
this::validateTimestampsForKey,
AccordObjectSizes::timestampsForKey);
- depsCommandsForKeyCache =
+ commandsForKeyCache =
stateCache.instance(RoutableKey.class,
AccordSafeCommandsForKey.class,
AccordSafeCommandsForKey::new,
- this::loadDepsCommandsForKey,
+ this::loadCommandsForKey,
this::saveCommandsForKey,
- this::validateDepsCommandsForKey,
+ this::validateCommandsForKey,
AccordObjectSizes::commandsForKey,
- keyCoordinator::createDepsCommandsNode);
- allCommandsForKeyCache =
- stateCache.instance(RoutableKey.class,
- AccordSafeCommandsForKey.class,
- AccordSafeCommandsForKey::new,
- this::loadAllCommandsForKey,
- this::saveCommandsForKey,
- this::validateAllCommandsForKey,
- AccordObjectSizes::commandsForKey,
- keyCoordinator::createDepsCommandsNode);
- updatesForKeyCache =
- stateCache.instance(RoutableKey.class,
- AccordSafeCommandsForKeyUpdate.class,
- AccordSafeCommandsForKeyUpdate::new,
- this::loadCommandsForKeyUpdate,
- this::saveCommandsForKeyUpdate,
- (key, evicting) -> true,
- CommandsForKeyUpdate::estimatedSizeOnHeap,
- keyCoordinator::createUpdatesNode);
+ AccordCachingState::new);
AccordKeyspace.loadCommandStoreMetadata(id, ((rejectBefore, durableBefore, redundantBefore, bootstrapBeganAt, safeToRead) -> {
executor.submit(() -> {
@@ -340,19 +316,9 @@ public class AccordCommandStore extends CommandStore implements CacheSize
return timestampsForKeyCache;
}
- public AccordStateCache.Instance<RoutableKey, CommandsForKey, AccordSafeCommandsForKey> depsCommandsForKeyCache()
+ public AccordStateCache.Instance<RoutableKey, CommandsForKey, AccordSafeCommandsForKey> commandsForKeyCache()
{
- return depsCommandsForKeyCache;
- }
-
- public AccordStateCache.Instance<RoutableKey, CommandsForKey, AccordSafeCommandsForKey> allCommandsForKeyCache()
- {
- return allCommandsForKeyCache;
- }
-
- public AccordStateCache.Instance<RoutableKey, CommandsForKeyUpdate, AccordSafeCommandsForKeyUpdate> updatesForKeyCache()
- {
- return updatesForKeyCache;
+ return commandsForKeyCache;
}
Command loadCommand(TxnId txnId)
@@ -384,39 +350,17 @@ public class AccordCommandStore extends CommandStore implements CacheSize
return AccordKeyspace.loadTimestampsForKey(this, (PartitionKey) key);
}
- CommandsForKey loadDepsCommandsForKey(RoutableKey key)
- {
- return AccordKeyspace.loadDepsCommandsForKey(this, (PartitionKey) key);
- }
-
- CommandsForKey loadAllCommandsForKey(RoutableKey key)
+ CommandsForKey loadCommandsForKey(RoutableKey key)
{
- return AccordKeyspace.loadAllCommandsForKey(this, (PartitionKey) key);
+ return AccordKeyspace.loadCommandsForKey(this, (PartitionKey) key);
}
- CommandsForKeyUpdate loadCommandsForKeyUpdate(RoutableKey key)
+ boolean validateCommandsForKey(RoutableKey key, CommandsForKey evicting)
{
- throw new IllegalStateException();
- }
-
- boolean validateDepsCommandsForKey(RoutableKey key, CommandsForKey evicting)
- {
- CommandsForKey reloaded = AccordKeyspace.loadDepsCommandsForKey(this, (PartitionKey) key);
- return Objects.equals(evicting, reloaded);
- }
-
- boolean validateAllCommandsForKey(RoutableKey key, CommandsForKey evicting)
- {
- CommandsForKey reloaded = AccordKeyspace.loadAllCommandsForKey(this, (PartitionKey) key);
+ CommandsForKey reloaded = AccordKeyspace.loadCommandsForKey(this, (PartitionKey) key);
return Objects.equals(evicting, reloaded);
}
- @Nullable
- private Runnable saveCommandsForKey(CommandsForKey before, CommandsForKey after)
- {
- throw new IllegalStateException();
- }
-
@Nullable
private Runnable saveTimestampsForKey(TimestampsForKey before, TimestampsForKey after)
{
@@ -425,7 +369,7 @@ public class AccordCommandStore extends CommandStore implements CacheSize
}
@Nullable
- private Runnable saveCommandsForKeyUpdate(CommandsForKeyUpdate before, CommandsForKeyUpdate after)
+ private Runnable saveCommandsForKey(CommandsForKey before, CommandsForKey after)
{
Mutation mutation = AccordKeyspace.getCommandsForKeyMutation(id, after, nextSystemTimestampMicros());
return null != mutation ? mutation::applyUnsafe : null;
@@ -523,15 +467,13 @@ public class AccordCommandStore extends CommandStore implements CacheSize
public AccordSafeCommandStore beginOperation(PreLoadContext preLoadContext,
Map<TxnId, AccordSafeCommand> commands,
NavigableMap<RoutableKey, AccordSafeTimestampsForKey> timestampsForKeys,
- NavigableMap<RoutableKey, AccordSafeCommandsForKey> depsCommandsForKeys,
- NavigableMap<RoutableKey, AccordSafeCommandsForKey> allCommandsForKeys,
- NavigableMap<RoutableKey, AccordSafeCommandsForKeyUpdate> updatesForKeys)
+ NavigableMap<RoutableKey, AccordSafeCommandsForKey> commandsForKeys)
{
Invariants.checkState(current == null);
commands.values().forEach(AccordSafeState::preExecute);
- depsCommandsForKeys.values().forEach(AccordSafeState::preExecute);
+ commandsForKeys.values().forEach(AccordSafeState::preExecute);
timestampsForKeys.values().forEach(AccordSafeState::preExecute);
- current = new AccordSafeCommandStore(preLoadContext, commands, timestampsForKeys, depsCommandsForKeys, allCommandsForKeys, updatesForKeys, this);
+ current = new AccordSafeCommandStore(preLoadContext, commands, timestampsForKeys, commandsForKeys, this);
return current;
}
@@ -547,7 +489,7 @@ public class AccordCommandStore extends CommandStore implements CacheSize
current = null;
}
- <O> O mapReduceForRange(Routables<?> keysOrRanges, Ranges slice, TriFunction<DomainTimestamps, DomainCommands, O, O> map, O accumulate, Predicate<? super O> terminate)
+ <O> O mapReduceForRange(Routables<?> keysOrRanges, Ranges slice, BiFunction<CommandsSummary, O, O> map, O accumulate)
{
keysOrRanges = keysOrRanges.slice(slice, Routables.Slice.Minimal);
switch (keysOrRanges.domain())
@@ -555,12 +497,8 @@ public class AccordCommandStore extends CommandStore implements CacheSize
case Key:
{
AbstractKeys<Key> keys = (AbstractKeys<Key>) keysOrRanges;
- for (CommandsForRanges.DomainInfo summary : commandsForRanges.search(keys))
- {
- accumulate = map.apply(summary, summary, accumulate);
- if (terminate.test(accumulate))
- return accumulate;
- }
+ for (CommandsSummary summary : commandsForRanges.search(keys))
+ accumulate = map.apply(summary, accumulate);
}
break;
case Range:
@@ -568,12 +506,10 @@ public class AccordCommandStore extends CommandStore implements CacheSize
AbstractRanges ranges = (AbstractRanges) keysOrRanges;
for (Range range : ranges)
{
- CommandsForRanges.DomainInfo summary = commandsForRanges.search(range);
+ CommandsSummary summary = commandsForRanges.search(range);
if (summary == null)
continue;
- accumulate = map.apply(summary, summary, accumulate);
- if (terminate.test(accumulate))
- return accumulate;
+ accumulate = map.apply(summary, accumulate);
}
}
break;
diff --git a/src/java/org/apache/cassandra/service/accord/AccordCommandsForKeys.java b/src/java/org/apache/cassandra/service/accord/AccordCommandsForKeys.java
deleted file mode 100644
index 340b8396d3..0000000000
--- a/src/java/org/apache/cassandra/service/accord/AccordCommandsForKeys.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.service.accord;
-
-import java.nio.ByteBuffer;
-import java.util.function.Function;
-
-import accord.api.Key;
-import accord.impl.CommandsForKey;
-import accord.impl.CommandsForKeyGroupUpdater;
-import accord.impl.CommandsForKeyUpdater;
-import accord.primitives.RoutableKey;
-import accord.utils.Invariants;
-import accord.utils.async.AsyncChain;
-import org.apache.cassandra.concurrent.ExecutorPlus;
-import org.apache.cassandra.service.accord.api.PartitionKey;
-import org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer;
-
-/**
- * Ties together the separate commands for key and commands for key update classes to make loading,
- * saving, and evicting coherent
- */
-public class AccordCommandsForKeys
-{
- private final AccordCommandStore commandStore;
-
- public AccordCommandsForKeys(AccordCommandStore commandStore)
- {
- this.commandStore = commandStore;
- }
-
- AccordCachingState<RoutableKey, CommandsForKey> createDepsCommandsNode(RoutableKey key, int index)
- {
- return new DepsCommandsCachingState(key, index);
- }
-
- AccordCachingState<RoutableKey, CommandsForKey> createAllCommandsNode(RoutableKey key, int index)
- {
- return new AllCommandsCachingState(key, index);
- }
-
- AccordCachingState<RoutableKey, CommandsForKeyUpdate> createUpdatesNode(RoutableKey key, int index)
- {
- return new UpdateCachingState(key, index);
- }
-
- protected static boolean hasEvictableStatus(AccordCachingState<?,?> state)
- {
- if (state == null)
- return true;
-
- switch (state.status())
- {
- case LOADING:
- case SAVING:
- return false;
- }
-
- return true;
- }
-
- boolean canEvictKey(RoutableKey key)
- {
- return hasEvictableStatus(commandStore.depsCommandsForKeyCache().getUnsafe(key))
- && hasEvictableStatus(commandStore.allCommandsForKeyCache().getUnsafe(key))
- && hasEvictableStatus(commandStore.updatesForKeyCache().getUnsafe(key));
- }
-
- public abstract class CommandsCachingState extends AccordCachingState<RoutableKey, CommandsForKey>
- {
- protected CommandsCachingState(RoutableKey key, int index)
- {
- super(key, index);
- }
-
- private CommandsForKey initializeIfNull(CommandsForKey commands)
- {
- if (commands != null)
- return commands;
- return new CommandsForKey((Key) key(), CommandsForKeySerializer.loader);
- }
-
- private State<RoutableKey, CommandsForKey> maybeApplyUpdates(State<RoutableKey, CommandsForKey> state)
- {
- if (!(state instanceof Loaded))
- return state;
-
- Loaded<RoutableKey, CommandsForKey> loaded = (Loaded<RoutableKey, CommandsForKey>) state;
- CommandsForKey commands = loaded.get();
- UpdateCachingState updates = (UpdateCachingState) commandStore.updatesForKeyCache().getUnsafe(key());
- if (updates == null)
- return loaded;
-
- CommandsForKeyUpdate update = updates.getUpdateIfAvailable();
- if (update == null)
- return loaded;
- CommandsForKey updated = apply(initializeIfNull(commands), update);
- if (updated == commands)
- return loaded;
-
- return new Loaded<>(updated);
- }
-
- protected abstract CommandsForKey apply(CommandsForKey current, CommandsForKeyUpdate update);
-
- private void maybeApplyUpdates(CommandsForKeyUpdate update)
- {
- if (status() != Status.LOADED)
- return;
-
- CommandsForKey commands = get();
- CommandsForKey updated = apply(initializeIfNull(commands), update);
- if (commands != updated)
- super.state(new Loaded<>(updated));
- }
-
- protected State<RoutableKey, CommandsForKey> state(State<RoutableKey, CommandsForKey> next)
- {
- Status nextStatus = next.status();
- Invariants.checkState(nextStatus != Status.MODIFIED && nextStatus != Status.SAVING,
- "CommandsForKey cannot have state %s", nextStatus);
-
- return super.state(maybeApplyUpdates(next));
- }
-
- @Override
- public boolean canEvict()
- {
- return canEvictKey(key());
- }
- }
-
- public class DepsCommandsCachingState extends CommandsCachingState
- {
- public DepsCommandsCachingState(RoutableKey key, int index)
- {
- super(key, index);
- }
-
- protected CommandsForKey apply(CommandsForKey current, CommandsForKeyUpdate update)
- {
- return update.applyToDeps(current);
- }
- }
-
- public class AllCommandsCachingState extends CommandsCachingState
- {
- public AllCommandsCachingState(RoutableKey key, int index)
- {
- super(key, index);
- }
-
- protected CommandsForKey apply(CommandsForKey current, CommandsForKeyUpdate update)
- {
- return update.applyToAll(current);
- }
- }
-
- public class UpdateCachingState extends AccordCachingState<RoutableKey, CommandsForKeyUpdate> implements CommandsForKeyGroupUpdater.Immutable.Factory<ByteBuffer, CommandsForKeyUpdate>
- {
- public UpdateCachingState(RoutableKey key, int index)
- {
- super(key, index);
- }
-
- public AsyncChain<CommandsForKeyUpdate> load(ExecutorPlus executor, Function<RoutableKey, CommandsForKeyUpdate> loadFunction)
- {
- if (status() == Status.UNINITIALIZED)
- {
- CommandsForKeyUpdate initialized = CommandsForKeyUpdate.empty(key());
- state(state().initialize(initialized));
- return null;
- }
-
- return super.load(executor, loadFunction);
- }
-
- // update in memory cfk data with the update results
- protected void maybeUpdateCommands(CommandsForKeyUpdate update, AccordStateCache.Instance<RoutableKey, CommandsForKey, AccordSafeCommandsForKey> cache)
- {
- CommandsCachingState commands = (CommandsCachingState) cache.getUnsafe(key());
- if (commands == null)
- return;
-
- commands.maybeApplyUpdates(update);
- }
-
- public CommandsForKeyUpdate create(CommandsForKeyUpdater.Immutable<ByteBuffer> deps, CommandsForKeyUpdater.Immutable<ByteBuffer> all, CommandsForKeyUpdater.Immutable<ByteBuffer> common)
- {
- return new CommandsForKeyUpdate((PartitionKey) key(), deps, all, common);
- }
-
- protected State<RoutableKey, CommandsForKeyUpdate> maybeProcessModification(State<RoutableKey, CommandsForKeyUpdate> next)
- {
- if (!(next instanceof Modified))
- return next;
-
- Modified<RoutableKey, CommandsForKeyUpdate> modified = (Modified<RoutableKey, CommandsForKeyUpdate>) next;
-
- CommandsForKeyUpdate current = modified.current;
- maybeUpdateCommands(current, commandStore.depsCommandsForKeyCache());
- maybeUpdateCommands(current, commandStore.allCommandsForKeyCache());
-
- // combine in memory updates
- current = CommandsForKeyGroupUpdater.Immutable.merge(modified.original, current, this);
-
- return new Modified<>(null, current);
- }
-
- protected State<RoutableKey, CommandsForKeyUpdate> state(State<RoutableKey, CommandsForKeyUpdate> next)
- {
- Status nextStatus = next.status();
- Invariants.checkState(nextStatus != Status.LOADING,
- "CommandsForKeyUpdate cannot have state %s", nextStatus);
-
- return super.state(maybeProcessModification(next));
- }
-
- CommandsForKeyUpdate getUpdateIfAvailable()
- {
- switch (status())
- {
- case LOADED:
- case MODIFIED:
- return get();
- }
-
- return null;
- }
-
- @Override
- public boolean canEvict()
- {
- return canEvictKey(key());
- }
- }
-}
diff --git a/src/java/org/apache/cassandra/service/accord/AccordConfigurationService.java b/src/java/org/apache/cassandra/service/accord/AccordConfigurationService.java
index 31565f8423..d2a14a44db 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordConfigurationService.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordConfigurationService.java
@@ -149,7 +149,7 @@ public class AccordConfigurationService extends AbstractConfigurationService<Acc
}
remoteSyncComplete.forEach(id -> receiveRemoteSyncComplete(id, epoch));
- // TODO (now): disk doesn't get updated until we see our own notification, so there is an edge case where this instance notified others and fails in the middle, but Apply was already sent! This could leave partial closed/redudant accross the cluster
+ // TODO (required): disk doesn't get updated until we see our own notification, so there is an edge case where this instance notified others and fails in the middle, but Apply was already sent! This could leave partial closed/redudant accross the cluster
receiveClosed(closed, epoch);
receiveRedundant(redundant, epoch);
}));
diff --git a/src/java/org/apache/cassandra/service/accord/AccordKeyspace.java b/src/java/org/apache/cassandra/service/accord/AccordKeyspace.java
index 68e2b403c3..5efccd8fcc 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordKeyspace.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordKeyspace.java
@@ -24,7 +24,6 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
@@ -34,14 +33,11 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
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.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@@ -49,9 +45,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import accord.api.Key;
-import accord.impl.CommandTimeseries;
import accord.impl.CommandsForKey;
-import accord.impl.CommandsForKeyUpdater;
import accord.impl.TimestampsForKey;
import accord.local.Command;
import accord.local.Command.WaitingOn;
@@ -152,8 +146,7 @@ import org.apache.cassandra.service.accord.serializers.KeySerializers;
import org.apache.cassandra.service.accord.serializers.ListenerSerializers;
import org.apache.cassandra.service.accord.serializers.TopologySerializers;
import org.apache.cassandra.service.accord.serializers.WaitingOnSerializer;
-import org.apache.cassandra.utils.Clock;
-import org.apache.cassandra.utils.MonotonicClock;
+import org.apache.cassandra.utils.Clock.Global;
import org.apache.cassandra.utils.Throwables;
import org.apache.cassandra.utils.btree.BTree;
import org.apache.cassandra.utils.bytecomparable.ByteComparable;
@@ -163,6 +156,8 @@ 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;
@@ -176,14 +171,13 @@ public class AccordKeyspace
public static final String COMMANDS = "commands";
public static final String TIMESTAMPS_FOR_KEY = "timestamps_for_key";
- public static final String DEPS_COMMANDS_FOR_KEY = "deps_commands_for_key";
- public static final String ALL_COMMANDS_FOR_KEY = "all_commands_for_key";
+ 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<String> TABLE_NAMES = ImmutableSet.of(COMMANDS, TIMESTAMPS_FOR_KEY, DEPS_COMMANDS_FOR_KEY,
- ALL_COMMANDS_FOR_KEY, TOPOLOGIES, EPOCH_METADATA,
+ public static final Set<String> TABLE_NAMES = ImmutableSet.of(COMMANDS, TIMESTAMPS_FOR_KEY, COMMANDS_FOR_KEY,
+ TOPOLOGIES, EPOCH_METADATA,
COMMAND_STORE_METADATA);
private static final TupleType TIMESTAMP_TYPE = new TupleType(Lists.newArrayList(LongType.instance, LongType.instance, Int32Type.instance));
@@ -421,7 +415,6 @@ public class AccordKeyspace
+ "store_id int, "
+ "key_token blob, " // can't use "token" as this is restricted word in CQL
+ format("key %s, ", KEY_TUPLE)
- + format("max_timestamp %s, ", TIMESTAMP_TUPLE)
+ format("last_executed_timestamp %s, ", TIMESTAMP_TUPLE)
+ "last_executed_micros bigint, "
+ format("last_write_timestamp %s, ", TIMESTAMP_TUPLE)
@@ -438,12 +431,11 @@ public class AccordKeyspace
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");
- public static final ColumnMetadata max_timestamp = getColumn(TimestampsForKeys, "max_timestamp");
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(max_timestamp, last_executed_timestamp, last_executed_micros, 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)
{
@@ -474,15 +466,6 @@ public class AccordKeyspace
return deserializeKey(partitionKeyComponents[key.position()]);
}
- @Nullable
- public static Timestamp getMaxTimestamp(Row row)
- {
- Cell cell = row.getCell(max_timestamp);
- if (cell == null)
- return null;
- return deserializeTimestampOrNull(cell.value(), cell.accessor(), Timestamp::fromBits);
- }
-
@Nullable
public static Timestamp getLastExecutedTimestamp(Row row)
{
@@ -509,12 +492,11 @@ public class AccordKeyspace
return deserializeTimestampOrNull(cell.value(), cell.accessor(), Timestamp::fromBits);
}
- public static Row truncateTimestampsForKeyRow(long nowInSec, Row row, Cell lastExecuteMicrosCell, Cell lastExecuteCell, Cell lastWriteCell, Cell maxTimestampCell)
+ public static Row truncateTimestampsForKeyRow(long nowInSec, Row row, Cell lastExecuteMicrosCell, Cell lastExecuteCell, Cell lastWriteCell)
{
checkArgument(lastExecuteMicrosCell == null || lastExecuteMicrosCell.column() == last_executed_micros);
checkArgument(lastExecuteCell == null || lastExecuteCell.column() == last_executed_timestamp);
checkArgument(lastWriteCell == null || lastWriteCell.column() == last_write_timestamp);
- checkArgument(maxTimestampCell == null || maxTimestampCell.column() == max_timestamp);
long timestamp = row.primaryKeyLivenessInfo().timestamp();
@@ -525,8 +507,6 @@ public class AccordKeyspace
colCount++;
if (lastWriteCell != null)
colCount++;
- if (maxTimestampCell != null)
- colCount++;
checkState(columns.size() >= colCount, "CommandsForKeyColumns.static_columns_metadata should include all the columns");
Object[] newLeaf = BTree.unsafeAllocateNonEmptyLeaf(colCount);
@@ -537,17 +517,14 @@ public class AccordKeyspace
if (lastExecuteCell != null)
newLeaf[colIndex++] = lastExecuteCell;
if (lastWriteCell != null)
- newLeaf[colIndex++] = lastWriteCell;
- if (maxTimestampCell != null)
- newLeaf[colIndex++] = maxTimestampCell;
+ newLeaf[colIndex] = lastWriteCell;
return BTreeRow.create(row.clustering(), LivenessInfo.create(timestamp, nowInSec),
Deletion.LIVE, newLeaf);
}
}
- private static final TableMetadata DepsCommandsForKeys = commandsForKeysTable(DEPS_COMMANDS_FOR_KEY);
- private static final TableMetadata AllCommandsForKeys = commandsForKeysTable(ALL_COMMANDS_FOR_KEY);
+ private static final TableMetadata CommandsForKeys = commandsForKeysTable(COMMANDS_FOR_KEY);
private static TableMetadata commandsForKeysTable(String tableName)
{
@@ -557,9 +534,8 @@ public class AccordKeyspace
+ "store_id int, "
+ "key_token blob, " // can't use "token" as this is restricted word in CQL
+ format("key %s, ", KEY_TUPLE)
- + format("timestamp %s, ", TIMESTAMP_TUPLE)
+ "data blob, "
- + "PRIMARY KEY((store_id, key_token, key), timestamp)"
+ + "PRIMARY KEY((store_id, key_token, key))"
+ ')')
.partitioner(FOR_KEYS_LOCAL_PARTITIONER)
.build();
@@ -574,8 +550,6 @@ public class AccordKeyspace
final ColumnMetadata store_id;
final ColumnMetadata key_token;
final ColumnMetadata key;
- final ColumnMetadata timestamp;
-
final ColumnMetadata data;
final RegularAndStaticColumns columns;
@@ -589,7 +563,6 @@ public class AccordKeyspace
this.store_id = getColumn(table, "store_id");
this.key_token = getColumn(table, "key_token");
this.key = getColumn(table, "key");
- this.timestamp = getColumn(table, "timestamp");
this.data = getColumn(table, "data");
this.columns = new RegularAndStaticColumns(Columns.NONE, Columns.from(Lists.newArrayList(data)));
}
@@ -609,15 +582,37 @@ public class AccordKeyspace
return deserializeKey(partitionKeyComponents[key.position()]);
}
- @Nullable
- public Timestamp getTimestamp(Row row)
+ public CommandsForKey getCommandsForKey(PartitionKey key, Row row)
+ {
+ Cell<?> cell = row.getCell(data);
+ if (cell == null)
+ return null;
+
+ return CommandsForKeySerializer.fromBytes(key, cell.buffer());
+ }
+
+ // TODO (expected): garbage-free filtering, reusing encoding
+ public Row withoutRedundantCommands(PartitionKey key, Row row, TxnId redundantBefore)
{
- return deserializeTimestampOrNull(row.clustering().bufferAt(timestamp.position()), Timestamp::fromBits);
+ Invariants.checkState(row.columnCount() == 1);
+ Cell<?> cell = row.getCell(data);
+ if (cell == null)
+ return row;
+
+ CommandsForKey current = CommandsForKeySerializer.fromBytes(key, cell.buffer());
+ CommandsForKey updated = current.withoutRedundant(redundantBefore);
+ if (current == updated)
+ return row;
+
+ if (updated.size() == 0)
+ return null;
+
+ ByteBuffer buffer = CommandsForKeySerializer.toBytesWithoutKey(updated);
+ return BTreeRow.singleCellRow(Clustering.EMPTY, BufferCell.live(data, cell.timestamp(), buffer));
}
}
- public static final CommandsForKeyAccessor DepsCommandsForKeysAccessor = new CommandsForKeyAccessor(DepsCommandsForKeys);
- public static final CommandsForKeyAccessor AllCommandsForKeysAccessor = new CommandsForKeyAccessor(AllCommandsForKeys);
+ public static final CommandsForKeyAccessor CommandsForKeysAccessor = new CommandsForKeyAccessor(CommandsForKeys);
private static final TableMetadata Topologies =
parse(TOPOLOGIES,
@@ -676,7 +671,7 @@ public class AccordKeyspace
public static Tables tables()
{
- return Tables.of(Commands, TimestampsForKeys, DepsCommandsForKeys, AllCommandsForKeys, Topologies, EpochMetadata, CommandStoreMetadata);
+ return Tables.of(Commands, TimestampsForKeys, CommandsForKeys, Topologies, EpochMetadata, CommandStoreMetadata);
}
private static <T> ByteBuffer serialize(T obj, LocalVersionedSerializer<T> serializer) throws IOException
@@ -837,7 +832,7 @@ public class AccordKeyspace
ByteBuffer key = CommandsColumns.keyComparator.make(storeId,
command.txnId().domain().ordinal(),
serializeTimestamp(command.txnId())).serializeAsPartitionKey();
- PartitionUpdate update = PartitionUpdate.singleRowUpdate(Commands, key, row);
+ PartitionUpdate update = singleRowUpdate(Commands, key, row);
return new Mutation(update);
}
catch (IOException e)
@@ -971,7 +966,7 @@ public class AccordKeyspace
private static abstract class TableWalk implements Runnable, DebuggableTask
{
- private final long creationTimeNanos = Clock.Global.nanoTime();
+ private final long creationTimeNanos = Global.nanoTime();
private final Executor executor;
private final Observable<UntypedResultSet.Row> callback;
private long startTimeNanos = -1;
@@ -997,7 +992,7 @@ public class AccordKeyspace
try
{
if (startTimeNanos == -1)
- startTimeNanos = Clock.Global.nanoTime();
+ startTimeNanos = Global.nanoTime();
numQueries++;
UntypedResultSet result = query(lastSeen);
if (result.isEmpty())
@@ -1328,7 +1323,6 @@ public class AccordKeyspace
int nowInSeconds = (int) TimeUnit.MICROSECONDS.toSeconds(timestampMicros);
LivenessInfo livenessInfo = LivenessInfo.create(timestampMicros, nowInSeconds);
builder.addPrimaryKeyLivenessInfo(livenessInfo);
- addCellIfModified(TimestampsForKeyColumns.max_timestamp, TimestampsForKey::max, AccordKeyspace::serializeTimestamp, builder, timestampMicros, nowInSeconds, original, current);
addCellIfModified(TimestampsForKeyColumns.last_executed_timestamp, TimestampsForKey::lastExecutedTimestamp, AccordKeyspace::serializeTimestamp, builder, timestampMicros, nowInSeconds, original, current);
addCellIfModified(TimestampsForKeyColumns.last_executed_micros, TimestampsForKey::rawLastExecutedHlc, accessor::valueOf, builder, timestampMicros, nowInSeconds, original, current);
addCellIfModified(TimestampsForKeyColumns.last_write_timestamp, TimestampsForKey::lastWriteTimestamp, AccordKeyspace::serializeTimestamp, builder, timestampMicros, nowInSeconds, original, current);
@@ -1338,7 +1332,7 @@ public class AccordKeyspace
return null;
ByteBuffer key = TimestampsForKeyColumns.makePartitionKey(storeId, current.key());
- PartitionUpdate update = PartitionUpdate.singleRowUpdate(TimestampsForKeys, key, row);
+ PartitionUpdate update = singleRowUpdate(TimestampsForKeys, key, row);
return new Mutation(update);
}
catch (IOException e)
@@ -1373,7 +1367,6 @@ public class AccordKeyspace
public static TimestampsForKey unsafeLoadTimestampsForKey(AccordCommandStore commandStore, PartitionKey key)
{
-
UntypedResultSet rows = loadTimestampsForKeyRow(commandStore, key);
if (rows.isEmpty())
@@ -1384,60 +1377,11 @@ public class AccordKeyspace
UntypedResultSet.Row row = rows.one();
checkState(deserializeKey(row).equals(key));
- Timestamp max = deserializeTimestampOrDefault(row, "max_timestamp", Timestamp::fromBits, Timestamp.NONE);
Timestamp lastExecutedTimestamp = deserializeTimestampOrDefault(row, "last_executed_timestamp", Timestamp::fromBits, Timestamp.NONE);
long lastExecutedMicros = row.has("last_executed_micros") ? row.getLong("last_executed_micros") : 0;
Timestamp lastWriteTimestamp = deserializeTimestampOrDefault(row, "last_write_timestamp", Timestamp::fromBits, Timestamp.NONE);
- return TimestampsForKey.SerializerSupport.create(key, max, lastExecutedTimestamp, lastExecutedMicros, lastWriteTimestamp);
- }
-
- private static <T extends Timestamp> void addSeriesMutations(CommandsForKeyAccessor accessor,
- CommandTimeseries.Update<T, ByteBuffer> update,
- PartitionUpdate.Builder partitionBuilder,
- Row.Builder rowBuilder,
- LivenessInfo livenessInfo,
- long timestampMicros,
- Row.Deletion deletion,
- Predicate<T> predicate)
- {
- if (update.isEmpty())
- return;
-
- update.forEachWrite((timestamp, bytes) -> {
- if (!predicate.test(timestamp))
- return;
- rowBuilder.newRow(Clustering.make(serializeTimestamp(timestamp)));
- rowBuilder.addCell(live(accessor.data, timestampMicros, bytes));
- rowBuilder.addPrimaryKeyLivenessInfo(livenessInfo);
- partitionBuilder.add(rowBuilder.build());
- });
- update.forEachDelete(timestamp -> {
- if (!predicate.test(timestamp))
- return;
- rowBuilder.newRow(Clustering.make(serializeTimestamp(timestamp)));
- rowBuilder.addRowDeletion(deletion);
- partitionBuilder.add(rowBuilder.build());
- });
- }
-
- private static <T extends Timestamp> void addSeriesMutations(CommandsForKeyAccessor accessor,
- CommandTimeseries.Update<T, ByteBuffer> common,
- CommandTimeseries.Update<T, ByteBuffer> update,
- PartitionUpdate.Builder partitionBuilder,
- Row.Builder rowBuilder,
- LivenessInfo livenessInfo,
- int nowInSeconds)
- {
-
- long timestampMicros = livenessInfo.timestamp();
-
- Row.Deletion deletion = common.numDeletes() + update.numDeletes() > 0 ?
- Row.Deletion.regular(DeletionTime.build(timestampMicros, nowInSeconds)) :
- null;
-
- addSeriesMutations(accessor, common, partitionBuilder, rowBuilder, livenessInfo, timestampMicros, deletion, ts -> !update.contains(ts));
- addSeriesMutations(accessor, update, partitionBuilder, rowBuilder, livenessInfo, timestampMicros, deletion, ts -> true);
+ return TimestampsForKey.SerializerSupport.create(key, lastExecutedTimestamp, lastExecutedMicros, lastWriteTimestamp);
}
private static DecoratedKey makeKey(CommandsForKeyAccessor accessor, int storeId, PartitionKey key)
@@ -1449,54 +1393,17 @@ public class AccordKeyspace
return accessor.table.partitioner.decorateKey(pk);
}
- private static PartitionUpdate getCommandsForKeyPartitionUpdate(CommandsForKeyAccessor accessor, int storeId, PartitionKey key, CommandsForKeyUpdater<ByteBuffer> common, CommandsForKeyUpdater<ByteBuffer> update, long timestampMicros)
+ private static PartitionUpdate getCommandsForKeyPartitionUpdate(int storeId, PartitionKey key, CommandsForKey commandsForKey, long timestampMicros)
{
-
- int nowInSeconds = (int) TimeUnit.MICROSECONDS.toSeconds(timestampMicros);
- LivenessInfo livenessInfo = LivenessInfo.create(timestampMicros, nowInSeconds);
-
- int expectedRows = common.totalChanges() + update.totalChanges();
-
- PartitionUpdate.Builder partitionBuilder = new PartitionUpdate.Builder(accessor.table,
- makeKey(accessor, storeId, key),
- accessor.columns,
- expectedRows);
-
- Row.Builder rowBuilder = BTreeRow.unsortedBuilder();
-
- addSeriesMutations(accessor, common.commands(), update.commands(), partitionBuilder, rowBuilder, livenessInfo, nowInSeconds);
-
- PartitionUpdate partitionUpdate = partitionBuilder.build();
- if (partitionUpdate.isEmpty())
- return null;
- return partitionUpdate;
+ ByteBuffer bytes = CommandsForKeySerializer.toBytesWithoutKey(commandsForKey);
+ return singleRowUpdate(CommandsForKeysAccessor.table,
+ makeKey(CommandsForKeysAccessor, storeId, key),
+ singleCellRow(Clustering.EMPTY, BufferCell.live(CommandsForKeysAccessor.data, timestampMicros, bytes)));
}
- public static Mutation getCommandsForKeyMutation(int storeId, CommandsForKeyUpdate update, long timestampMicros)
+ public static Mutation getCommandsForKeyMutation(int storeId, CommandsForKey update, long timestampMicros)
{
- PartitionUpdate depsUpdate = getCommandsForKeyPartitionUpdate(DepsCommandsForKeysAccessor,
- storeId,
- update.key(),
- update.common(),
- update.deps(),
- timestampMicros);
- PartitionUpdate allUpdate = getCommandsForKeyPartitionUpdate(AllCommandsForKeysAccessor,
- storeId,
- update.key(),
- update.common(),
- update.all(),
- timestampMicros);
-
- if (depsUpdate == null && allUpdate == null)
- return null;
- if (depsUpdate == null)
- return new Mutation(allUpdate);
- else if (allUpdate == null)
- return new Mutation(depsUpdate);
-
- return new Mutation(ACCORD_KEYSPACE_NAME, depsUpdate.partitionKey(),
- ImmutableMap.of(depsUpdate.metadata().id, depsUpdate, allUpdate.metadata().id, allUpdate),
- MonotonicClock.Global.approxTime.now(), false);
+ return new Mutation(getCommandsForKeyPartitionUpdate(storeId, (PartitionKey) update.key(), update, timestampMicros));
}
private static <T> ByteBuffer cellValue(Cell<T> cell)
@@ -1526,50 +1433,31 @@ public class AccordKeyspace
FULL_PARTITION);
}
- public static SinglePartitionReadCommand getDepsCommandsForKeyRead(int storeId, PartitionKey key, int nowInSeconds)
- {
- return getCommandsForKeyRead(DepsCommandsForKeysAccessor, storeId, key, nowInSeconds);
- }
-
- public static SinglePartitionReadCommand getAllCommandsForKeyRead(int storeId, PartitionKey key, int nowInSeconds)
+ public static SinglePartitionReadCommand getCommandsForKeyRead(int storeId, PartitionKey key, int nowInSeconds)
{
- return getCommandsForKeyRead(AllCommandsForKeysAccessor, storeId, key, nowInSeconds);
+ return getCommandsForKeyRead(CommandsForKeysAccessor, storeId, key, nowInSeconds);
}
static CommandsForKey unsafeLoadCommandsForKey(CommandsForKeyAccessor accessor, AccordCommandStore commandStore, PartitionKey key)
{
- long timestampMicros = TimeUnit.MILLISECONDS.toMicros(Clock.Global.currentTimeMillis());
+ long timestampMicros = TimeUnit.MILLISECONDS.toMicros(Global.currentTimeMillis());
int nowInSeconds = (int) TimeUnit.MICROSECONDS.toSeconds(timestampMicros);
SinglePartitionReadCommand command = getCommandsForKeyRead(accessor, commandStore.id(), key, nowInSeconds);
-
- ImmutableSortedMap.Builder<Timestamp, ByteBuffer> commands = new ImmutableSortedMap.Builder<>(Comparator.naturalOrder());
-
try (ReadExecutionController controller = command.executionController();
FilteredPartitions partitions = FilteredPartitions.filter(command.executeLocally(controller), nowInSeconds))
{
if (!partitions.hasNext())
- {
return null;
- }
try (RowIterator partition = partitions.next())
{
- while (partition.hasNext())
- {
- Row row = partition.next();
- Clustering<?> clustering = row.clustering();
- Timestamp timestamp = deserializeTimestampOrNull(clusteringValue(clustering, 0), Timestamp::fromBits);
- ByteBuffer data = cellValue(row, accessor.data);
- if (data == null)
- continue;
- commands.put(timestamp, data);
- }
+ Invariants.checkState(partition.hasNext());
+ Row row = partition.next();
+ ByteBuffer data = cellValue(row, accessor.data);
+ return CommandsForKeySerializer.fromBytes(key, data);
}
- checkState(!partitions.hasNext());
-
- return CommandsForKey.SerializerSupport.create(key, CommandsForKeySerializer.loader, commands.build());
}
catch (Throwable t)
{
@@ -1578,26 +1466,15 @@ public class AccordKeyspace
}
}
- public static CommandsForKey unsafeLoadDepsCommandsForKey(AccordCommandStore commandStore, PartitionKey key)
- {
- return unsafeLoadCommandsForKey(DepsCommandsForKeysAccessor, commandStore, key);
- }
-
- public static CommandsForKey unsafeLoadAllCommandsForKey(AccordCommandStore commandStore, PartitionKey key)
+ public static CommandsForKey unsafeLoadCommandsForKey(AccordCommandStore commandStore, PartitionKey key)
{
- return unsafeLoadCommandsForKey(AllCommandsForKeysAccessor, commandStore, key);
- }
-
- public static CommandsForKey loadDepsCommandsForKey(AccordCommandStore commandStore, PartitionKey key)
- {
- commandStore.checkNotInStoreThread();
- return unsafeLoadCommandsForKey(DepsCommandsForKeysAccessor, commandStore, key);
+ return unsafeLoadCommandsForKey(CommandsForKeysAccessor, commandStore, key);
}
- public static CommandsForKey loadAllCommandsForKey(AccordCommandStore commandStore, PartitionKey key)
+ public static CommandsForKey loadCommandsForKey(AccordCommandStore commandStore, PartitionKey key)
{
commandStore.checkNotInStoreThread();
- return unsafeLoadCommandsForKey(AllCommandsForKeysAccessor, commandStore, key);
+ return unsafeLoadCommandsForKey(CommandsForKeysAccessor, commandStore, key);
}
public static class EpochDiskState
@@ -1859,7 +1736,7 @@ public class AccordKeyspace
ModificationStatement statement = (ModificationStatement) QueryProcessor.parseStatement(cql).prepare(ClientState.forInternalCalls());
QueryOptions options = QueryOptions.forInternalCalls(Arrays.asList(values));
- long tsMicros = TimeUnit.MILLISECONDS.toMicros(Clock.Global.currentTimeMillis());
+ long tsMicros = TimeUnit.MILLISECONDS.toMicros(Global.currentTimeMillis());
while (true)
{
@@ -1871,7 +1748,7 @@ public class AccordKeyspace
break;
}
- return Iterables.getOnlyElement(statement.getMutations(clientState, options, true, tsMicros, (int) TimeUnit.MICROSECONDS.toSeconds(tsMicros), Clock.Global.nanoTime(), false));
+ return Iterables.getOnlyElement(statement.getMutations(clientState, options, true, tsMicros, (int) TimeUnit.MICROSECONDS.toSeconds(tsMicros), Global.nanoTime(), false));
}
diff --git a/src/java/org/apache/cassandra/service/accord/AccordMessageSink.java b/src/java/org/apache/cassandra/service/accord/AccordMessageSink.java
index 17c801df10..73827a3771 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordMessageSink.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordMessageSink.java
@@ -145,7 +145,7 @@ public class AccordMessageSink implements MessageSink
builder.put(MessageType.WAIT_ON_COMMIT_REQ, Verb.ACCORD_WAIT_ON_COMMIT_REQ);
builder.put(MessageType.WAIT_ON_COMMIT_RSP, Verb.ACCORD_WAIT_ON_COMMIT_RSP);
builder.put(MessageType.WAIT_UNTIL_APPLIED_REQ, Verb.ACCORD_WAIT_UNTIL_APPLIED_REQ);
- builder.put(MessageType.APPLY_THEN_WAIT_UNTIL_APPLIED_REQ, Verb.ACCORD_APPLY_AND_WAIT_UNTIL_APPLIED_REQ);
+ builder.put(MessageType.APPLY_THEN_WAIT_UNTIL_APPLIED_REQ, Verb.ACCORD_APPLY_AND_WAIT_REQ);
builder.put(MessageType.INFORM_OF_TXN_REQ, Verb.ACCORD_INFORM_OF_TXN_REQ);
builder.put(MessageType.INFORM_DURABLE_REQ, Verb.ACCORD_INFORM_DURABLE_REQ);
builder.put(MessageType.INFORM_HOME_DURABLE_REQ, Verb.ACCORD_INFORM_HOME_DURABLE_REQ);
diff --git a/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java b/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java
index 3f506302db..a1704ba52b 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordObjectSizes.java
@@ -18,17 +18,14 @@
package org.apache.cassandra.service.accord;
-import java.nio.ByteBuffer;
-import java.util.Map;
import java.util.UUID;
import java.util.function.ToLongFunction;
-import com.google.common.collect.ImmutableSortedMap;
-
import accord.api.Key;
import accord.api.Result;
import accord.api.RoutingKey;
import accord.impl.CommandsForKey;
+import accord.impl.CommandsForKey.Info;
import accord.impl.TimestampsForKey;
import accord.local.Command;
import accord.local.Command.WaitingOn;
@@ -69,7 +66,6 @@ import org.apache.cassandra.service.accord.txn.TxnQuery;
import org.apache.cassandra.service.accord.txn.TxnRead;
import org.apache.cassandra.service.accord.txn.TxnResult;
import org.apache.cassandra.service.accord.txn.TxnWrite;
-import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.ObjectSizes;
import static org.apache.cassandra.utils.ObjectSizes.measure;
@@ -259,16 +255,10 @@ public class AccordObjectSizes
}
private static final long EMPTY_COMMAND_LISTENER = measure(new Command.ProxyListener(null));
- private static final long EMPTY_CFK_LISTENER = measure(new CommandsForKey.Listener((Key) null));
- private static final long EMPTY_CFR_LISTENER = measure(new CommandsForRanges.Listener(null));
public static long listener(Command.DurableAndIdempotentListener listener)
{
if (listener instanceof Command.ProxyListener)
return EMPTY_COMMAND_LISTENER + timestamp(((Command.ProxyListener) listener).txnId());
- if (listener instanceof CommandsForKey.Listener)
- return EMPTY_CFK_LISTENER + key(((CommandsForKey.Listener) listener).key());
- if (listener instanceof CommandsForRanges.Listener)
- return EMPTY_CFR_LISTENER + timestamp(((CommandsForRanges.Listener) listener).txnId);
throw new IllegalArgumentException("Unhandled listener type: " + listener.getClass());
}
@@ -362,34 +352,34 @@ public class AccordObjectSizes
return size;
}
- private static long cfkSeriesSize(ImmutableSortedMap<Timestamp, ByteBuffer> series)
- {
- long size = 0;
- for (Map.Entry<Timestamp, ByteBuffer> entry : series.entrySet())
- {
- size += timestamp(entry.getKey());
- size += ByteBufferUtil.estimatedSizeOnHeap(entry.getValue());
- }
- return size;
- }
-
- private static long EMPTY_TFK_SIZE = measure(TimestampsForKey.SerializerSupport.create(null, null, null, 0, null));
+ private static long EMPTY_TFK_SIZE = measure(TimestampsForKey.SerializerSupport.create(null, null, 0, null));
public static long timestampsForKey(TimestampsForKey timestamps)
{
long size = EMPTY_TFK_SIZE;
- size += timestamp(timestamps.max());
size += timestamp(timestamps.lastExecutedTimestamp());
size += timestamp(timestamps.lastWriteTimestamp());
return size;
}
- private static long EMPTY_CFK_SIZE = measure(CommandsForKey.SerializerSupport.create(null, null, ImmutableSortedMap.of()));
+ private static long EMPTY_CFK_SIZE = measure(new CommandsForKey(null));
+ private static long EMPTY_INFO_SIZE = measure(CommandsForKey.Info.createMock(null, null, null));
public static long commandsForKey(CommandsForKey cfk)
{
long size = EMPTY_CFK_SIZE;
size += key(cfk.key());
- size += cfkSeriesSize((ImmutableSortedMap<Timestamp, ByteBuffer>) cfk.commands().commands);
+ size += 2 * ObjectSizes.sizeOfReferenceArray(cfk.size());
+ size += cfk.size() * TIMESTAMP_SIZE;
+ for (int i = 0 ; i < cfk.size() ; ++i)
+ {
+ Info info = cfk.info(i);
+ if (info.getClass() == CommandsForKey.NoInfo.class)
+ continue;
+
+ size += EMPTY_INFO_SIZE;
+ if (info.missing.length > 0)
+ size += ObjectSizes.sizeOfReferenceArray(info.missing.length);
+ }
return size;
}
}
diff --git a/src/java/org/apache/cassandra/service/accord/AccordSafeCommandStore.java b/src/java/org/apache/cassandra/service/accord/AccordSafeCommandStore.java
index ea364d04f6..c1b09ccb27 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordSafeCommandStore.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordSafeCommandStore.java
@@ -18,53 +18,43 @@
package org.apache.cassandra.service.accord;
+import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
-import java.util.function.Predicate;
-import javax.annotation.Nullable;
+import java.util.function.BiFunction;
-import com.google.common.base.Predicates;
+import javax.annotation.Nullable;
import accord.api.Agent;
import accord.api.DataStore;
import accord.api.Key;
import accord.api.ProgressLog;
import accord.impl.AbstractSafeCommandStore;
-import accord.impl.CommandTimeseries;
-import accord.impl.CommandTimeseries.CommandLoader;
import accord.impl.CommandsForKey;
import accord.impl.CommandsForKeys;
-import accord.impl.DomainCommands;
-import accord.impl.DomainTimestamps;
-import accord.impl.SafeTimestampsForKey;
+import accord.impl.CommandsSummary;
+import accord.local.Command;
import accord.local.CommandStores.RangesForEpoch;
-import accord.local.CommonAttributes;
-import accord.local.KeyHistory;
import accord.local.NodeTimeService;
import accord.local.PreLoadContext;
-import accord.local.SafeCommand;
-import accord.local.Status;
import accord.primitives.AbstractKeys;
import accord.primitives.Deps;
-import accord.primitives.Range;
import accord.primitives.Ranges;
import accord.primitives.RoutableKey;
import accord.primitives.Routables;
-import accord.primitives.Seekable;
import accord.primitives.Seekables;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
-import accord.utils.TriFunction;
-import org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer;
-public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeCommand, AccordSafeTimestampsForKey, AccordSafeCommandsForKey, AccordSafeCommandsForKeyUpdate>
+import static accord.primitives.Routable.Domain.Range;
+
+public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeCommand, AccordSafeTimestampsForKey, AccordSafeCommandsForKey>
{
private final Map<TxnId, AccordSafeCommand> commands;
- private final NavigableMap<RoutableKey, AccordSafeCommandsForKey> depsCommandsForKeys;
- private final NavigableMap<RoutableKey, AccordSafeCommandsForKey> allCommandsForKeys;
+ private final NavigableMap<RoutableKey, AccordSafeCommandsForKey> commandsForKeys;
private final NavigableMap<RoutableKey, AccordSafeTimestampsForKey> timestampsForKeys;
- private final NavigableMap<RoutableKey, AccordSafeCommandsForKeyUpdate> updatesForKeys;
private final AccordCommandStore commandStore;
private final RangesForEpoch ranges;
CommandsForRanges.Updater rangeUpdates = null;
@@ -72,17 +62,13 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
public AccordSafeCommandStore(PreLoadContext context,
Map<TxnId, AccordSafeCommand> commands,
NavigableMap<RoutableKey, AccordSafeTimestampsForKey> timestampsForKey,
- NavigableMap<RoutableKey, AccordSafeCommandsForKey> depsCommandsForKey,
- NavigableMap<RoutableKey, AccordSafeCommandsForKey> allCommandsForKeys,
- NavigableMap<RoutableKey, AccordSafeCommandsForKeyUpdate> updatesForKeys,
+ NavigableMap<RoutableKey, AccordSafeCommandsForKey> commandsForKey,
AccordCommandStore commandStore)
{
super(context);
this.commands = commands;
this.timestampsForKeys = timestampsForKey;
- this.depsCommandsForKeys = depsCommandsForKey;
- this.allCommandsForKeys = allCommandsForKeys;
- this.updatesForKeys = updatesForKeys;
+ this.commandsForKeys = commandsForKey;
this.commandStore = commandStore;
this.ranges = commandStore.updateRangesForEpoch();
}
@@ -107,55 +93,22 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
return command;
}
- private NavigableMap<RoutableKey, AccordSafeCommandsForKey> commandsForKeyMap(KeyHistory history)
- {
- switch (history)
- {
- case DEPS:
- return depsCommandsForKeys;
- case ALL:
- return allCommandsForKeys;
- default:
- throw new IllegalArgumentException();
- }
- }
-
- @Override
- protected AccordSafeCommandsForKey getDepsCommandsForKeyInternal(RoutableKey key)
- {
- return depsCommandsForKeys.get(key);
- }
-
- @Override
- protected void addDepsCommandsForKeyInternal(AccordSafeCommandsForKey cfk)
- {
- depsCommandsForKeys.put(cfk.key(), cfk);
- }
-
@Override
- protected AccordSafeCommandsForKey getDepsCommandsForKeyIfLoaded(RoutableKey key)
+ protected AccordSafeCommandsForKey getCommandsForKeyInternal(RoutableKey key)
{
- AccordSafeCommandsForKey cfk = commandStore.depsCommandsForKeyCache().acquireIfLoaded(key);
- if (cfk != null) cfk.preExecute();
- return cfk;
+ return commandsForKeys.get(key);
}
@Override
- protected AccordSafeCommandsForKey getAllCommandsForKeyInternal(RoutableKey key)
+ protected void addCommandsForKeyInternal(AccordSafeCommandsForKey cfk)
{
- return allCommandsForKeys.get(key);
+ commandsForKeys.put(cfk.key(), cfk);
}
@Override
- protected void addAllCommandsForKeyInternal(AccordSafeCommandsForKey cfk)
+ protected AccordSafeCommandsForKey getCommandsForKeyIfLoaded(RoutableKey key)
{
- allCommandsForKeys.put(cfk.key(), cfk);
- }
-
- @Override
- protected AccordSafeCommandsForKey getAllCommandsForKeyIfLoaded(RoutableKey key)
- {
- AccordSafeCommandsForKey cfk = commandStore.allCommandsForKeyCache().acquireIfLoaded(key);
+ AccordSafeCommandsForKey cfk = commandStore.commandsForKeyCache().acquireIfLoaded(key);
if (cfk != null) cfk.preExecute();
return cfk;
}
@@ -180,27 +133,6 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
return cfk;
}
- protected AccordSafeCommandsForKeyUpdate getCommandsForKeyUpdateInternal(RoutableKey key)
- {
- return updatesForKeys.get(key);
- }
-
- protected AccordSafeCommandsForKeyUpdate createCommandsForKeyUpdateInternal(RoutableKey key)
- {
- throw new IllegalStateException("CFK updates should be initialized for operation");
- }
-
- protected void addCommandsForKeyUpdateInternal(AccordSafeCommandsForKeyUpdate update)
- {
- updatesForKeys.put(update.key(), update);
- }
-
- protected void applyCommandForKeyUpdates()
- {
- // TODO (now): should this happen as part of invalidate? Less obvious it's happening, but eliminates possibility of post update changes
- updatesForKeys.values().forEach(AccordSafeCommandsForKeyUpdate::setUpdates);
- }
-
@Override
public AccordCommandStore commandStore()
{
@@ -237,19 +169,6 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
return commandStore().unsafeRangesForEpoch();
}
- @Override
- public long latestEpoch()
- {
- return commandStore().time().epoch();
- }
-
- @Override
- public Timestamp maxConflict(Seekables<?, ?> keysOrRanges, Ranges slice)
- {
- Timestamp maxConflict = mapReduce(keysOrRanges, slice, KeyHistory.NONE, (ts, commands, accum) -> Timestamp.max(ts.max(), accum), Timestamp.NONE, Predicates.isNull());
- return Timestamp.nonNullOrMax(maxConflict, commandStore.commandsForRanges().maxRedundant());
- }
-
@Override
public void registerHistoricalTransactions(Deps deps)
{
@@ -257,6 +176,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
// 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 -> {
// TODO (desired, efficiency): this can be made more efficient by batching by epoch
if (ranges.coordinates(txnId).contains(key))
@@ -282,20 +202,13 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
});
}
- @Override
- public void erase(SafeCommand safeCommand)
- {
- }
-
- private <O> O mapReduce(Routables<?> keysOrRanges, Ranges slice, KeyHistory keyHistory, TriFunction<DomainTimestamps, DomainCommands, O, O> map, O accumulate, Predicate<? super O> terminate)
+ private <O> O mapReduce(Routables<?> keysOrRanges, Ranges slice, BiFunction<CommandsSummary, O, O> map, O accumulate)
{
- accumulate = commandStore.mapReduceForRange(keysOrRanges, slice, map, accumulate, terminate);
- if (terminate.test(accumulate))
- return accumulate;
- return mapReduceForKey(keysOrRanges, slice, keyHistory, map, accumulate, terminate);
+ accumulate = commandStore.mapReduceForRange(keysOrRanges, slice, map, accumulate);
+ return mapReduceForKey(keysOrRanges, slice, map, accumulate);
}
- private <O> O mapReduceForKey(Routables<?> keysOrRanges, Ranges slice, KeyHistory keyHistory, TriFunction<DomainTimestamps, DomainCommands, O, O> map, O accumulate, Predicate<? super O> terminate)
+ private <O> O mapReduceForKey(Routables<?> keysOrRanges, Ranges slice, BiFunction<CommandsSummary, O, O> map, O accumulate)
{
switch (keysOrRanges.domain())
{
@@ -308,11 +221,8 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
for (Key key : keys)
{
if (!slice.contains(key)) continue;
- SafeTimestampsForKey timestamps = timestampsForKey(key);
- CommandsForKey commands = !keyHistory.isNone() ? commandsForKey(key, keyHistory).current() : null;
- accumulate = map.apply(timestamps.current(), commands, accumulate);
- if (terminate.test((accumulate)))
- return accumulate;
+ CommandsForKey commands = commandsForKey(key).current();
+ accumulate = map.apply(commands, accumulate);
}
}
break;
@@ -327,11 +237,8 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
{
//TODO (duplicate code): this is a repeat of Key... only change is checking contains in range
if (!sliced.contains(key)) continue;
- SafeTimestampsForKey timestamps = timestampsForKey(key);
- CommandsForKey commands = !keyHistory.isNone() ? commandsForKey(key, keyHistory).current() : null;
- accumulate = map.apply(timestamps.current(), commands, accumulate);
- if (terminate.test(accumulate))
- return accumulate;
+ CommandsForKey commands = commandsForKey(key).current();
+ accumulate = map.apply(commands, accumulate);
}
}
break;
@@ -340,85 +247,41 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
}
@Override
-//<<<<<<< HEAD
-// public <P1, T> T mapReduce(Seekables<?, ?> keysOrRanges, Ranges slice, Txn.Kind.Kinds testKind, TestTimestamp testTimestamp, Timestamp timestamp, TestDep testDep, @Nullable TxnId depId, @Nullable Status minStatus, @Nullable Status maxStatus, CommandFunction<P1, T, T> map, P1 p1, T accumulate, Predicate<? super T> terminate) {
-// accumulate = mapReduce(keysOrRanges, slice, (forKey, prev) -> {
-// CommandTimeseries<?> timeseries;
-//=======
- public <P1, T> T mapReduce(Seekables<?, ?> keysOrRanges, Ranges slice, KeyHistory keyHistory, Txn.Kind.Kinds testKind, TestTimestamp testTimestamp, Timestamp timestamp, TestDep testDep, @Nullable TxnId depId, @Nullable Status minStatus, @Nullable Status maxStatus, CommandFunction<P1, T, T> map, P1 p1, T accumulate, Predicate<? super T> terminate)
+ public <P1, T> T mapReduceActive(Seekables<?, ?> keysOrRanges, Ranges slice, @Nullable Timestamp withLowerTxnId, Txn.Kind.Kinds testKind, CommandFunction<P1, T, T> map, P1 p1, T accumulate)
{
- accumulate = mapReduce(keysOrRanges, slice, keyHistory, (timestamps, commands, prev) -> {
- CommandTimeseries.TimestampType timestampType;
- switch (testTimestamp)
- {
- default: throw new AssertionError();
- case STARTED_AFTER:
- case STARTED_BEFORE:
- timestampType = CommandTimeseries.TimestampType.TXN_ID;
- break;
- case EXECUTES_AFTER:
- case MAY_EXECUTE_BEFORE:
- timestampType = CommandTimeseries.TimestampType.EXECUTE_AT;
- }
- CommandTimeseries.TestTimestamp remapTestTimestamp;
- switch (testTimestamp)
- {
- default: throw new AssertionError();
- case STARTED_AFTER:
- case EXECUTES_AFTER:
- remapTestTimestamp = CommandTimeseries.TestTimestamp.AFTER;
- break;
- case STARTED_BEFORE:
- case MAY_EXECUTE_BEFORE:
- remapTestTimestamp = CommandTimeseries.TestTimestamp.BEFORE;
- }
- return commands.commands().mapReduce(testKind, timestampType, remapTestTimestamp, timestamp, testDep, depId, minStatus, maxStatus, map, p1, prev, terminate);
- }, accumulate, terminate);
-
- return accumulate;
+ return mapReduce(keysOrRanges, slice, (summary, in) -> {
+ return summary.mapReduceActive(withLowerTxnId, testKind, map, p1, in);
+ }, accumulate);
}
@Override
- public CommonAttributes completeRegistration(Seekables<?, ?> seekables, Ranges ranges, AccordSafeCommand liveCommand, CommonAttributes attrs)
+ public <P1, T> T mapReduceFull(Seekables<?, ?> keysOrRanges, Ranges slice, TxnId testTxnId, Txn.Kind.Kinds testKind, TestStartedAt testStartedAt, TestDep testDep, TestStatus testStatus, CommandFunction<P1, T, T> map, P1 p1, T accumulate)
{
- for (Seekable seekable : seekables)
- attrs = completeRegistration(seekable, ranges, liveCommand, attrs);
- return attrs;
+ return mapReduce(keysOrRanges, slice, (summary, in) -> {
+ return summary.mapReduceFull(testTxnId, testKind, testStartedAt, testDep, testStatus, map, p1, in);
+ }, accumulate);
}
@Override
- public CommonAttributes completeRegistration(Seekable seekable, Ranges ranges, AccordSafeCommand liveCommand, CommonAttributes attrs)
+ protected void update(Command prev, Command updated, @Nullable Seekables<?, ?> keysOrRanges)
{
- switch (seekable.domain())
+ super.update(prev, updated, keysOrRanges);
+
+ if (updated.txnId().domain() == Range && CommandsForKey.needsUpdate(prev, updated))
{
- case Key:
+ if (keysOrRanges == null)
{
- Key key = seekable.asKey();
- if (ranges.contains(key))
- {
- CommandsForKeys.registerCommand(this, key, liveCommand.current());
- attrs = attrs.mutable().addListener(new CommandsForKey.Listener(key));
- }
+ if (updated.known().isDefinitionKnown()) keysOrRanges = updated.partialTxn().keys();
+ else if (prev.known().isDefinitionKnown()) keysOrRanges = prev.partialTxn().keys();
+ else return;
}
- break;
- case Range:
- Range range = seekable.asRange();
- if (!ranges.intersects(range))
- return attrs;
- // TODO (api) : cleaner way to deal with this? This is tracked at the Ranges level and not Range level
- // but we register at the Range level...
- if (!attrs.durableListeners().stream().anyMatch(l -> l instanceof CommandsForRanges.Listener))
- {
- CommandsForRanges.Listener listener = new CommandsForRanges.Listener(liveCommand.txnId());
- attrs = attrs.mutable().addListener(listener);
- // trigger to allow it to run right away
- listener.onChange(this, liveCommand);
- }
- break;
- default:
- throw new UnsupportedOperationException("Unknown domain: " + seekable.domain());
+ List<TxnId> waitingOn;
+
+ if (updated.partialDeps() == null) waitingOn = Collections.emptyList();
+ // TODO (required): this is faulty: we cannot simply save the raw transaction ids, as they may be for other ranges
+ else waitingOn = updated.partialDeps().txnIds();
+ updateRanges().put(updated.txnId(), (Ranges)keysOrRanges, updated.saveStatus(), updated.executeAt(), waitingOn);
}
- return attrs;
}
protected CommandsForRanges.Updater updateRanges()
@@ -433,30 +296,18 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
{
commands.values().forEach(AccordSafeCommand::invalidate);
timestampsForKeys.values().forEach(AccordSafeTimestampsForKey::invalidate);
- depsCommandsForKeys.values().forEach(AccordSafeCommandsForKey::invalidate);
- allCommandsForKeys.values().forEach(AccordSafeCommandsForKey::invalidate);
- updatesForKeys.values().forEach(AccordSafeCommandsForKeyUpdate::invalidate);
- }
-
- @Override
- public CommandLoader<?> cfkLoader(RoutableKey key)
- {
- return CommandsForKeySerializer.loader;
+ commandsForKeys.values().forEach(AccordSafeCommandsForKey::invalidate);
}
public void postExecute(Map<TxnId, AccordSafeCommand> commands,
Map<RoutableKey, AccordSafeTimestampsForKey> timestampsForKey,
- Map<RoutableKey, AccordSafeCommandsForKey> depsCommandsForKeys,
- Map<RoutableKey, AccordSafeCommandsForKey> allCommandsForKeys,
- Map<RoutableKey, AccordSafeCommandsForKeyUpdate> updatesForKeys
+ Map<RoutableKey, AccordSafeCommandsForKey> commandsForKeys
)
{
postExecute();
commands.values().forEach(AccordSafeState::postExecute);
timestampsForKey.values().forEach(AccordSafeState::postExecute);
- depsCommandsForKeys.values().forEach(AccordSafeState::postExecute);
- allCommandsForKeys.values().forEach(AccordSafeState::postExecute);
- updatesForKeys.values().forEach(AccordSafeState::postExecute);
+ commandsForKeys.values().forEach(AccordSafeState::postExecute);
if (rangeUpdates != null)
rangeUpdates.apply();
}
diff --git a/src/java/org/apache/cassandra/service/accord/AccordSafeCommandsForKey.java b/src/java/org/apache/cassandra/service/accord/AccordSafeCommandsForKey.java
index 97fc604649..748143f333 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordSafeCommandsForKey.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordSafeCommandsForKey.java
@@ -76,7 +76,7 @@ public class AccordSafeCommandsForKey extends SafeCommandsForKey implements Acco
// cfk initialization is legal, but doesn't need to be propagated to the cache (and would
// cause an exception to be thrown if it were). Making an exception on the cache side could
// throw away applied cfk updates as well, so it's special cased here
- if (hasUpdate && original == null && current != null && current.commands().isEmpty())
+ if (hasUpdate && original == null && current != null && current.size() == 0)
return false;
return hasUpdate;
@@ -122,7 +122,8 @@ public class AccordSafeCommandsForKey extends SafeCommandsForKey implements Acco
public void postExecute()
{
checkNotInvalidated();
- // updates are applied directly by CommandsForKeyUpdate
+ if (current != original)
+ global.set(current);
}
@Override
diff --git a/src/java/org/apache/cassandra/service/accord/AccordSafeCommandsForKeyUpdate.java b/src/java/org/apache/cassandra/service/accord/AccordSafeCommandsForKeyUpdate.java
deleted file mode 100644
index b881a45241..0000000000
--- a/src/java/org/apache/cassandra/service/accord/AccordSafeCommandsForKeyUpdate.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.service.accord;
-
-import java.nio.ByteBuffer;
-
-import com.google.common.annotations.VisibleForTesting;
-
-import accord.api.Key;
-import accord.impl.SafeCommandsForKey;
-import accord.primitives.RoutableKey;
-import accord.utils.async.AsyncChain;
-import org.apache.cassandra.service.accord.api.PartitionKey;
-import org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer;
-
-public class AccordSafeCommandsForKeyUpdate extends SafeCommandsForKey.Update<CommandsForKeyUpdate, ByteBuffer> implements AccordSafeState<RoutableKey, CommandsForKeyUpdate>
-{
- private boolean invalidated;
- private final AccordCachingState<RoutableKey, CommandsForKeyUpdate> global;
- private CommandsForKeyUpdate original;
- private CommandsForKeyUpdate current;
-
- public AccordSafeCommandsForKeyUpdate(AccordCachingState<RoutableKey, CommandsForKeyUpdate> global)
- {
- super((Key) global.key(), CommandsForKeySerializer.loader);
- this.global = global;
- this.original = null;
- this.current = null;
- }
-
- @Override
- public void initialize()
- {
- set(CommandsForKeyUpdate.empty((PartitionKey) key()));
- }
-
- @Override
- public AccordCachingState<RoutableKey, CommandsForKeyUpdate> global()
- {
- checkNotInvalidated();
- return global;
- }
-
- @Override
- public CommandsForKeyUpdate current()
- {
- checkNotInvalidated();
- return current;
- }
-
- public AsyncChain<?> loading()
- {
- throw new IllegalStateException("Updates aren't loaded");
- }
-
- @Override
- @VisibleForTesting
- public void set(CommandsForKeyUpdate cfk)
- {
- checkNotInvalidated();
- this.current = cfk;
- }
-
- public CommandsForKeyUpdate original()
- {
- checkNotInvalidated();
- return original;
- }
-
- public CommandsForKeyUpdate setUpdates()
- {
- CommandsForKeyUpdate next = new CommandsForKeyUpdate((PartitionKey) key(),
- deps().toImmutable(),
- all().toImmutable(),
- common().toImmutable());
- set(next);
- return next;
- }
-
- @Override
- public void preExecute()
- {
- checkNotInvalidated();
- original = global.get();
- current = original;
- }
-
- @Override
- public void postExecute()
- {
- checkNotInvalidated();
- global.set(current);
- }
-
- @Override
- public void invalidate()
- {
- invalidated = true;
- }
-
- @Override
- public boolean invalidated()
- {
- return invalidated;
- }
-}
diff --git a/src/java/org/apache/cassandra/service/accord/AccordService.java b/src/java/org/apache/cassandra/service/accord/AccordService.java
index 358d007e25..877ba44f8e 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordService.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordService.java
@@ -30,9 +30,12 @@ import javax.annotation.concurrent.GuardedBy;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import com.google.common.primitives.Ints;
import accord.coordinate.TopologyMismatch;
+import accord.impl.CoordinateDurabilityScheduling;
import org.apache.cassandra.cql3.statements.RequestValidations;
+import org.apache.cassandra.service.accord.interop.AccordInteropAdapter.AccordInteropFactory;
import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.tcm.transformations.AddAccordTable;
import org.slf4j.Logger;
@@ -89,9 +92,6 @@ import org.apache.cassandra.service.accord.api.AccordTopologySorter;
import org.apache.cassandra.service.accord.api.CompositeTopologySorter;
import org.apache.cassandra.service.accord.exceptions.ReadPreemptedException;
import org.apache.cassandra.service.accord.exceptions.WritePreemptedException;
-import org.apache.cassandra.service.accord.interop.AccordInteropApply;
-import org.apache.cassandra.service.accord.interop.AccordInteropExecution;
-import org.apache.cassandra.service.accord.interop.AccordInteropPersist;
import org.apache.cassandra.service.accord.txn.TxnResult;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
@@ -109,6 +109,7 @@ import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import static accord.messages.SimpleReply.Ok;
import static accord.utils.Invariants.checkState;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
+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;
@@ -132,6 +133,7 @@ public class AccordService implements IAccordService, Shutdownable
private final AccordScheduler scheduler;
private final AccordDataStore dataStore;
private final AccordJournal journal;
+ private final CoordinateDurabilityScheduling durabilityScheduling;
private final AccordVerbHandler<? extends Request> verbHandler;
private final LocalConfig configuration;
@GuardedBy("this")
@@ -306,11 +308,10 @@ public class AccordService implements IAccordService, Shutdownable
new AccordTopologySorter.Supplier(configService, DatabaseDescriptor.getEndpointSnitch())),
SimpleProgressLog::new,
AccordCommandStores.factory(journal),
- new AccordInteropExecution.Factory(agent, configService),
- AccordInteropPersist.FACTORY,
- AccordInteropApply.FACTORY,
+ new AccordInteropFactory(agent, configService),
configuration);
this.nodeShutdown = toShutdownable(node);
+ this.durabilityScheduling = new CoordinateDurabilityScheduling(node);
this.verbHandler = new AccordVerbHandler<>(node, configService, journal);
}
@@ -324,6 +325,11 @@ public class AccordService implements IAccordService, Shutdownable
ClusterMetadataService.instance().log().addListener(configService);
fastPathCoordinator.start();
ClusterMetadataService.instance().log().addListener(fastPathCoordinator);
+ durabilityScheduling.setGlobalCycleTime(Ints.checkedCast(DatabaseDescriptor.getAccordGlobalDurabilityCycle(SECONDS)), SECONDS);
+ 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();
state = State.STARTED;
}
diff --git a/src/java/org/apache/cassandra/service/accord/AccordStateCache.java b/src/java/org/apache/cassandra/service/accord/AccordStateCache.java
index d08bd86c42..1196089d62 100644
--- a/src/java/org/apache/cassandra/service/accord/AccordStateCache.java
+++ b/src/java/org/apache/cassandra/service/accord/AccordStateCache.java
@@ -143,8 +143,6 @@ public class AccordStateCache extends IntrusiveLinkedList<AccordCachingState<?,?
AccordCachingState<?, ?> node = iter.next();
checkState(node.references == 0);
- if (!node.canEvict())
- continue;
/*
* TODO (expected, efficiency):
* can this be reworked so we're not skipping unevictable nodes everytime we try to evict?
diff --git a/src/java/org/apache/cassandra/service/accord/CommandsForKeyUpdate.java b/src/java/org/apache/cassandra/service/accord/CommandsForKeyUpdate.java
deleted file mode 100644
index 4ab08b5273..0000000000
--- a/src/java/org/apache/cassandra/service/accord/CommandsForKeyUpdate.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.service.accord;
-
-import java.nio.ByteBuffer;
-import java.util.Map;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
-import accord.impl.CommandTimeseries;
-import accord.impl.CommandsForKey;
-import accord.impl.CommandsForKeyGroupUpdater;
-import accord.impl.CommandsForKeyUpdater;
-import accord.primitives.RoutableKey;
-import accord.primitives.Timestamp;
-import org.apache.cassandra.db.marshal.ByteBufferAccessor;
-import org.apache.cassandra.service.accord.api.PartitionKey;
-
-import static org.apache.cassandra.utils.ObjectSizes.measure;
-
-public class CommandsForKeyUpdate extends CommandsForKeyGroupUpdater.Immutable<ByteBuffer> implements CommandsForKey.Update
-{
- private static final CommandsForKeyUpdate EMPTY = new CommandsForKeyUpdate(null, null, null, null);
-
- static long EMPTY_SIZE = measure(EMPTY);
-
- private static long EMPTY_TIMESERIES_UPDATE_SIZE = measure(new CommandTimeseries.ImmutableUpdate(ImmutableMap.of(), ImmutableSet.of()));
-
- private static <T extends Timestamp> long immutableTimeseriesUpdate(CommandTimeseries.ImmutableUpdate<T, ByteBuffer> update)
- {
- long size = EMPTY_TIMESERIES_UPDATE_SIZE;
- for (Map.Entry<T, ByteBuffer> write : update.writes.entrySet())
- {
- size += AccordObjectSizes.timestamp(write.getKey());
- size += ByteBufferAccessor.instance.size(write.getValue());
- }
-
- for (T delete : update.deletes)
- size += AccordObjectSizes.timestamp(delete);
-
- return size;
- }
-
- private static long EMPTY_UPDATER_SIZE = measure(new CommandsForKeyUpdater.Immutable<>(null));
-
- private static long updaterSize(CommandsForKeyUpdater.Immutable<ByteBuffer> updater)
- {
- long size = EMPTY_UPDATER_SIZE;
- size += immutableTimeseriesUpdate(updater.commands());
- return size;
- }
-
- private final PartitionKey key;
-
- public CommandsForKeyUpdate(PartitionKey key, CommandsForKeyUpdater.Immutable<ByteBuffer> deps, CommandsForKeyUpdater.Immutable<ByteBuffer> all, CommandsForKeyUpdater.Immutable<ByteBuffer> common)
- {
- super(deps, all, common);
- this.key = key;
- }
-
- public static CommandsForKeyUpdate empty(RoutableKey key)
- {
- return new CommandsForKeyUpdate((PartitionKey) key,
- CommandsForKeyUpdater.Immutable.empty(),
- CommandsForKeyUpdater.Immutable.empty(),
- CommandsForKeyUpdater.Immutable.empty());
-
- }
-
- public PartitionKey key()
- {
- return key;
- }
-
- public long estimatedSizeOnHeap()
- {
- long size = EMPTY_SIZE;
- size += AccordObjectSizes.key(key.asKey());
- size += updaterSize(deps());
- size += updaterSize(all());
- size += updaterSize(common());
- return size;
- }
-}
diff --git a/src/java/org/apache/cassandra/service/accord/CommandsForRanges.java b/src/java/org/apache/cassandra/service/accord/CommandsForRanges.java
index 4145d241cd..acf342bf25 100644
--- a/src/java/org/apache/cassandra/service/accord/CommandsForRanges.java
+++ b/src/java/org/apache/cassandra/service/accord/CommandsForRanges.java
@@ -31,6 +31,7 @@ import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.BiFunction;
import java.util.function.Function;
+import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.annotations.VisibleForTesting;
@@ -40,23 +41,17 @@ import com.google.common.collect.ImmutableSortedMap;
import accord.api.Key;
import accord.api.RoutingKey;
-import accord.impl.CommandTimeseries;
-import accord.impl.DomainCommands;
-import accord.impl.DomainTimestamps;
+import accord.impl.CommandsForKey;
+import accord.impl.CommandsSummary;
import accord.local.Command;
-import accord.local.PreLoadContext;
-import accord.local.SafeCommand;
-import accord.local.SafeCommandStore;
import accord.local.SaveStatus;
import accord.primitives.AbstractKeys;
-import accord.primitives.PartialDeps;
import accord.primitives.Range;
import accord.primitives.Ranges;
-import accord.primitives.Routable;
import accord.primitives.RoutableKey;
import accord.primitives.Seekable;
-import accord.primitives.Seekables;
import accord.primitives.Timestamp;
+import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.utils.Invariants;
import org.apache.cassandra.schema.TableId;
@@ -66,10 +61,17 @@ import org.apache.cassandra.service.accord.api.PartitionKey;
import org.apache.cassandra.utils.Interval;
import org.apache.cassandra.utils.IntervalTree;
+import static accord.local.SafeCommandStore.*;
+import static accord.local.SafeCommandStore.TestDep.ANY_DEPS;
+import static accord.local.SafeCommandStore.TestDep.WITH;
+import static accord.local.SafeCommandStore.TestStartedAt.ANY;
+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;
+
public class CommandsForRanges
{
- public interface DomainInfo extends DomainCommands, DomainTimestamps {}
-
public enum TxnType
{
UNKNOWN, LOCAL, REMOTE;
@@ -138,42 +140,6 @@ public class CommandsForRanges
}
}
- private enum RangeCommandSummaryLoader implements CommandTimeseries.CommandLoader<RangeCommandSummary>
- {
- INSTANCE;
-
- @Override
- public RangeCommandSummary saveForCFK(Command command)
- {
- //TODO split write from read?
- throw new UnsupportedOperationException();
- }
-
- @Override
- public TxnId txnId(RangeCommandSummary data)
- {
- return data.txnId;
- }
-
- @Override
- public Timestamp executeAt(RangeCommandSummary data)
- {
- return data.executeAt;
- }
-
- @Override
- public SaveStatus saveStatus(RangeCommandSummary data)
- {
- return data.status;
- }
-
- @Override
- public List<TxnId> depsIds(RangeCommandSummary data)
- {
- return data.deps;
- }
- }
-
public static abstract class AbstractBuilder<T extends AbstractBuilder<T>>
{
protected final Set<TxnId> localTxns = new HashSet<>();
@@ -307,50 +273,6 @@ public class CommandsForRanges
}
}
- public static class Listener implements Command.DurableAndIdempotentListener
- {
- public final TxnId txnId;
- private transient SaveStatus saveStatus;
-
- public Listener(TxnId txnId)
- {
- this.txnId = txnId;
- }
-
- @Override
- public void onChange(SafeCommandStore safeStore, SafeCommand safeCommand)
- {
- Command current = safeCommand.current();
- if (current.saveStatus() == saveStatus)
- return;
- saveStatus = current.saveStatus();
- PartialDeps deps = current.partialDeps();
- if (deps == null)
- return;
- Seekables<?, ?> keysOrRanges = current.partialTxn().keys();
- Invariants.checkArgument(keysOrRanges.domain() == Routable.Domain.Range, "Expected txn %s to be a Range txn, but was a %s", txnId, keysOrRanges.domain());
-
- List<TxnId> dependsOn = deps.txnIds();
- ((AccordSafeCommandStore) safeStore).updateRanges()
- .put(txnId, (Ranges) keysOrRanges, current.saveStatus(), current.executeAt(), dependsOn);
- }
-
- @Override
- public PreLoadContext listenerPreLoadContext(TxnId caller)
- {
- return caller.equals(txnId) ? PreLoadContext.contextFor(txnId) : PreLoadContext.contextFor(txnId, Collections.singletonList(caller));
- }
-
- @Override
- public String toString()
- {
- return "Listener{" +
- "txnId=" + txnId +
- ", saveStatus=" + saveStatus +
- '}';
- }
- }
-
private ImmutableSet<TxnId> localCommands;
private ImmutableSortedMap<TxnId, RangeCommandSummary> commandsToRanges;
private IntervalTree<RoutableKey, RangeCommandSummary, Interval<RoutableKey, RangeCommandSummary>> rangesToCommands;
@@ -394,24 +316,29 @@ public class CommandsForRanges
return maxRedundant;
}
+ public static boolean needsUpdate(Command prev, Command updated)
+ {
+ return CommandsForKey.needsUpdate(prev, updated);
+ }
+
public boolean containsLocally(TxnId txnId)
{
return localCommands.contains(txnId);
}
- public Iterable<DomainInfo> search(AbstractKeys<Key> keys)
+ public Iterable<CommandsSummary> search(AbstractKeys<Key> keys)
{
// group by the table, as ranges are based off TokenKey, which is scoped to a range
Map<TableId, List<Key>> groupByTable = new TreeMap<>();
for (Key key : keys)
groupByTable.computeIfAbsent(((PartitionKey) key).table(), ignore -> new ArrayList<>()).add(key);
- return () -> new AbstractIterator<DomainInfo>()
+ return () -> new AbstractIterator<CommandsSummary>()
{
Iterator<TableId> tblIt = groupByTable.keySet().iterator();
Iterator<Map.Entry<Range, Set<RangeCommandSummary>>> rangeIt;
@Override
- protected DomainInfo computeNext()
+ protected CommandsSummary computeNext()
{
while (true)
{
@@ -447,20 +374,20 @@ public class CommandsForRanges
{
TokenKey start = (TokenKey) interval.min;
TokenKey end = (TokenKey) interval.max;
- // TODO (correctness) : accord doesn't support wrap around, so decreaseSlightly may fail in some cases
- // TODO (correctness) : this logic is mostly used for testing, so is it actually safe for all partitioners?
+ // TODO (required, correctness) : accord doesn't support wrap around, so decreaseSlightly may fail in some cases
+ // TODO (required, correctness) : this logic is mostly used for testing, so is it actually safe for all partitioners?
return new TokenRange(start.withToken(start.token().decreaseSlightly()), end);
}
@Nullable
- public DomainInfo search(Range range)
+ public CommandsSummary search(Range range)
{
List<RangeCommandSummary> matches = rangesToCommands.search(Interval.create(normalize(range.start(), range.startInclusive(), true),
normalize(range.end(), range.endInclusive(), false)));
return result(range, matches);
}
- private DomainInfo result(Seekable seekable, Collection<RangeCommandSummary> matches)
+ private CommandsSummary result(Seekable seekable, Collection<RangeCommandSummary> matches)
{
if (matches.isEmpty())
return null;
@@ -492,18 +419,20 @@ public class CommandsForRanges
switch (ak.kindOfRoutingKey())
{
case SENTINEL:
- key = ak.asSentinelKey().toTokenKey();
+ // TODO (required, correctness): this doesn't work
+ key = ak.asSentinelKey().toTokenKeyBroken();
continue;
case TOKEN:
TokenKey tk = ak.asTokenKey();
- return tk.withToken(upOrDown ? tk.token().nextValidToken() : tk.token().decreaseSlightly());
+ // TODO (required, correctness): this doesn't work for ordered partitioner
+ return tk.withToken(upOrDown ? tk.token().increaseSlightly() : tk.token().decreaseSlightly());
default:
throw new IllegalArgumentException("Unknown kind: " + ak.kindOfRoutingKey());
}
}
}
- private static class Holder implements DomainInfo
+ private static class Holder implements CommandsSummary
{
private final Seekable keyOrRange;
private final Collection<RangeCommandSummary> matches;
@@ -515,27 +444,95 @@ public class CommandsForRanges
}
@Override
- public CommandTimeseries<?> commands()
+ public <P1, T> T mapReduceFull(TxnId testTxnId, Txn.Kind.Kinds testKind, TestStartedAt testStartedAt, TestDep testDep, TestStatus testStatus, CommandFunction<P1, T, T> map, P1 p1, T accumulate)
{
- return build();
+ return mapReduce(testTxnId, testTxnId, testKind, testStartedAt, testDep, testStatus, map, p1, accumulate);
}
@Override
- public Timestamp max()
+ public <P1, T> T mapReduceActive(Timestamp startedBefore, Txn.Kind.Kinds testKind, CommandFunction<P1, T, T> map, P1 p1, T accumulate)
{
- return commands().maxTimestamp();
+ return mapReduce(startedBefore, null, testKind, STARTED_BEFORE, ANY_DEPS, ANY_STATUS, map, p1, accumulate);
}
- private CommandTimeseries<?> build()
+ private <P1, T> T mapReduce(@Nonnull Timestamp testTimestamp, @Nullable TxnId testTxnId, Txn.Kind.Kinds testKind, TestStartedAt testStartedAt, TestDep testDep, TestStatus testStatus, CommandFunction<P1, T, T> map, P1 p1, T accumulate)
{
- CommandTimeseries.Builder<RangeCommandSummary> builder = new CommandTimeseries.Builder<>(keyOrRange, RangeCommandSummaryLoader.INSTANCE);
- for (RangeCommandSummary m : matches)
+ // TODO (required): reconsider how we build this, to avoid having to provide range keys in order (or ensure our range search does this for us)
+ Map<Range, List<RangeCommandSummary>> collect = new TreeMap<>(Range::compare);
+ matches.forEach((summary -> {
+ if (summary.status.compareTo(SaveStatus.Erased) >= 0)
+ return;
+
+ switch (testStartedAt)
+ {
+ default: throw new AssertionError();
+ case STARTED_AFTER:
+ if (summary.txnId.compareTo(testTimestamp) <= 0) return;
+ else break;
+ case STARTED_BEFORE:
+ if (summary.txnId.compareTo(testTimestamp) >= 0) return;
+ case ANY:
+ if (testDep != ANY_DEPS && (summary.executeAt == null || summary.executeAt.compareTo(testTxnId) < 0))
+ return;
+ }
+
+ switch (testStatus)
+ {
+ default: throw new AssertionError("Unhandled TestStatus: " + testStatus);
+ case ANY_STATUS:
+ break;
+ case IS_PROPOSED:
+ switch (summary.status)
+ {
+ default: return;
+ case PreCommitted:
+ case Committed:
+ case Accepted:
+ }
+ break;
+ case IS_STABLE:
+ if (!summary.status.hasBeen(Stable) || summary.status.hasBeen(Truncated))
+ return;
+ }
+
+ if (!testKind.test(summary.txnId.kind()))
+ return;
+
+ if (testDep != ANY_DEPS)
+ {
+ if (!summary.status.known.deps.hasProposedOrDecidedDeps())
+ return;
+
+ // TODO (required): we must ensure these txnId are limited to those we intersect in this command store
+ // We are looking for transactions A that have (or have not) B as a dependency.
+ // If B covers ranges [1..3] and A covers [2..3], but the command store only covers ranges [1..2],
+ // we could have A adopt B as a dependency on [3..3] only, and have that A intersects B on this
+ // command store, but also that there is no dependency relation between them on the overlapping
+ // key range [2..2].
+
+ // This can lead to problems on recovery, where we believe a transaction is a dependency
+ // and so it is safe to execute, when in fact it is only a dependency on a different shard
+ // (and that other shard, perhaps, does not know that it is a dependency - and so it is not durably known)
+ // TODO (required): consider this some more
+ if ((testDep == WITH) == !summary.deps.contains(testTxnId))
+ return;
+ }
+
+ // TODO (required): ensure we are excluding any ranges that are now shard-redundant (not sure if this is enforced yet)
+ for (Range range : summary.ranges)
+ collect.computeIfAbsent(range, ignore -> new ArrayList<>()).add(summary);
+ }));
+
+ for (Map.Entry<Range, List<RangeCommandSummary>> e : collect.entrySet())
{
- if (m.status == SaveStatus.Invalidated)
- continue;
- builder.add(m.txnId, m);
+ for (RangeCommandSummary command : e.getValue())
+ {
+ T initial = accumulate;
+ accumulate = map.apply(p1, e.getKey(), command.txnId, command.executeAt, initial);
+ }
}
- return builder.build();
+
+ return accumulate;
}
@Override
diff --git a/src/java/org/apache/cassandra/service/accord/TokenRange.java b/src/java/org/apache/cassandra/service/accord/TokenRange.java
index b03eaf39d9..be3eaa80ca 100644
--- a/src/java/org/apache/cassandra/service/accord/TokenRange.java
+++ b/src/java/org/apache/cassandra/service/accord/TokenRange.java
@@ -73,7 +73,7 @@ public class TokenRange extends Range.EndInclusive
{
RoutingKey pick = super.someIntersectingRoutingKey(ranges);
if (pick instanceof SentinelKey)
- pick = ((SentinelKey) pick).toTokenKey();
+ pick = ((SentinelKey) pick).toTokenKeyBroken();
return pick;
}
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 acf4da1923..9606be5492 100644
--- a/src/java/org/apache/cassandra/service/accord/api/AccordRoutingKey.java
+++ b/src/java/org/apache/cassandra/service/accord/api/AccordRoutingKey.java
@@ -131,12 +131,12 @@ public abstract class AccordRoutingKey extends AccordRoutableKey implements Rout
return new SentinelKey(table, false);
}
- public TokenKey toTokenKey()
+ public TokenKey toTokenKeyBroken()
{
IPartitioner partitioner = getPartitioner();
return new TokenKey(table, isMin ?
partitioner.getMinimumToken().nextValidToken() :
- partitioner.getMaximumToken().decreaseSlightly());
+ partitioner.getMaximumTokenForSplitting().decreaseSlightly());
}
@Override
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 52f632c6c5..b8494e7d44 100644
--- a/src/java/org/apache/cassandra/service/accord/async/AsyncLoader.java
+++ b/src/java/org/apache/cassandra/service/accord/async/AsyncLoader.java
@@ -116,6 +116,7 @@ public class AsyncLoader
case FAILED_TO_SAVE:
break;
case FAILED_TO_LOAD:
+ // TODO (required): if this triggers, we trigger some other illegal state in cache management
throw new RuntimeException(safeRef.failure());
}
}
@@ -124,19 +125,18 @@ public class AsyncLoader
AsyncOperation.Context context,
List<AsyncChain<?>> listenChains)
{
- referenceAndAssembleReadsForKey(key, context.timestampsForKey, commandStore.timestampsForKeyCache(), listenChains);
// recovery operations also need the deps data for their preaccept logic
switch (keyHistory)
{
- case ALL:
- referenceAndAssembleReadsForKey(key, context.allCommandsForKeys, commandStore.allCommandsForKeyCache(), listenChains);
- case DEPS:
- referenceAndAssembleReadsForKey(key, context.depsCommandsForKeys, commandStore.depsCommandsForKeyCache(), listenChains);
+ case TIMESTAMPS:
+ referenceAndAssembleReadsForKey(key, context.timestampsForKey, commandStore.timestampsForKeyCache(), listenChains);
+ break;
+ case COMMANDS:
+ referenceAndAssembleReadsForKey(key, context.commandsForKey, commandStore.commandsForKeyCache(), listenChains);
case NONE:
break;
default: throw new IllegalArgumentException("Unhandled keyhistory: " + keyHistory);
}
- referenceAndAssembleReadsForKey(key, context.updatesForKeys, commandStore.updatesForKeyCache(), listenChains);
}
private <K, V, S extends AccordSafeState<K, V>> void referenceAndAssembleReads(Iterable<? extends K> keys,
@@ -195,7 +195,7 @@ public class AsyncLoader
private AsyncChain<Set<PartitionKey>> findOverlappingKeys(Range range)
{
- Set<PartitionKey> cached = commandStore.depsCommandsForKeyCache().stream()
+ Set<PartitionKey> cached = commandStore.commandsForKeyCache().stream()
.map(n -> (PartitionKey) n.key())
.filter(range::contains)
.collect(Collectors.toSet());
@@ -214,7 +214,7 @@ public class AsyncLoader
if (start instanceof TokenKey)
return (TokenKey) start;
if (start instanceof AccordRoutingKey.SentinelKey)
- return ((AccordRoutingKey.SentinelKey) start).toTokenKey();
+ return ((AccordRoutingKey.SentinelKey) start).toTokenKeyBroken();
throw new IllegalArgumentException(String.format("Unable to convert RoutingKey %s (type %s) to TokenKey", start, start.getClass()));
}
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 69002b1fef..bef57bcf0b 100644
--- a/src/java/org/apache/cassandra/service/accord/async/AsyncOperation.java
+++ b/src/java/org/apache/cassandra/service/accord/async/AsyncOperation.java
@@ -42,7 +42,6 @@ import org.apache.cassandra.service.accord.AccordCommandStore;
import org.apache.cassandra.service.accord.AccordSafeCommand;
import org.apache.cassandra.service.accord.AccordSafeCommandsForKey;
import org.apache.cassandra.service.accord.AccordSafeCommandStore;
-import org.apache.cassandra.service.accord.AccordSafeCommandsForKeyUpdate;
import org.apache.cassandra.service.accord.AccordSafeState;
import org.apache.cassandra.service.accord.AccordSafeTimestampsForKey;
@@ -69,26 +68,20 @@ public abstract class AsyncOperation<R> extends AsyncChains.Head<R> implements R
{
final HashMap<TxnId, AccordSafeCommand> commands = new HashMap<>();
final TreeMap<RoutableKey, AccordSafeTimestampsForKey> timestampsForKey = new TreeMap<>();
- final TreeMap<RoutableKey, AccordSafeCommandsForKey> depsCommandsForKeys = new TreeMap<>();
- final TreeMap<RoutableKey, AccordSafeCommandsForKey> allCommandsForKeys = new TreeMap<>();
- final TreeMap<RoutableKey, AccordSafeCommandsForKeyUpdate> updatesForKeys = new TreeMap<>();
+ final TreeMap<RoutableKey, AccordSafeCommandsForKey> commandsForKey = new TreeMap<>();
void releaseResources(AccordCommandStore commandStore)
{
commands.values().forEach(commandStore.commandCache()::release);
timestampsForKey.values().forEach(commandStore.timestampsForKeyCache()::release);
- depsCommandsForKeys.values().forEach(commandStore.depsCommandsForKeyCache()::release);
- allCommandsForKeys.values().forEach(commandStore.allCommandsForKeyCache()::release);
- updatesForKeys.values().forEach(commandStore.updatesForKeyCache()::release);
+ commandsForKey.values().forEach(commandStore.commandsForKeyCache()::release);
}
void revertChanges()
{
commands.values().forEach(AccordSafeState::revert);
timestampsForKey.values().forEach(AccordSafeState::revert);
- depsCommandsForKeys.values().forEach(AccordSafeState::revert);
- allCommandsForKeys.values().forEach(AccordSafeState::revert);
- updatesForKeys.values().forEach(AccordSafeState::revert);
+ commandsForKey.values().forEach(AccordSafeState::revert);
}
}
@@ -261,11 +254,11 @@ public abstract class AsyncOperation<R> extends AsyncChains.Head<R> implements R
return;
state(PREPARING);
case PREPARING:
- safeStore = commandStore.beginOperation(preLoadContext, context.commands, context.timestampsForKey, context.depsCommandsForKeys, context.allCommandsForKeys, context.updatesForKeys);
+ safeStore = commandStore.beginOperation(preLoadContext, context.commands, context.timestampsForKey, context.commandsForKey);
state(RUNNING);
case RUNNING:
result = apply(safeStore);
- safeStore.postExecute(context.commands, context.timestampsForKey, context.depsCommandsForKeys, context.allCommandsForKeys, context.updatesForKeys);
+ safeStore.postExecute(context.commands, context.timestampsForKey, context.commandsForKey);
context.releaseResources(commandStore);
commandStore.completeOperation(safeStore);
commandStore.executionOrder().unregister(this);
diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java
new file mode 100644
index 0000000000..788ed11c70
--- /dev/null
+++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropAdapter.java
@@ -0,0 +1,121 @@
+/*
+ * 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.interop;
+
+import java.util.function.BiConsumer;
+
+import accord.api.Result;
+import accord.api.Update;
+import accord.coordinate.CoordinationAdapter;
+import accord.coordinate.CoordinationAdapter.Adapters.AbstractTxnAdapter;
+import accord.coordinate.ExecutePath;
+import accord.coordinate.PersistTxn;
+import accord.local.Node;
+import accord.messages.Apply;
+import accord.primitives.Deps;
+import accord.primitives.FullRoute;
+import accord.primitives.Timestamp;
+import accord.primitives.Txn;
+import accord.primitives.TxnId;
+import accord.primitives.Writes;
+import accord.topology.Topologies;
+import org.apache.cassandra.db.ConsistencyLevel;
+import org.apache.cassandra.service.accord.AccordEndpointMapper;
+import org.apache.cassandra.service.accord.api.AccordAgent;
+import org.apache.cassandra.service.accord.interop.AccordInteropExecution.InteropExecutor;
+import org.apache.cassandra.service.accord.txn.AccordUpdate;
+import org.apache.cassandra.service.accord.txn.TxnRead;
+
+import static accord.messages.Apply.Kind.Maximal;
+import static accord.messages.Apply.Kind.Minimal;
+
+public class AccordInteropAdapter extends AbstractTxnAdapter
+{
+ public static final class AccordInteropFactory implements CoordinationAdapter.Factory
+ {
+ final AccordInteropAdapter standard, recovery;
+
+ public AccordInteropFactory(AccordAgent agent, AccordEndpointMapper endpointMapper)
+ {
+ final InteropExecutor executor = new InteropExecutor(agent);
+ standard = new AccordInteropAdapter(executor, endpointMapper, Minimal);
+ recovery = new AccordInteropAdapter(executor, endpointMapper, Maximal);
+ }
+
+ @Override
+ public <R> CoordinationAdapter<R> get(TxnId txnId, Step step)
+ {
+ return (CoordinationAdapter<R>) (step == Step.InitiateRecovery ? recovery : standard);
+ }
+ };
+
+ private final InteropExecutor executor;
+ private final AccordEndpointMapper endpointMapper;
+ private final Apply.Kind applyKind;
+
+ private AccordInteropAdapter(InteropExecutor executor, AccordEndpointMapper endpointMapper, Apply.Kind applyKind)
+ {
+ this.executor = executor;
+ this.endpointMapper = endpointMapper;
+ this.applyKind = applyKind;
+ }
+
+ @Override
+ public void execute(Node node, Topologies all, FullRoute<?> route, ExecutePath path, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super Result, Throwable> callback)
+ {
+ if (!doInteropExecute(node, route, txnId, txn, executeAt, deps, callback))
+ super.execute(node, all, route, path, txnId, txn, executeAt, deps, callback);
+ }
+
+ @Override
+ public void persist(Node node, Topologies all, Topologies executes, FullRoute<?> route, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, BiConsumer<? super Result, Throwable> callback)
+ {
+ if (applyKind == Minimal && doInteropPersist(node, all, executes, route, txnId, txn, executeAt, deps, writes, result, callback))
+ return;
+
+ if (callback != null) callback.accept(result, null);
+ new PersistTxn(node, all, txnId, route, txn, executeAt, deps, writes, result)
+ .start(Apply.FACTORY, applyKind, all, executes, writes, result);
+ }
+
+ private boolean doInteropExecute(Node node, FullRoute<?> route, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super Result, Throwable> callback)
+ {
+ // Unrecoverable repair always needs to be run by AccordInteropExecution
+ AccordUpdate.Kind updateKind = AccordUpdate.kind(txn.update());
+ ConsistencyLevel consistencyLevel = txn.read() instanceof TxnRead ? ((TxnRead) txn.read()).cassandraConsistencyLevel() : null;
+ if (updateKind != AccordUpdate.Kind.UNRECOVERABLE_REPAIR && (consistencyLevel == null || consistencyLevel == ConsistencyLevel.ONE || txn.read().keys().isEmpty()))
+ return false;
+
+ new AccordInteropExecution(node, txnId, txn, updateKind, route, txn.read().keys().toParticipants(), executeAt, deps, callback, executor, consistencyLevel, endpointMapper)
+ .start();
+ return true;
+ }
+
+ private static boolean doInteropPersist(Node node, Topologies all, Topologies executes, FullRoute<?> route, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, BiConsumer<? super Result, Throwable> callback)
+ {
+ Update update = txn.update();
+ ConsistencyLevel consistencyLevel = update instanceof AccordUpdate ? ((AccordUpdate) update).cassandraCommitCL() : null;
+ if (consistencyLevel == null || consistencyLevel == ConsistencyLevel.ANY || writes.isEmpty())
+ return false;
+
+ new AccordInteropPersist(node, all, txnId, route, txn, executeAt, deps, writes, result, consistencyLevel, callback)
+ .start(AccordInteropApply.FACTORY, Minimal, all, executes, writes, result);
+ return true;
+ }
+}
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 22821bab39..6ab32803c5 100644
--- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropApply.java
+++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropApply.java
@@ -31,11 +31,11 @@ import accord.local.Status;
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.PartialRoute;
import accord.primitives.PartialTxn;
-import accord.primitives.Route;
import accord.primitives.Seekables;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
@@ -63,7 +63,7 @@ public class AccordInteropApply extends Apply implements Command.TransientListen
public static final Apply.Factory FACTORY = new Apply.Factory()
{
@Override
- public Apply create(Kind kind, Id to, Topologies participates, Topologies executes, TxnId txnId, Route<?> route, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result)
+ public Apply create(Kind kind, Id to, Topologies participates, Topologies executes, TxnId txnId, FullRoute<?> route, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result)
{
checkArgument(kind != Kind.Maximal, "Shouldn't need to send a maximal commit with interop support");
ConsistencyLevel commitCL = txn.update() instanceof AccordUpdate ? ((AccordUpdate) txn.update()).cassandraCommitCL() : null;
@@ -77,21 +77,21 @@ public class AccordInteropApply extends Apply implements Command.TransientListen
public static final IVersionedSerializer<AccordInteropApply> serializer = new ApplySerializer<AccordInteropApply>()
{
@Override
- protected AccordInteropApply deserializeApply(TxnId txnId, PartialRoute<?> scope, long waitForEpoch, Apply.Kind kind, Seekables<?, ?> keys, Timestamp executeAt, PartialDeps deps, PartialTxn txn, Writes writes, Result result)
+ protected AccordInteropApply deserializeApply(TxnId txnId, PartialRoute<?> scope, long waitForEpoch, Apply.Kind kind, Seekables<?, ?> keys, Timestamp executeAt, PartialDeps deps, PartialTxn txn, @Nullable FullRoute<?> fullRoute, Writes writes, Result result)
{
- return new AccordInteropApply(kind, txnId, scope, waitForEpoch, keys, executeAt, deps, txn, writes, result);
+ return new AccordInteropApply(kind, txnId, scope, waitForEpoch, keys, executeAt, deps, txn, fullRoute, writes, result);
}
};
transient BitSet waitingOn;
transient int waitingOnCount;
- private AccordInteropApply(Kind kind, TxnId txnId, PartialRoute<?> route, long waitForEpoch, Seekables<?, ?> keys, Timestamp executeAt, PartialDeps deps, @Nullable PartialTxn txn, Writes writes, Result result)
+ private AccordInteropApply(Kind kind, TxnId txnId, PartialRoute<?> route, long waitForEpoch, Seekables<?, ?> keys, Timestamp executeAt, PartialDeps deps, @Nullable PartialTxn txn, @Nullable FullRoute<?> fullRoute, Writes writes, Result result)
{
- super(kind, txnId, route, waitForEpoch, keys, executeAt, deps, txn, writes, result);
+ super(kind, txnId, route, waitForEpoch, keys, executeAt, deps, txn, fullRoute, writes, result);
}
- private AccordInteropApply(Kind kind, Id to, Topologies participates, Topologies executes, TxnId txnId, Route<?> route, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result)
+ private AccordInteropApply(Kind kind, Id to, Topologies participates, Topologies executes, TxnId txnId, FullRoute<?> route, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result)
{
super(kind, to, participates, executes, txnId, route, txn, executeAt, deps, writes, result);
}
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 e92edb1ec9..cd13b99f93 100644
--- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropCommit.java
+++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropCommit.java
@@ -31,6 +31,7 @@ import accord.primitives.FullRoute;
import accord.primitives.PartialDeps;
import accord.primitives.PartialRoute;
import accord.primitives.PartialTxn;
+import accord.primitives.Seekables;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
@@ -45,15 +46,15 @@ public class AccordInteropCommit extends Commit
public static final IVersionedSerializer<AccordInteropCommit> serializer = new CommitSerializer<AccordInteropCommit, AccordInteropRead>(AccordInteropRead.class, AccordInteropRead.requestSerializer)
{
@Override
- protected AccordInteropCommit deserializeCommit(TxnId txnId, PartialRoute<?> scope, long waitForEpoch, Kind kind, Ballot ballot, Timestamp executeAt, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute, @Nullable ReadData read)
+ protected AccordInteropCommit deserializeCommit(TxnId txnId, PartialRoute<?> scope, long waitForEpoch, Kind kind, Ballot ballot, Timestamp executeAt, Seekables<?, ?> keys, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute, @Nullable ReadData read)
{
- return new AccordInteropCommit(kind, txnId, scope, waitForEpoch, ballot, executeAt, partialTxn, partialDeps, fullRoute, read);
+ return new AccordInteropCommit(kind, txnId, scope, waitForEpoch, ballot, executeAt, keys, partialTxn, partialDeps, fullRoute, read);
}
};
- public AccordInteropCommit(Kind kind, TxnId txnId, PartialRoute<?> scope, long waitForEpoch, Ballot ballot, Timestamp executeAt, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute, @Nonnull ReadData readData)
+ public AccordInteropCommit(Kind kind, TxnId txnId, PartialRoute<?> scope, long waitForEpoch, Ballot ballot, Timestamp executeAt, Seekables<?, ?> keys, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute, @Nonnull ReadData readData)
{
- super(kind, txnId, scope, waitForEpoch, ballot, executeAt, partialTxn, partialDeps, fullRoute, readData);
+ super(kind, txnId, scope, waitForEpoch, ballot, executeAt, keys, 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/AccordInteropExecution.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java
index 2eda7d88f1..1b387d6383 100644
--- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java
+++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropExecution.java
@@ -37,9 +37,6 @@ import org.slf4j.LoggerFactory;
import accord.api.Agent;
import accord.api.Data;
import accord.api.Result;
-import accord.coordinate.Execute;
-import accord.coordinate.Persist;
-import accord.coordinate.ExecuteTxn;
import accord.local.AgentExecutor;
import accord.local.CommandStore;
import accord.local.Node;
@@ -95,6 +92,8 @@ import org.apache.cassandra.service.reads.ReadCoordinator;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.utils.Clock;
+import static accord.coordinate.CoordinationAdapter.Factory.Step.Continue;
+import static accord.coordinate.CoordinationAdapter.Invoke.persist;
import static accord.utils.Invariants.checkArgument;
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.accordReadMetrics;
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.accordWriteMetrics;
@@ -110,11 +109,11 @@ import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.accordWri
* on its inputs.
*
*/
-public class AccordInteropExecution implements Execute, ReadCoordinator, MaximalCommitSender
+public class AccordInteropExecution implements ReadCoordinator, MaximalCommitSender
{
private static final Logger logger = LoggerFactory.getLogger(AccordInteropExecution.class);
- private static class InteropExecutor implements AgentExecutor
+ static class InteropExecutor implements AgentExecutor
{
private final AccordAgent agent;
@@ -143,29 +142,6 @@ public class AccordInteropExecution implements Execute, ReadCoordinator, Maximal
}
}
- public static class Factory implements Execute.Factory
- {
- private final InteropExecutor executor;
- private final AccordEndpointMapper endpointMapper;
-
- public Factory(AccordAgent agent, AccordEndpointMapper endpointMapper)
- {
- this.executor = new InteropExecutor(agent);
- this.endpointMapper = endpointMapper;
- }
-
- @Override
- public Execute create(Node node, Topologies topologies, Path path, TxnId txnId, Txn txn, FullRoute<?> route, Participants<?> readScope, Timestamp executeAt, Deps deps, BiConsumer<? super Result, Throwable> callback)
- {
- // Unrecoverable repair always needs to be run by AccordInteropExecution
- AccordUpdate.Kind updateKind = AccordUpdate.kind(txn.update());
- ConsistencyLevel consistencyLevel = txn.read() instanceof TxnRead ? ((TxnRead) txn.read()).cassandraConsistencyLevel() : null;
- if (updateKind != AccordUpdate.Kind.UNRECOVERABLE_REPAIR && (consistencyLevel == null || consistencyLevel == ConsistencyLevel.ONE || txn.read().keys().isEmpty()))
- return ExecuteTxn.FACTORY.create(node, topologies, path, txnId, txn, route, readScope, executeAt, deps, callback);
- return new AccordInteropExecution(node, txnId, txn, updateKind, route, readScope, executeAt, deps, callback, executor, consistencyLevel, endpointMapper);
- }
- }
-
private final Node node;
private final TxnId txnId;
private final Txn txn;
@@ -350,7 +326,6 @@ public class AccordInteropExecution implements Execute, ReadCoordinator, Maximal
node.send(to, new Commit(Kind.StableFastPath, to, coordinateTopology, allTopologies, txnId, txn, route, Ballot.ZERO, executeAt, deps, (ReadTxnData) null));
}
- @Override
public void start()
{
if (coordinateTopology != executeTopology)
@@ -370,7 +345,7 @@ public class AccordInteropExecution implements Execute, ReadCoordinator, Maximal
CommandStore cs = node.commandStores().select(route.homeKey());
result.beginAsResult().withExecutor(cs).begin((data, failure) -> {
if (failure == null)
- Persist.persist(node, executes, route, txnId, txn, executeAt, deps, txn.execute(txnId, executeAt, data), txn.result(txnId, executeAt, data), callback);
+ persist(node.coordinationAdapter(txnId, Continue), node, executes, route, txnId, txn, executeAt, deps, txn.execute(txnId, executeAt, data), txn.result(txnId, executeAt, data), callback);
else
callback.accept(null, failure);
});
diff --git a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropPersist.java b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropPersist.java
index 5144546959..857d082a2c 100644
--- a/src/java/org/apache/cassandra/service/accord/interop/AccordInteropPersist.java
+++ b/src/java/org/apache/cassandra/service/accord/interop/AccordInteropPersist.java
@@ -21,9 +21,7 @@ package org.apache.cassandra.service.accord.interop;
import java.util.function.BiConsumer;
import accord.api.Result;
-import accord.api.Update;
import accord.coordinate.Persist;
-import accord.coordinate.PersistTxn;
import accord.coordinate.tracking.AppliedTracker;
import accord.coordinate.tracking.QuorumTracker;
import accord.coordinate.tracking.RequestStatus;
@@ -39,7 +37,6 @@ import accord.primitives.Writes;
import accord.topology.Topologies;
import accord.utils.Invariants;
import org.apache.cassandra.db.ConsistencyLevel;
-import org.apache.cassandra.service.accord.txn.AccordUpdate;
import org.apache.cassandra.utils.Throwables;
/**
@@ -49,19 +46,6 @@ import org.apache.cassandra.utils.Throwables;
*/
public class AccordInteropPersist extends Persist
{
- public static Persist.Factory FACTORY = new Persist.Factory()
- {
- @Override
- public Persist create(Node node, Topologies topologies, TxnId txnId, FullRoute<?> route, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result)
- {
- Update update = txn.update();
- ConsistencyLevel consistencyLevel = update instanceof AccordUpdate ? ((AccordUpdate) update).cassandraCommitCL() : null;
- if (consistencyLevel == null || consistencyLevel == ConsistencyLevel.ANY || writes.isEmpty())
- return PersistTxn.FACTORY.create(node, topologies, txnId, route, txn, executeAt, deps, writes, result);
- return new AccordInteropPersist(node, topologies, txnId, route, txn, executeAt, deps, writes, result, consistencyLevel);
- }
- };
-
private static class CallbackHolder
{
private final ResponseTracker tracker;
@@ -97,7 +81,6 @@ public class AccordInteropPersist extends Persist
handleStatus(tracker.recordSuccess(node));
}
-
public void recordFailure(Node.Id node, Throwable throwable)
{
failure = Throwables.merge(failure, throwable);
@@ -106,29 +89,28 @@ public class AccordInteropPersist extends Persist
}
private final ConsistencyLevel consistencyLevel;
- private CallbackHolder holder = null;
+ private CallbackHolder callback;
- public AccordInteropPersist(Node node, Topologies topologies, TxnId txnId, FullRoute<?> route, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, ConsistencyLevel consistencyLevel)
+ public AccordInteropPersist(Node node, Topologies topologies, TxnId txnId, FullRoute<?> route, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, ConsistencyLevel consistencyLevel, BiConsumer<? super Result, Throwable> clientCallback)
{
super(node, topologies, txnId, route, txn, executeAt, deps, writes, result);
Invariants.checkArgument(consistencyLevel == ConsistencyLevel.QUORUM || consistencyLevel == ConsistencyLevel.ALL || consistencyLevel == ConsistencyLevel.SERIAL || consistencyLevel == ConsistencyLevel.ONE);
this.consistencyLevel = consistencyLevel;
+ registerClientCallback(result, clientCallback);
}
- @Override
- public void registerClientCallback(Writes writes, Result result, BiConsumer<? super Result, Throwable> clientCallback)
+ public void registerClientCallback(Result result, BiConsumer<? super Result, Throwable> clientCallback)
{
-
- Invariants.checkState(holder == null);
+ Invariants.checkState(callback == null);
switch (consistencyLevel)
{
case ONE: // Can safely upgrade ONE to QUORUM/SERIAL to get a synchronous commit
case SERIAL:
case QUORUM:
- holder = new CallbackHolder(new QuorumTracker(topologies), result, clientCallback);
+ callback = new CallbackHolder(new QuorumTracker(topologies), result, clientCallback);
break;
case ALL:
- holder = new CallbackHolder(new AppliedTracker(topologies), result, clientCallback);
+ callback = new CallbackHolder(new AppliedTracker(topologies), result, clientCallback);
break;
default:
throw new IllegalArgumentException("Unhandled consistency level: " + consistencyLevel);
@@ -143,7 +125,7 @@ public class AccordInteropPersist extends Persist
{
case Redundant:
case Applied:
- holder.recordSuccess(from);
+ callback.recordSuccess(from);
return;
case Insufficient:
// On insufficient Persist will send a commit with the missing information
@@ -156,12 +138,12 @@ public class AccordInteropPersist extends Persist
@Override
public void onFailure(Node.Id from, Throwable failure)
{
- holder.recordFailure(from, failure);
+ callback.recordFailure(from, failure);
}
@Override
public void onCallbackFailure(Node.Id from, Throwable failure)
{
- holder.recordFailure(from, failure);
+ callback.recordFailure(from, failure);
}
}
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 d75d924ca5..102ffb57b9 100644
--- a/src/java/org/apache/cassandra/service/accord/serializers/ApplySerializers.java
+++ b/src/java/org/apache/cassandra/service/accord/serializers/ApplySerializers.java
@@ -22,6 +22,7 @@ import java.io.IOException;
import accord.api.Result;
import accord.messages.Apply;
+import accord.primitives.FullRoute;
import accord.primitives.PartialDeps;
import accord.primitives.PartialRoute;
import accord.primitives.PartialTxn;
@@ -68,11 +69,12 @@ public class ApplySerializers
CommandSerializers.timestamp.serialize(apply.executeAt, out, version);
DepsSerializer.partialDeps.serialize(apply.deps, out, version);
CommandSerializers.nullablePartialTxn.serialize(apply.txn, out, version);
+ KeySerializers.nullableFullRoute.serialize(apply.fullRoute, out, version);
CommandSerializers.writes.serialize(apply.writes, out, version);
}
protected abstract A deserializeApply(TxnId txnId, PartialRoute<?> scope, long waitForEpoch, Apply.Kind kind, Seekables<?, ?> keys,
- Timestamp executeAt, PartialDeps deps, PartialTxn txn, Writes writes, Result result);
+ Timestamp executeAt, PartialDeps deps, PartialTxn txn, FullRoute<?> fullRoute, Writes writes, Result result);
@Override
public A deserializeBody(DataInputPlus in, int version, TxnId txnId, PartialRoute<?> scope, long waitForEpoch) throws IOException
@@ -83,6 +85,7 @@ public class ApplySerializers
CommandSerializers.timestamp.deserialize(in, version),
DepsSerializer.partialDeps.deserialize(in, version),
CommandSerializers.nullablePartialTxn.deserialize(in, version),
+ KeySerializers.nullableFullRoute.deserialize(in, version),
CommandSerializers.writes.deserialize(in, version),
CommandSerializers.APPLIED);
}
@@ -95,6 +98,7 @@ public class ApplySerializers
+ CommandSerializers.timestamp.serializedSize(apply.executeAt, version)
+ DepsSerializer.partialDeps.serializedSize(apply.deps, version)
+ CommandSerializers.nullablePartialTxn.serializedSize(apply.txn, version)
+ + KeySerializers.nullableFullRoute.serializedSize(apply.fullRoute, version)
+ CommandSerializers.writes.serializedSize(apply.writes, version);
}
}
@@ -103,9 +107,9 @@ public class ApplySerializers
{
@Override
protected Apply deserializeApply(TxnId txnId, PartialRoute<?> scope, long waitForEpoch, Apply.Kind kind, Seekables<?, ?> keys,
- Timestamp executeAt, PartialDeps deps, PartialTxn txn, Writes writes, Result result)
+ Timestamp executeAt, PartialDeps deps, PartialTxn txn, FullRoute<?> fullRoute, Writes writes, Result result)
{
- return Apply.SerializationSupport.create(txnId, scope, waitForEpoch, kind, keys, executeAt, deps, txn, writes, result);
+ return Apply.SerializationSupport.create(txnId, scope, waitForEpoch, kind, keys, executeAt, deps, txn, fullRoute, writes, result);
}
};
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 caefcec6f2..fbc3aeb22f 100644
--- a/src/java/org/apache/cassandra/service/accord/serializers/CommandSerializers.java
+++ b/src/java/org/apache/cassandra/service/accord/serializers/CommandSerializers.java
@@ -46,6 +46,8 @@ import org.apache.cassandra.db.marshal.ValueAccessor;
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;
@@ -140,7 +142,7 @@ public class CommandSerializers
}
}
- public static class PartialTxnSerializer implements IVersionedSerializer<PartialTxn>
+ public static class PartialTxnSerializer extends AbstractWithKeysSerializer implements IVersionedWithKeysSerializer<Seekables<?, ?>, PartialTxn>
{
private final IVersionedSerializer<Read> readSerializer;
private final IVersionedSerializer<Query> querySerializer;
@@ -155,10 +157,52 @@ public class CommandSerializers
@Override
public void serialize(PartialTxn txn, DataOutputPlus out, int version) throws IOException
+ {
+ KeySerializers.seekables.serialize(txn.keys(), out, version);
+ serializeWithoutKeys(txn, out, version);
+ }
+
+ @Override
+ public PartialTxn deserialize(DataInputPlus in, int version) throws IOException
+ {
+ Seekables<?, ?> keys = KeySerializers.seekables.deserialize(in, version);
+ return deserializeWithoutKeys(keys, in, version);
+ }
+
+ @Override
+ public long serializedSize(PartialTxn txn, int version)
+ {
+ long size = KeySerializers.seekables.serializedSize(txn.keys(), version);
+ size += serializedSizeWithoutKeys(txn, version);
+ 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);
KeySerializers.ranges.serialize(txn.covering(), out, version);
- KeySerializers.seekables.serialize(txn.keys(), out, version);
readSerializer.serialize(txn.read(), out, version);
querySerializer.serialize(txn.query(), out, version);
out.writeBoolean(txn.update() != null);
@@ -166,24 +210,21 @@ public class CommandSerializers
updateSerializer.serialize(txn.update(), out, version);
}
- @Override
- public PartialTxn deserialize(DataInputPlus in, int version) throws IOException
+ private PartialTxn deserializeWithoutKeys(Seekables<?, ?> keys, DataInputPlus in, int version) throws IOException
{
Txn.Kind kind = CommandSerializers.kind.deserialize(in, version);
Ranges covering = KeySerializers.ranges.deserialize(in, version);
- Seekables<?, ?> keys = KeySerializers.seekables.deserialize(in, version);
Read read = readSerializer.deserialize(in, version);
Query query = querySerializer.deserialize(in, version);
Update update = in.readBoolean() ? updateSerializer.deserialize(in, version) : null;
return new PartialTxn.InMemory(covering, kind, keys, read, query, update);
}
- @Override
- public long serializedSize(PartialTxn txn, int version)
+
+ private long serializedSizeWithoutKeys(PartialTxn txn, int version)
{
long size = CommandSerializers.kind.serializedSize(txn.kind(), version);
size += KeySerializers.ranges.serializedSize(txn.covering(), version);
- size += KeySerializers.seekables.serializedSize(txn.keys(), version);
size += readSerializer.serializedSize(txn.read(), version);
size += querySerializer.serializedSize(txn.query(), version);
size += TypeSizes.sizeof(txn.update() != null);
@@ -197,8 +238,8 @@ public class CommandSerializers
private static final IVersionedSerializer<Query> query = new CastingSerializer<>(TxnQuery.class, TxnQuery.serializer);
private static final IVersionedSerializer<Update> update = new CastingSerializer<>(AccordUpdate.class, AccordUpdate.serializer);
- public static final IVersionedSerializer<PartialTxn> partialTxn = new PartialTxnSerializer(read, query, update);
- public static final IVersionedSerializer<PartialTxn> nullablePartialTxn = NullableSerializer.wrap(partialTxn);
+ public static final IVersionedWithKeysSerializer<Seekables<?, ?>, PartialTxn> partialTxn = new PartialTxnSerializer(read, query, update);
+ public static final IVersionedWithKeysSerializer<Seekables<?, ?>, PartialTxn> nullablePartialTxn = new NullableWithKeysSerializer<>(partialTxn);
public static final EnumSerializer<SaveStatus> saveStatus = new EnumSerializer<>(SaveStatus.class);
public static final EnumSerializer<Status> status = new EnumSerializer<>(Status.class);
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 48ec6fae6c..d5144cbe8d 100644
--- a/src/java/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializer.java
+++ b/src/java/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializer.java
@@ -18,190 +18,832 @@
package org.apache.cassandra.service.accord.serializers;
-import java.io.IOException;
import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Arrays;
-import com.google.common.annotations.VisibleForTesting;
+import com.google.common.primitives.Ints;
-import accord.impl.CommandTimeseries.CommandLoader;
-import accord.local.Command;
-import accord.local.SaveStatus;
-import accord.primitives.PartialDeps;
+import accord.api.Key;
+import accord.impl.CommandsForKey;
+import accord.impl.CommandsForKey.Info;
+import accord.impl.CommandsForKey.InternalStatus;
+import accord.impl.CommandsForKey.NoInfo;
+import accord.local.Node;
+import accord.primitives.Routable.Domain;
import accord.primitives.Timestamp;
+import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.utils.Invariants;
import org.apache.cassandra.db.TypeSizes;
-import org.apache.cassandra.db.marshal.ByteBufferAccessor;
-import org.apache.cassandra.db.marshal.ValueAccessor;
-import org.apache.cassandra.io.IVersionedSerializer;
-import org.apache.cassandra.io.LocalVersionedSerializer;
-import org.apache.cassandra.io.util.DataInputBuffer;
-import org.apache.cassandra.io.util.DataInputPlus;
-import org.apache.cassandra.io.util.DataOutputBuffer;
-import org.apache.cassandra.io.util.DataOutputPlus;
-import org.apache.cassandra.service.accord.AccordSerializerVersion;
+import org.apache.cassandra.utils.vint.VIntCoding;
+
+import static accord.primitives.Txn.Kind.ExclusiveSyncPoint;
+import static accord.primitives.Txn.Kind.Read;
+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;
public class CommandsForKeySerializer
{
- @VisibleForTesting
- public static final IVersionedSerializer<List<TxnId>> depsIdSerializer = new IVersionedSerializer<List<TxnId>>()
+ 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_NON_STANDARD_FLAGS = 0x4;
+
+ /**
+ * We read/write a fixed number of intial bytes for each command, with an initial flexible number of flag bits
+ * and the remainder interpreted as the HLC/epoch/node.
+ *
+ * The preamble encodes:
+ * vint32: number of commands
+ * vint32: number of unique node Ids
+ * [unique node ids]
+ * two flag bytes:
+ * 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 queries present besides reads/writes
+ * bits 3-4 number of header bytes to read for each command
+ * bits 5-6: level 0 extra hlc bytes to read
+ * bits 7-8: level 1 extra hlc bytes to read (+ 1 + level 0)
+ * bits 9-10: level 2 extra hlc bytes to read (+ 1 + level 1)
+ * bits 12-13: level 3 extra hlc bytes to read (+ 1 + level 2)
+ *
+ * In order, for each command, we consume:
+ * 3 bits for the InternalStatus of the command
+ * 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
+ * 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
+ * N node id bits (where 2^N unique node ids in the CFK)
+ * 2 bits indicating how many more payload bytes should be read, with mapping written in header
+ * all remaining bits are interpreted as a delta from the prior HLC
+ *
+ * if txnId kind flag is 3, read an additional 2 bytes for TxnId flag
+ * if epoch increment flag is 2 or 3, read additional 1 or 4 bytes for epoch delta
+ * if executeAt is expected, read vint32 for epoch, vint32 for delta from txnId hlc, and ceil(N/8) bytes for node id
+ *
+ * After writing all transactions, we then write out the missing txnid collections. This is written at the end
+ * so that on deserialization we have already read all of the TxnId. This also permits more efficient serialization,
+ * as we can encode a single bit stream with the optimal number of bits.
+ * TODO (desired): we could prefix this collection with the subset of TxnId that are actually missing from any other
+ * deps, so as to shrink this collection much further.
+ */
+ // TODO (expected): offer filtering option that does not need to reconstruct objects/info, reusing prior encoding decisions
+ // TODO (expected): accept new redundantBefore on load to avoid deserializing stale data
+ // TODO (desired): determine timestamp resolution as a factor of 10
+ public static ByteBuffer toBytesWithoutKey(CommandsForKey cfk)
{
- @Override
- public void serialize(List<TxnId> ids, DataOutputPlus out, int version) throws IOException
+ int commandCount = cfk.size();
+ if (commandCount == 0)
{
- out.writeInt(ids.size());
- for (int i=0,mi=ids.size(); i<mi; i++)
- CommandSerializers.txnId.serialize(ids.get(i), out, version);
+ // TODO (expected): we should not need to special-case, but best solution here is not to store redundantBefore;
+ // but this requires some modest deeper changes, so for now special-case serialization when empty
+ Timestamp redundantBefore = cfk.redundantBefore();
+ ByteBuffer out = ByteBuffer.allocate(TypeSizes.sizeofUnsignedVInt(0) +
+ TypeSizes.sizeofUnsignedVInt(redundantBefore.epoch()) +
+ TypeSizes.sizeofUnsignedVInt(redundantBefore.hlc()) +
+ TypeSizes.sizeofUnsignedVInt(redundantBefore.flags()) +
+ TypeSizes.sizeofUnsignedVInt(redundantBefore.node.id));
+ VIntCoding.writeUnsignedVInt32(0, out);
+ VIntCoding.writeUnsignedVInt(redundantBefore.epoch(), out);
+ VIntCoding.writeUnsignedVInt(redundantBefore.hlc(), out);
+ VIntCoding.writeUnsignedVInt32(redundantBefore.flags(), out);
+ VIntCoding.writeUnsignedVInt32(redundantBefore.node.id, out);
+ out.flip();
+ return out;
}
- @Override
- public List<TxnId> deserialize(DataInputPlus in, int version) throws IOException
+ int[] nodeIds = cachedInts().getInts(Math.min(64, commandCount));
+ try
{
- int size = in.readInt();
- List<TxnId> ids = new ArrayList<>(size);
- for (int i=0; i<size; i++)
- ids.add(CommandSerializers.txnId.deserialize(in, version));
- return ids;
- }
+ // first compute the unique Node Ids and some basic characteristics of the data, such as
+ // 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, bitsPerExecuteAtFlags = 0;
+ int bitsPerExecuteAtEpochDelta = 0, bitsPerExecuteAtHlcDelta = 1; // to permit us to use full 64 bits and encode in 5 bits we force at least one hlc bit
+ {
+ nodeIdCount = 1;
+ nodeIds[0] = cfk.redundantBefore().node.id;
+ for (int i = 0 ; i < commandCount ; ++i)
+ {
+ if (nodeIdCount + 1 >= nodeIds.length)
+ {
+ nodeIdCount = compact(nodeIds);
+ if (nodeIdCount > nodeIds.length/2)
+ nodeIds = cachedInts().resize(nodeIds, nodeIds.length, nodeIds.length * 2);
+ }
- @Override
- public long serializedSize(List<TxnId> ids, int version)
- {
- long size = TypeSizes.INT_SIZE;
- for (int i=0,mi=ids.size(); i<mi; i++)
- size += CommandSerializers.txnId.serializedSize(ids.get(i), version);
- return size;
- }
- };
+ TxnId txnId = cfk.txnId(i);
+ Info info = cfk.info(i);
- private static final LocalVersionedSerializer<List<TxnId>> depsIdsLocalSerializer = new LocalVersionedSerializer<>(AccordSerializerVersion.CURRENT, AccordSerializerVersion.serializer, depsIdSerializer);
+ hasNonStandardFlags |= txnIdFlags(txnId) != STANDARD;
+ nodeIds[nodeIdCount++] = txnId.node.id;
- public static final CommandLoader<ByteBuffer> loader = new AccordCFKLoader();
- private static class AccordCFKLoader implements CommandLoader<ByteBuffer>
- {
- private static final int HAS_DEPS = 0x01;
- private static final int HAS_EXECUTE_AT = 0x02;
+ if (info.getClass() == NoInfo.class)
+ continue;
- private static final long FIXED_SIZE;
- private static final int FLAG_OFFSET;
- private static final int STATUS_OFFSET;
- private static final int TXNID_OFFSET;
- private static final int EXECUTEAT_OFFSET;
- private static final int DEPS_OFFSET;
+ missingIdCount += info.missing.length;
- static
- {
- long size = 0;
+ if (info.executeAt == txnId)
+ continue;
+
+ nodeIds[nodeIdCount++] = info.executeAt.node.id;
+ bitsPerExecuteAtEpochDelta = Math.max(bitsPerExecuteAtEpochDelta, numberOfBitsToRepresent(info.executeAt.epoch() - txnId.epoch()));
+ bitsPerExecuteAtHlcDelta = Math.max(bitsPerExecuteAtHlcDelta, numberOfBitsToRepresent(info.executeAt.hlc() - txnId.hlc()));
+ bitsPerExecuteAtFlags = Math.max(bitsPerExecuteAtFlags, numberOfBitsToRepresent(info.executeAt.flags()));
+ executeAtCount += 1;
+ }
+ nodeIdCount = compact(nodeIds);
+ Invariants.checkState(nodeIdCount > 0);
+ }
+
+ // 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 infoHeaderBits = (executeAtCount > 0 ? 1 : 0) + (missingIdCount > 0 ? 1 : 0);
+ int maxHeaderBits = minHeaderBits;
+ int totalBytes = 0;
+
+ long prevEpoch = cfk.redundantBefore().epoch();
+ long prevHlc = cfk.redundantBefore().hlc();
+ int[] bytesHistogram = cachedInts().getInts(12);
+ Arrays.fill(bytesHistogram, 0);
+ for (int i = 0 ; i < commandCount ; ++i)
+ {
+ int headerBits = minHeaderBits;
+ int payloadBits = 0;
+
+ TxnId txnId = cfk.txnId(i);
+ {
+ long epoch = txnId.epoch();
+ Invariants.checkState(epoch >= prevEpoch);
+ long epochDelta = epoch - prevEpoch;
+ long hlc = txnId.hlc();
+ long hlcDelta = hlc - prevHlc;
+
+ if (epochDelta > 0)
+ {
+ if (hlcDelta < 0)
+ hlcDelta = -1 - hlcDelta;
+
+ headerBits += 3;
+ if (epochDelta > 1)
+ {
+ if (epochDelta <= 0xf) headerBits += 4;
+ else if (epochDelta <= 0xff) totalBytes += 1;
+ else { totalBytes += 4; Invariants.checkState(epochDelta <= 0xffffffffL); }
+ }
+ }
+
+ payloadBits += numberOfBitsToRepresent(hlcDelta);
+ prevEpoch = epoch;
+ prevHlc = hlc;
+ }
+
+ if (hasNonStandardFlags && txnIdFlags(txnId) == RAW)
+ totalBytes += 2;
+
+ Info info = cfk.info(i);
+ if (info.status.hasInfo)
+ headerBits += infoHeaderBits;
+ maxHeaderBits = Math.max(headerBits, maxHeaderBits);
+ int basicBytes = (headerBits + payloadBits + 7)/8;
+ bytesHistogram[basicBytes]++;
+ }
+
+ int minBasicBytes = -1, maxBasicBytes = 0;
+ for (int i = 0 ; i < bytesHistogram.length ; ++i)
+ {
+ if (bytesHistogram[i] == 0) continue;
+ if (minBasicBytes == -1) minBasicBytes = i;
+ maxBasicBytes = i;
+ }
+ for (int i = minBasicBytes + 1 ; i <= maxBasicBytes ; ++i)
+ bytesHistogram[i] += bytesHistogram[i-1];
+
+ int flags = (missingIdCount > 0 ? HAS_MISSING_DEPS_HEADER_BIT : 0)
+ | (executeAtCount > 0 ? HAS_EXECUTE_AT_HEADER_BIT : 0)
+ | (hasNonStandardFlags ? HAS_NON_STANDARD_FLAGS : 0);
+
+ int headerBytes = (maxHeaderBits+7)/8;
+ flags |= Invariants.checkArgument(headerBytes - 1, headerBytes <= 4) << 3;
+
+ int hlcBytesLookup;
+ { // 2bits per size, first value may be zero and remainder may be increments of 1-4;
+ // only need to be able to encode a distribution of approx. 8 bytes at most, so
+ // pick lowest number we need first, then next lowest as 25th %ile while ensuring value of 1-4;
+ // then pick highest number we need, ensuring at least 2 greater than second (leaving room for third)
+ // then pick third number as 75th %ile, but at least 1 less than highest, and one more than second
+ // finally, ensure third then second are distributed so that there is no more than a gap of 4 between them and the next
+ int l0 = Math.max(0, Math.min(3, minBasicBytes - headerBytes));
+ int l1 = Math.max(l0+1, Math.min(l0+4,Arrays.binarySearch(bytesHistogram, commandCount/4) - headerBytes));
+ int l3 = Math.max(l1+2, maxBasicBytes - headerBytes);
+ int l2 = Math.max(l1+1, Math.min(l3-1, Arrays.binarySearch(bytesHistogram, (3*commandCount)/4) - headerBytes));
+ while (l3-l2 > 4) ++l2;
+ while (l2-l1 > 4) ++l1;
+ hlcBytesLookup = setHlcBytes(l0, l1, l2, l3);
+ flags |= (l0 | ((l1-(1+l0))<<2) | ((l2-(1+l1))<<4) | ((l3-(1+l2))<<6)) << 5;
+ }
+ int hlcFlagLookup = hlcBytesLookupToHlcFlagLookup(hlcBytesLookup);
+
+ totalBytes += bytesHistogram[minBasicBytes] * (headerBytes + getHlcBytes(hlcBytesLookup, getHlcFlag(hlcFlagLookup, minBasicBytes - headerBytes)));
+ for (int i = minBasicBytes + 1 ; i <= maxBasicBytes ; ++i)
+ totalBytes += (bytesHistogram[i] - bytesHistogram[i-1]) * (headerBytes + getHlcBytes(hlcBytesLookup, getHlcFlag(hlcFlagLookup, i - headerBytes)));
+ totalBytes += TypeSizes.sizeofUnsignedVInt(commandCount);
+ totalBytes += TypeSizes.sizeofUnsignedVInt(nodeIdCount);
+ totalBytes += TypeSizes.sizeofUnsignedVInt(nodeIds[0]);
+ for (int i = 1 ; i < nodeIdCount ; ++i)
+ totalBytes += TypeSizes.sizeofUnsignedVInt(nodeIds[i] - nodeIds[i-1]);
+ totalBytes += 2;
+
+ Arrays.fill(bytesHistogram, minBasicBytes, maxBasicBytes + 1, 0);
+ cachedInts().forceDiscard(bytesHistogram);
+
+ prevEpoch = cfk.redundantBefore().epoch();
+ prevHlc = cfk.redundantBefore().hlc();
+ // account for encoding redundantBefore
+ totalBytes += TypeSizes.sizeofUnsignedVInt(prevEpoch);
+ totalBytes += TypeSizes.sizeofUnsignedVInt(prevHlc);
+ totalBytes += 2; // flags TODO (expected): pack this along with uniqueIdBits, as usually zero bits should be needed
+ totalBytes += (bitsPerNodeId+7)/8;
+
+ if (missingIdCount + executeAtCount > 0)
+ {
+ // account for encoding missing id stream
+ int missingIdBits = 1 + numberOfBitsToRepresent(commandCount);
+ int executeAtBits = bitsPerNodeId
+ + bitsPerExecuteAtEpochDelta
+ + bitsPerExecuteAtHlcDelta
+ + bitsPerExecuteAtFlags;
+ totalBytes += (missingIdBits * missingIdCount + executeAtBits * executeAtCount + 7)/8;
+ if (executeAtCount > 0)
+ totalBytes += 2;
+ }
+
+ ByteBuffer out = ByteBuffer.allocate(totalBytes);
+ VIntCoding.writeUnsignedVInt32(commandCount, out);
+ VIntCoding.writeUnsignedVInt32(nodeIdCount, out);
+ VIntCoding.writeUnsignedVInt32(nodeIds[0], out);
+ for (int i = 1 ; i < nodeIdCount ; ++i) // TODO (desired): can encode more efficiently as a stream of N bit integers
+ VIntCoding.writeUnsignedVInt32(nodeIds[i] - nodeIds[i-1], out);
+ out.putShort((short)flags);
+
+ VIntCoding.writeUnsignedVInt(prevEpoch, out);
+ VIntCoding.writeUnsignedVInt(prevHlc, out);
+ out.putShort((short) cfk.redundantBefore().flags());
+ writeLeastSignificantBytes(Arrays.binarySearch(nodeIds, 0, nodeIdCount, cfk.redundantBefore().node.id), (bitsPerNodeId+7)/8, out);
- FLAG_OFFSET = (int) size;
- size += TypeSizes.BYTE_SIZE;
+ int executeAtMask = executeAtCount > 0 ? 1 : 0;
+ int missingDepsMask = missingIdCount > 0 ? 1 : 0;
+ int flagsIncrement = hasNonStandardFlags ? 2 : 1;
+ // 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);
+ Info info = cfk.info(i);
+ InternalStatus status = info.status;
+
+ long bits = status.ordinal();
+ int bitIndex = 3;
+
+ int statusHasInfo = status.hasInfo ? 1 : 0;
+ long hasExecuteAt = info.executeAt != null & info.executeAt != txnId ? 1 : 0;
+ bits |= hasExecuteAt << bitIndex;
+ bitIndex += statusHasInfo & executeAtMask;
+
+ long hasMissingIds = info.missing != CommandsForKey.NO_TXNIDS ? 1 : 0;
+ bits |= hasMissingIds << bitIndex;
+ bitIndex += statusHasInfo & missingDepsMask;
+
+ long flagBits = txnIdFlagsBits(txnId);
+ boolean writeFullFlags = flagBits == RAW_BITS;
+ bits |= flagBits << bitIndex;
+ bitIndex += flagsIncrement;
+
+ long hlcBits;
+ int extraEpochDeltaBytes = 0;
+ {
+ long epoch = txnId.epoch();
+ long delta = epoch - prevEpoch;
+ long hlc = txnId.hlc();
+ hlcBits = hlc - prevHlc;
+ if (delta == 0)
+ {
+ bitIndex++;
+ }
+ else
+ {
+ bits |= 1L << bitIndex++;
+ if (hlcBits < 0)
+ {
+ hlcBits = -1 - hlcBits;
+ bits |= 1L << bitIndex;
+ }
+ bitIndex++;
+ if (delta > 1)
+ {
+ if (delta <= 0xf)
+ {
+ bits |= 1L << bitIndex;
+ bits |= delta << (bitIndex + 2);
+ bitIndex += 4;
+ }
+ else
+ {
+ bits |= (delta <= 0xff ? 2L : 3L) << bitIndex;
+ extraEpochDeltaBytes = Ints.checkedCast(delta);
+ }
+ }
+ bitIndex += 2;
+ }
+ prevEpoch = epoch;
+ prevHlc = hlc;
+ }
+
+ bits |= ((long)Arrays.binarySearch(nodeIds, 0, nodeIdCount, txnId.node.id)) << bitIndex;
+ bitIndex += bitsPerNodeId;
+
+ bits |= hlcBits << (bitIndex + 2);
+ hlcBits >>>= 8*headerBytes - (bitIndex + 2);
+ int hlcFlag = getHlcFlag(hlcFlagLookup, (7 + numberOfBitsToRepresent(hlcBits))/8);
+ bits |= ((long)hlcFlag) << bitIndex;
- STATUS_OFFSET = (int) size;
- size += TypeSizes.BYTE_SIZE;
+ writeLeastSignificantBytes(bits, headerBytes, out);
+ writeLeastSignificantBytes(hlcBits, getHlcBytes(hlcBytesLookup, hlcFlag), out);
- TXNID_OFFSET = (int) size;
- size += CommandSerializers.txnId.serializedSize();
+ if (writeFullFlags)
+ out.putShort((short)txnId.flags());
- FIXED_SIZE = size;
+ if (extraEpochDeltaBytes > 0)
+ {
+ if (extraEpochDeltaBytes <= 0xff) out.put((byte)extraEpochDeltaBytes);
+ else out.putInt(extraEpochDeltaBytes);
+ }
+ }
+
+ if ((executeAtCount | missingIdCount) > 0)
+ {
+ int bitsPerCommandId = numberOfBitsToRepresent(commandCount);
+ int bitsPerMissingId = 1 + bitsPerCommandId;
+ int bitsPerExecuteAt = bitsPerExecuteAtEpochDelta + bitsPerExecuteAtHlcDelta + bitsPerExecuteAtFlags + bitsPerNodeId;
+ Invariants.checkState(bitsPerExecuteAtEpochDelta < 64);
+ Invariants.checkState(bitsPerExecuteAtHlcDelta <= 64);
+ Invariants.checkState(bitsPerExecuteAtFlags <= 16);
+ if (executeAtMask > 0) // we encode both 15 and 16 bits for flag length as 15 to fit in a short
+ out.putShort((short) ((bitsPerExecuteAtEpochDelta << 10) | ((bitsPerExecuteAtHlcDelta-1) << 4) | (Math.min(15, bitsPerExecuteAtFlags))));
+ long buffer = 0L;
+ int bufferCount = 0;
- EXECUTEAT_OFFSET = (int) size;
- size += CommandSerializers.timestamp.serializedSize();
+ for (int i = 0 ; i < commandCount ; ++i)
+ {
+ Info info = cfk.info(i);
+ if (info.getClass() == NoInfo.class)
+ continue;
+
+ TxnId txnId = cfk.txnId(i);
+ if (info.executeAt != txnId)
+ {
+ Timestamp executeAt = info.executeAt;
+ int nodeIdx = Arrays.binarySearch(nodeIds, 0, nodeIdCount, executeAt.node.id);
+ if (bitsPerExecuteAt <= 64)
+ {
+ Invariants.checkState(executeAt.epoch() >= txnId.epoch());
+ long executeAtBits = executeAt.epoch() - txnId.epoch();
+ int offset = bitsPerExecuteAtEpochDelta;
+ executeAtBits |= (executeAt.hlc() - txnId.hlc()) << offset ;
+ offset += bitsPerExecuteAtHlcDelta;
+ executeAtBits |= ((long)executeAt.flags()) << offset;
+ offset += bitsPerExecuteAtFlags;
+ executeAtBits |= ((long)nodeIdx) << offset;
+ buffer = flushBits(buffer, bufferCount, executeAtBits, bitsPerExecuteAt, out);
+ bufferCount = (bufferCount + bitsPerExecuteAt) & 63;
+ }
+ else
+ {
+ buffer = flushBits(buffer, bufferCount, executeAt.epoch() - txnId.epoch(), bitsPerExecuteAtEpochDelta, out);
+ bufferCount = (bufferCount + bitsPerExecuteAtEpochDelta) & 63;
+ buffer = flushBits(buffer, bufferCount, executeAt.hlc() - txnId.hlc(), bitsPerExecuteAtHlcDelta, out);
+ bufferCount = (bufferCount + bitsPerExecuteAtHlcDelta) & 63;
+ buffer = flushBits(buffer, bufferCount, executeAt.flags(), bitsPerExecuteAtFlags, out);
+ bufferCount = (bufferCount + bitsPerExecuteAtFlags) & 63;
+ buffer = flushBits(buffer, bufferCount, nodeIdx, bitsPerNodeId, out);
+ bufferCount = (bufferCount + bitsPerNodeId) & 63;
+ }
+ }
- DEPS_OFFSET = (int) size;
+ if (info.missing.length > 0)
+ {
+ int j = 0;
+ while (j < info.missing.length - 1)
+ {
+ int missingId = cfk.indexOf(info.missing[j++]);
+ buffer = flushBits(buffer, bufferCount, missingId, bitsPerMissingId, out);
+ bufferCount = (bufferCount + bitsPerMissingId) & 63;
+ }
+ int missingId = cfk.indexOf(info.missing[info.missing.length - 1]);
+ missingId |= 1L << bitsPerCommandId;
+ buffer = flushBits(buffer, bufferCount, missingId, bitsPerMissingId, out);
+ bufferCount = (bufferCount + bitsPerMissingId) & 63;
+ }
+ }
+
+ writeMostSignificantBytes(buffer, (bufferCount + 7)/8, out);
+ }
+
+ out.flip();
+ return out;
+ }
+ finally
+ {
+ cachedInts().forceDiscard(nodeIds);
}
+ }
- private int serializedSize(Command command)
+ private static long flushBits(long buffer, int bufferCount, long add, int addCount, ByteBuffer out)
+ {
+ Invariants.checkArgument(addCount == 64 || 0 == (add & (-1L << addCount)));
+ int total = bufferCount + addCount;
+ if (total < 64)
{
- return (int) (FIXED_SIZE
- + (command.executeAt() != null ? CommandSerializers.timestamp.serializedSize() : 0)
- + (command.partialDeps() != null ? depsIdsLocalSerializer.serializedSize(command.partialDeps().txnIds()) : 0));
+ return buffer | (add << 64 - total);
}
+ else
+ {
+ buffer |= add >>> total - 64;
+ out.putLong(buffer);
+ return total == 64 ? 0 : (add << (128 - total));
+ }
+ }
- private static final ValueAccessor<ByteBuffer> accessor = ByteBufferAccessor.instance;
+ public static CommandsForKey fromBytes(Key key, ByteBuffer in)
+ {
+ if (!in.hasRemaining())
+ return null;
- private static byte toByte(int v)
+ in = in.duplicate();
+ int commandCount = VIntCoding.readUnsignedVInt32(in);
+ if (commandCount == 0)
{
- Invariants.checkArgument(v < Byte.MAX_VALUE, "Value %d is larger than %d", v, Byte.MAX_VALUE);
- return (byte) v;
+ long epoch = VIntCoding.readUnsignedVInt(in);
+ long hlc = VIntCoding.readUnsignedVInt(in);
+ int flags = VIntCoding.readUnsignedVInt32(in);
+ Node.Id id = new Node.Id(VIntCoding.readUnsignedVInt32(in));
+ return new CommandsForKey(key).withoutRedundant(TxnId.fromValues(epoch, hlc, flags, id));
}
- private AccordCFKLoader() {}
+ TxnId[] txnIds = new TxnId[commandCount];
+ Info[] infos = new Info[commandCount];
+ int nodeIdCount = VIntCoding.readUnsignedVInt32(in);
+ int bitsPerNodeId = numberOfBitsToRepresent(nodeIdCount);
+ long nodeIdMask = (1L << bitsPerNodeId) - 1;
+ Node.Id[] nodeIds = new Node.Id[nodeIdCount]; // TODO (expected): use a shared reusable scratch buffer
+ {
+ int prev = VIntCoding.readUnsignedVInt32(in);
+ nodeIds[0] = new Node.Id(prev);
+ for (int i = 1 ; i < nodeIdCount ; ++i)
+ nodeIds[i] = new Node.Id(prev += VIntCoding.readUnsignedVInt32(in));
+ }
- @Override
- public ByteBuffer saveForCFK(Command command)
+ int missingDepsMasks, executeAtMasks, txnIdFlagsMask;
+ int headerByteCount, hlcBytesLookup;
{
- int flags = 0;
+ int flags = in.getShort();
+ missingDepsMasks = 0 != (flags & HAS_MISSING_DEPS_HEADER_BIT) ? 1 : 0;
+ executeAtMasks = 0 != (flags & HAS_EXECUTE_AT_HEADER_BIT) ? 1 : 0;
+ txnIdFlagsMask = 0 != (flags & HAS_NON_STANDARD_FLAGS) ? 3 : 1;
+ headerByteCount = 1 + ((flags >>> 3) & 0x3);
+ hlcBytesLookup = setHlcByteDeltas((flags >>> 5) & 0x3, (flags >>> 7) & 0x3, (flags >>> 9) & 0x3, (flags >>> 11) & 0x3);
+ }
+
+ long prevEpoch = VIntCoding.readUnsignedVInt32(in);
+ long prevHlc = VIntCoding.readUnsignedVInt32(in);
+ TxnId redundantBefore = TxnId.fromValues(prevEpoch, prevHlc, in.getShort(),
+ nodeIds[(int)readLeastSignificantBytes((bitsPerNodeId+7)/8, in)]);
- PartialDeps deps = command.partialDeps();
- Timestamp executeAt = command.executeAt();
- if (deps != null)
- flags |= HAS_DEPS;
- if (executeAt != null)
- flags |= HAS_EXECUTE_AT;
- int size = serializedSize(command);
- ByteBuffer buffer = accessor.allocate(size);
- accessor.putByte(buffer, FLAG_OFFSET, toByte(flags));
- accessor.putByte(buffer, STATUS_OFFSET, toByte(command.saveStatus().ordinal()));
- CommandSerializers.txnId.serialize(command.txnId(), buffer, accessor, TXNID_OFFSET);
- if (executeAt != null)
- CommandSerializers.timestamp.serialize(executeAt, buffer, accessor, EXECUTEAT_OFFSET);
- if (deps != null)
+ for (int i = 0 ; i < commandCount ; ++i)
+ {
+ long header = readLeastSignificantBytes(headerByteCount, in);
+ header |= 1L << (8 * headerByteCount); // marker so we know where to shift-left most-significant bytes to
+ InternalStatus status = InternalStatus.get((int) (header & 0x7));
+ header >>>= 3;
+
+ int executeAtInfoOffset, missingDepsInfoOffset;
{
- ByteBuffer duplicate = buffer.duplicate();
- duplicate.position(executeAt != null ? DEPS_OFFSET : EXECUTEAT_OFFSET);
- try (DataOutputBuffer out = new DataOutputBuffer(duplicate))
- {
- depsIdsLocalSerializer.serialize(deps.txnIds(), out);
- }
- catch (IOException e)
+ int infoMask = status.hasInfo ? 1 : 0;
+ int executeAtMask = infoMask & executeAtMasks, missingDepsMask = infoMask & missingDepsMasks;
+ executeAtInfoOffset = ((int)header & executeAtMask) << 1;
+ header >>>= executeAtMask;
+ missingDepsInfoOffset = (int)header & missingDepsMask;
+ header >>>= missingDepsMask;
+ }
+
+ Txn.Kind kind = TXN_ID_FLAG_BITS_KIND_LOOKUP[((int)header & txnIdFlagsMask)];
+ header >>>= Integer.bitCount(txnIdFlagsMask);
+
+ boolean hlcIsNegative = false;
+ long epoch = prevEpoch;
+ int readEpochBytes = 0;
+ {
+ boolean hasEpochDelta = (header & 1) == 1;
+ header >>>= 1;
+ if (hasEpochDelta)
{
- throw new RuntimeException(e);
+ hlcIsNegative = (header & 1) == 1;
+ header >>>= 1;
+
+ int epochFlag = ((int)header & 0x3);
+ header >>>= 2;
+ switch (epochFlag)
+ {
+ default: throw new AssertionError("Unexpected value not 0-3");
+ case 0: ++epoch; break;
+ case 1: epoch += (header & 0xf); header >>>= 4; break;
+ case 2: readEpochBytes = 1; break;
+ case 3: readEpochBytes = 4; break;
+ }
}
}
- return buffer;
+
+ Node.Id node = nodeIds[(int)(header & nodeIdMask)];
+ header >>>= bitsPerNodeId;
+
+ int readHlcBytes = getHlcBytes(hlcBytesLookup, (int)(header & 0x3));
+ header >>>= 2;
+
+ long hlc = header;
+ {
+ long highestBit = Long.highestOneBit(hlc);
+ hlc ^= highestBit;
+ int hlcShift = Long.numberOfTrailingZeros(highestBit);
+ hlc |= readLeastSignificantBytes(readHlcBytes, in) << hlcShift;
+ }
+ if (hlcIsNegative)
+ hlc = -1-hlc;
+ hlc += prevHlc;
+
+ int flags = kind != null ? 0 : in.getShort();
+ if (readEpochBytes > 0)
+ epoch += readEpochBytes == 1 ? (in.get() & 0xff) : in.getInt();
+
+ TxnId txnId = kind != null ? new TxnId(epoch, hlc, kind, Domain.Key, node)
+ : TxnId.fromValues(epoch, hlc, flags, node);
+
+ txnIds[i] = txnId;
+ infos[i] = DECODE_INFOS[(executeAtInfoOffset | missingDepsInfoOffset)*STATUS_COUNT + status.ordinal()];
+
+ prevEpoch = epoch;
+ prevHlc = hlc;
}
- @Override
- public TxnId txnId(ByteBuffer data)
+ if (executeAtMasks + missingDepsMasks > 0)
{
- return CommandSerializers.txnId.deserialize(data, accessor, TXNID_OFFSET);
+ TxnId[] missingIdBuffer = cachedTxnIds().get(8);
+ int missingIdCount = 0, maxIdBufferCount = 0;
+ int bitsPerTxnId = numberOfBitsToRepresent(commandCount);
+ int txnIdMask = (1 << bitsPerTxnId) - 1;
+ int bitsPerMissingId = bitsPerTxnId + 1;
+
+ int decodeBits = executeAtMasks > 0 ? in.getShort() & 0xffff : 0;
+ int bitsPerEpochDelta = decodeBits >>> 10;
+ int bitsPerHlcDelta = 1 + ((decodeBits >>> 4) & 0x3f);
+ int bitsPerFlags = decodeBits & 0xf;
+ if (bitsPerFlags == 15) bitsPerFlags = 16;
+ int bitsPerExecuteAt = bitsPerEpochDelta + bitsPerHlcDelta + bitsPerFlags + bitsPerNodeId;
+
+ long epochDeltaMask = bitsPerEpochDelta == 0 ? 0 : (-1L >>> (64 - bitsPerEpochDelta));
+ long hlcDeltaMask = (-1L >>> (64 - bitsPerHlcDelta));
+ long flagsMask = bitsPerFlags == 0 ? 0 : (-1L >>> (64 - bitsPerFlags));
+
+ final BitReader reader = new BitReader();
+
+ for (int i = 0 ; i < commandCount ; ++i)
+ {
+ Info info = infos[i];
+ if (info.getClass() == NoInfo.class)
+ continue;
+
+ TxnId txnId = txnIds[i];
+ Timestamp executeAt = txnId;
+ if (info.executeAt == null)
+ {
+ long epoch, hlc;
+ int flags;
+ Node.Id id;
+ if (bitsPerExecuteAt <= 64)
+ {
+ long executeAtBits = reader.read(bitsPerExecuteAt, in);
+ epoch = txnId.epoch() + (executeAtBits & epochDeltaMask);
+ executeAtBits >>>= bitsPerEpochDelta;
+ hlc = txnId.hlc() + (executeAtBits & hlcDeltaMask);
+ executeAtBits >>>= bitsPerHlcDelta;
+ flags = (int)(executeAtBits & flagsMask);
+ executeAtBits >>>= bitsPerFlags;
+ id = nodeIds[(int)(executeAtBits & nodeIdMask)];
+ }
+ else
+ {
+ epoch = txnId.epoch() + reader.read(bitsPerEpochDelta, in);
+ hlc = txnId.hlc() + reader.read(bitsPerHlcDelta, in);
+ flags = (int) reader.read(bitsPerFlags, in);
+ id = nodeIds[(int)(reader.read(bitsPerNodeId, in))];
+ }
+ executeAt = Timestamp.fromValues(epoch, hlc, flags, id);
+ }
+
+ TxnId[] missing = info.missing;
+ if (missing == null)
+ {
+ int prev = -1;
+ while (true)
+ {
+ if (missingIdCount == missingIdBuffer.length)
+ missingIdBuffer = cachedTxnIds().resize(missingIdBuffer, missingIdCount, missingIdCount * 2);
+
+ int next = (int) reader.read(bitsPerMissingId, in);
+ Invariants.checkState(next > prev);
+ missingIdBuffer[missingIdCount++] = txnIds[next & txnIdMask];
+ if (next >= commandCount)
+ break; // finished this array
+ prev = next;
+ }
+
+ missing = Arrays.copyOf(missingIdBuffer, missingIdCount);
+ maxIdBufferCount = missingIdCount;
+ missingIdCount = 0;
+ }
+
+ infos[i] = Info.create(txnId, info.status, executeAt, missing);
+ }
+
+ cachedTxnIds().forceDiscard(missingIdBuffer, maxIdBufferCount);
}
- @Override
- public Timestamp executeAt(ByteBuffer data)
+ return CommandsForKey.SerializerSupport.create(key, redundantBefore, txnIds, infos);
+ }
+
+ private static int getHlcBytes(int lookup, int index)
+ {
+ return (lookup >>> (index * 4)) & 0xf;
+ }
+
+ private static int setHlcBytes(int value1, int value2, int value3, int value4)
+ {
+ return value1 | (value2 << 4) | (value3 << 8) | (value4 << 12);
+ }
+
+ private static int setHlcByteDeltas(int value1, int value2, int value3, int value4)
+ {
+ value2 += 1 + value1;
+ value3 += 1 + value2;
+ value4 += 1 + value3;
+ return setHlcBytes(value1, value2, value3, value4);
+ }
+
+ private static int getHlcFlag(int flagsLookup, int bytes)
+ {
+ return (flagsLookup >>> (bytes * 2)) & 0x3;
+ }
+
+ private static int hlcBytesLookupToHlcFlagLookup(int bytesLookup)
+ {
+ int flagsLookup = 0;
+ int flagIndex = 0;
+ for (int bytesIndex = 0 ; bytesIndex < 4 ; bytesIndex++)
{
- byte flags = accessor.getByte(data, FLAG_OFFSET);
- if ((flags & HAS_EXECUTE_AT) == 0)
- return null;
- return CommandSerializers.timestamp.deserialize(data, accessor, EXECUTEAT_OFFSET);
+ int flagLimit = getHlcBytes(bytesLookup, bytesIndex);
+ while (flagIndex <= flagLimit)
+ flagsLookup |= bytesIndex << (2 * flagIndex++);
}
+ return flagsLookup;
+ }
- @Override
- public SaveStatus saveStatus(ByteBuffer data)
+ private static int compact(int[] buffer)
+ {
+ Arrays.sort(buffer);
+ int count = 0;
+ int j = 0;
+ while (j < buffer.length)
{
- return SaveStatus.values()[accessor.getByte(data, STATUS_OFFSET)];
+ int prev;
+ buffer[count++] = prev = buffer[j];
+ while (++j < buffer.length && buffer[j] == prev) {}
}
+ return count;
+ }
+
+ private static int numberOfBitsToRepresent(long value)
+ {
+ return 64 - Long.numberOfLeadingZeros(value);
+ }
- @Override
- public List<TxnId> depsIds(ByteBuffer data)
+ private static int numberOfBitsToRepresent(int value)
+ {
+ return 32 - Integer.numberOfLeadingZeros(value);
+ }
+
+ static final class BitReader
+ {
+ private long bitBuffer;
+ private int bitCount;
+
+ long read(int readCount, ByteBuffer in)
{
- byte flags = accessor.getByte(data, FLAG_OFFSET);
- if ((flags & HAS_DEPS) == 0)
- return null;
- ByteBuffer buffer = data.duplicate();
- int offset = (flags & HAS_EXECUTE_AT) == 0 ? EXECUTEAT_OFFSET : DEPS_OFFSET;
- buffer.position(data.position() + offset);
- try (DataInputBuffer in = new DataInputBuffer(buffer, false))
+ long result = bitBuffer >>> (64 - readCount);
+ int remaining = bitCount - readCount;
+ if (remaining >= 0)
{
- return depsIdsLocalSerializer.deserialize(in);
+ bitBuffer <<= readCount;
+ bitCount = remaining;
}
- catch (IOException e)
+ else if (in.remaining() >= 8)
{
- throw new RuntimeException(e);
+ readCount -= bitCount;
+ bitBuffer = in.getLong();
+ bitCount = 64 - readCount;
+ result |= (bitBuffer >>> bitCount);
+ bitBuffer <<= readCount;
+ }
+ else
+ {
+ readCount -= bitCount;
+ while (readCount > 8)
+ {
+ long next = in.get() & 0xff;
+ readCount -= 8;
+ result |= next << readCount;
+ }
+ long next = in.get() & 0xff;
+ bitCount = 8 - readCount;
+ result |= next >>> bitCount;
+ bitBuffer = next << (64 - bitCount);
}
+ return result;
}
}
+
+ enum TxnIdFlags
+ {
+ STANDARD, EXTENDED, RAW;
+ static final int EXTENDED_BITS = 0x2;
+ static final int RAW_BITS = 0x3;
+ }
+
+ private static TxnIdFlags txnIdFlags(TxnId txnId)
+ {
+ if (txnId.flags() > Timestamp.IDENTITY_FLAGS || txnId.domain() != Domain.Key)
+ return RAW;
+ switch (txnId.kind())
+ {
+ default: throw new AssertionError("Unhandled Kind: " + txnId.kind());
+ case Read:
+ case Write:
+ return STANDARD;
+ case ExclusiveSyncPoint:
+ return EXTENDED;
+ case SyncPoint:
+ case LocalOnly:
+ case EphemeralRead:
+ return RAW;
+ }
+ }
+
+ private static long txnIdFlagsBits(TxnId txnId)
+ {
+ 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;
+ }
+ }
+
+ private static final Txn.Kind[] TXN_ID_FLAG_BITS_KIND_LOOKUP = new Txn.Kind[] { Read, Write, ExclusiveSyncPoint, null };
+ private static final int STATUS_COUNT = InternalStatus.values().length;
+ private static final Info[] DECODE_INFOS = new Info[4 * STATUS_COUNT];
+ static
+ {
+ for (InternalStatus status : InternalStatus.values())
+ {
+ int ordinal = status.ordinal();
+ DECODE_INFOS[ordinal] = status.asNoInfo;
+ DECODE_INFOS[STATUS_COUNT+ordinal] = Info.createMock(status, Timestamp.NONE, null);
+ DECODE_INFOS[2*STATUS_COUNT+ordinal] = Info.createMock(status, null, CommandsForKey.NO_TXNIDS);
+ DECODE_INFOS[3*STATUS_COUNT+ordinal] = Info.createMock(status, null, null);
+ }
+ }
+
}
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 cd704d3db1..629da34f77 100644
--- a/src/java/org/apache/cassandra/service/accord/serializers/CommitSerializers.java
+++ b/src/java/org/apache/cassandra/service/accord/serializers/CommitSerializers.java
@@ -28,6 +28,7 @@ import accord.primitives.FullRoute;
import accord.primitives.PartialDeps;
import accord.primitives.PartialRoute;
import accord.primitives.PartialTxn;
+import accord.primitives.Seekables;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import accord.primitives.Unseekables;
@@ -60,29 +61,30 @@ public class CommitSerializers
kind.serialize(msg.kind, out, version);
CommandSerializers.ballot.serialize(msg.ballot, out, version);
CommandSerializers.timestamp.serialize(msg.executeAt, out, version);
- CommandSerializers.nullablePartialTxn.serialize(msg.partialTxn, out, version);
- DepsSerializer.partialDeps.serialize(msg.partialDeps, 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);
serializeNullable(msg.route, out, version, KeySerializers.fullRoute);
serializeNullable(msg.readData, out, version, read);
}
protected abstract C deserializeCommit(TxnId txnId, PartialRoute<?> scope, long waitForEpoch, Commit.Kind kind,
Ballot ballot, Timestamp executeAt,
- @Nullable PartialTxn partialTxn, PartialDeps partialDeps,
+ Seekables<?, ?> keys, @Nullable PartialTxn partialTxn, PartialDeps partialDeps,
@Nullable FullRoute<?> fullRoute, @Nullable ReadData read);
@Override
public C deserializeBody(DataInputPlus in, int version, TxnId txnId, PartialRoute<?> scope, long waitForEpoch) throws IOException
{
- return deserializeCommit(txnId, scope, waitForEpoch,
- kind.deserialize(in, version),
- CommandSerializers.ballot.deserialize(in, version),
- CommandSerializers.timestamp.deserialize(in, version),
- CommandSerializers.nullablePartialTxn.deserialize(in, version),
- DepsSerializer.partialDeps.deserialize(in, version),
- deserializeNullable(in, version, KeySerializers.fullRoute),
- deserializeNullable(in, version, read)
- );
+ 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);
+ 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);
}
@Override
@@ -91,8 +93,9 @@ public class CommitSerializers
return kind.serializedSize(msg.kind, version)
+ CommandSerializers.ballot.serializedSize(msg.ballot, version)
+ CommandSerializers.timestamp.serializedSize(msg.executeAt, version)
- + CommandSerializers.nullablePartialTxn.serializedSize(msg.partialTxn, version)
- + DepsSerializer.partialDeps.serializedSize(msg.partialDeps, version)
+ + KeySerializers.seekables.serializedSize(msg.keys, version)
+ + CommandSerializers.nullablePartialTxn.serializedSize(msg.keys, msg.partialTxn, version)
+ + DepsSerializer.partialDeps.serializedSize(msg.keys, msg.partialDeps, version)
+ serializedNullableSize(msg.route, version, KeySerializers.fullRoute)
+ serializedNullableSize(msg.readData, version, read);
}
@@ -101,13 +104,13 @@ public class CommitSerializers
public static final IVersionedSerializer<Commit> request = new CommitSerializer<Commit, ReadData>(ReadData.class, ReadDataSerializers.readData)
{
@Override
- protected Commit deserializeCommit(TxnId txnId, PartialRoute<?> scope, long waitForEpoch, Commit.Kind kind, Ballot ballot, Timestamp executeAt, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute, @Nullable ReadData read)
+ protected Commit deserializeCommit(TxnId txnId, PartialRoute<?> scope, long waitForEpoch, Commit.Kind kind, Ballot ballot, Timestamp executeAt, Seekables<?, ?> keys, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute, @Nullable ReadData read)
{
- return Commit.SerializerSupport.create(txnId, scope, waitForEpoch, kind, ballot, executeAt, partialTxn, partialDeps, fullRoute, read);
+ return Commit.SerializerSupport.create(txnId, scope, waitForEpoch, kind, ballot, executeAt, keys, partialTxn, partialDeps, fullRoute, read);
}
};
- public static final IVersionedSerializer<Commit.Invalidate> invalidate = new IVersionedSerializer<Commit.Invalidate>()
+ public static final IVersionedSerializer<Commit.Invalidate> invalidate = new IVersionedSerializer<>()
{
@Override
public void serialize(Commit.Invalidate invalidate, DataOutputPlus out, int version) throws IOException
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 9498bef0f2..0eba398ff9 100644
--- a/src/java/org/apache/cassandra/service/accord/serializers/DepsSerializer.java
+++ b/src/java/org/apache/cassandra/service/accord/serializers/DepsSerializer.java
@@ -28,6 +28,7 @@ import accord.primitives.PartialDeps;
import accord.primitives.Range;
import accord.primitives.RangeDeps;
import accord.primitives.Ranges;
+import accord.primitives.Seekables;
import accord.primitives.TxnId;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.util.DataInputPlus;
@@ -39,9 +40,10 @@ import static accord.primitives.KeyDeps.SerializerSupport.keysToTxnIds;
import static accord.primitives.KeyDeps.SerializerSupport.keysToTxnIdsCount;
import static accord.primitives.RangeDeps.SerializerSupport.rangesToTxnIds;
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<D extends Deps> implements IVersionedSerializer<D>
+public abstract class DepsSerializer<D extends Deps> extends IVersionedWithKeysSerializer.AbstractWithKeysSerializer implements IVersionedWithKeysSerializer<Seekables<?, ?>, D>
{
public static final DepsSerializer<Deps> deps = new DepsSerializer<Deps>()
{
@@ -69,23 +71,85 @@ public abstract class DepsSerializer<D extends Deps> implements IVersionedSerial
KeySerializers.ranges.serialize(partialDeps.covering, out, version);
}
+ @Override
+ public void serialize(Seekables<?, ?> keys, PartialDeps partialDeps, DataOutputPlus out, int version) throws IOException
+ {
+ super.serialize(keys, partialDeps, out, version);
+ KeySerializers.ranges.serialize(partialDeps.covering, out, version);
+ }
+
@Override
public long serializedSize(PartialDeps partialDeps, int version)
{
return super.serializedSize(partialDeps, version)
+ KeySerializers.ranges.serializedSize(partialDeps.covering, version);
}
+
+ @Override
+ public long serializedSize(Seekables<?, ?> keys, PartialDeps partialDeps, int version)
+ {
+ return super.serializedSize(keys, partialDeps, version)
+ + KeySerializers.ranges.serializedSize(partialDeps.covering, version);
+ }
};
public static final IVersionedSerializer<PartialDeps> nullablePartialDeps = NullableSerializer.wrap(partialDeps);
+ abstract D deserialize(KeyDeps keyDeps, RangeDeps rangeDeps, DataInputPlus in, int version) throws IOException;
+
@Override
public void serialize(D deps, DataOutputPlus out, int version) throws IOException
+ {
+ KeySerializers.keys.serialize(deps.keyDeps.keys(), out, version);
+ serializeWithoutKeys(deps, out, version);
+ }
+
+ @Override
+ public void serialize(Seekables<?, ?> keys, D deps, DataOutputPlus out, int version) throws IOException
+ {
+ if (keys.domain() == Key) serializeSubset(deps.keyDeps.keys(), keys, out);
+ else KeySerializers.keys.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);
+ return deserializeWithoutKeys(keys, in, version);
+ }
+
+ @Override
+ public D deserialize(Seekables<?, ?> 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);
+ return deserializeWithoutKeys(keys, in, version);
+ }
+
+ @Override
+ public long serializedSize(D deps, int version)
+ {
+ long size = KeySerializers.keys.serializedSize(deps.keyDeps.keys(), version);
+ size += serializedSizeWithoutKeys(deps, version);
+ return size;
+ }
+
+ @Override
+ public long serializedSize(Seekables<?, ?> 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);
+ size += serializedSizeWithoutKeys(deps, version);
+ return size;
+ }
+
+ private void serializeWithoutKeys(D deps, DataOutputPlus out, int version) throws IOException
{
KeyDeps keyDeps = deps.keyDeps;
{
- KeySerializers.keys.serialize(keyDeps.keys(), out, version);
-
int txnIdCount = keyDeps.txnIdCount();
out.writeUnsignedVInt32(txnIdCount);
for (int i = 0; i < txnIdCount; i++)
@@ -116,13 +180,10 @@ public abstract class DepsSerializer<D extends Deps> implements IVersionedSerial
}
}
- @Override
- public D deserialize(DataInputPlus in, int version) throws IOException
+ private D deserializeWithoutKeys(Keys keys, DataInputPlus in, int version) throws IOException
{
KeyDeps keyDeps;
{
- Keys keys = KeySerializers.keys.deserialize(in, version);
-
int txnIdCount = in.readUnsignedVInt32();
TxnId[] txnIds = new TxnId[txnIdCount];
for (int i = 0; i < txnIdCount; i++)
@@ -159,17 +220,12 @@ public abstract class DepsSerializer<D extends Deps> implements IVersionedSerial
return deserialize(keyDeps, rangeDeps, in, version);
}
- abstract D deserialize(KeyDeps keyDeps, RangeDeps rangeDeps, DataInputPlus in, int version) throws IOException;
-
- @Override
- public long serializedSize(D deps, int version)
+ private long serializedSizeWithoutKeys(D deps, int version)
{
long size = 0L;
KeyDeps keyDeps = deps.keyDeps;
{
- size += KeySerializers.keys.serializedSize(keyDeps.keys(), version);
-
int txnIdCount = keyDeps.txnIdCount();
size += sizeofUnsignedVInt(txnIdCount);
for (int i = 0; i < txnIdCount; i++)
@@ -201,4 +257,5 @@ public abstract class DepsSerializer<D extends Deps> implements IVersionedSerial
return size;
}
+
}
diff --git a/src/java/org/apache/cassandra/service/accord/serializers/FetchSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/FetchSerializers.java
index 5e6bbfa1dc..370f88b73d 100644
--- a/src/java/org/apache/cassandra/service/accord/serializers/FetchSerializers.java
+++ b/src/java/org/apache/cassandra/service/accord/serializers/FetchSerializers.java
@@ -64,7 +64,6 @@ public class FetchSerializers
KeySerializers.ranges.serialize((Ranges) request.readScope, out, version);
DepsSerializer.partialDeps.serialize(request.partialDeps, out, version);
StreamingTxn.serializer.serialize(request.read, out, version);
- out.writeBoolean(request.collectMaxApplied);
}
@Override
@@ -74,8 +73,7 @@ public class FetchSerializers
CommandSerializers.txnId.deserialize(in, version),
KeySerializers.ranges.deserialize(in, version),
DepsSerializer.partialDeps.deserialize(in, version),
- StreamingTxn.serializer.deserialize(in, version),
- in.readBoolean());
+ StreamingTxn.serializer.deserialize(in, version));
}
@Override
@@ -85,8 +83,7 @@ public class FetchSerializers
+ CommandSerializers.txnId.serializedSize(request.txnId, version)
+ KeySerializers.ranges.serializedSize((Ranges) request.readScope, version)
+ DepsSerializer.partialDeps.serializedSize(request.partialDeps, version)
- + StreamingTxn.serializer.serializedSize(request.read, version)
- + TypeSizes.BYTE_SIZE;
+ + StreamingTxn.serializer.serializedSize(request.read, version);
}
};
diff --git a/src/java/org/apache/cassandra/service/accord/serializers/GetMaxConflictSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/GetMaxConflictSerializers.java
new file mode 100644
index 0000000000..d50e6993b9
--- /dev/null
+++ b/src/java/org/apache/cassandra/service/accord/serializers/GetMaxConflictSerializers.java
@@ -0,0 +1,85 @@
+/*
+ * 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.serializers;
+
+import java.io.IOException;
+
+import accord.messages.GetMaxConflict;
+import accord.messages.GetMaxConflict.GetMaxConflictOk;
+import accord.primitives.PartialRoute;
+import accord.primitives.Seekables;
+import accord.primitives.Timestamp;
+import accord.primitives.TxnId;
+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;
+
+public class GetMaxConflictSerializers
+{
+ public static final IVersionedSerializer<GetMaxConflict> request = new TxnRequestSerializer.WithUnsyncedSerializer<GetMaxConflict>()
+ {
+ @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, PartialRoute<?> scope, long waitForEpoch, long minEpoch, boolean doNotComputeProgressKey) throws IOException
+ {
+ Seekables<?, ?> keys = KeySerializers.seekables.deserialize(in, version);
+ long executionEpoch = in.readUnsignedVInt();
+ return GetMaxConflict.SerializationSupport.create(scope, waitForEpoch, minEpoch, keys, executionEpoch);
+ }
+
+ @Override
+ public long serializedBodySize(GetMaxConflict msg, int version)
+ {
+ return KeySerializers.seekables.serializedSize(msg.keys, version)
+ + TypeSizes.sizeofUnsignedVInt(msg.executionEpoch);
+ }
+ };
+
+ public static final IVersionedSerializer<GetMaxConflictOk> reply = new IVersionedSerializer<GetMaxConflictOk>()
+ {
+ @Override
+ public void serialize(GetMaxConflictOk reply, DataOutputPlus out, int version) throws IOException
+ {
+ CommandSerializers.timestamp.serialize(reply.maxConflict, out, version);
+ out.writeUnsignedVInt(reply.latestEpoch);
+ }
+
+ @Override
+ public GetMaxConflictOk deserialize(DataInputPlus in, int version) throws IOException
+ {
+ Timestamp maxConflict = CommandSerializers.timestamp.deserialize(in, version);
+ long latestEpoch = in.readUnsignedVInt();
+ return new GetMaxConflictOk(maxConflict, latestEpoch);
+ }
+
+ @Override
+ public long serializedSize(GetMaxConflictOk reply, int version)
+ {
+ return CommandSerializers.timestamp.serializedSize(reply.maxConflict, version)
+ + TypeSizes.sizeofUnsignedVInt(reply.latestEpoch);
+ }
+ };
+}
diff --git a/src/java/org/apache/cassandra/service/accord/serializers/IVersionedWithKeysSerializer.java b/src/java/org/apache/cassandra/service/accord/serializers/IVersionedWithKeysSerializer.java
new file mode 100644
index 0000000000..ef4b6d4202
--- /dev/null
+++ b/src/java/org/apache/cassandra/service/accord/serializers/IVersionedWithKeysSerializer.java
@@ -0,0 +1,414 @@
+/*
+ * 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.serializers;
+
+import java.io.IOException;
+
+import accord.api.Key;
+import accord.primitives.AbstractKeys;
+import accord.primitives.Keys;
+import accord.primitives.Range;
+import accord.primitives.Ranges;
+import accord.primitives.RoutableKey;
+import accord.primitives.Routables;
+import accord.primitives.Seekables;
+import net.nicoulaj.compilecommand.annotations.DontInline;
+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 static accord.utils.SortedArrays.Search.FAST;
+
+/**
+ * De/serialize a structure that can refer to a known superset of RoutingKeys/Keys/Ranges...
+ */
+public interface IVersionedWithKeysSerializer<K extends Routables<?>, T> extends IVersionedSerializer<T>
+{
+ /**
+ * Serialize the specified type into the specified DataOutputStream instance.
+ *
+ * @param t type that needs to be serialized
+ * @param out DataOutput into which serialization needs to happen.
+ * @param version protocol version
+ * @throws IOException if serialization fails
+ */
+ void serialize(K keys, T t, DataOutputPlus out, int version) throws IOException;
+
+ /**
+ * Deserialize into the specified DataInputStream instance.
+ * @param in DataInput from which deserialization needs to happen.
+ * @param version protocol version
+ * @return the type that was deserialized
+ * @throws IOException if deserialization fails
+ */
+ T deserialize(K keys, DataInputPlus in, int version) throws IOException;
+
+ /**
+ * Calculate serialized size of object without actually serializing.
+ * @param t object to calculate serialized size
+ * @param version protocol version
+ * @return serialized size of object t
+ */
+ long serializedSize(K keys, T t, int version);
+
+ final class NullableWithKeysSerializer<K extends Routables<?>, T> implements IVersionedWithKeysSerializer<K, T>
+ {
+ final IVersionedWithKeysSerializer<K, T> wrapped;
+ public NullableWithKeysSerializer(IVersionedWithKeysSerializer<K, T> wrapped)
+ {
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public void serialize(T t, DataOutputPlus out, int version) throws IOException
+ {
+ out.writeByte(t == null ? 0 : 1);
+ if (t != null) wrapped.serialize(t, out, version);
+ }
+
+ @Override
+ public T deserialize(DataInputPlus in, int version) throws IOException
+ {
+ if (in.readByte() == 0) return null;
+ return wrapped.deserialize(in, version);
+ }
+
+ @Override
+ public long serializedSize(T t, int version)
+ {
+ return t == null ? 1 : 1 + wrapped.serializedSize(t, version);
+ }
+
+ @Override
+ public void serialize(K keys, T t, DataOutputPlus out, int version) throws IOException
+ {
+ out.writeByte(t == null ? 0 : 1);
+ if (t != null) wrapped.serialize(keys, t, out, version);
+ }
+
+ @Override
+ public T deserialize(K keys, DataInputPlus in, int version) throws IOException
+ {
+ if (in.readByte() == 0) return null;
+ return wrapped.deserialize(keys, in, version);
+ }
+
+ @Override
+ public long serializedSize(K keys, T t, int version)
+ {
+ return t == null ? 1 : 1 + wrapped.serializedSize(keys, t, version);
+ }
+ }
+
+ abstract class AbstractWithKeysSerializer
+ {
+ /**
+ * 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
+ {
+ /**
+ * We weight this towards small sets, and sets where the majority of items are present, since
+ * we expect this to mostly be used for serializing result sets.
+ *
+ * For supersets with fewer than 64 columns, we encode a bitmap of *missing* columns,
+ * which equates to a zero (single byte) when all columns are present, and otherwise
+ * a positive integer that can typically be vint encoded efficiently.
+ *
+ * If we have 64 or more columns, we cannot neatly perform a bitmap encoding, so we just switch
+ * to a vint encoded set of deltas, either adding or subtracting (whichever is most efficient).
+ * We indicate this switch by sending our bitmap with every bit set, i.e. -1L
+ */
+ int serializeCount = serialize.size();
+ int supersetCount = superset.size();
+ if (serializeCount == supersetCount)
+ {
+ out.writeUnsignedVInt(0L);
+ }
+ else if (supersetCount < 64)
+ {
+ switch (serialize.domain())
+ {
+ default: throw new AssertionError("Unhandled domain: " + serialize.domain());
+ case Key:
+ out.writeUnsignedVInt(encodeBitmap((Keys)serialize, (Keys)superset, supersetCount));
+ break;
+ case Range:
+ out.writeUnsignedVInt(encodeBitmap((Ranges)serialize, (Ranges)superset, supersetCount));
+ break;
+ }
+ }
+ else
+ {
+ switch (serialize.domain())
+ {
+ default: throw new AssertionError("Unhandled domain: " + serialize.domain());
+ case Key:
+ serializeLargeSubset((Keys)serialize, serializeCount, (Keys)superset, supersetCount, out);
+ break;
+ case Range:
+ serializeLargeSubset((Ranges)serialize, serializeCount, (Ranges)superset, supersetCount, out);
+ break;
+ }
+ }
+ }
+
+ public long serializedSubsetSize(Seekables<?, ?> serialize, Seekables<?, ?> superset)
+ {
+ int columnCount = serialize.size();
+ int supersetCount = superset.size();
+ if (columnCount == supersetCount)
+ {
+ return TypeSizes.sizeofUnsignedVInt(0);
+ }
+ else if (supersetCount < 64)
+ {
+ switch (serialize.domain())
+ {
+ default: throw new AssertionError("Unhandled domain: " + serialize.domain());
+ case Key:
+ return TypeSizes.sizeofUnsignedVInt(encodeBitmap((Keys)serialize, (Keys)superset, supersetCount));
+ case Range:
+ return TypeSizes.sizeofUnsignedVInt(encodeBitmap((Ranges)serialize, (Ranges)superset, supersetCount));
+ }
+ }
+ else
+ {
+ switch (serialize.domain())
+ {
+ default: throw new AssertionError("Unhandled domain: " + serialize.domain());
+ case Key:
+ return serializeLargeSubsetSize((Keys)serialize, columnCount, (Keys)superset, supersetCount);
+ case Range:
+ return serializeLargeSubsetSize((Ranges)serialize, columnCount, (Ranges)superset, supersetCount);
+ }
+ }
+ }
+
+ public Seekables<?, ?> deserializeSubset(Seekables<?, ?> superset, DataInputPlus in) throws IOException
+ {
+ long encoded = in.readUnsignedVInt();
+ int supersetCount = superset.size();
+ if (encoded == 0L)
+ {
+ return superset;
+ }
+ else if (supersetCount >= 64)
+ {
+ return deserializeLargeSubset(in, superset, supersetCount, (int) encoded);
+ }
+ else
+ {
+ encoded ^= -1L >>> (64 - supersetCount);
+ int deserializeCount = Long.bitCount(encoded);
+ switch (superset.domain())
+ {
+ default: throw new AssertionError("Unhandled domain: " + superset.domain());
+ case Key:
+ {
+ Keys keys = (Keys)superset;
+ Key[] out = new Key[deserializeCount];
+ int count = 0;
+ while (encoded != 0)
+ {
+ long lowestBit = Long.lowestOneBit(encoded);
+ out[count++] = keys.get(Long.numberOfTrailingZeros(lowestBit));
+ encoded ^= lowestBit;
+ }
+ return Keys.ofSortedUnique(out);
+ }
+ case Range:
+ {
+ Ranges ranges = (Ranges)superset;
+ Range[] out = new Range[deserializeCount];
+ int count = 0;
+ while (encoded != 0)
+ {
+ long lowestBit = Long.lowestOneBit(encoded);
+ out[count++] = ranges.get(Long.numberOfTrailingZeros(lowestBit));
+ encoded ^= lowestBit;
+ }
+ return Ranges.ofSortedAndDeoverlapped(out);
+ }
+ }
+ }
+ }
+
+ // encodes a 1 bit for every *missing* column, on the assumption presence is more common,
+ // and because this is consistent with encoding 0 to represent all present
+ private static <K extends RoutableKey> long encodeBitmap(AbstractKeys<K> serialize, AbstractKeys<K> superset, int supersetCount)
+ {
+ // the index we would encounter next if all columns are present
+ long bitmap = superset.foldl(serialize, (k, p1, v, i) -> {
+ return v | (1L << i);
+ }, 0L, 0L, -1L);
+ bitmap ^= -1L >>> (64 - supersetCount);
+ return bitmap;
+ }
+
+ private static long encodeBitmap(Ranges serialize, Ranges superset, int supersetCount)
+ {
+ // the index we would encounter next if all columns are present
+ long bitmap = superset.foldl(serialize, (k, p1, v, i) -> {
+ return v | (1L << i);
+ }, 0L, 0L, -1L);
+ bitmap ^= -1L >>> (64 - supersetCount);
+ return bitmap;
+ }
+
+ @DontInline
+ private <K extends RoutableKey> void serializeLargeSubset(AbstractKeys<K> serialize, int serializeCount, AbstractKeys<K> superset, int supersetCount, DataOutputPlus out) throws IOException
+ {
+ out.writeUnsignedVInt32(supersetCount - serializeCount);
+ int serializeIndex = 0, supersetIndex = 0;
+ while (serializeIndex < serializeCount)
+ {
+ int prevSupersetIndex = supersetIndex;
+ int nextSupersetIndex;
+ do
+ {
+ nextSupersetIndex = superset.findNext(supersetIndex, serialize.get(serializeIndex++), FAST);
+ if (supersetIndex + 1 != nextSupersetIndex)
+ break;
+ supersetIndex++;
+ }
+ while (serializeIndex < serializeCount);
+
+ out.writeUnsignedVInt32(supersetIndex - prevSupersetIndex);
+ out.writeUnsignedVInt32(nextSupersetIndex - supersetIndex);
+ supersetIndex = nextSupersetIndex;
+ }
+ }
+
+ @DontInline
+ private void serializeLargeSubset(Ranges serialize, int serializeCount, Ranges superset, int supersetCount, DataOutputPlus out) throws IOException
+ {
+ out.writeUnsignedVInt32(supersetCount - serializeCount);
+ int serializeIndex = 0, supersetIndex = 0;
+ while (serializeIndex < serializeCount)
+ {
+ int prevSupersetIndex = supersetIndex;
+ int nextSupersetIndex;
+ do
+ {
+ nextSupersetIndex = superset.findNext(supersetIndex, serialize.get(serializeIndex++), FAST);
+ if (supersetIndex + 1 != nextSupersetIndex)
+ break;
+ supersetIndex++;
+ }
+ while (serializeIndex < serializeCount);
+
+ out.writeUnsignedVInt32(supersetIndex - prevSupersetIndex);
+ out.writeUnsignedVInt32(nextSupersetIndex - supersetIndex);
+ supersetIndex = nextSupersetIndex;
+ }
+ }
+
+ @DontInline
+ private Seekables<?, ?> deserializeLargeSubset(DataInputPlus in, Seekables<?, ?> superset, int supersetCount, int delta) throws IOException
+ {
+ int deserializeCount = supersetCount - delta;
+ switch (superset.domain())
+ {
+ default: throw new AssertionError("Unhandled domain: " + superset.domain());
+ case Key:
+ {
+ Keys keys = (Keys)superset;
+ Key[] out = new Key[deserializeCount];
+ int supersetIndex = 0;
+ int count = 0;
+ while (count < deserializeCount)
+ {
+ int takeCount = in.readUnsignedVInt32();
+ while (takeCount-- > 0) out[count++] = keys.get(supersetIndex++);
+ supersetIndex += in.readUnsignedVInt32();
+ }
+ return Keys.ofSortedUnique(out);
+ }
+ case Range:
+ {
+ Ranges ranges = (Ranges)superset;
+ Range[] out = new Range[deserializeCount];
+ int supersetIndex = 0;
+ int count = 0;
+ while (count < deserializeCount)
+ {
+ int takeCount = in.readUnsignedVInt32();
+ while (takeCount-- > 0) out[count++] = ranges.get(supersetIndex++);
+ supersetIndex += in.readUnsignedVInt32();
+ }
+ return Ranges.ofSortedAndDeoverlapped(out);
+ }
+ }
+ }
+
+ @DontInline
+ private <K extends RoutableKey> long serializeLargeSubsetSize(AbstractKeys<K> serialize, int serializeCount, AbstractKeys<K> superset, int supersetCount)
+ {
+ long size = TypeSizes.sizeofUnsignedVInt(supersetCount - serializeCount);
+ int serializeIndex = 0, supersetIndex = 0;
+ while (serializeIndex < serializeCount)
+ {
+ int prevSupersetIndex = supersetIndex;
+ int nextSupersetIndex;
+ do
+ {
+ nextSupersetIndex = superset.findNext(supersetIndex, serialize.get(serializeIndex++), FAST);
+ if (supersetIndex + 1 != nextSupersetIndex)
+ break;
+ supersetIndex++;
+ }
+ while (serializeIndex < serializeCount);
+
+ size += TypeSizes.sizeofUnsignedVInt(supersetIndex - prevSupersetIndex);
+ size += TypeSizes.sizeofUnsignedVInt(nextSupersetIndex - supersetIndex);
+ supersetIndex = nextSupersetIndex;
+ }
+ return size;
+ }
+
+ @DontInline
+ private long serializeLargeSubsetSize(Ranges serialize, int serializeCount, Ranges superset, int supersetCount)
+ {
+ long size = TypeSizes.sizeofUnsignedVInt(supersetCount - serializeCount);
+ int serializeIndex = 0, supersetIndex = 0;
+ while (serializeIndex < serializeCount)
+ {
+ int prevSupersetIndex = supersetIndex;
+ int nextSupersetIndex;
+ do
+ {
+ nextSupersetIndex = superset.findNext(supersetIndex, serialize.get(serializeIndex++), FAST);
+ if (supersetIndex + 1 != nextSupersetIndex)
+ break;
+ supersetIndex++;
+ }
+ while (serializeIndex < serializeCount);
+
+ size += TypeSizes.sizeofUnsignedVInt(supersetIndex - prevSupersetIndex);
+ size += TypeSizes.sizeofUnsignedVInt(nextSupersetIndex - supersetIndex);
+ supersetIndex = nextSupersetIndex;
+ }
+ return size;
+ }
+ }
+
+}
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 8b102ae13d..6d2e86e8bb 100644
--- a/src/java/org/apache/cassandra/service/accord/serializers/KeySerializers.java
+++ b/src/java/org/apache/cassandra/service/accord/serializers/KeySerializers.java
@@ -211,6 +211,8 @@ public class KeySerializers
EnumSet.of(UnseekablesKind.FullKeyRoute, UnseekablesKind.FullRangeRoute)
);
+ public static final IVersionedSerializer<FullRoute<?>> nullableFullRoute = NullableSerializer.wrap(fullRoute);
+
public static final IVersionedSerializer<Unseekables<?>> unseekables = new AbstractRoutablesSerializer<>(
EnumSet.allOf(UnseekablesKind.class)
);
@@ -352,6 +354,8 @@ public class KeySerializers
}
};
+ public static final IVersionedSerializer<Seekables<?, ?>> nullableSeekables = NullableSerializer.wrap(seekables);
+
public static abstract class AbstractKeysSerializer<K extends RoutableKey, KS extends AbstractKeys<K>> implements IVersionedSerializer<KS>
{
final IVersionedSerializer<K> keySerializer;
diff --git a/src/java/org/apache/cassandra/service/accord/serializers/ListenerSerializers.java b/src/java/org/apache/cassandra/service/accord/serializers/ListenerSerializers.java
index f649a3b325..150c5af394 100644
--- a/src/java/org/apache/cassandra/service/accord/serializers/ListenerSerializers.java
+++ b/src/java/org/apache/cassandra/service/accord/serializers/ListenerSerializers.java
@@ -20,32 +20,23 @@ package org.apache.cassandra.service.accord.serializers;
import java.io.IOException;
-import accord.impl.CommandsForKey;
import accord.local.Command;
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.CommandsForRanges;
-import org.apache.cassandra.service.accord.api.PartitionKey;
public class ListenerSerializers
{
public enum Kind
{
- COMMAND, COMMANDS_FOR_KEY, COMMANDS_FOR_RANGE;
+ COMMAND;
private static Kind of(Command.DurableAndIdempotentListener listener)
{
if (listener instanceof Command.ProxyListener)
return COMMAND;
- if (listener instanceof CommandsForKey.Listener)
- return COMMANDS_FOR_KEY;
-
- if (listener instanceof CommandsForRanges.Listener)
- return COMMANDS_FOR_RANGE;
-
throw new IllegalArgumentException("Unsupported listener type: " + listener.getClass().getName());
}
}
@@ -72,48 +63,6 @@ public class ListenerSerializers
}
};
- private static final IVersionedSerializer<CommandsForRanges.Listener> cfrListener = new IVersionedSerializer<CommandsForRanges.Listener>()
- {
- @Override
- public void serialize(CommandsForRanges.Listener listener, DataOutputPlus out, int version) throws IOException
- {
- CommandSerializers.txnId.serialize(listener.txnId, out, version);
- }
-
- @Override
- public CommandsForRanges.Listener deserialize(DataInputPlus in, int version) throws IOException
- {
- return new CommandsForRanges.Listener(CommandSerializers.txnId.deserialize(in, version));
- }
-
- @Override
- public long serializedSize(CommandsForRanges.Listener listener, int version)
- {
- return CommandSerializers.txnId.serializedSize(listener.txnId, version);
- }
- };
-
- private static final IVersionedSerializer<CommandsForKey.Listener> cfkListener = new IVersionedSerializer<CommandsForKey.Listener>()
- {
- @Override
- public void serialize(CommandsForKey.Listener listener, DataOutputPlus out, int version) throws IOException
- {
- PartitionKey.serializer.serialize((PartitionKey) listener.key(), out, version);
- }
-
- @Override
- public CommandsForKey.Listener deserialize(DataInputPlus in, int version) throws IOException
- {
- return CommandsForKey.SerializerSupport.listener(PartitionKey.serializer.deserialize(in, version));
- }
-
- @Override
- public long serializedSize(CommandsForKey.Listener listener, int version)
- {
- return PartitionKey.serializer.serializedSize((PartitionKey) listener.key(), version);
- }
- };
-
public static final IVersionedSerializer<Command.DurableAndIdempotentListener> listener = new IVersionedSerializer<Command.DurableAndIdempotentListener>()
{
@Override
@@ -126,12 +75,6 @@ public class ListenerSerializers
case COMMAND:
commandListener.serialize((Command.ProxyListener) listener, out, version);
break;
- case COMMANDS_FOR_KEY:
- cfkListener.serialize((CommandsForKey.Listener) listener, out, version);
- break;
- case COMMANDS_FOR_RANGE:
- cfrListener.serialize((CommandsForRanges.Listener) listener, out, version);
- break;
default:
throw new IllegalArgumentException();
}
@@ -145,10 +88,6 @@ public class ListenerSerializers
{
case COMMAND:
return commandListener.deserialize(in, version);
- case COMMANDS_FOR_KEY:
- return cfkListener.deserialize(in, version);
- case COMMANDS_FOR_RANGE:
- return cfrListener.deserialize(in, version);
default:
throw new IllegalArgumentException();
}
@@ -164,12 +103,6 @@ public class ListenerSerializers
case COMMAND:
size += commandListener.serializedSize((Command.ProxyListener) listener, version);
break;
- case COMMANDS_FOR_KEY:
- size += cfkListener.serializedSize((CommandsForKey.Listener) listener, version);
- break;
- case COMMANDS_FOR_RANGE:
- size += cfrListener.serializedSize((CommandsForRanges.Listener) listener, version);
- break;
default:
throw new IllegalArgumentException();
}
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 1bb783f1a5..1b48a53554 100644
--- a/src/java/org/apache/cassandra/service/accord/serializers/ReadDataSerializers.java
+++ b/src/java/org/apache/cassandra/service/accord/serializers/ReadDataSerializers.java
@@ -77,15 +77,17 @@ public class ReadDataSerializers
public static class ApplyThenWaitUntilAppliedSerializer implements ReadDataSerializer<ApplyThenWaitUntilApplied>
{
@Override
- public void serialize(ApplyThenWaitUntilApplied applyThenWaitUntilApplied, DataOutputPlus out, int version) throws IOException
+ public void serialize(ApplyThenWaitUntilApplied msg, DataOutputPlus out, int version) throws IOException
{
- CommandSerializers.txnId.serialize(applyThenWaitUntilApplied.txnId, out, version);
- KeySerializers.partialRoute.serialize(applyThenWaitUntilApplied.route, out, version);
- DepsSerializer.partialDeps.serialize(applyThenWaitUntilApplied.deps, out, version);
- KeySerializers.seekables.serialize(applyThenWaitUntilApplied.partialTxnKeys, out, version);
- CommandSerializers.writes.serialize(applyThenWaitUntilApplied.writes, out, version);
- TxnResult.serializer.serialize((TxnResult) applyThenWaitUntilApplied.txnResult, out, version);
- out.writeBoolean(applyThenWaitUntilApplied.notifyAgent);
+ CommandSerializers.txnId.serialize(msg.txnId, out, version);
+ KeySerializers.participants.serialize(msg.readScope, out, version);
+ out.writeUnsignedVInt(msg.executeAtEpoch);
+ KeySerializers.fullRoute.serialize(msg.route, out, version);
+ CommandSerializers.partialTxn.serialize(msg.txn, out, version);
+ 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
@@ -93,24 +95,28 @@ public class ReadDataSerializers
{
return ApplyThenWaitUntilApplied.SerializerSupport.create(
CommandSerializers.txnId.deserialize(in, version),
- KeySerializers.partialRoute.deserialize(in, version),
+ KeySerializers.participants.deserialize(in, version),
+ in.readUnsignedVInt(),
+ KeySerializers.fullRoute.deserialize(in, version),
+ CommandSerializers.partialTxn.deserialize(in, version),
DepsSerializer.partialDeps.deserialize(in, version),
- KeySerializers.seekables.deserialize(in, version),
CommandSerializers.writes.deserialize(in, version),
TxnResult.serializer.deserialize(in, version),
- in.readBoolean());
+ KeySerializers.nullableSeekables.deserialize(in, version));
}
@Override
- public long serializedSize(ApplyThenWaitUntilApplied applyThenWaitUntilApplied, int version)
+ public long serializedSize(ApplyThenWaitUntilApplied msg, int version)
{
- return CommandSerializers.txnId.serializedSize(applyThenWaitUntilApplied.txnId, version)
- + KeySerializers.partialRoute.serializedSize(applyThenWaitUntilApplied.route, version)
- + DepsSerializer.partialDeps.serializedSize(applyThenWaitUntilApplied.deps, version)
- + KeySerializers.seekables.serializedSize(applyThenWaitUntilApplied.partialTxnKeys, version)
- + CommandSerializers.writes.serializedSize(applyThenWaitUntilApplied.writes, version)
- + TxnResult.serializer.serializedSize((TxnData)applyThenWaitUntilApplied.txnResult, version)
- + sizeof(applyThenWaitUntilApplied.notifyAgent);
+ return CommandSerializers.txnId.serializedSize(msg.txnId, version)
+ + KeySerializers.participants.serializedSize(msg.readScope, version)
+ + TypeSizes.sizeofUnsignedVInt(msg.executeAtEpoch)
+ + KeySerializers.fullRoute.serializedSize(msg.route, version)
+ + 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);
}
}
@@ -210,7 +216,7 @@ public class ReadDataSerializers
public static final class ReplySerializer<D extends Data> implements IVersionedSerializer<ReadReply>
{
- // TODO (now): use something other than ordinal
+ // TODO (expected): use something other than ordinal
final CommitOrReadNack[] nacks = CommitOrReadNack.values();
private final IVersionedSerializer<D> dataSerializer;
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 e9cabbbd15..f42ff86870 100644
--- a/src/java/org/apache/cassandra/service/accord/serializers/SetDurableSerializers.java
+++ b/src/java/org/apache/cassandra/service/accord/serializers/SetDurableSerializers.java
@@ -26,7 +26,6 @@ import accord.primitives.Deps;
import accord.primitives.Seekables;
import accord.primitives.SyncPoint;
import accord.primitives.TxnId;
-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;
@@ -59,19 +58,21 @@ 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(CommandStoreSerializers.durableBefore.deserialize(in, version));
+ return new SetGloballyDurable(CommandSerializers.txnId.deserialize(in, version), CommandStoreSerializers.durableBefore.deserialize(in, version));
}
@Override
public long serializedSize(SetGloballyDurable msg, int version)
{
- return CommandStoreSerializers.durableBefore.serializedSize(msg.durableBefore, version);
+ return CommandSerializers.txnId.serializedSize(msg.txnId, version)
+ + CommandStoreSerializers.durableBefore.serializedSize(msg.durableBefore, version);
}
};
@@ -84,7 +85,6 @@ public class SetDurableSerializers
DepsSerializer.deps.serialize(sp.waitFor, out, version);
KeySerializers.seekables.serialize(sp.keysOrRanges, out, version);
KeySerializers.routingKey.serialize(sp.homeKey, out, version);
- out.writeBoolean(sp.finishedAsync);
}
@Override
@@ -94,8 +94,7 @@ public class SetDurableSerializers
Deps waitFor = DepsSerializer.deps.deserialize(in, version);
Seekables<?, ?> keysOrRanges = KeySerializers.seekables.deserialize(in, version);
RoutingKey homeKey = KeySerializers.routingKey.deserialize(in, version);
- boolean finishedAsync = in.readBoolean();
- return SyncPoint.SerializationSupport.construct(syncId, waitFor, keysOrRanges, homeKey, finishedAsync);
+ return SyncPoint.SerializationSupport.construct(syncId, waitFor, keysOrRanges, homeKey);
}
@Override
@@ -104,8 +103,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)
- + TypeSizes.sizeof(sp.finishedAsync);
+ + KeySerializers.routingKey.serializedSize(sp.homeKey, version);
}
};
}
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 dff192a196..ebcab735d8 100644
--- a/src/java/org/apache/cassandra/service/accord/txn/TxnWrite.java
+++ b/src/java/org/apache/cassandra/service/accord/txn/TxnWrite.java
@@ -375,7 +375,7 @@ public class TxnWrite extends AbstractKeySorted<TxnWrite.Update> implements Writ
// 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 = CommandsForKeys.updateLastExecutionTimestamps((AbstractSafeCommandStore<?,?,?,?>) safeStore, (RoutableKey) key, executeAt, true);
+ TimestampsForKey cfk = CommandsForKeys.updateLastExecutionTimestamps((AbstractSafeCommandStore<?,?,?>) safeStore, (RoutableKey) key, 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/utils/ByteBufferUtil.java b/src/java/org/apache/cassandra/utils/ByteBufferUtil.java
index a4a637dc0e..7965decddb 100644
--- a/src/java/org/apache/cassandra/utils/ByteBufferUtil.java
+++ b/src/java/org/apache/cassandra/utils/ByteBufferUtil.java
@@ -43,6 +43,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
+import net.nicoulaj.compilecommand.annotations.DontInline;
import net.nicoulaj.compilecommand.annotations.Inline;
import org.apache.cassandra.db.TypeSizes;
@@ -997,6 +998,104 @@ public class ByteBufferUtil
}
}
+ public static void writeLeastSignificantBytes(long register, int bytes, ByteBuffer out)
+ {
+ writeMostSignificantBytesSlow(register << ((8 - bytes)*8), bytes, out);
+ }
+
+ public static void writeMostSignificantBytes(long register, int bytes, ByteBuffer out)
+ {
+ int position = out.position();
+ int limit = out.limit();
+ if (limit - position < Long.BYTES)
+ {
+ writeMostSignificantBytesSlow(register, bytes, out);
+ }
+ else
+ {
+ out.putLong(position, register);
+ out.position(position + bytes);
+ }
+ }
+
+ @DontInline
+ private static void writeMostSignificantBytesSlow(long register, int bytes, ByteBuffer out)
+ {
+ switch (bytes)
+ {
+ case 0:
+ break;
+ case 1:
+ out.put((byte)(register >>> 56));
+ break;
+ case 2:
+ out.putShort((short)(register >> 48));
+ break;
+ case 3:
+ out.putShort((short)(register >> 48));
+ out.put((byte)(register >> 40));
+ break;
+ case 4:
+ out.putInt((int)(register >> 32));
+ break;
+ case 5:
+ out.putInt((int)(register >> 32));
+ out.put((byte)(register >> 24));
+ break;
+ case 6:
+ out.putInt((int)(register >> 32));
+ out.putShort((short)(register >> 16));
+ break;
+ case 7:
+ out.putInt((int)(register >> 32));
+ out.putShort((short)(register >> 16));
+ out.put((byte)(register >> 8));
+ break;
+ case 8:
+ out.putLong(register);
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ public static long readLeastSignificantBytes(int bytes, ByteBuffer in)
+ {
+ if (bytes == 0)
+ return 0L;
+
+ int position = in.position();
+ int limit = in.limit();
+ if (limit - position < Long.BYTES)
+ {
+ return readLeastSignificantBytesSlow(bytes, in);
+ }
+ else
+ {
+ long result = in.getLong(position);
+ in.position(position + bytes);
+ return result >>> (64 - 8*bytes);
+ }
+ }
+
+ @DontInline
+ private static long readLeastSignificantBytesSlow(int bytes, ByteBuffer out)
+ {
+ switch (bytes)
+ {
+ case 0: return 0;
+ case 1: return out.get() & 0xffL;
+ case 2: return out.getShort() & 0xffffL;
+ case 3: return ((out.getShort() & 0xffffL) << 8) | (out.get() & 0xffL);
+ case 4: return out.getInt() & 0xffffffffL;
+ case 5: return ((out.getInt() & 0xffffffffL) << 8) | (out.get() & 0xffL);
+ case 6: return ((out.getInt() & 0xffffffffL) << 16) | (out.getShort() & 0xffffL);
+ case 7: return ((out.getInt() & 0xffffffffL) << 24) | ((out.getShort() & 0xffffL) << 8) | (out.get() & 0xffL);
+ case 8: return out.getLong();
+ default: throw new IllegalArgumentException();
+ }
+ }
+
public static final IVersionedSerializer<ByteBuffer> byteBufferSerializer = new IVersionedSerializer<ByteBuffer>()
{
@Override
diff --git a/src/java/org/apache/cassandra/utils/vint/VIntCoding.java b/src/java/org/apache/cassandra/utils/vint/VIntCoding.java
index c2bb510049..8b52bb41e4 100644
--- a/src/java/org/apache/cassandra/utils/vint/VIntCoding.java
+++ b/src/java/org/apache/cassandra/utils/vint/VIntCoding.java
@@ -49,8 +49,10 @@ package org.apache.cassandra.utils.vint;
import java.io.DataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import io.netty.util.concurrent.FastThreadLocal;
+import net.nicoulaj.compilecommand.annotations.DontInline;
import net.nicoulaj.compilecommand.annotations.Inline;
import org.apache.cassandra.db.marshal.ValueAccessor;
import org.apache.cassandra.io.util.DataInputPlus;
@@ -109,6 +111,50 @@ public class VIntCoding
return retval;
}
+ @DontInline
+ private static long readUnsignedVIntSlow(ByteBuffer in, byte firstByte)
+ {
+ int size = numberOfExtraBytesToRead(firstByte);
+ long retval = firstByte & firstByteValueMask(size);
+ for (int ii = 0; ii < size; ii++)
+ {
+ byte b = in.get();
+ retval <<= 8;
+ retval |= b & 0xff;
+ }
+
+ return retval;
+ }
+
+ public static long readUnsignedVInt(ByteBuffer in)
+ {
+ byte firstByte = in.get();
+ if (firstByte >= 0)
+ return firstByte;
+
+
+ int position = in.position();
+ int limit = in.limit();
+ if (limit - position < 8)
+ return readUnsignedVIntSlow(in, firstByte);
+
+ int extraBytes = VIntCoding.numberOfExtraBytesToRead(firstByte);
+ int extraBits = extraBytes * 8;
+
+ long retval = in.getLong(position);
+ if (in.order() == ByteOrder.LITTLE_ENDIAN)
+ retval = Long.reverseBytes(retval);
+ in.position(position + extraBytes);
+
+ // truncate the bytes we read in excess of those we needed
+ retval >>>= 64 - extraBits;
+ // remove the non-value bits from the first byte
+ firstByte &= VIntCoding.firstByteValueMask(extraBytes);
+ // shift the first byte up to its correct position
+ retval |= (long) firstByte << extraBits;
+ return retval;
+ }
+
public static void skipUnsignedVInt(DataInputPlus input) throws IOException
{
int firstByte = input.readByte();
@@ -271,6 +317,19 @@ public class VIntCoding
return checkedCast(readUnsignedVInt(input));
}
+ /**
+ * Read up to a 32-bit integer.
+ *
+ * This method assumes the original integer was written using {@link #writeUnsignedVInt32(int, DataOutputPlus)}
+ * or similar that doesn't zigzag encodes the vint.
+ *
+ * @throws VIntOutOfRangeException If the vint doesn't fit into a 32-bit integer
+ */
+ public static int readUnsignedVInt32(ByteBuffer input)
+ {
+ return checkedCast(readUnsignedVInt(input));
+ }
+
// & this with the first byte to give the value part for a given extraBytesToRead encoded in the byte
public static int firstByteValueMask(int extraBytesToRead)
{
diff --git a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationTest.java b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationTest.java
index 1293495274..f6e9e03664 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/accord/AccordMigrationTest.java
@@ -154,7 +154,7 @@ public class AccordMigrationTest extends AccordTestBase
StorageService.instance.setPartitionerUnsafe(partitioner);
ServerTestUtils.prepareServerNoRegister();
minToken = partitioner.getMinimumToken();
- maxToken = partitioner.getMaximumToken();
+ maxToken = partitioner.getMaximumTokenForSplitting();
midToken = partitioner.midpoint(minToken, maxToken);
upperMidToken = partitioner.midpoint(midToken, maxToken);
lowerMidToken = partitioner.midpoint(minToken, midToken);
diff --git a/test/unit/org/apache/cassandra/db/DiskBoundaryManagerTest.java b/test/unit/org/apache/cassandra/db/DiskBoundaryManagerTest.java
index 0b25f19d99..606bdf33de 100644
--- a/test/unit/org/apache/cassandra/db/DiskBoundaryManagerTest.java
+++ b/test/unit/org/apache/cassandra/db/DiskBoundaryManagerTest.java
@@ -186,7 +186,7 @@ public class DiskBoundaryManagerTest extends CQLTester
SSTableReader disk1Boundary = MockSchema.sstable(gen++, (long)sstableFirstDisk1.getTokenValue(), (long)tokens.get(0).getTokenValue(), 0, mock);
SSTableReader disk2Full = MockSchema.sstable(gen++, (long)tokens.get(0).nextValidToken().getTokenValue(), (long)tokens.get(1).getTokenValue(), 0, mock);
- SSTableReader disk3Full = MockSchema.sstable(gen++, (long)tokens.get(1).nextValidToken().getTokenValue(), (long)partitioner.getMaximumToken().getTokenValue(), 0, mock);
+ SSTableReader disk3Full = MockSchema.sstable(gen++, (long)tokens.get(1).nextValidToken().getTokenValue(), (long)partitioner.getMaximumTokenForSplitting().getTokenValue(), 0, mock);
Assert.assertEquals(tableDirs, mock.getDirectoriesForFiles(ImmutableSet.of()));
Assert.assertEquals(Lists.newArrayList(tableDirs.get(0)), mock.getDirectoriesForFiles(ImmutableSet.of(containedDisk1)));
diff --git a/test/unit/org/apache/cassandra/db/compaction/CancelCompactionsTest.java b/test/unit/org/apache/cassandra/db/compaction/CancelCompactionsTest.java
index ba054eac4c..03c3317021 100644
--- a/test/unit/org/apache/cassandra/db/compaction/CancelCompactionsTest.java
+++ b/test/unit/org/apache/cassandra/db/compaction/CancelCompactionsTest.java
@@ -456,7 +456,7 @@ public class CancelCompactionsTest extends CQLTester
try (LifecycleTransaction txn = idx.getTracker().tryModify(idx.getLiveSSTables(), OperationType.COMPACTION))
{
IPartitioner partitioner = getCurrentColumnFamilyStore().getPartitioner();
- getCurrentColumnFamilyStore().forceCompactionForTokenRange(Collections.singleton(new Range<>(partitioner.getMinimumToken(), partitioner.getMaximumToken())));
+ getCurrentColumnFamilyStore().forceCompactionForTokenRange(Collections.singleton(new Range<>(partitioner.getMinimumToken(), partitioner.getMaximumTokenForSplitting())));
}
}
diff --git a/test/unit/org/apache/cassandra/db/compaction/CompactionAccordIteratorsTest.java b/test/unit/org/apache/cassandra/db/compaction/CompactionAccordIteratorsTest.java
index 7765b9c1ad..95c495a350 100644
--- a/test/unit/org/apache/cassandra/db/compaction/CompactionAccordIteratorsTest.java
+++ b/test/unit/org/apache/cassandra/db/compaction/CompactionAccordIteratorsTest.java
@@ -29,7 +29,6 @@ import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -37,8 +36,11 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import accord.api.Key;
import accord.api.Result;
+import accord.impl.CommandsForKey;
import accord.local.CheckedCommands;
+import accord.local.Command;
import accord.local.CommandStore;
import accord.local.DurableBefore;
import accord.local.RedundantBefore;
@@ -85,10 +87,13 @@ import org.apache.cassandra.service.accord.AccordCommandStore;
import org.apache.cassandra.service.accord.AccordKeyspace;
import org.apache.cassandra.service.accord.AccordTestUtils;
import org.apache.cassandra.service.accord.IAccordService;
+import org.apache.cassandra.service.accord.api.PartitionKey;
+import org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import static accord.impl.TimestampsForKey.NO_LAST_EXECUTED_HLC;
+import static accord.local.KeyHistory.COMMANDS;
import static accord.local.PreLoadContext.contextFor;
import static accord.utils.async.AsyncChains.getUninterruptibly;
import static org.apache.cassandra.Util.spinAssertEquals;
@@ -123,8 +128,7 @@ public class CompactionAccordIteratorsTest
static ColumnFamilyStore commands;
static ColumnFamilyStore timestampsForKey;
- static ColumnFamilyStore depsCommandsForKey;
- static ColumnFamilyStore allCommandsForKey;
+ static ColumnFamilyStore commandsForKey;
static TableMetadata table;
static FullRoute<?> route;
Random random;
@@ -144,17 +148,14 @@ public class CompactionAccordIteratorsTest
parse("CREATE TABLE tbl (k int, c int, v int, primary key (k, c))", "ks"));
StorageService.instance.initServer();
- commands = ColumnFamilyStore.getIfExists(SchemaConstants.ACCORD_KEYSPACE_NAME, COMMANDS);
+ commands = ColumnFamilyStore.getIfExists(SchemaConstants.ACCORD_KEYSPACE_NAME, AccordKeyspace.COMMANDS);
commands.disableAutoCompaction();
timestampsForKey = ColumnFamilyStore.getIfExists(SchemaConstants.ACCORD_KEYSPACE_NAME, TIMESTAMPS_FOR_KEY);
timestampsForKey.disableAutoCompaction();
- depsCommandsForKey = ColumnFamilyStore.getIfExists(SchemaConstants.ACCORD_KEYSPACE_NAME, DEPS_COMMANDS_FOR_KEY);
- depsCommandsForKey.disableAutoCompaction();
-
- allCommandsForKey = ColumnFamilyStore.getIfExists(SchemaConstants.ACCORD_KEYSPACE_NAME, ALL_COMMANDS_FOR_KEY);
- allCommandsForKey.disableAutoCompaction();
+ commandsForKey = ColumnFamilyStore.getIfExists(SchemaConstants.ACCORD_KEYSPACE_NAME, COMMANDS_FOR_KEY);
+ commandsForKey.disableAutoCompaction();
table = ColumnFamilyStore.getIfExists("ks", "tbl").metadata();
route = AccordTestUtils.keys(table, 42).toRoute(AccordTestUtils.key(table, 42).toUnseekable());
@@ -209,7 +210,7 @@ public class CompactionAccordIteratorsTest
{
testWithCommandStore((commandStore) -> {
IAccordService mockAccordService = mockAccordService(commandStore, redundantBefore, durableBefore);
- ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(ACCORD_KEYSPACE_NAME, COMMANDS);
+ ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(ACCORD_KEYSPACE_NAME, AccordKeyspace.COMMANDS);
List<Partition> result = compactCFS(mockAccordService, cfs);
expectedResult.accept(result);
}, false);
@@ -249,7 +250,6 @@ public class CompactionAccordIteratorsTest
Partition partition = partitions.get(0);
Row row = partition.getRow(Clustering.EMPTY);
- assertEquals(SECOND_TXN_ID, TimestampsForKeyRows.getMaxTimestamp(row));
assertEquals(TXN_ID, TimestampsForKeyRows.getLastExecutedTimestamp(row));
assertEquals(TXN_ID, TimestampsForKeyRows.getLastWriteTimestamp(row));
@@ -264,27 +264,17 @@ public class CompactionAccordIteratorsTest
return partitions -> {
assertEquals(1, partitions.size());
Partition partition = partitions.get(0);
- assertEquals(2, Iterators.size(partition.unfilteredIterator()));
- UnfilteredRowIterator rows = partition.unfilteredIterator();
- // One row per txn per series
- for (TxnId txnId : TXN_IDS)
- assertEquals(txnId, DepsCommandsForKeysAccessor.getTimestamp((Row)rows.next()));
+ PartitionKey partitionKey = new PartitionKey(partition.metadata().id, partition.partitionKey());
+ CommandsForKey cfk = CommandsForKeysAccessor.getCommandsForKey(partitionKey, ((Row)partition.unfilteredIterator().next()));
+ assertEquals(TXN_IDS.length, cfk.size());
+ for (int i = 0 ; i < TXN_IDS.length ; ++i)
+ assertEquals(TXN_IDS[i], cfk.txnId(i));
};
}
private static Consumer<List<Partition>> expectedAccordTimestampsForKeyEraseOne()
{
- return partitions -> {
- assertEquals(1, partitions.size());
- Partition partition = partitions.get(0);
- Row row = partition.getRow(Clustering.EMPTY);
- // Only expect one column to remain because the second transaction is a read
- assertEquals(1, Iterables.size(row));
- assertEquals(SECOND_TXN_ID, AccordKeyspace.TimestampsForKeyRows.getMaxTimestamp(row));
- assertNull(AccordKeyspace.TimestampsForKeyRows.getLastExecutedTimestamp(row));
- assertNull(AccordKeyspace.TimestampsForKeyRows.getLastWriteTimestamp(row));
- assertEquals(NO_LAST_EXECUTED_HLC, AccordKeyspace.TimestampsForKeyRows.getLastExecutedMicros(row));
- };
+ return partitions -> assertEquals(0, partitions.size());
}
private static Consumer<List<Partition>> expectedAccordCommandsForKeyEraseOne()
@@ -294,7 +284,7 @@ public class CompactionAccordIteratorsTest
Partition partition = partitions.get(0);
assertEquals(1, Iterators.size(partition.unfilteredIterator()));
UnfilteredRowIterator rows = partition.unfilteredIterator();
- assertEquals(TXN_IDS[1], DepsCommandsForKeysAccessor.getTimestamp((Row)rows.next()));
+// assertEquals(TXN_IDS[1], CommandsForKeysAccessor.getTimestamp((Row)rows.next()));
};
}
@@ -322,7 +312,7 @@ public class CompactionAccordIteratorsTest
{
testWithCommandStore((commandStore) -> {
IAccordService mockAccordService = mockAccordService(commandStore, redundantBefore, DurableBefore.EMPTY);
- ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(ACCORD_KEYSPACE_NAME, DEPS_COMMANDS_FOR_KEY);
+ ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(ACCORD_KEYSPACE_NAME, COMMANDS_FOR_KEY);
List<Partition> result = compactCFS(mockAccordService, cfs);
expectedResult.accept(result);
}, true);
@@ -447,8 +437,7 @@ public class CompactionAccordIteratorsTest
});
commands.forceBlockingFlush(FlushReason.UNIT_TESTS);
timestampsForKey.forceBlockingFlush(FlushReason.UNIT_TESTS);
- depsCommandsForKey.forceBlockingFlush(FlushReason.UNIT_TESTS);
- allCommandsForKey.forceBlockingFlush(FlushReason.UNIT_TESTS);
+ commandsForKey.forceBlockingFlush(FlushReason.UNIT_TESTS);
}
private void testWithCommandStore(TestWithCommandStore test, boolean additionalCommand) throws Throwable
@@ -457,62 +446,68 @@ public class CompactionAccordIteratorsTest
clock.set(CLOCK_START);
AccordCommandStore commandStore = AccordTestUtils.createAccordCommandStore(clock::incrementAndGet, "ks", "tbl");
TxnId[] txnIds = additionalCommand ? TXN_IDS : new TxnId[] {TXN_ID};
+ Txn writeTxn = AccordTestUtils.createWriteTxn(42);
+ Txn readTxn = AccordTestUtils.createTxn(42);
+ Seekable key = writeTxn.keys().get(0);
for (TxnId txnId : txnIds)
{
- Txn txn = txnId.kind().isWrite() ? AccordTestUtils.createWriteTxn(42) : AccordTestUtils.createTxn(42);
- Seekable key = txn.keys().get(0);
+ Txn txn = txnId.kind().isWrite() ? writeTxn : readTxn;
PartialDeps partialDeps = Deps.NONE.slice(AccordTestUtils.fullRange(txn));
PartialTxn partialTxn = txn.slice(commandStore.unsafeRangesForEpoch().currentRanges(), true);
PartialRoute<?> partialRoute = route.slice(commandStore.unsafeRangesForEpoch().currentRanges());
- getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys()), safe -> {
+ getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys(), COMMANDS), safe -> {
PreAccept preAccept =
PreAccept.SerializerSupport.create(txnId, partialRoute, txnId.epoch(), txnId.epoch(), false, txnId.epoch(), partialTxn, route);
commandStore.appendToJournal(preAccept);
CheckedCommands.preaccept(safe, txnId, partialTxn, route, null);
}).beginAsResult());
flush(commandStore);
- getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys()), safe -> {
+ getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys(), COMMANDS), safe -> {
Accept accept =
Accept.SerializerSupport.create(txnId, partialRoute, txnId.epoch(), txnId.epoch(), false, Ballot.ZERO, txnId, partialTxn.keys(), partialDeps);
commandStore.appendToJournal(accept);
CheckedCommands.accept(safe, txnId, Ballot.ZERO, partialRoute, partialTxn.keys(), null, txnId, partialDeps);
}).beginAsResult());
flush(commandStore);
- getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys()), safe -> {
+ getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys(), COMMANDS), safe -> {
Commit commit =
- Commit.SerializerSupport.create(txnId, partialRoute, txnId.epoch(), Commit.Kind.StableFastPath, Ballot.ZERO, txnId, partialTxn, partialDeps, route, null);
+ Commit.SerializerSupport.create(txnId, partialRoute, txnId.epoch(), Commit.Kind.StableFastPath, Ballot.ZERO, txnId, partialTxn.keys(), partialTxn, partialDeps, route, null);
commandStore.appendToJournal(commit);
CheckedCommands.commit(safe, SaveStatus.Stable, Ballot.ZERO, txnId, route, null, partialTxn, txnId, partialDeps);
}).beginAsResult());
flush(commandStore);
- getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys()), safe -> {
+ getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys(), COMMANDS), safe -> {
Pair<Writes, Result> result = AccordTestUtils.processTxnResultDirect(safe, txnId, partialTxn, txnId);
Apply apply =
- Apply.SerializationSupport.create(txnId, partialRoute, txnId.epoch(), Apply.Kind.Minimal, partialTxn.keys(), txnId, partialDeps, partialTxn, result.left, result.right);
+ Apply.SerializationSupport.create(txnId, partialRoute, txnId.epoch(), Apply.Kind.Minimal, partialTxn.keys(), txnId, partialDeps, partialTxn, null, result.left, result.right);
commandStore.appendToJournal(apply);
CheckedCommands.apply(safe, txnId, route, null, txnId, partialDeps, partialTxn, result.left, result.right);
}).beginAsResult());
+ getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys(), COMMANDS), safe -> {
+ safe.get(txnId, txnId, route).addListener(new Command.ProxyListener(txnId)); // add a junk listener just to test it in compaction
+ }).beginAsResult());
flush(commandStore);
// 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, () ->
- getUninterruptibly(commandStore.submit(contextFor(txnId, txn.keys()), safe -> safe.get(txnId, route.homeKey()).current().hasBeen(Status.Applied)
+ getUninterruptibly(commandStore.submit(contextFor(txnId, txn.keys(), COMMANDS), safe -> safe.get(txnId, route.homeKey()).current().hasBeen(Status.Applied)
).beginAsResult()));
flush(commandStore);
}
- UntypedResultSet commandsTable = QueryProcessor.executeInternal("SELECT * FROM " + ACCORD_KEYSPACE_NAME + "." + COMMANDS + ";");
+ UntypedResultSet commandsTable = QueryProcessor.executeInternal("SELECT * FROM " + ACCORD_KEYSPACE_NAME + "." + AccordKeyspace.COMMANDS + ";");
logger.info(commandsTable.toStringUnsafe());
assertEquals(txnIds.length, commandsTable.size());
Iterator<UntypedResultSet.Row> commandsTableIterator = commandsTable.iterator();
for (TxnId txnId : txnIds)
assertEquals(txnId, AccordKeyspace.deserializeTimestampOrNull(commandsTableIterator.next().getBytes("txn_id"), TxnId::fromBits));
- UntypedResultSet commandsForKeyTable = QueryProcessor.executeInternal("SELECT * FROM " + ACCORD_KEYSPACE_NAME + "." + DEPS_COMMANDS_FOR_KEY + ";");
+ UntypedResultSet commandsForKeyTable = QueryProcessor.executeInternal("SELECT * FROM " + ACCORD_KEYSPACE_NAME + "." + COMMANDS_FOR_KEY + ";");
logger.info(commandsForKeyTable.toStringUnsafe());
- assertEquals(txnIds.length, commandsForKeyTable.size());
- Iterator<UntypedResultSet.Row> commandsForKeyTableIterator = commandsTable.iterator();
- for (TxnId txnId : txnIds)
- assertEquals(txnId, AccordKeyspace.deserializeTimestampOrNull(commandsForKeyTableIterator.next().getBytes("txn_id"), TxnId::fromBits));
+ assertEquals(1, commandsForKeyTable.size());
+ CommandsForKey cfk = CommandsForKeySerializer.fromBytes((Key)key, commandsForKeyTable.iterator().next().getBytes("data"));
+ assertEquals(txnIds.length, cfk.size());
+ for (int i = 0 ; i < txnIds.length ; ++i)
+ assertEquals(txnIds[i], cfk.txnId(i));
test.test(commandStore);
}
@@ -525,7 +520,7 @@ public class CompactionAccordIteratorsTest
{
List<Partition> outputPartitions = new ArrayList<>();
List<ISSTableScanner> nextInputScanners = new ArrayList<>();
- if (singleCompaction)
+ if (singleCompaction || numScanners == 1)
{
nextInputScanners = ImmutableList.copyOf(scanners);
scanners.clear();
@@ -554,7 +549,7 @@ public class CompactionAccordIteratorsTest
scanners.add(random.nextInt(scanners.size()), new Scanner(cfs.metadata(), outputPartitions.stream().map(Partition::unfilteredIterator).collect(Collectors.toList())));
} while (!scanners.isEmpty());
- verify(mockAccordService, times(singleCompaction ? 1 : numScanners - 1)).getRedundantBeforesAndDurableBefore();
+ verify(mockAccordService, times(singleCompaction || numScanners == 1 ? 1 : numScanners - 1)).getRedundantBeforesAndDurableBefore();
return result;
}
}
diff --git a/test/unit/org/apache/cassandra/db/compaction/CompactionsBytemanTest.java b/test/unit/org/apache/cassandra/db/compaction/CompactionsBytemanTest.java
index d338c8b169..e49847b443 100644
--- a/test/unit/org/apache/cassandra/db/compaction/CompactionsBytemanTest.java
+++ b/test/unit/org/apache/cassandra/db/compaction/CompactionsBytemanTest.java
@@ -179,7 +179,7 @@ public class CompactionsBytemanTest extends CQLTester
{
testStopCompactionRepaired((cfs) -> {
Collection<Range<Token>> ranges = Collections.singleton(new Range<>(cfs.getPartitioner().getMinimumToken(),
- cfs.getPartitioner().getMaximumToken()));
+ cfs.getPartitioner().getMaximumTokenForSplitting()));
CompactionManager.instance.forceCompactionForTokenRange(cfs, ranges);
});
}
diff --git a/test/unit/org/apache/cassandra/db/compaction/UnifiedCompactionStrategyTest.java b/test/unit/org/apache/cassandra/db/compaction/UnifiedCompactionStrategyTest.java
index 7b3d85d0b7..740d88c9ec 100644
--- a/test/unit/org/apache/cassandra/db/compaction/UnifiedCompactionStrategyTest.java
+++ b/test/unit/org/apache/cassandra/db/compaction/UnifiedCompactionStrategyTest.java
@@ -263,7 +263,7 @@ public class UnifiedCompactionStrategyTest
IPartitioner partitioner = cfs.getPartitioner();
DecoratedKey first = new BufferDecoratedKey(partitioner.getMinimumToken(), ByteBuffer.allocate(0));
- DecoratedKey last = new BufferDecoratedKey(partitioner.getMaximumToken(), ByteBuffer.allocate(0));
+ DecoratedKey last = new BufferDecoratedKey(partitioner.getMaximumTokenForSplitting(), ByteBuffer.allocate(0));
List<SSTableReader> sstables = new ArrayList<>();
long dataSetSizeBytes = 0;
@@ -503,7 +503,7 @@ public class UnifiedCompactionStrategyTest
{
List<SSTableReader> mockSSTables = new ArrayList<>();
Token min = partitioner.getMinimumToken();
- Token max = partitioner.getMaximumToken();
+ Token max = partitioner.getMaximumTokenForSplitting();
ByteBuffer bb = ByteBuffer.allocate(0);
sstablesMap.forEach((size, num) -> {
Token first = min.getPartitioner().split(min, max, 0.01);
@@ -911,6 +911,6 @@ public class UnifiedCompactionStrategyTest
private Token boundary(int numSSTables, int i)
{
- return partitioner.split(partitioner.getMinimumToken(), partitioner.getMaximumToken(), i * 1.0 / numSSTables);
+ return partitioner.split(partitioner.getMinimumToken(), partitioner.getMaximumTokenForSplitting(), i * 1.0 / numSSTables);
}
}
diff --git a/test/unit/org/apache/cassandra/dht/LengthPartitioner.java b/test/unit/org/apache/cassandra/dht/LengthPartitioner.java
index e57a714e7b..1fcfa14ee1 100644
--- a/test/unit/org/apache/cassandra/dht/LengthPartitioner.java
+++ b/test/unit/org/apache/cassandra/dht/LengthPartitioner.java
@@ -76,7 +76,7 @@ public class LengthPartitioner extends AccordSplitter implements IPartitioner
}
@Override
- public Token getMaximumToken()
+ public Token getMaximumTokenForSplitting()
{
return null;
}
diff --git a/test/unit/org/apache/cassandra/dht/SplitterTest.java b/test/unit/org/apache/cassandra/dht/SplitterTest.java
index 707d294a80..560bc82413 100644
--- a/test/unit/org/apache/cassandra/dht/SplitterTest.java
+++ b/test/unit/org/apache/cassandra/dht/SplitterTest.java
@@ -168,7 +168,7 @@ public class SplitterTest
for (int i = 0; i < tokens.size(); i++)
{
- Token end = i == tokens.size() - 1 ? partitioner.getMaximumToken() : tokens.get(i);
+ Token end = i == tokens.size() - 1 ? partitioner.getMaximumTokenForSplitting() : tokens.get(i);
splits.add(sumOwnedBetween(localRanges, start, end, splitter, splitIndividualRanges));
start = end;
}
@@ -250,7 +250,7 @@ public class SplitterTest
boolean isRandom = partitioner instanceof RandomPartitioner;
Splitter splitter = getSplitter(partitioner);
BigInteger min = splitter.valueForToken(partitioner.getMinimumToken());
- BigInteger max = splitter.valueForToken(partitioner.getMaximumToken());
+ BigInteger max = splitter.valueForToken(partitioner.getMaximumTokenForSplitting());
BigInteger first = isRandom ? RandomPartitioner.ZERO : min;
BigInteger last = isRandom ? max.subtract(BigInteger.valueOf(1)) : max;
BigInteger midpoint = last.add(first).divide(BigInteger.valueOf(2));
@@ -373,8 +373,8 @@ public class SplitterTest
Splitter splitter = getSplitter(partitioner);
// test full range
- Range<Token> fullRange = new Range<>(partitioner.getMinimumToken(), partitioner.getMaximumToken());
- BigInteger fullRangeSize = splitter.valueForToken(partitioner.getMaximumToken()).subtract(splitter.valueForToken(partitioner.getMinimumToken()));
+ Range<Token> fullRange = new Range<>(partitioner.getMinimumToken(), partitioner.getMaximumTokenForSplitting());
+ BigInteger fullRangeSize = splitter.valueForToken(partitioner.getMaximumTokenForSplitting()).subtract(splitter.valueForToken(partitioner.getMinimumToken()));
assertEquals(fullRangeSize, splitter.tokensInRange(fullRange));
fullRange = new Range<>(splitter.tokenForValue(BigInteger.valueOf(-10)), splitter.tokenForValue(BigInteger.valueOf(-10)));
assertEquals(fullRangeSize, splitter.tokensInRange(fullRange));
@@ -413,13 +413,13 @@ public class SplitterTest
// wrapped range
BigInteger min = splitter.valueForToken(partitioner.getMinimumToken());
- BigInteger max = splitter.valueForToken(partitioner.getMaximumToken());
+ BigInteger max = splitter.valueForToken(partitioner.getMaximumTokenForSplitting());
Range<Token> wrappedRange = new Range<>(splitter.tokenForValue(max.subtract(BigInteger.valueOf(1350))),
splitter.tokenForValue(min.add(BigInteger.valueOf(20394))));
testElapsedTokens(partitioner, wrappedRange, true);
// full range
- Range<Token> fullRange = new Range<>(partitioner.getMinimumToken(), partitioner.getMaximumToken());
+ Range<Token> fullRange = new Range<>(partitioner.getMinimumToken(), partitioner.getMaximumTokenForSplitting());
testElapsedTokens(partitioner, fullRange, false);
}
@@ -490,15 +490,15 @@ public class SplitterTest
testPositionInRange(partitioner, splitter, range);
// Test wrap-around range
- start = splitter.tokenForValue(splitter.valueForToken(partitioner.getMaximumToken()).subtract(BigInteger.valueOf(123456789)));
+ start = splitter.tokenForValue(splitter.valueForToken(partitioner.getMaximumTokenForSplitting()).subtract(BigInteger.valueOf(123456789)));
end = splitter.tokenForValue(splitter.valueForToken(partitioner.getMinimumToken()).add(BigInteger.valueOf(123456789)));
range = new Range<>(start, end);
testPositionInRange(partitioner, splitter, range);
// Test full range
- testPositionInRange(partitioner, splitter, new Range<>(partitioner.getMinimumToken(), partitioner.getMaximumToken()));
+ testPositionInRange(partitioner, splitter, new Range<>(partitioner.getMinimumToken(), partitioner.getMaximumTokenForSplitting()));
testPositionInRange(partitioner, splitter, new Range<>(partitioner.getMinimumToken(), partitioner.getMinimumToken()));
- testPositionInRange(partitioner, splitter, new Range<>(partitioner.getMaximumToken(), partitioner.getMaximumToken()));
+ testPositionInRange(partitioner, splitter, new Range<>(partitioner.getMaximumTokenForSplitting(), partitioner.getMaximumTokenForSplitting()));
testPositionInRange(partitioner, splitter, new Range<>(splitter.tokenForValue(BigInteger.ONE), splitter.tokenForValue(BigInteger.ONE)));
}
@@ -508,7 +508,7 @@ public class SplitterTest
//full range case
if (range.left.equals(range.right))
{
- actualRange = new Range<>(partitioner.getMinimumToken(), partitioner.getMaximumToken());
+ actualRange = new Range<>(partitioner.getMinimumToken(), partitioner.getMaximumTokenForSplitting());
}
assertEquals(0.0, splitter.positionInRange(actualRange.left, range), 0.01);
assertEquals(0.25, splitter.positionInRange(getTokenInPosition(partitioner, actualRange, 0.25), range), 0.01);
@@ -523,7 +523,7 @@ public class SplitterTest
{
if (range.left.equals(range.right))
{
- range = new Range<>(partitioner.getMinimumToken(), partitioner.getMaximumToken());
+ range = new Range<>(partitioner.getMinimumToken(), partitioner.getMaximumTokenForSplitting());
}
Splitter splitter = getSplitter(partitioner);
BigInteger totalTokens = splitter.tokensInRange(range);
@@ -535,7 +535,7 @@ public class SplitterTest
private static Token getWrappedToken(IPartitioner partitioner, BigInteger position)
{
Splitter splitter = getSplitter(partitioner);
- BigInteger maxTokenValue = splitter.valueForToken(partitioner.getMaximumToken());
+ BigInteger maxTokenValue = splitter.valueForToken(partitioner.getMaximumTokenForSplitting());
BigInteger minTokenValue = splitter.valueForToken(partitioner.getMinimumToken());
if (position.compareTo(maxTokenValue) > 0)
{
diff --git a/test/unit/org/apache/cassandra/index/sai/cql/VectorTypeTest.java b/test/unit/org/apache/cassandra/index/sai/cql/VectorTypeTest.java
index 216f46c8fc..7d6f5bd527 100644
--- a/test/unit/org/apache/cassandra/index/sai/cql/VectorTypeTest.java
+++ b/test/unit/org/apache/cassandra/index/sai/cql/VectorTypeTest.java
@@ -551,7 +551,7 @@ public class VectorTypeTest extends VectorTester
private Collection<Integer> keysWithLowerBound(Collection<Integer> keys, int leftKey, boolean leftInclusive)
{
return keysInTokenRange(keys, partitioner.getToken(Int32Type.instance.decompose(leftKey)), leftInclusive,
- partitioner.getMaximumToken().getToken(), true);
+ partitioner.getMaximumTokenForSplitting().getToken(), true);
}
private Collection<Integer> keysWithUpperBound(Collection<Integer> keys, int rightKey, boolean rightInclusive)
diff --git a/test/unit/org/apache/cassandra/index/sai/disk/v1/InvertedIndexSearcherTest.java b/test/unit/org/apache/cassandra/index/sai/disk/v1/InvertedIndexSearcherTest.java
index d69fbfd83d..032ed46c8d 100644
--- a/test/unit/org/apache/cassandra/index/sai/disk/v1/InvertedIndexSearcherTest.java
+++ b/test/unit/org/apache/cassandra/index/sai/disk/v1/InvertedIndexSearcherTest.java
@@ -188,7 +188,7 @@ public class InvertedIndexSearcherTest extends SAIRandomizedTester
0,
Long.MAX_VALUE,
SAITester.TEST_FACTORY.create(DatabaseDescriptor.getPartitioner().getMinimumToken()),
- SAITester.TEST_FACTORY.create(DatabaseDescriptor.getPartitioner().getMaximumToken()),
+ SAITester.TEST_FACTORY.create(DatabaseDescriptor.getPartitioner().getMaximumTokenForSplitting()),
wrap(termsEnum.get(0).left),
wrap(termsEnum.get(terms - 1).left),
indexMetas);
diff --git a/test/unit/org/apache/cassandra/index/sai/disk/v1/SegmentTest.java b/test/unit/org/apache/cassandra/index/sai/disk/v1/SegmentTest.java
index 6a0869fbfd..d3d22e2489 100644
--- a/test/unit/org/apache/cassandra/index/sai/disk/v1/SegmentTest.java
+++ b/test/unit/org/apache/cassandra/index/sai/disk/v1/SegmentTest.java
@@ -50,7 +50,7 @@ public class SegmentTest
DatabaseDescriptor.setPartitionerUnsafe(Murmur3Partitioner.instance);
partitioner = DatabaseDescriptor.getPartitioner();
min = partitioner.getMinimumToken();
- max = partitioner.getMaximumToken();
+ max = partitioner.getMaximumTokenForSplitting();
tokens = IntStream.rangeClosed(0, 10).boxed().map(i -> partitioner.getRandomToken())
.distinct().sorted().collect(Collectors.toList());
}
diff --git a/test/unit/org/apache/cassandra/repair/RepairJobTest.java b/test/unit/org/apache/cassandra/repair/RepairJobTest.java
index 020249ba7e..e59d77d417 100644
--- a/test/unit/org/apache/cassandra/repair/RepairJobTest.java
+++ b/test/unit/org/apache/cassandra/repair/RepairJobTest.java
@@ -106,7 +106,7 @@ public class RepairJobTest
private static final Range<Token> RANGE_3 = range(4, 5);
private static final RepairJobDesc JOB_DESC = new RepairJobDesc(nextTimeUUID(), nextTimeUUID(), KEYSPACE, CF, Collections.emptyList());
private static final List<Range<Token>> FULL_RANGE = Collections.singletonList(new Range<>(MURMUR3_PARTITIONER.getMinimumToken(),
- MURMUR3_PARTITIONER.getMaximumToken()));
+ MURMUR3_PARTITIONER.getMaximumTokenForSplitting()));
private static InetAddressAndPort addr1;
private static InetAddressAndPort addr2;
private static InetAddressAndPort addr3;
diff --git a/test/unit/org/apache/cassandra/repair/ValidationTaskTest.java b/test/unit/org/apache/cassandra/repair/ValidationTaskTest.java
index 5639d4105c..957976bbe6 100644
--- a/test/unit/org/apache/cassandra/repair/ValidationTaskTest.java
+++ b/test/unit/org/apache/cassandra/repair/ValidationTaskTest.java
@@ -67,7 +67,7 @@ public class ValidationTaskTest
IPartitioner partitioner = Murmur3Partitioner.instance;
MerkleTrees trees = new MerkleTrees(partitioner);
- trees.addMerkleTree(128, new Range<>(partitioner.getMinimumToken(), partitioner.getMaximumToken()));
+ trees.addMerkleTree(128, new Range<>(partitioner.getMinimumToken(), partitioner.getMaximumTokenForSplitting()));
task.treesReceived(trees);
assertEquals(1, trees.size());
diff --git a/test/unit/org/apache/cassandra/service/accord/AccordCommandStoreTest.java b/test/unit/org/apache/cassandra/service/accord/AccordCommandStoreTest.java
index c47f61987d..626311fc03 100644
--- a/test/unit/org/apache/cassandra/service/accord/AccordCommandStoreTest.java
+++ b/test/unit/org/apache/cassandra/service/accord/AccordCommandStoreTest.java
@@ -18,12 +18,10 @@
package org.apache.cassandra.service.accord;
-import java.nio.ByteBuffer;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicLong;
-import com.google.common.collect.ImmutableSortedMap;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -33,23 +31,18 @@ import org.slf4j.LoggerFactory;
import accord.api.Key;
import accord.api.Result;
-import accord.impl.CommandTimeseries;
import accord.impl.CommandsForKey;
import accord.impl.CommandsForKeys;
import accord.impl.TimestampsForKey;
import accord.local.Command;
import accord.local.CommonAttributes;
-import accord.local.KeyHistory;
-import accord.local.PreLoadContext;
import accord.local.SaveStatus;
import accord.messages.Apply;
import accord.primitives.Ballot;
-import accord.primitives.Keys;
import accord.primitives.PartialDeps;
import accord.primitives.PartialTxn;
import accord.primitives.Ranges;
import accord.primitives.Route;
-import accord.primitives.RoutableKey;
import accord.primitives.RoutingKeys;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
@@ -65,10 +58,8 @@ import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.StorageService;
-import org.apache.cassandra.service.accord.AccordCachingState.Modified;
import org.apache.cassandra.service.accord.api.PartitionKey;
import org.apache.cassandra.service.accord.serializers.CommandSerializers;
-import org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer;
import org.apache.cassandra.utils.Pair;
import static accord.local.Status.Durability.Majority;
@@ -157,6 +148,7 @@ public class AccordCommandStoreTest
executeAt,
dependencies,
txn,
+ null,
result.left,
CommandSerializers.APPLIED);
commandStore.appendToJournal(apply);
@@ -186,7 +178,6 @@ public class AccordCommandStoreTest
AccordSafeTimestampsForKey tfk = new AccordSafeTimestampsForKey(loaded(key, null));
tfk.initialize();
- tfk.updateMax(maxTimestamp);
CommandsForKeys.updateLastExecutionTimestamps(commandStore, tfk, txnId1, true);
Assert.assertEquals(txnId1.hlc(), AccordSafeTimestampsForKey.timestampMicrosFor(tfk.current(), txnId1, true));
@@ -198,13 +189,10 @@ public class AccordCommandStoreTest
Assert.assertEquals(txnId2.hlc(), tfk.lastExecutedMicros());
AccordSafeCommandsForKey cfk = new AccordSafeCommandsForKey(loaded(key, null));
- cfk.initialize(CommandsForKeySerializer.loader);
+ cfk.initialize();
- AccordSafeCommandsForKeyUpdate ufk = new AccordSafeCommandsForKeyUpdate(loaded(key, null));
- ufk.initialize();
-
- CommandsForKeys.registerCommand(tfk, ufk, command1);
- CommandsForKeys.registerCommand(tfk, ufk, command2);
+ cfk.set(cfk.current().update(null, command1));
+ cfk.set(cfk.current().update(null, command2));
AccordKeyspace.getTimestampsForKeyMutation(commandStore, tfk, commandStore.nextSystemTimestampMicros()).apply();
logger.info("E: {}", tfk);
@@ -232,20 +220,17 @@ public class AccordCommandStoreTest
tfk.initialize();
AccordSafeCommandsForKey cfk = new AccordSafeCommandsForKey(loaded(key, null));
- cfk.initialize(CommandsForKeySerializer.loader);
-
- AccordSafeCommandsForKeyUpdate ufk = new AccordSafeCommandsForKeyUpdate(loaded(key, null));
- ufk.initialize();
+ cfk.initialize();
- CommandsForKeys.registerCommand(tfk, ufk, command1);
- CommandsForKeys.registerCommand(tfk, ufk, command2);
+ cfk.set(cfk.current().update(null, command1));
+ cfk.set(cfk.current().update(null, command2));
- AccordKeyspace.getCommandsForKeyMutation(commandStore.id(), ufk.setUpdates(), commandStore.nextSystemTimestampMicros()).apply();
+ AccordKeyspace.getCommandsForKeyMutation(commandStore.id(), cfk.current(), commandStore.nextSystemTimestampMicros()).apply();
logger.info("E: {}", cfk);
- CommandsForKey actual = AccordKeyspace.loadDepsCommandsForKey(commandStore, key);
+ CommandsForKey actual = AccordKeyspace.loadCommandsForKey(commandStore, key);
logger.info("A: {}", actual);
- Assert.assertEquals(ufk.applyToDeps(cfk.current()), actual);
+ Assert.assertEquals(cfk.current(), actual);
}
private static <K, V extends AccordSafeState<K, ?>> NavigableMap<K, V> toNavigableMap(V safeState)
@@ -254,109 +239,4 @@ public class AccordCommandStoreTest
map.put(safeState.key(), safeState);
return map;
}
-
- @Test
- public void commandsForKeyUpdateTest()
- {
- // check that updates are reflected in CFKs without marking them modified
- AtomicLong clock = new AtomicLong(0);
- AccordCommandStore commandStore = createAccordCommandStore(clock::incrementAndGet, "ks", "tbl");
-
- PartialTxn txn = createPartialTxn(1);
- PartitionKey key = (PartitionKey) getOnlyElement(txn.keys());
-
- TxnId txnId = txnId(1, clock.incrementAndGet(), 1);
-
- AccordSafeCommand safeCommand = commandStore.commandCache().acquireOrInitialize(txnId, t -> preaccepted(txnId, txn, timestamp(1, clock.incrementAndGet(), 1)));
- AccordSafeTimestampsForKey timestamps = commandStore.timestampsForKeyCache().acquireOrInitialize(key, k -> new TimestampsForKey((Key) k));
- AccordSafeCommandsForKey commands = commandStore.depsCommandsForKeyCache().acquireOrInitialize(key, k -> new CommandsForKey((Key) k, CommandsForKeySerializer.loader));
- AccordSafeCommandsForKeyUpdate update = commandStore.updatesForKeyCache().acquireOrInitialize(key, CommandsForKeyUpdate::empty);
-
- Assert.assertEquals(AccordCachingState.Status.LOADED, commandStore.commandCache().getUnsafe(txnId).status());
- Assert.assertEquals(AccordCachingState.Status.LOADED, commandStore.timestampsForKeyCache().getUnsafe(key).status());
- Assert.assertEquals(AccordCachingState.Status.LOADED, commandStore.depsCommandsForKeyCache().getUnsafe(key).status());
- Assert.assertEquals(AccordCachingState.Status.LOADED, commandStore.updatesForKeyCache().getUnsafe(key).status());
-
- AccordSafeCommandStore safeStore = commandStore.beginOperation(PreLoadContext.contextFor(txnId, Keys.of(key), KeyHistory.DEPS),
- toNavigableMap(safeCommand),
- toNavigableMap(timestamps),
- toNavigableMap(commands),
- new TreeMap<>(),
- toNavigableMap(update));
-
- AccordSafeCommandsForKeyUpdate updates = safeStore.getOrCreateCommandsForKeyUpdate(key);
- Assert.assertEquals(AccordCachingState.Status.LOADED, commandStore.updatesForKeyCache().getUnsafe(key).status());
-
- Command initialCommand = safeCommand.current();
- CommandsForKey initialCFK = commands.current();
- CommandsForKeyUpdate initialUpdate = updates.current();
-
- updates.common().commands().add(txnId, initialCommand);
-
- CommandsForKeyUpdate expected = new CommandsForKeyUpdate(key, updates.deps().toImmutable(), updates.all().toImmutable(), updates.common().toImmutable());
- Assert.assertEquals(1, expected.common().commands().numChanges());
- Assert.assertTrue(expected.deps().isEmpty());
- Assert.assertTrue(expected.all().isEmpty());
-
- Assert.assertSame(initialCFK, commands.current());
- Assert.assertSame(initialUpdate, updates.current());
-
- safeStore.postExecute(toNavigableMap(safeCommand),
- toNavigableMap(timestamps),
- toNavigableMap(commands),
- new TreeMap<>(),
- toNavigableMap(updates));
- safeStore.complete();
-
-
- Assert.assertEquals(AccordCachingState.Status.LOADED, commandStore.commandCache().getUnsafe(txnId).status());
- Assert.assertEquals(AccordCachingState.Status.LOADED, commandStore.timestampsForKeyCache().getUnsafe(key).status());
- Assert.assertEquals(AccordCachingState.Status.LOADED, commandStore.depsCommandsForKeyCache().getUnsafe(key).status());
- Assert.assertEquals(AccordCachingState.Status.MODIFIED, commandStore.updatesForKeyCache().getUnsafe(key).status());
-
- CommandsForKey finalCFK = commandStore.depsCommandsForKeyCache().getUnsafe(key).get();
- Assert.assertEquals(txnId, getOnlyElement(finalCFK.commands().commands.keySet()));
-
- Modified<RoutableKey, CommandsForKeyUpdate> loadedUpdate = (Modified<RoutableKey, CommandsForKeyUpdate>) commandStore.updatesForKeyCache().getUnsafe(key).state();
- Assert.assertNull(loadedUpdate.original);
- Assert.assertEquals(expected, loadedUpdate.get());
- }
-
- /**
- * Test that in memory cfk updates are applied to
- */
- @Test
- public void commandsForKeyUpdateOnLoadTest()
- {
- AtomicLong clock = new AtomicLong(0);
- AccordCommandStore commandStore = createAccordCommandStore(clock::incrementAndGet, "ks", "tbl");
-
- PartialTxn txn = createPartialTxn(1);
- PartitionKey key = (PartitionKey) getOnlyElement(txn.keys());
-
- TxnId txnId = txnId(1, clock.incrementAndGet(), 1);
-
- Command command = preaccepted(txnId, txn, timestamp(1, clock.incrementAndGet(), 1));
-
- // make a cached update
- AccordSafeCommandsForKeyUpdate updates = commandStore.updatesForKeyCache().acquireOrInitialize(key, k -> null);
- updates.preExecute();
- updates.common().commands().remove(command.txnId());
- updates.setUpdates(); // apply the updates applied to the safe state to the cached value
- updates.postExecute(); // apply the cached value to the global state
- commandStore.updatesForKeyCache().release(updates);
-
- // make an out of date CFK
- CommandTimeseries.CommandLoader<ByteBuffer> loader = CommandsForKeySerializer.loader;
- CommandsForKey staleCFK = CommandsForKey.SerializerSupport.create(key, loader,
- ImmutableSortedMap.of(command.txnId(), loader.saveForCFK(command)));
-
- Assert.assertEquals(txnId, getOnlyElement(staleCFK.commands().commands.keySet()));
-
- // on loading the cfk into the cache, the in memory update should be applied
- AccordSafeCommandsForKey commands = commandStore.depsCommandsForKeyCache().acquireOrInitialize(key, k -> new CommandsForKey((Key) k, CommandsForKeySerializer.loader));
- commands.preExecute();
-
- Assert.assertEquals(txnId, getOnlyElement(staleCFK.commands().commands.keySet()));
- }
}
diff --git a/test/unit/org/apache/cassandra/service/accord/AccordCommandTest.java b/test/unit/org/apache/cassandra/service/accord/AccordCommandTest.java
index 06a55d24cb..93cdaeb5a4 100644
--- a/test/unit/org/apache/cassandra/service/accord/AccordCommandTest.java
+++ b/test/unit/org/apache/cassandra/service/accord/AccordCommandTest.java
@@ -27,7 +27,6 @@ import org.junit.Test;
import accord.api.Key;
import accord.api.RoutingKey;
import accord.impl.CommandsForKey;
-import accord.impl.TimestampsForKey;
import accord.local.Command;
import accord.local.KeyHistory;
import accord.local.Node;
@@ -118,11 +117,8 @@ public class AccordCommandTest
Assert.assertEquals(Status.PreAccepted, command.status());
Assert.assertTrue(command.partialDeps() == null || command.partialDeps().isEmpty());
- TimestampsForKey tfk = ((AccordSafeCommandStore) instance).timestampsForKey(key(1)).current();
- Assert.assertEquals(txnId, tfk.max());
-
- CommandsForKey cfk = ((AccordSafeCommandStore) instance).depsCommandsForKey(key(1)).current();
- Assert.assertNotNull((cfk.commands()).get(txnId));
+ CommandsForKey cfk = ((AccordSafeCommandStore) instance).commandsForKey(key(1)).current();
+ Assert.assertTrue(cfk.indexOf(txnId) >= 0);
}));
// check accept
@@ -149,26 +145,23 @@ public class AccordCommandTest
Assert.assertEquals(Status.Accepted, command.status());
Assert.assertEquals(deps, command.partialDeps());
- TimestampsForKey tfk = ((AccordSafeCommandStore) instance).timestampsForKey(key(1)).current();
- Assert.assertEquals(executeAt, tfk.max());
-
- CommandsForKey cfk = ((AccordSafeCommandStore) instance).depsCommandsForKey(key(1)).current();
- Assert.assertNotNull((cfk.commands()).get(txnId));
+ CommandsForKey cfk = ((AccordSafeCommandStore) instance).commandsForKey(key(1)).current();
+ Assert.assertTrue(cfk.indexOf(txnId) >= 0);
}));
// check commit
- Commit commit = Commit.SerializerSupport.create(txnId, route, 1, Commit.Kind.StableWithTxnAndDeps, Ballot.ZERO, executeAt, partialTxn, deps, fullRoute, null);
+ Commit commit = Commit.SerializerSupport.create(txnId, route, 1, Commit.Kind.StableWithTxnAndDeps, Ballot.ZERO, executeAt, partialTxn.keys(), partialTxn, deps, fullRoute, null);
commandStore.appendToJournal(commit);
getUninterruptibly(commandStore.execute(commit, commit::apply));
- getUninterruptibly(commandStore.execute(PreLoadContext.contextFor(txnId, Keys.of(key), KeyHistory.DEPS), instance -> {
+ getUninterruptibly(commandStore.execute(PreLoadContext.contextFor(txnId, Keys.of(key), KeyHistory.COMMANDS), instance -> {
Command command = instance.ifInitialised(txnId).current();
Assert.assertEquals(commit.executeAt, command.executeAt());
Assert.assertTrue(command.hasBeen(Status.Committed));
Assert.assertEquals(commit.partialDeps, command.partialDeps());
- CommandsForKey cfk = ((AccordSafeCommandStore) instance).depsCommandsForKey(key(1)).current();
- Assert.assertNotNull((cfk.commands()).get(txnId));
+ CommandsForKey cfk = ((AccordSafeCommandStore) instance).commandsForKey(key(1)).current();
+ Assert.assertTrue(cfk.indexOf(txnId) >= 0);
}));
}
diff --git a/test/unit/org/apache/cassandra/service/accord/AccordKeyspaceTest.java b/test/unit/org/apache/cassandra/service/accord/AccordKeyspaceTest.java
index 0be7d69254..1986971602 100644
--- a/test/unit/org/apache/cassandra/service/accord/AccordKeyspaceTest.java
+++ b/test/unit/org/apache/cassandra/service/accord/AccordKeyspaceTest.java
@@ -88,7 +88,7 @@ public class AccordKeyspaceTest extends CQLTester.InMemory
AccordSafeCommand safeCommand = new AccordSafeCommand(AccordTestUtils.loaded(id, null));
safeCommand.set(committed);
- Commit commit = Commit.SerializerSupport.create(id, route.slice(scope), 1, Commit.Kind.StableFastPath, Ballot.ZERO, id, partialTxn, partialDeps, route, null);
+ Commit commit = Commit.SerializerSupport.create(id, route.slice(scope), 1, Commit.Kind.StableFastPath, Ballot.ZERO, id, partialTxn.keys(), partialTxn, partialDeps, route, null);
store.appendToJournal(commit);
Mutation mutation = AccordKeyspace.getCommandMutation(store, safeCommand, 42);
diff --git a/test/unit/org/apache/cassandra/service/accord/AccordMessageSinkTest.java b/test/unit/org/apache/cassandra/service/accord/AccordMessageSinkTest.java
index cd8d82aaed..47ba369a09 100644
--- a/test/unit/org/apache/cassandra/service/accord/AccordMessageSinkTest.java
+++ b/test/unit/org/apache/cassandra/service/accord/AccordMessageSinkTest.java
@@ -89,7 +89,7 @@ public class AccordMessageSinkTest
Txn txn = Utils.readTxn(Keys.of(IntKey.key(42)));
TxnId id = nextTxnId(epoch, txn);
PartialTxn partialTxn = txn.slice(Ranges.of(IntKey.range(40, 50)), true);
- Request request = new AbstractFetchCoordinator.FetchRequest(epoch, id, partialTxn.covering(), PartialDeps.NONE, partialTxn, true);
+ Request request = new AbstractFetchCoordinator.FetchRequest(epoch, id, partialTxn.covering(), PartialDeps.NONE, partialTxn);
checkRequestReplies(request,
new AbstractFetchCoordinator.FetchResponse(null, null, id),
diff --git a/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java b/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java
index 77911183de..66d0c8e436 100644
--- a/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java
+++ b/test/unit/org/apache/cassandra/service/accord/AccordTestUtils.java
@@ -35,11 +35,9 @@ import com.google.common.collect.Sets;
import org.junit.Assert;
import accord.api.Data;
-import accord.api.Key;
import accord.api.ProgressLog;
import accord.api.Result;
import accord.api.RoutingKey;
-import accord.impl.CommandsForKey;
import accord.impl.InMemoryCommandStore;
import accord.local.Command;
import accord.local.CommandStore;
@@ -87,7 +85,6 @@ import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.accord.api.AccordAgent;
import org.apache.cassandra.service.accord.api.PartitionKey;
-import org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer;
import org.apache.cassandra.service.accord.txn.TxnData;
import org.apache.cassandra.service.accord.txn.TxnRead;
import org.apache.cassandra.utils.Pair;
@@ -152,11 +149,6 @@ public class AccordTestUtils
}
}
- public static CommandsForKey commandsForKey(Key key)
- {
- return new CommandsForKey(key, CommandsForKeySerializer.loader);
- }
-
public static <K, V> AccordCachingState<K, V> loaded(K key, V value, int index)
{
AccordCachingState<K, V> global = new AccordCachingState<>(key, index);
diff --git a/test/unit/org/apache/cassandra/service/accord/AccordTopologyTest.java b/test/unit/org/apache/cassandra/service/accord/AccordTopologyTest.java
index 8ad8bb1ba4..215d589b1e 100644
--- a/test/unit/org/apache/cassandra/service/accord/AccordTopologyTest.java
+++ b/test/unit/org/apache/cassandra/service/accord/AccordTopologyTest.java
@@ -166,9 +166,9 @@ public class AccordTopologyTest
{
List<Range<Token>> ranges = ImmutableList.of(range(partitioner.getMinimumToken(), token(-100)),
range(-100, 100),
- range(token(100), partitioner.getMaximumToken()));
+ range(token(100), partitioner.getMaximumTokenForSplitting()));
Assert.assertEquals(partitioner.getMinimumToken(), ranges.get(0).left);
- Assert.assertEquals(partitioner.getMaximumToken(), ranges.get(2).right);
+ Assert.assertEquals(partitioner.getMaximumTokenForSplitting(), ranges.get(2).right);
ClusterMetadata metadata = configureCluster(ranges, Keyspaces.of(keyspace));
Topology topology = AccordTopology.createAccordTopology(metadata, ks -> true);
@@ -204,7 +204,7 @@ public class AccordTopologyTest
{
List<Range<Token>> ranges = ImmutableList.of(range(partitioner.getMinimumToken(), token(-100)),
range(-100, 100),
- range(token(100), partitioner.getMaximumToken()));
+ range(token(100), partitioner.getMaximumTokenForSplitting()));
ClusterMetadata metadata = configureCluster(ranges, Keyspaces.of(keyspace));
Topology topology = AccordTopology.createAccordTopology(metadata, ks -> true);
Topology expected = new Topology(1,
@@ -235,7 +235,7 @@ public class AccordTopologyTest
{
List<Range<Token>> ranges = ImmutableList.of(range(partitioner.getMinimumToken(), token(-100)),
range(-100, 100),
- range(token(100), partitioner.getMaximumToken()));
+ range(token(100), partitioner.getMaximumTokenForSplitting()));
ClusterMetadata metadata = configureCluster(ranges, Keyspaces.of(keyspace));
Topology topology = AccordTopology.createAccordTopology(metadata, ks -> true);
Topology expected = new Topology(1,
diff --git a/test/unit/org/apache/cassandra/service/accord/api/AccordKeyTest.java b/test/unit/org/apache/cassandra/service/accord/api/AccordKeyTest.java
index 0c74fac768..ba80664c39 100644
--- a/test/unit/org/apache/cassandra/service/accord/api/AccordKeyTest.java
+++ b/test/unit/org/apache/cassandra/service/accord/api/AccordKeyTest.java
@@ -78,7 +78,7 @@ public class AccordKeyTest
PartitionKey pk = new PartitionKey(TABLE1, dk);
TokenKey tk = new TokenKey(TABLE1, dk.getToken());
TokenKey tkLow = new TokenKey(TABLE1, dk.getToken().decreaseSlightly());
- TokenKey tkHigh = new TokenKey(TABLE1, dk.getToken().nextValidToken());
+ TokenKey tkHigh = new TokenKey(TABLE1, dk.getToken().increaseSlightly());
Assert.assertTrue(tk.compareTo(pk) > 0);
Assert.assertTrue(tkLow.compareTo(pk) < 0);
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 569de82392..0f96516566 100644
--- a/test/unit/org/apache/cassandra/service/accord/async/AsyncLoaderTest.java
+++ b/test/unit/org/apache/cassandra/service/accord/async/AsyncLoaderTest.java
@@ -18,9 +18,12 @@
package org.apache.cassandra.service.accord.async;
+import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiFunction;
+import java.util.function.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@@ -29,6 +32,7 @@ import org.junit.BeforeClass;
import org.junit.Test;
import accord.api.Key;
+import accord.impl.CommandsForKey;
import accord.impl.TimestampsForKey;
import accord.local.Command;
import accord.local.KeyHistory;
@@ -47,14 +51,15 @@ import org.apache.cassandra.service.accord.AccordCommandStore;
import org.apache.cassandra.service.accord.AccordKeyspace;
import org.apache.cassandra.service.accord.AccordCachingState;
import org.apache.cassandra.service.accord.AccordSafeCommand;
-import org.apache.cassandra.service.accord.AccordSafeCommandsForKeyUpdate;
+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.CommandsForKeyUpdate;
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 static accord.local.KeyHistory.COMMANDS;
+import static accord.local.KeyHistory.TIMESTAMPS;
import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
import static org.apache.cassandra.cql3.statements.schema.CreateTableStatement.parse;
@@ -108,7 +113,7 @@ public class AsyncLoaderTest
testLoad(executor, safeTimestamps, new TimestampsForKey(key));
timestampsCache.release(safeTimestamps);
- AsyncLoader loader = new AsyncLoader(commandStore, singleton(txnId), Keys.of(key), KeyHistory.NONE);
+ AsyncLoader loader = new AsyncLoader(commandStore, singleton(txnId), Keys.of(key), TIMESTAMPS);
// everything is cached, so the loader should return immediately
commandStore.executeBlocking(() -> {
@@ -148,7 +153,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), KeyHistory.DEPS);
+ AsyncLoader loader = new AsyncLoader(commandStore, singleton(txnId), Keys.of(key), TIMESTAMPS);
AsyncPromise<Void> cbFired = new AsyncPromise<>();
Context context = new Context();
commandStore.executeBlocking(() -> {
@@ -196,7 +201,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), KeyHistory.NONE);
+ AsyncLoader loader = new AsyncLoader(commandStore, singleton(txnId), Keys.of(key), TIMESTAMPS);
AsyncPromise<Void> cbFired = new AsyncPromise<>();
Context context = new Context();
commandStore.executeBlocking(() -> {
@@ -234,17 +239,10 @@ public class AsyncLoaderTest
createAccordCommandStore(clock::incrementAndGet, "ks", "tbl", executor, executor);
commandStore.executor().submit(() -> commandStore.setCapacity(1024)).get();
AccordStateCache.Instance<TxnId, Command, AccordSafeCommand> commandCache = commandStore.commandCache();
- AccordStateCache.Instance<RoutableKey, TimestampsForKey, AccordSafeTimestampsForKey> timestampsCache = commandStore.timestampsForKeyCache();
TxnId txnId = txnId(1, clock.incrementAndGet(), 1);
PartialTxn txn = createPartialTxn(0);
PartitionKey key = (PartitionKey) Iterables.getOnlyElement(txn.keys());
- // acquire / release
- timestampsCache.unsafeSetLoadFunction(k -> new TimestampsForKey((PartitionKey) k));
- AccordSafeTimestampsForKey safeTimestamps = timestampsCache.acquire(key);
- testLoad(executor, safeTimestamps, new TimestampsForKey(key));
- timestampsCache.release(safeTimestamps);
-
commandCache.unsafeSetLoadFunction(id -> { Assert.assertEquals(txnId, id); return notDefined(id, txn); });
AccordSafeCommand safeCommand = commandCache.acquire(txnId);
Assert.assertEquals(AccordCachingState.Status.LOADING, safeCommand.globalStatus());
@@ -260,7 +258,7 @@ public class AsyncLoaderTest
boolean result = loader.load(context, (o, t) -> {
Assert.assertNull(t);
Assert.assertTrue(context.commands.containsKey(txnId));
- Assert.assertTrue(context.timestampsForKey.containsKey(key));
+ Assert.assertFalse(context.timestampsForKey.containsKey(key));
cbFired.setSuccess(null);
});
Assert.assertFalse(result);
@@ -276,7 +274,7 @@ public class AsyncLoaderTest
commandStore.executeBlocking(() -> {
boolean result = loader.load(context, (o, t) -> Assert.fail());
Assert.assertTrue(context.commands.containsKey(txnId));
- Assert.assertTrue(context.timestampsForKey.containsKey(key));
+ Assert.assertFalse(context.timestampsForKey.containsKey(key));
Assert.assertTrue(result);
});
}
@@ -306,7 +304,7 @@ public class AsyncLoaderTest
throw new AssertionError("Unknown txnId: " + txnId);
});
- AsyncLoader loader = new AsyncLoader(commandStore, ImmutableList.of(txnId1, txnId2), Keys.EMPTY, KeyHistory.DEPS);
+ AsyncLoader loader = new AsyncLoader(commandStore, ImmutableList.of(txnId1, txnId2), Keys.EMPTY, KeyHistory.COMMANDS);
boolean result = loader.load(new Context(), (u, t) -> {
Assert.assertFalse(callback.isDone());
@@ -376,19 +374,25 @@ public class AsyncLoaderTest
@Test
public void inProgressCFKSaveTest()
+ {
+ inProgressCFKSaveTest(COMMANDS, AccordCommandStore::commandsForKeyCache, context -> context.commandsForKey, CommandsForKey::new, (cfk, u) -> cfk.update(null, u));
+ }
+
+ @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()));
+ }
+
+ private <T1, T2 extends AccordSafeState<RoutableKey, T1>, C extends AccordStateCache.Instance<RoutableKey, T1, T2>> void inProgressCFKSaveTest(KeyHistory history, Function<AccordCommandStore, C> getter, Function<Context, TreeMap<?, ?>> inContext, Function<Key, T1> initialiser, BiFunction<T1, Command, T1> update)
{
AtomicLong clock = new AtomicLong(0);
ManualExecutor executor = new ManualExecutor();
AccordCommandStore commandStore =
createAccordCommandStore(clock::incrementAndGet, "ks", "tbl", executor, executor);
- AccordStateCache.Instance<RoutableKey, TimestampsForKey, AccordSafeTimestampsForKey> timestampsCache = commandStore.timestampsForKeyCache();
- timestampsCache.unsafeSetLoadFunction(k -> new TimestampsForKey((PartitionKey) k));
- timestampsCache.unsafeSetSaveFunction((before, after) -> () -> { throw new AssertionError("nodes expected to be saved manually"); });
-
- AccordStateCache.Instance<RoutableKey, CommandsForKeyUpdate, AccordSafeCommandsForKeyUpdate> updateCache = commandStore.updatesForKeyCache();
- updateCache.unsafeSetLoadFunction(k -> { throw new AssertionError("updates shouldn't be loaded"); });
- updateCache.unsafeSetSaveFunction((before, after) -> () -> { throw new AssertionError("nodes expected to be saved manually"); });
+ C cache = getter.apply(commandStore);
+ cache.unsafeSetSaveFunction((before, after) -> () -> { throw new AssertionError("nodes expected to be saved manually"); });
TxnId txnId = txnId(1, clock.incrementAndGet(), 1);
PartialTxn txn = createPartialTxn(0);
@@ -396,42 +400,37 @@ public class AsyncLoaderTest
Command preaccepted = preaccepted(txnId, txn, txnId);
// acquire / release
+ T2 safe = cache.acquireOrInitialize(key, k -> initialiser.apply((Key)k));
+ safe.preExecute();
+ safe.set(update.apply(safe.current(), preaccepted));
+ cache.release(safe);
- AccordSafeTimestampsForKey safeTimestamps = timestampsCache.acquireOrInitialize(key, k -> new TimestampsForKey((Key) k));
- timestampsCache.release(safeTimestamps);
- Assert.assertEquals(AccordCachingState.Status.LOADED, timestampsCache.getUnsafe(key).status());
-
- AccordSafeCommandsForKeyUpdate safeUpdate = updateCache.acquireOrInitialize(key, CommandsForKeyUpdate::empty);
- safeUpdate.common().commands().add(txnId, preaccepted);
- safeUpdate.setUpdates();
- updateCache.release(safeUpdate);
-
- Assert.assertEquals(AccordCachingState.Status.MODIFIED, updateCache.getUnsafe(key).status());
- updateCache.getUnsafe(key).save(executor, (before, after) -> () -> {});
- Assert.assertEquals(AccordCachingState.Status.SAVING, updateCache.getUnsafe(key).status());
+ Assert.assertEquals(AccordCachingState.Status.MODIFIED, cache.getUnsafe(key).status());
+ cache.getUnsafe(key).save(executor, (before, after) -> () -> {});
+ 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), KeyHistory.NONE);
+ AsyncLoader loader = new AsyncLoader(commandStore, emptyList(), Keys.of(key), history);
AsyncPromise<Void> cbFired = new AsyncPromise<>();
Context context = new Context();
commandStore.executeBlocking(() -> {
boolean result = loader.load(context, (o, t) -> {
Assert.assertNull(t);
- Assert.assertTrue(context.timestampsForKey.containsKey(key));
+ Assert.assertEquals(context.timestampsForKey.containsKey(key), inContext.apply(context) == context.timestampsForKey);
+ Assert.assertEquals(context.commandsForKey.containsKey(key), inContext.apply(context) == context.commandsForKey);
cbFired.setSuccess(null);
});
Assert.assertFalse(result);
});
- Assert.assertEquals(AccordCachingState.Status.SAVING, updateCache.getUnsafe(key).status());
executor.runOne();
cbFired.awaitUninterruptibly(1, TimeUnit.SECONDS);
- Assert.assertEquals(AccordCachingState.Status.LOADED, updateCache.getUnsafe(key).status());
// then return immediately after the callback has fired
commandStore.executeBlocking(() -> {
boolean result = loader.load(context, (o, t) -> Assert.fail());
- Assert.assertTrue(context.timestampsForKey.containsKey(key));
+ Assert.assertEquals(context.timestampsForKey.containsKey(key), inContext.apply(context) == context.timestampsForKey);
+ Assert.assertEquals(context.commandsForKey.containsKey(key), inContext.apply(context) == context.commandsForKey);
Assert.assertTrue(result);
});
}
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 ca695433f9..f421c993a0 100644
--- a/test/unit/org/apache/cassandra/service/accord/async/AsyncOperationTest.java
+++ b/test/unit/org/apache/cassandra/service/accord/async/AsyncOperationTest.java
@@ -85,6 +85,7 @@ import org.assertj.core.api.Assertions;
import org.awaitility.Awaitility;
import org.mockito.Mockito;
+import static accord.local.KeyHistory.COMMANDS;
import static accord.local.PreLoadContext.contextFor;
import static accord.utils.Property.qt;
import static accord.utils.async.AsyncChains.getUninterruptibly;
@@ -115,8 +116,7 @@ public class AsyncOperationTest
{
QueryProcessor.executeInternal(String.format("TRUNCATE %s.%s", SchemaConstants.ACCORD_KEYSPACE_NAME, AccordKeyspace.COMMANDS));
QueryProcessor.executeInternal(String.format("TRUNCATE %s.%s", SchemaConstants.ACCORD_KEYSPACE_NAME, AccordKeyspace.TIMESTAMPS_FOR_KEY));
- QueryProcessor.executeInternal(String.format("TRUNCATE %s.%s", SchemaConstants.ACCORD_KEYSPACE_NAME, AccordKeyspace.DEPS_COMMANDS_FOR_KEY));
- QueryProcessor.executeInternal(String.format("TRUNCATE %s.%s", SchemaConstants.ACCORD_KEYSPACE_NAME, AccordKeyspace.ALL_COMMANDS_FOR_KEY));
+ QueryProcessor.executeInternal(String.format("TRUNCATE %s.%s", SchemaConstants.ACCORD_KEYSPACE_NAME, AccordKeyspace.COMMANDS_FOR_KEY));
}
/**
@@ -150,12 +150,12 @@ public class AsyncOperationTest
PartitionKey key = (PartitionKey) Iterables.getOnlyElement(txn.keys());
getUninterruptibly(commandStore.execute(contextFor(key), instance -> {
- SafeCommandsForKey cfk = ((AccordSafeCommandStore) instance).maybeDepsCommandsForKey(key);
+ SafeCommandsForKey cfk = ((AccordSafeCommandStore) instance).maybeCommandsForKey(key);
Assert.assertNull(cfk);
}));
long nowInSeconds = FBUtilities.nowInSeconds();
- SinglePartitionReadCommand command = AccordKeyspace.getDepsCommandsForKeyRead(commandStore.id(), key, (int) nowInSeconds);
+ SinglePartitionReadCommand command = AccordKeyspace.getCommandsForKeyRead(commandStore.id(), key, (int) nowInSeconds);
try(ReadExecutionController controller = command.executionController();
FilteredPartitions partitions = FilteredPartitions.filter(command.executeLocally(controller), nowInSeconds))
{
@@ -177,6 +177,7 @@ public class AsyncOperationTest
Commit.Kind.StableWithTxnAndDeps,
Ballot.ZERO,
executeAt,
+ command.partialTxn().keys(),
command.partialTxn(),
command.partialDeps(),
Route.castToFullRoute(command.route()),
@@ -209,14 +210,14 @@ public class AsyncOperationTest
PreAccept preAccept =
PreAccept.SerializerSupport.create(txnId, partialRoute, txnId.epoch(), txnId.epoch(), false, txnId.epoch(), partialTxn, route);
Commit stable =
- Commit.SerializerSupport.create(txnId, partialRoute, txnId.epoch(), Commit.Kind.StableFastPath, Ballot.ZERO, executeAt, partialTxn, deps, route, null);
+ Commit.SerializerSupport.create(txnId, partialRoute, txnId.epoch(), Commit.Kind.StableFastPath, Ballot.ZERO, executeAt, partialTxn.keys(), partialTxn, deps, route, null);
commandStore.appendToJournal(preAccept);
commandStore.appendToJournal(stable);
try
{
- Command command = getUninterruptibly(commandStore.submit(contextFor(txnId, partialTxn.keys()), safe -> {
+ Command command = getUninterruptibly(commandStore.submit(contextFor(txnId, partialTxn.keys(), COMMANDS), safe -> {
CheckedCommands.preaccept(safe, txnId, partialTxn, route, null);
CheckedCommands.commit(safe, SaveStatus.Stable, Ballot.ZERO, txnId, route, null, partialTxn, executeAt, deps);
return safe.ifInitialised(txnId).current();
@@ -258,9 +259,9 @@ public class AsyncOperationTest
Accept accept =
Accept.SerializerSupport.create(txnId, partialRoute, txnId.epoch(), txnId.epoch(), false, Ballot.ZERO, executeAt, partialTxn.keys(), deps);
Commit commit =
- Commit.SerializerSupport.create(txnId, partialRoute, txnId.epoch(), Commit.Kind.Commit, Ballot.ZERO, executeAt, partialTxn, deps, route, null);
+ Commit.SerializerSupport.create(txnId, partialRoute, txnId.epoch(), Commit.Kind.Commit, Ballot.ZERO, executeAt, partialTxn.keys(), partialTxn, deps, route, null);
Commit stable =
- Commit.SerializerSupport.create(txnId, partialRoute, txnId.epoch(), Commit.Kind.StableSlowPath, Ballot.ZERO, executeAt, partialTxn, deps, route, null);
+ Commit.SerializerSupport.create(txnId, partialRoute, txnId.epoch(), Commit.Kind.StableSlowPath, Ballot.ZERO, executeAt, partialTxn.keys(), partialTxn, deps, route, null);
commandStore.appendToJournal(preAccept);
commandStore.appendToJournal(accept);
@@ -269,7 +270,7 @@ public class AsyncOperationTest
try
{
- Command command = getUninterruptibly(commandStore.submit(contextFor(txnId, partialTxn.keys()), safe -> {
+ Command command = getUninterruptibly(commandStore.submit(contextFor(txnId, partialTxn.keys(), COMMANDS), safe -> {
CheckedCommands.preaccept(safe, txnId, partialTxn, route, null);
CheckedCommands.accept(safe, txnId, Ballot.ZERO, partialRoute, partialTxn.keys(), null, executeAt, deps);
CheckedCommands.commit(safe, SaveStatus.Committed, Ballot.ZERO, txnId, route, null, partialTxn, executeAt, deps);
@@ -320,7 +321,7 @@ public class AsyncOperationTest
createStableAndPersist(commandStore, txnId);
- Consumer<SafeCommandStore> consumer = safeStore -> safeStore.ifInitialised(txnId).readyToExecute();
+ Consumer<SafeCommandStore> consumer = safeStore -> safeStore.ifInitialised(txnId).readyToExecute(safeStore);
PreLoadContext ctx = contextFor(txnId);
AsyncOperation<Void> operation = new AsyncOperation.ForConsumer(commandStore, ctx, consumer)
{
@@ -380,7 +381,7 @@ public class AsyncOperationTest
assertNoReferences(commandStore, ids, keys);
- PreLoadContext ctx = contextFor(ids, keys);
+ PreLoadContext ctx = contextFor(null, ids, keys, COMMANDS);
Consumer<SafeCommandStore> consumer = Mockito.mock(Consumer.class);
@@ -406,7 +407,7 @@ public class AsyncOperationTest
// can we recover?
commandStore.commandCache().unsafeSetLoadFunction(txnId -> AccordKeyspace.loadCommand(commandStore, txnId));
- AsyncOperation.ForConsumer o2 = new AsyncOperation.ForConsumer(commandStore, ctx, store -> ids.forEach(id -> store.ifInitialised(id).readyToExecute()));
+ AsyncOperation.ForConsumer o2 = new AsyncOperation.ForConsumer(commandStore, ctx, store -> ids.forEach(id -> store.ifInitialised(id).readyToExecute(store)));
getUninterruptibly(o2);
});
}
@@ -428,7 +429,7 @@ public class AsyncOperationTest
createCommand(commandStore, rs, ids);
assertNoReferences(commandStore, ids, keys);
- PreLoadContext ctx = contextFor(ids, keys);
+ PreLoadContext ctx = contextFor(null, ids, keys, COMMANDS);
Consumer<SafeCommandStore> consumer = Mockito.mock(Consumer.class);
String errorMsg = "txn_ids " + ids;
@@ -482,7 +483,7 @@ public class AsyncOperationTest
try
{
//TODO this is due to bad typing for Instance, it doesn't use ? extends RoutableKey
- assertNoReferences(commandStore.depsCommandsForKeyCache(), (Iterable<RoutableKey>) (Iterable<?>) keys);
+ assertNoReferences(commandStore.commandsForKeyCache(), (Iterable<RoutableKey>) (Iterable<?>) keys);
}
catch (AssertionError e)
{
@@ -524,7 +525,7 @@ public class AsyncOperationTest
{
awaitDone(commandStore.commandCache(), ids);
//TODO this is due to bad typing for Instance, it doesn't use ? extends RoutableKey
- awaitDone(commandStore.depsCommandsForKeyCache(), (Iterable<RoutableKey>) (Iterable<?>) keys);
+ awaitDone(commandStore.commandsForKeyCache(), (Iterable<RoutableKey>) (Iterable<?>) keys);
}
private static <T> void awaitDone(AccordStateCache.Instance<T, ?, ?> cache, Iterable<T> keys)
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 13485f90b3..4be4c11115 100644
--- a/test/unit/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializerTest.java
+++ b/test/unit/org/apache/cassandra/service/accord/serializers/CommandsForKeySerializerTest.java
@@ -19,26 +19,70 @@
package org.apache.cassandra.service.accord.serializers;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.BooleanSupplier;
+import java.util.function.Function;
+import java.util.function.IntSupplier;
+import java.util.function.LongUnaryOperator;
+import java.util.function.Supplier;
+import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
-import accord.impl.CommandTimeseries;
+import accord.api.Key;
+import accord.impl.CommandsForKey;
+import accord.impl.CommandsForKey.InternalStatus;
+import accord.local.Command;
+import accord.local.CommonAttributes;
+import accord.local.CommonAttributes.Mutable;
+import accord.local.Listeners;
+import accord.local.Node;
+import accord.local.SaveStatus;
+import accord.local.Status;
+import accord.primitives.Ballot;
+import accord.primitives.KeyDeps;
+import accord.primitives.PartialDeps;
+import accord.primitives.PartialTxn;
+import accord.primitives.RangeDeps;
+import accord.primitives.Routable;
+import accord.primitives.Timestamp;
+import accord.primitives.Txn;
import accord.primitives.TxnId;
+import accord.primitives.Writes;
import accord.utils.AccordGens;
import accord.utils.Gens;
+import accord.utils.RandomSource;
+import accord.utils.SortedArrays;
+import org.agrona.collections.Int2ObjectHashMap;
import org.apache.cassandra.SchemaLoader;
-import org.apache.cassandra.io.util.DataInputBuffer;
-import org.apache.cassandra.io.util.DataOutputBuffer;
-import org.apache.cassandra.net.MessagingService;
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.dht.Murmur3Partitioner;
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.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.utils.Property.qt;
+import static accord.utils.SortedArrays.Search.FAST;
import static org.apache.cassandra.cql3.statements.schema.CreateTableStatement.parse;
-import static org.assertj.core.api.Assertions.assertThat;
+import static org.apache.cassandra.service.accord.AccordTestUtils.createPartialTxn;
public class CommandsForKeySerializerTest
{
@@ -52,45 +96,396 @@ public class CommandsForKeySerializerTest
StorageService.instance.initServer();
}
- @Test
- public void serdeDeps()
+ static class Cmd
{
- DataOutputBuffer buffer = new DataOutputBuffer();
- int version = MessagingService.Version.VERSION_40.value;
- qt().forAll(Gens.lists(AccordGens.txnIds()).ofSizeBetween(0, 10)).check(ids -> {
- buffer.clear();
+ final TxnId txnId;
+ final SaveStatus saveStatus;
+ final PartialTxn txn;
+ final Timestamp executeAt;
+ final List<TxnId> deps = new ArrayList<>();
+ final List<TxnId> missing = new ArrayList<>();
+ boolean invisible;
+
+ Cmd(TxnId txnId, PartialTxn txn, SaveStatus saveStatus, Timestamp executeAt)
+ {
+ this.txnId = txnId;
+ this.saveStatus = saveStatus;
+ this.txn = txn;
+ this.executeAt = executeAt;
+ }
- long expectedSize = CommandsForKeySerializer.depsIdSerializer.serializedSize(ids, version);
+ CommonAttributes attributes()
+ {
+ Mutable mutable = new Mutable(txnId);
+ if (saveStatus.known.isDefinitionKnown())
+ mutable.partialTxn(txn);
- CommandsForKeySerializer.depsIdSerializer.serialize(ids, buffer, version);
- assertThat(buffer.position()).isEqualTo(expectedSize);
- try (DataInputBuffer in = new DataInputBuffer(buffer.unsafeGetBufferAndFlip(), false))
+ mutable.route(txn.keys().toRoute(txn.keys().get(0).someIntersectingRoutingKey(null)));
+ mutable.durability(NotDurable);
+ if (saveStatus.known.deps.hasProposedOrDecidedDeps())
{
- List<TxnId> read = CommandsForKeySerializer.depsIdSerializer.deserialize(in, version);
- assertThat(read).isEqualTo(ids);
+ try (KeyDeps.Builder builder = KeyDeps.builder();)
+ {
+ for (TxnId id : deps)
+ builder.add((Key)txn.keys().get(0), id);
+ mutable.partialDeps(new PartialDeps(AccordTestUtils.fullRange(txn), builder.build(), RangeDeps.NONE));
+ }
}
- });
+
+ return mutable;
+ }
+
+ Command toCommand()
+ {
+ switch (saveStatus)
+ {
+ default: throw new AssertionError("Unhandled saveStatus: " + saveStatus);
+ case Uninitialised:
+ case NotDefined:
+ return Command.SerializerSupport.notDefined(attributes(), Ballot.ZERO);
+ case PreAccepted:
+ return Command.SerializerSupport.preaccepted(attributes(), executeAt, Ballot.ZERO);
+ case Accepted:
+ case AcceptedInvalidate:
+ case AcceptedWithDefinition:
+ case AcceptedInvalidateWithDefinition:
+ case PreCommittedWithDefinition:
+ case PreCommittedWithDefinitionAndAcceptedDeps:
+ case PreCommittedWithAcceptedDeps:
+ case PreCommitted:
+ return Command.SerializerSupport.accepted(attributes(), saveStatus, executeAt, Ballot.ZERO, Ballot.ZERO);
+
+ case Committed:
+ return Command.SerializerSupport.committed(attributes(), saveStatus, executeAt, Ballot.ZERO, Ballot.ZERO, null);
+
+ case Stable:
+ case ReadyToExecute:
+ return Command.SerializerSupport.committed(attributes(), saveStatus, executeAt, Ballot.ZERO, Ballot.ZERO, Command.WaitingOn.EMPTY);
+
+ case PreApplied:
+ case Applying:
+ case Applied:
+ return Command.SerializerSupport.executed(attributes(), saveStatus, executeAt, Ballot.ZERO, Ballot.ZERO, Command.WaitingOn.EMPTY, new Writes(txnId, executeAt, txn.keys(), new TxnWrite(Collections.emptyList(), true)), new TxnData());
+
+ case TruncatedApplyWithDeps:
+ case TruncatedApply:
+ if (txnId.kind().awaitsOnlyDeps()) return Command.SerializerSupport.truncatedApply(attributes(), saveStatus, executeAt, null, null, txnId);
+ else return Command.SerializerSupport.truncatedApply(attributes(), saveStatus, executeAt, null, null);
+
+ case TruncatedApplyWithOutcome:
+ if (txnId.kind().awaitsOnlyDeps()) return Command.SerializerSupport.truncatedApply(attributes(), saveStatus, executeAt, new Writes(txnId, executeAt, txn.keys(), new TxnWrite(Collections.emptyList(), true)), new TxnData(), txnId);
+ else return Command.SerializerSupport.truncatedApply(attributes(), saveStatus, executeAt, new Writes(txnId, executeAt, txn.keys(), new TxnWrite(Collections.emptyList(), true)), new TxnData());
+
+ case Erased:
+ case ErasedOrInvalidated:
+ case Invalidated:
+ return Command.SerializerSupport.invalidated(txnId, Listeners.Immutable.EMPTY);
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Cmd{" +
+ "txnId=" + txnId +
+ ", saveStatus=" + saveStatus +
+ ", txn=" + txn +
+ ", executeAt=" + executeAt +
+ ", deps=" + deps +
+ ", missing=" + missing +
+ ", invisible=" + invisible +
+ '}';
+ }
+ }
+
+ static class ObjectGraph
+ {
+ final Cmd[] cmds;
+ ObjectGraph(Cmd[] cmds)
+ {
+ this.cmds = cmds;
+ }
+
+ List<Command> toCommands()
+ {
+ List<Command> commands = new ArrayList<>(cmds.length);
+ for (int i = 0 ; i < cmds.length ; ++i)
+ commands.add(cmds[i].toCommand());
+ return commands;
+ }
+ }
+
+ private static ObjectGraph generateObjectGraph(int txnIdCount, Supplier<TxnId> txnIdSupplier, Supplier<SaveStatus> saveStatusSupplier, Function<TxnId, PartialTxn> txnSupplier, Function<TxnId, Timestamp> timestampSupplier, IntSupplier missingCountSupplier, RandomSource source)
+ {
+ Cmd[] cmds = new Cmd[txnIdCount];
+ for (int i = 0 ; i < txnIdCount ; ++i)
+ {
+ TxnId txnId = txnIdSupplier.get();
+ SaveStatus saveStatus = saveStatusSupplier.get();
+ Timestamp executeAt = txnId;
+ if (saveStatus.known.executeAt != ExecuteAtErased && saveStatus.known.executeAt != ExecuteAtUnknown)
+ executeAt = timestampSupplier.apply(txnId);
+
+ cmds[i] = new Cmd(txnId, txnSupplier.apply(txnId), saveStatus, executeAt);
+ }
+ Arrays.sort(cmds, Comparator.comparing(o -> o.txnId));
+ for (int i = 0 ; i < txnIdCount ; ++i)
+ {
+ if (!cmds[i].saveStatus.known.deps.hasProposedOrDecidedDeps())
+ continue;
+
+ Timestamp knownBefore = cmds[i].saveStatus.known.deps.hasCommittedOrDecidedDeps() ? cmds[i].executeAt : cmds[i].txnId;
+ int limit = SortedArrays.binarySearch(cmds, 0, cmds.length, knownBefore, (a, b) -> a.compareTo(b.txnId), FAST);
+ if (limit < 0) limit = -1 - limit;
+
+ List<TxnId> deps = cmds[i].deps;
+ List<TxnId> missing = cmds[i].missing;
+ for (int j = 0 ; j < limit ; ++j)
+ if (i != j) deps.add(cmds[j].txnId);
+
+ int missingCount = Math.min(limit - (limit > i ? 1 : 0), missingCountSupplier.getAsInt());
+ while (missingCount > 0)
+ {
+ int remove = source.nextInt(deps.size());
+ int cmdIndex = SortedArrays.binarySearch(cmds, 0, cmds.length, deps.get(remove), (a, b) -> a.compareTo(b.txnId), FAST);
+ if (!cmds[cmdIndex].saveStatus.hasBeen(Status.Committed))
+ missing.add(deps.get(remove));
+ deps.set(remove, deps.get(deps.size() - 1));
+ deps.remove(deps.size() - 1);
+ --missingCount;
+ }
+ deps.sort(TxnId::compareTo);
+ missing.sort(TxnId::compareTo);
+ }
+
+ outer: for (int i = 0 ; i < cmds.length ; ++i)
+ {
+ if (null != InternalStatus.from(cmds[i].saveStatus))
+ continue;
+
+ for (int j = 0 ; j < i ; ++j)
+ {
+ InternalStatus status = InternalStatus.from(cmds[j].saveStatus);
+ if (status == null || !status.hasInfo) continue;
+ if (status.depsKnownBefore(cmds[j].txnId, cmds[j].executeAt).compareTo(cmds[i].txnId) > 0 && Collections.binarySearch(cmds[j].missing, cmds[i].txnId) < 0)
+ continue outer;
+ }
+ for (int j = i + 1 ; j < cmds.length ; ++j)
+ {
+ InternalStatus status = InternalStatus.from(cmds[j].saveStatus);
+ if (status == null || !status.hasInfo) continue;
+ if (Collections.binarySearch(cmds[j].missing, cmds[i].txnId) < 0)
+ continue outer;
+ }
+ cmds[i].invisible = true;
+ for (int j = 0 ; j < i ; ++j)
+ {
+ if (cmds[j].executeAt.compareTo(cmds[i].txnId) > 0)
+ {
+ int remove = Collections.binarySearch(cmds[j].missing, cmds[i].txnId);
+ if (remove >= 0) cmds[j].missing.remove(remove);
+ }
+ }
+ for (int j = i + 1 ; j < cmds.length ; ++j)
+ {
+ int remove = Collections.binarySearch(cmds[j].missing, cmds[i].txnId);
+ if (remove >= 0) cmds[j].missing.remove(remove);
+ }
+ }
+ return new ObjectGraph(cmds);
+ }
+
+ private static Function<Timestamp, TxnId> txnIdSupplier(LongUnaryOperator epochSupplier, LongUnaryOperator hlcSupplier, Supplier<Txn.Kind> kindSupplier, Supplier<Node.Id> idSupplier)
+ {
+ return min -> new TxnId(epochSupplier.applyAsLong(min == null ? 1 : min.epoch()), hlcSupplier.applyAsLong(min == null ? 1 : min.hlc() + 1), kindSupplier.get(), Routable.Domain.Key, idSupplier.get());
+ }
+
+ private static Function<Timestamp, Timestamp> timestampSupplier(LongUnaryOperator epochSupplier, LongUnaryOperator hlcSupplier, IntSupplier flagSupplier, Supplier<Node.Id> idSupplier)
+ {
+ return min -> Timestamp.fromValues(epochSupplier.applyAsLong(min == null ? 1 : min.epoch()), hlcSupplier.applyAsLong(min == null ? 1 : min.hlc() + 1), flagSupplier.getAsInt(), idSupplier.get());
+ }
+
+ private static <T extends Timestamp> Function<Timestamp, T> timestampSupplier(Set<Timestamp> unique, Function<Timestamp, T> supplier)
+ {
+ return min -> {
+ T candidate = supplier.apply(min);
+ while (!unique.add(candidate))
+ {
+ T next = supplier.apply(min);
+ if (next.equals(candidate)) min = candidate;
+ else candidate = next;
+ }
+ return candidate;
+ };
}
@Test
public void serde()
{
- CommandTimeseries.CommandLoader<ByteBuffer> loader = CommandsForKeySerializer.loader;
- qt().forAll(AccordGenerators.commands()).check(cmd -> {
- ByteBuffer bb = loader.saveForCFK(cmd);
- int size = bb.remaining();
+// testOne(1821931462020409370L);
+ Random random = new Random();
+ for (int i = 0 ; i < 10000 ; ++i)
+ {
+ long seed = random.nextLong();
+ testOne(seed);
+ }
+ }
- assertThat(loader.txnId(bb)).isEqualTo(cmd.txnId());
- assertThat(bb.remaining()).describedAs("ByteBuffer was mutated").isEqualTo(size);
+ private static void testOne(long seed)
+ {
+ try
+ {
+ System.out.println(seed);
+ RandomSource source = RandomSource.wrap(new Random(seed));
- assertThat(loader.executeAt(bb)).isEqualTo(cmd.executeAt());
- assertThat(bb.remaining()).describedAs("ByteBuffer was mutated").isEqualTo(size);
+ // TODO (required): produce broader variety of distributions, including executeAt with lower HLC but higher epoch
+ final LongUnaryOperator epochSupplier; {
+ long maxEpoch = source.nextLong(1, 10);
+ epochSupplier = min -> min >= maxEpoch ? min : maxEpoch == 1 ? 1 : source.nextLong(min, maxEpoch);
+ }
+ final LongUnaryOperator hlcSupplier; {
+ long maxHlc = source.nextLong(10, 1000000);
+ hlcSupplier = min -> min >= maxHlc ? min : source.nextLong(min, maxHlc);
+ }
+ final Supplier<Node.Id> idSupplier; {
+ int maxId = source.nextInt(1, 10);
+ Int2ObjectHashMap<Node.Id> lookup = new Int2ObjectHashMap<>();
+ idSupplier = () -> lookup.computeIfAbsent(maxId == 1 ? 1 : source.nextInt(1, maxId), Node.Id::new);
+ }
+ final IntSupplier flagSupplier = () -> 0;
+ final Supplier<Txn.Kind> kindSupplier = () -> {
+ float v = source.nextFloat();
+ if (v < 0.5) return Txn.Kind.Read;
+ if (v < 0.95) return Txn.Kind.Write;
+ if (v < 0.99) return Txn.Kind.ExclusiveSyncPoint;
+ return Txn.Kind.EphemeralRead; // not actually a valid value for CFK
+ };
+
+ boolean permitMissing = source.decide(0.75f);
+ final IntSupplier missingCountSupplier; {
+ if (!permitMissing)
+ {
+ missingCountSupplier = () -> 0;
+ }
+ else
+ {
+ float zeroChance = source.nextFloat();
+ int maxMissing = source.nextInt(1, 10);
+ missingCountSupplier = () -> {
+ float v = source.nextFloat();
+ if (v < zeroChance) return 0;
+ return source.nextInt(0, maxMissing);
+ };
+ }
+ }
- assertThat(loader.saveStatus(bb)).isEqualTo(cmd.saveStatus());
- assertThat(bb.remaining()).describedAs("ByteBuffer was mutated").isEqualTo(size);
+ Choices<SaveStatus> saveStatusChoices = Choices.uniform(SaveStatus.values());
+ Supplier<SaveStatus> saveStatusSupplier = () -> {
+ SaveStatus result = saveStatusChoices.choose(source);
+ while (result == SaveStatus.TruncatedApplyWithDeps) // not a real save status
+ result = saveStatusChoices.choose(source);
+ return result;
+ };
- assertThat(loader.depsIds(bb)).isEqualTo(cmd.partialDeps() == null ? null : cmd.partialDeps().txnIds());
- assertThat(bb.remaining()).describedAs("ByteBuffer was mutated").isEqualTo(size);
+ Set<Timestamp> uniqueTs = new TreeSet<>();
+ final Function<Timestamp, TxnId> txnIdSupplier = timestampSupplier(uniqueTs, txnIdSupplier(epochSupplier, hlcSupplier, kindSupplier, idSupplier));
+ boolean permitExecuteAt = source.decide(0.75f);
+ final Function<TxnId, Timestamp> executeAtSupplier;
+ {
+ if (!permitExecuteAt)
+ {
+ executeAtSupplier = id -> id;
+ }
+ else
+ {
+ Function<Timestamp, Timestamp> rawTimestampSupplier = timestampSupplier(uniqueTs, timestampSupplier(epochSupplier, hlcSupplier, flagSupplier, idSupplier));
+ float useTxnIdChance = source.nextFloat();
+ BooleanSupplier useTxnId = () -> source.decide(useTxnIdChance);
+ executeAtSupplier = txnId -> useTxnId.getAsBoolean() ? txnId : rawTimestampSupplier.apply(txnId);
+ }
+ }
+
+ PartialTxn txn = createPartialTxn(0);
+ Key key = (Key) txn.keys().get(0);
+ ObjectGraph graph = generateObjectGraph(source.nextInt(0, 100), () -> txnIdSupplier.apply(null), saveStatusSupplier, ignore -> txn, executeAtSupplier, missingCountSupplier, source);
+ List<Command> commands = graph.toCommands();
+ CommandsForKey cfk = new CommandsForKey(key);
+ while (commands.size() > 0)
+ {
+ int next = source.nextInt(commands.size());
+ cfk = cfk.update(null, commands.get(next));
+ commands.set(next, commands.get(commands.size() - 1));
+ commands.remove(commands.size() - 1);
+ }
+
+ for (int i = 0, j = 0 ; j < graph.cmds.length ; ++j)
+ {
+ Cmd cmd = graph.cmds[j];
+ if (i >= cfk.size() || !cfk.txnId(i).equals(cmd.txnId))
+ {
+ Assert.assertTrue(cmd.invisible);
+ continue;
+ }
+ CommandsForKey.Info info = cfk.info(i);
+ InternalStatus expectStatus = InternalStatus.from(cmd.saveStatus);
+ if (expectStatus == null) expectStatus = InternalStatus.TRANSITIVELY_KNOWN;
+ if (expectStatus.hasInfo)
+ Assert.assertEquals(cmd.executeAt, info.executeAt(cfk.txnId(i)));
+ Assert.assertEquals(expectStatus, info.status);
+ Assert.assertArrayEquals(cmd.missing.toArray(TxnId[]::new), info.missing);
+ ++i;
+ }
+
+ ByteBuffer buffer = CommandsForKeySerializer.toBytesWithoutKey(cfk);
+ CommandsForKey roundTrip = CommandsForKeySerializer.fromBytes(key, buffer);
+ Assert.assertEquals(cfk, roundTrip);
+ }
+ catch (Throwable t)
+ {
+ throw new AssertionError(seed + " seed failed", t);
+ }
+ }
+
+ @Test
+ public void test()
+ {
+ var tableGen = AccordGenerators.fromQT(CassandraGenerators.TABLE_ID_GEN);
+ 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())));
+ var redudentBefore = txnIdGen.next(rs);
+ TxnId[] ids = Gens.arrays(TxnId.class, rs0 -> {
+ TxnId next = txnIdGen.next(rs0);
+ while (next.compareTo(redudentBefore) <= 0)
+ next = txnIdGen.next(rs0);
+ return next;
+ }).unique().ofSizeBetween(0, 10).next(rs);
+ CommandsForKey.Info[] info = new CommandsForKey.Info[ids.length];
+ for (int i = 0; i < info.length; i++)
+ info[i] = rs.pick(InternalStatus.values()).asNoInfo;
+ Arrays.sort(ids, Comparator.naturalOrder());
+ CommandsForKey expected = CommandsForKey.SerializerSupport.create(pk, redudentBefore, ids, info);
+
+ ByteBuffer buffer = CommandsForKeySerializer.toBytesWithoutKey(expected);
+ CommandsForKey roundTrip = CommandsForKeySerializer.fromBytes(pk, buffer);
+ Assert.assertEquals(expected, roundTrip);
});
}
+
+ @Test
+ 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);
+ CommandsForKey expected = CommandsForKey.SerializerSupport.create(pk,
+ TxnId.fromValues(0,0,0,0),
+ new TxnId[] {TxnId.fromValues(11,34052499,2,1)},
+ new CommandsForKey.Info[] { InternalStatus.PREACCEPTED.asNoInfo});
+
+ ByteBuffer buffer = CommandsForKeySerializer.toBytesWithoutKey(expected);
+ CommandsForKey roundTrip = CommandsForKeySerializer.fromBytes(pk, buffer);
+ Assert.assertEquals(expected, roundTrip);
+ }
}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org