mirror of https://github.com/apache/cassandra
Implement Journal replay on startup:
* reconstruct CFK, TFK, progressLog * migrate CommandStore collection state from Accord table to the log * make memtable writes non-durable; reconstruct memtable state from Writes Patch by Alex Petrov and Benedict Elliott Smith; reviewed by Benedict Elliott Smith and Alex Petrov for CASSANDRA-19869
This commit is contained in:
parent
1c7c311a2d
commit
1c269348a4
|
|
@ -1 +1 @@
|
|||
Subproject commit 593e042535d60e773cfa5f7c4b6a63e2fb6e5b30
|
||||
Subproject commit 2a7aceb96cb1e03bcfe150403b9d245b1d2562f9
|
||||
|
|
@ -35,7 +35,6 @@ import org.apache.cassandra.schema.TableMetadata;
|
|||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.*;
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
|
||||
|
||||
/**
|
||||
* The parsed version of a {@code SimpleRestriction} as outputed by the CQL parser.
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
|
|||
SCHEMA_CHANGE,
|
||||
OWNED_RANGES_CHANGE,
|
||||
ACCORD,
|
||||
ACCORD_TXN_GC,
|
||||
UNIT_TESTS // explicitly requested flush needed for a test
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,9 +35,10 @@ import accord.local.Cleanup;
|
|||
import accord.local.CommandStores.RangesForEpoch;
|
||||
import accord.local.DurableBefore;
|
||||
import accord.local.RedundantBefore;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.local.Status.Durability;
|
||||
import accord.primitives.Route;
|
||||
import accord.local.StoreParticipants;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.Status.Durability;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import org.agrona.collections.Int2ObjectHashMap;
|
||||
|
|
@ -85,19 +86,19 @@ import org.apache.cassandra.service.accord.AccordKeyspace.CommandsForKeyAccessor
|
|||
import org.apache.cassandra.service.accord.AccordKeyspace.TimestampsForKeyRows;
|
||||
import org.apache.cassandra.service.accord.AccordService;
|
||||
import org.apache.cassandra.service.accord.IAccordService;
|
||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
||||
import org.apache.cassandra.service.paxos.PaxosRepairHistory;
|
||||
import org.apache.cassandra.service.paxos.uncommitted.PaxosRows;
|
||||
import org.apache.cassandra.utils.TimeUUID;
|
||||
|
||||
import static accord.local.Cleanup.TRUNCATE_WITH_OUTCOME;
|
||||
import static accord.local.Cleanup.shouldCleanup;
|
||||
import static accord.local.Cleanup.shouldCleanupPartial;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static java.util.concurrent.TimeUnit.MICROSECONDS;
|
||||
import static org.apache.cassandra.config.Config.PaxosStatePurging.legacy;
|
||||
import static org.apache.cassandra.config.DatabaseDescriptor.paxosStatePurging;
|
||||
import static org.apache.cassandra.service.accord.AccordKeyspace.CommandRows.invalidated;
|
||||
import static org.apache.cassandra.service.accord.AccordKeyspace.CommandRows.maybeDropTruncatedCommandColumns;
|
||||
import static org.apache.cassandra.service.accord.AccordKeyspace.CommandRows.expungePartial;
|
||||
import static org.apache.cassandra.service.accord.AccordKeyspace.CommandRows.saveStatusOnly;
|
||||
import static org.apache.cassandra.service.accord.AccordKeyspace.CommandRows.truncatedApply;
|
||||
import static org.apache.cassandra.service.accord.AccordKeyspace.CommandsForKeysAccessor;
|
||||
import static org.apache.cassandra.service.accord.AccordKeyspace.TimestampsForKeyColumns.last_executed_micros;
|
||||
|
|
@ -105,7 +106,7 @@ import static org.apache.cassandra.service.accord.AccordKeyspace.TimestampsForKe
|
|||
import static org.apache.cassandra.service.accord.AccordKeyspace.TimestampsForKeyColumns.last_write_timestamp;
|
||||
import static org.apache.cassandra.service.accord.AccordKeyspace.TimestampsForKeyRows.truncateTimestampsForKeyRow;
|
||||
import static org.apache.cassandra.service.accord.AccordKeyspace.deserializeDurabilityOrNull;
|
||||
import static org.apache.cassandra.service.accord.AccordKeyspace.deserializeRouteOrNull;
|
||||
import static org.apache.cassandra.service.accord.AccordKeyspace.deserializeParticipantsOrNull;
|
||||
import static org.apache.cassandra.service.accord.AccordKeyspace.deserializeSaveStatusOrNull;
|
||||
import static org.apache.cassandra.service.accord.AccordKeyspace.deserializeTimestampOrNull;
|
||||
|
||||
|
|
@ -816,33 +817,31 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
|
|||
if (redundantBefore == null)
|
||||
return row;
|
||||
|
||||
// When commands end up being sliced by compaction we need this to discard tombstones and slices
|
||||
// without enough information to run the rest of the cleanup logic
|
||||
if (Cleanup.isSafeToCleanup(durableBefore, txnId, ranges.get(storeId).allAt(txnId.epoch())))
|
||||
return null;
|
||||
|
||||
Cell durabilityCell = row.getCell(CommandsColumns.durability);
|
||||
Durability durability = deserializeDurabilityOrNull(durabilityCell);
|
||||
Cell executeAtCell = row.getCell(CommandsColumns.execute_at);
|
||||
Timestamp executeAt = deserializeTimestampOrNull(executeAtCell);
|
||||
Cell routeCell = row.getCell(CommandsColumns.route);
|
||||
Route<?> route = deserializeRouteOrNull(routeCell);
|
||||
Cell participantsCell = row.getCell(CommandsColumns.participants);
|
||||
StoreParticipants participants = deserializeParticipantsOrNull(participantsCell);
|
||||
Cell statusCell = row.getCell(CommandsColumns.status);
|
||||
SaveStatus saveStatus = deserializeSaveStatusOrNull(statusCell);
|
||||
|
||||
// With a sliced row we might not have enough columns to determine what to do so output the
|
||||
// the row unmodified and we will try again later once it merges with the rest of the command state
|
||||
// or is dropped by `durableBefore.min(txnId) == Universal`
|
||||
if (executeAt == null || durability == null || saveStatus == null || route == null)
|
||||
if (saveStatus == null)
|
||||
return row;
|
||||
|
||||
Cleanup cleanup = shouldCleanup(txnId, saveStatus.status,
|
||||
durability, executeAt, route,
|
||||
redundantBefore, durableBefore,
|
||||
false);
|
||||
if (saveStatus.is(Status.Invalidated))
|
||||
return saveStatusOnly(saveStatus, row, nowInSec);
|
||||
|
||||
Cleanup cleanup = shouldCleanupPartial(txnId, saveStatus, durability, participants,
|
||||
redundantBefore, durableBefore);
|
||||
switch (cleanup)
|
||||
{
|
||||
default: throw new AssertionError(String.format("Unexpected cleanup task: %s", cleanup));
|
||||
case EXPUNGE:
|
||||
return null;
|
||||
|
||||
case EXPUNGE_PARTIAL:
|
||||
return expungePartial(row, durabilityCell, executeAtCell, participantsCell);
|
||||
|
||||
case ERASE:
|
||||
// Emit a tombstone so if this is slicing the command and making it not possible to determine if it
|
||||
// can be truncated later it can still be dropped via the tombstone.
|
||||
|
|
@ -850,24 +849,20 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
|
|||
// We can still encounter sliced command state just because compaction inputs are random
|
||||
return BTreeRow.emptyDeletedRow(row.clustering(), new Row.Deletion(DeletionTime.build(row.primaryKeyLivenessInfo().timestamp(), nowInSec), false));
|
||||
|
||||
case VESTIGIAL:
|
||||
case INVALIDATE:
|
||||
return invalidated(cleanup.appliesIfNot, row, nowInSec);
|
||||
return saveStatusOnly(cleanup.appliesIfNot, row, nowInSec);
|
||||
|
||||
case TRUNCATE_WITH_OUTCOME:
|
||||
case TRUNCATE:
|
||||
if (saveStatus.compareTo(cleanup.appliesIfNot) >= 0)
|
||||
return maybeDropTruncatedCommandColumns(row, durabilityCell, executeAtCell, routeCell, statusCell);
|
||||
return truncatedApply(cleanup.appliesIfNot,
|
||||
row, nowInSec, durability, durabilityCell, executeAtCell, routeCell, cleanup == TRUNCATE_WITH_OUTCOME);
|
||||
return truncatedApply(cleanup.appliesIfNot, row, nowInSec, durability, durabilityCell, executeAtCell, participantsCell, cleanup == TRUNCATE_WITH_OUTCOME);
|
||||
|
||||
case NO:
|
||||
// TODO (required): when we port this to journal, make sure to expunge extra fields beyond those we need to retain
|
||||
return row;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected Row applyToStatic(Row row)
|
||||
{
|
||||
|
|
@ -880,7 +875,7 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
|
|||
{
|
||||
final Int2ObjectHashMap<RedundantBefore> redundantBefores;
|
||||
int storeId;
|
||||
PartitionKey partitionKey;
|
||||
TokenKey partitionKey;
|
||||
|
||||
AccordTimestampsForKeyPurger(Supplier<IAccordService> accordService)
|
||||
{
|
||||
|
|
@ -955,7 +950,7 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
|
|||
final CommandsForKeyAccessor accessor;
|
||||
final Int2ObjectHashMap<RedundantBefore> redundantBefores;
|
||||
int storeId;
|
||||
PartitionKey partitionKey;
|
||||
TokenKey partitionKey;
|
||||
|
||||
AccordCommandsForKeyPurger(CommandsForKeyAccessor accessor, Supplier<IAccordService> accordService)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import org.apache.cassandra.db.partitions.Partition;
|
|||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.db.rows.EncodingStats;
|
||||
import org.apache.cassandra.db.rows.UnfilteredSource;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.index.transactions.UpdateTransaction;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableWriter;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
|
|
@ -441,4 +442,9 @@ public interface Memtable extends Comparable<Memtable>, UnfilteredSource
|
|||
super(copy.segmentId, copy.position);
|
||||
}
|
||||
}
|
||||
|
||||
default Token lastToken()
|
||||
{
|
||||
throw new UnsupportedOperationException("lastToken is not supported");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import org.apache.cassandra.dht.AbstractBounds;
|
|||
import org.apache.cassandra.dht.Bounds;
|
||||
import org.apache.cassandra.dht.IncludingExcludingBounds;
|
||||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.index.transactions.UpdateTransaction;
|
||||
import org.apache.cassandra.io.sstable.SSTableReadsListener;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
|
|
@ -112,6 +113,25 @@ public class ShardedSkipListMemtable extends AbstractShardedMemtable
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token lastToken()
|
||||
{
|
||||
Token lastToken = null;
|
||||
for (MemtableShard shard : shards)
|
||||
{
|
||||
Iterator<PartitionPosition> ppIterator = shard.partitions.descendingKeySet().iterator();
|
||||
if (ppIterator.hasNext())
|
||||
{
|
||||
Token token = ppIterator.next().getToken();
|
||||
if (lastToken == null)
|
||||
lastToken = token;
|
||||
else if (lastToken.compareTo(token) < 0)
|
||||
lastToken = token;
|
||||
}
|
||||
}
|
||||
return lastToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be called by ColumnFamilyStore.apply via Keyspace.apply, which supplies the appropriate
|
||||
* OpOrdering.
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import org.apache.cassandra.dht.AbstractBounds;
|
|||
import org.apache.cassandra.dht.Bounds;
|
||||
import org.apache.cassandra.dht.IncludingExcludingBounds;
|
||||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.index.transactions.UpdateTransaction;
|
||||
import org.apache.cassandra.io.sstable.SSTableReadsListener;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
|
|
@ -97,6 +98,15 @@ public class SkipListMemtable extends AbstractAllocatorMemtable
|
|||
return partitions.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token lastToken()
|
||||
{
|
||||
Iterator<PartitionPosition> iterator = partitions.keySet().iterator();
|
||||
if (iterator.hasNext())
|
||||
return iterator.next().getToken();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be called by ColumnFamilyStore.apply via Keyspace.apply, which supplies the appropriate
|
||||
* OpOrdering.
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import accord.local.CommandStores;
|
||||
import accord.local.Status;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.utils.Invariants;
|
||||
import accord.utils.async.AsyncChain;
|
||||
|
|
@ -62,7 +62,7 @@ import org.apache.cassandra.service.accord.AccordKeyspace;
|
|||
import org.apache.cassandra.service.accord.AccordService;
|
||||
import org.apache.cassandra.service.accord.AccordStateCache;
|
||||
import org.apache.cassandra.service.accord.CommandStoreTxnBlockedGraph;
|
||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
||||
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
|
||||
import org.apache.cassandra.service.consensus.migration.TableMigrationState;
|
||||
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||
|
|
@ -288,7 +288,7 @@ public class AccordVirtualTables
|
|||
Arrays.asList(UTF8Type.instance, UTF8Type.instance), false);
|
||||
}
|
||||
|
||||
private ByteBuffer pk(PartitionKey pk)
|
||||
private ByteBuffer pk(TokenKey pk)
|
||||
{
|
||||
TableMetadata tm = Schema.instance.getTableMetadata(pk.table());
|
||||
return partitionKeyType.pack(UTF8Type.instance.decompose(tm.toString()),
|
||||
|
|
@ -346,7 +346,7 @@ public class AccordVirtualTables
|
|||
if (processed.contains(blockedBy)) continue; // already listed
|
||||
process(ds, shard, processed, userTxn, depth + 1, blockedBy, Reason.Txn, null);
|
||||
}
|
||||
for (PartitionKey blockedBy : txn.blockedByKey)
|
||||
for (TokenKey blockedBy : txn.blockedByKey)
|
||||
{
|
||||
TxnId blocking = shard.keys.get(blockedBy);
|
||||
if (processed.contains(blocking)) continue; // already listed
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import org.apache.cassandra.tcm.serialization.Version;
|
|||
import org.apache.cassandra.utils.bytecomparable.ByteComparable;
|
||||
import org.apache.cassandra.utils.bytecomparable.ByteSource;
|
||||
import org.apache.cassandra.utils.bytecomparable.ByteSourceInverse;
|
||||
import org.apache.cassandra.utils.vint.VIntCoding;
|
||||
|
||||
public abstract class Token implements RingPosition<Token>, Serializable
|
||||
{
|
||||
|
|
@ -95,7 +96,7 @@ public abstract class Token implements RingPosition<Token>, Serializable
|
|||
out.write(toByteArray(token));
|
||||
}
|
||||
|
||||
public void serialize(Token token, ByteBuffer out) throws IOException
|
||||
public void serialize(Token token, ByteBuffer out)
|
||||
{
|
||||
out.put(toByteArray(token));
|
||||
}
|
||||
|
|
@ -202,6 +203,26 @@ public abstract class Token implements RingPosition<Token>, Serializable
|
|||
p.getTokenFactory().serialize(token, out);
|
||||
}
|
||||
|
||||
public void serialize(Token token, ByteBuffer out)
|
||||
{
|
||||
IPartitioner p = token.getPartitioner();
|
||||
if (logPartitioner && serializePartitioners.add(p.getClass()))
|
||||
logger.debug("Serializing token with partitioner " + p);
|
||||
if (!p.isFixedLength())
|
||||
VIntCoding.writeUnsignedVInt32(p.getTokenFactory().byteSize(token), out);
|
||||
p.getTokenFactory().serialize(token, out);
|
||||
}
|
||||
|
||||
public Token deserialize(ByteBuffer in, IPartitioner p)
|
||||
{
|
||||
int size = p.isFixedLength() ? p.getMaxTokenSize() : VIntCoding.readUnsignedVInt32(in);
|
||||
if (logPartitioner && deserializePartitioners.add(p.getClass()))
|
||||
logger.debug("Deserializing token with partitioner " + p);
|
||||
byte[] bytes = new byte[size];
|
||||
in.get(bytes);
|
||||
return p.getTokenFactory().fromByteArray(ByteBuffer.wrap(bytes));
|
||||
}
|
||||
|
||||
public Token deserialize(DataInputPlus in, IPartitioner p, int version) throws IOException
|
||||
{
|
||||
int size = p.isFixedLength() ? p.getMaxTokenSize() : in.readUnsignedVInt32();
|
||||
|
|
@ -213,6 +234,11 @@ public abstract class Token implements RingPosition<Token>, Serializable
|
|||
}
|
||||
|
||||
public long serializedSize(Token object, int version)
|
||||
{
|
||||
return serializedSize(object);
|
||||
}
|
||||
|
||||
public long serializedSize(Token object)
|
||||
{
|
||||
IPartitioner p = object.getPartitioner();
|
||||
int byteSize = p.getTokenFactory().byteSize(object);
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ public class RangeMemoryIndex
|
|||
Route<?> route;
|
||||
try
|
||||
{
|
||||
route = AccordKeyspace.deserializeRouteOrNull(value);
|
||||
route = AccordKeyspace.deserializeParticipantsRouteOnlyOrNull(value);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
|
@ -118,7 +118,7 @@ public class RangeMemoryIndex
|
|||
|
||||
public synchronized long add(DecoratedKey key, Route<?> route)
|
||||
{
|
||||
if (route.domain() != Routable.Domain.Range)
|
||||
if (route == null || route.domain() != Routable.Domain.Range)
|
||||
return 0;
|
||||
long sum = 0;
|
||||
for (Unseekable keyOrRange : route)
|
||||
|
|
@ -146,7 +146,7 @@ public class RangeMemoryIndex
|
|||
return TableId.EMPTY_SIZE + range.unsharedHeapSize();
|
||||
}
|
||||
|
||||
public NavigableSet<ByteBuffer> search(int storeId, TableId tableId, byte[] start, boolean startInclusive, byte[] end, boolean endInclusive)
|
||||
public synchronized NavigableSet<ByteBuffer> search(int storeId, TableId tableId, byte[] start, boolean startInclusive, byte[] end, boolean endInclusive)
|
||||
{
|
||||
RangeTree<byte[], Range, DecoratedKey> rangesToPks = map.get(new Group(storeId, tableId));
|
||||
if (rangesToPks == null || rangesToPks.isEmpty())
|
||||
|
|
@ -172,7 +172,7 @@ public class RangeMemoryIndex
|
|||
return map.isEmpty();
|
||||
}
|
||||
|
||||
public Segment write(IndexDescriptor id) throws IOException
|
||||
public synchronized Segment write(IndexDescriptor id) throws IOException
|
||||
{
|
||||
if (map.isEmpty())
|
||||
throw new AssertionError("Unable to write empty index");
|
||||
|
|
|
|||
|
|
@ -117,11 +117,11 @@ public class RouteIndex implements Index, INotificationConsumer
|
|||
|
||||
TableMetadata tableMetadata = baseCfs.metadata();
|
||||
Pair<ColumnMetadata, IndexTarget.Type> target = TargetParser.parse(tableMetadata, indexMetadata);
|
||||
if (!AccordKeyspace.CommandsColumns.route.name.equals(target.left.name))
|
||||
throw new IllegalArgumentException("Attempted to index the wrong column; needed " + AccordKeyspace.CommandsColumns.route.name + " but given " + target.left.name);
|
||||
if (!AccordKeyspace.CommandsColumns.participants.name.equals(target.left.name))
|
||||
throw new IllegalArgumentException("Attempted to index the wrong column; needed " + AccordKeyspace.CommandsColumns.participants.name + " but given " + target.left.name);
|
||||
|
||||
if (target.right != IndexTarget.Type.VALUES)
|
||||
throw new IllegalArgumentException("Attempted to index " + AccordKeyspace.CommandsColumns.route.name + " with index type " + target.right + "; only " + IndexTarget.Type.VALUES + " is supported");
|
||||
throw new IllegalArgumentException("Attempted to index " + AccordKeyspace.CommandsColumns.participants.name + " with index type " + target.right + "; only " + IndexTarget.Type.VALUES + " is supported");
|
||||
|
||||
this.baseCfs = baseCfs;
|
||||
this.indexMetadata = indexMetadata;
|
||||
|
|
@ -383,7 +383,7 @@ public class RouteIndex implements Index, INotificationConsumer
|
|||
Integer storeId = null;
|
||||
for (RowFilter.Expression e : expressions)
|
||||
{
|
||||
if (e.column() == AccordKeyspace.CommandsColumns.route)
|
||||
if (e.column() == AccordKeyspace.CommandsColumns.participants)
|
||||
{
|
||||
switch (e.operator())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ public class RoutesSearcher
|
|||
{
|
||||
private final ColumnFamilyStore cfs = Keyspace.open("system_accord").getColumnFamilyStore("commands");
|
||||
private final Index index = cfs.indexManager.getIndexByName("route");;
|
||||
private final ColumnMetadata route = AccordKeyspace.CommandsColumns.route;
|
||||
private final ColumnMetadata participants = AccordKeyspace.CommandsColumns.participants;
|
||||
private final ColumnMetadata store_id = AccordKeyspace.CommandsColumns.store_id;
|
||||
private final ColumnMetadata txn_id = AccordKeyspace.CommandsColumns.txn_id;
|
||||
private final ColumnFilter columnFilter = ColumnFilter.selectionBuilder().add(store_id).add(txn_id).build();
|
||||
|
|
@ -58,8 +58,8 @@ public class RoutesSearcher
|
|||
private CloseableIterator<Entry> searchKeysAccord(int store, AccordRoutingKey start, AccordRoutingKey end)
|
||||
{
|
||||
RowFilter rowFilter = RowFilter.create(false);
|
||||
rowFilter.add(route, Operator.GT, OrderedRouteSerializer.serializeRoutingKey(start));
|
||||
rowFilter.add(route, Operator.LTE, OrderedRouteSerializer.serializeRoutingKey(end));
|
||||
rowFilter.add(participants, Operator.GT, OrderedRouteSerializer.serializeRoutingKey(start));
|
||||
rowFilter.add(participants, Operator.LTE, OrderedRouteSerializer.serializeRoutingKey(end));
|
||||
rowFilter.add(store_id, Operator.EQ, Int32Type.instance.decompose(store));
|
||||
|
||||
PartitionRangeReadCommand cmd = PartitionRangeReadCommand.create(cfs.metadata(),
|
||||
|
|
|
|||
|
|
@ -48,6 +48,11 @@ public class LocalVersionedSerializer<I>
|
|||
this.serializer = Objects.requireNonNull(serializer);
|
||||
}
|
||||
|
||||
public MessageVersionProvider deserializeVersion(DataInputPlus in) throws IOException
|
||||
{
|
||||
return versionSerializer.deserialize(in, currentVersion.messageVersion());
|
||||
}
|
||||
|
||||
public IVersionedSerializer<I> serializer()
|
||||
{
|
||||
return serializer;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,11 @@ final class Compactor<K, V> implements Runnable, Shutdownable
|
|||
|
||||
try
|
||||
{
|
||||
Collection<StaticSegment<K, V>> newSegments = segmentCompactor.compact(toCompact, journal.keySupport);
|
||||
Collection<StaticSegment<K, V>> newSegments = segmentCompactor.compact(toCompact);
|
||||
// No-op compaction
|
||||
if (newSegments == null)
|
||||
return;
|
||||
|
||||
for (StaticSegment<K, V> segment : newSegments)
|
||||
toCompact.remove(segment);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,12 +17,14 @@
|
|||
*/
|
||||
package org.apache.cassandra.journal;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.ClosedByInterruptException;
|
||||
import java.nio.file.FileStore;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
|
@ -59,7 +61,6 @@ import org.apache.cassandra.utils.concurrent.WaitQueue;
|
|||
import org.jctools.queues.MpscUnboundedArrayQueue;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
|
||||
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Daemon.NON_DAEMON;
|
||||
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.SYNCHRONIZED;
|
||||
|
|
@ -203,7 +204,8 @@ public class Journal<K, V> implements Shutdownable
|
|||
|
||||
public void onFlush(RecordPointer recordPointer, Runnable runnable)
|
||||
{
|
||||
flusherCallbacks.submit(recordPointer, runnable);
|
||||
if (isFlushed(recordPointer)) runnable.run();
|
||||
else flusherCallbacks.submit(recordPointer, runnable);
|
||||
}
|
||||
|
||||
public void start()
|
||||
|
|
@ -229,7 +231,7 @@ public class Journal<K, V> implements Shutdownable
|
|||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void runCompactorForTesting()
|
||||
public void runCompactorForTesting()
|
||||
{
|
||||
compactor.run();
|
||||
}
|
||||
|
|
@ -254,6 +256,7 @@ public class Journal<K, V> implements Shutdownable
|
|||
try
|
||||
{
|
||||
allocator.shutdown();
|
||||
wakeAllocator(); // Wake allocator to force it into shutdown
|
||||
allocator.awaitTermination(1, TimeUnit.MINUTES);
|
||||
compactor.shutdown();
|
||||
compactor.awaitTermination(1, TimeUnit.MINUTES);
|
||||
|
|
@ -329,7 +332,6 @@ public class Journal<K, V> implements Shutdownable
|
|||
try (ReferencedSegments<K, V> segments = selectAndReference(id))
|
||||
{
|
||||
consumer.init();
|
||||
|
||||
for (Segment<K, V> segment : segments.allSorted())
|
||||
segment.readAll(id, holder, consumer);
|
||||
}
|
||||
|
|
@ -736,6 +738,17 @@ public class Journal<K, V> implements Shutdownable
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
ReferencedSegments<K, V> selectAndReference(Predicate<Segment<K,V>> selector)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
ReferencedSegments<K, V> referenced = segments().selectAndReference(selector);
|
||||
if (null != referenced)
|
||||
return referenced;
|
||||
}
|
||||
}
|
||||
|
||||
Segments<K, V> segments()
|
||||
{
|
||||
return segments.get();
|
||||
|
|
@ -854,24 +867,6 @@ public class Journal<K, V> implements Shutdownable
|
|||
closer.execute(new CloseActiveSegmentRunnable(activeSegment));
|
||||
}
|
||||
|
||||
/*
|
||||
* Replay logic
|
||||
*/
|
||||
|
||||
/**
|
||||
* Iterate over and invoke the supplied callback on every record,
|
||||
* with segments iterated in segment timestamp order. Only visits
|
||||
* finished, on-disk segments.
|
||||
*/
|
||||
public void replayStaticSegments(RecordConsumer<K> consumer)
|
||||
{
|
||||
List<StaticSegment<K, V>> staticSegments = new ArrayList<>();
|
||||
segments().selectStatic(staticSegments);
|
||||
staticSegments.sort(comparing(s -> s.descriptor));
|
||||
for (StaticSegment<K, V> segment : staticSegments)
|
||||
segment.forEachRecord(consumer);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void closeCurrentSegmentForTesting()
|
||||
{
|
||||
|
|
@ -954,4 +949,59 @@ public class Journal<K, V> implements Shutdownable
|
|||
{
|
||||
void write(DataOutputPlus out, int userVersion) throws IOException;
|
||||
}
|
||||
|
||||
public StaticSegmentIterator staticSegmentIterator()
|
||||
{
|
||||
return new StaticSegmentIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Static segment iterator iterates all _static_ segments in _key_ order.
|
||||
*/
|
||||
public class StaticSegmentIterator implements Closeable
|
||||
{
|
||||
private final PriorityQueue<StaticSegment.KeyOrderReader<K>> readers;
|
||||
private final ReferencedSegments<K, V> segments;
|
||||
|
||||
private StaticSegmentIterator()
|
||||
{
|
||||
this.segments = selectAndReference(Segment::isStatic);
|
||||
this.readers = new PriorityQueue<>((o1, o2) -> keySupport.compare(o1.key(), o2.key()));
|
||||
for (Segment<K, V> segment : this.segments.all())
|
||||
{
|
||||
StaticSegment<K, V> staticSegment = (StaticSegment<K, V>)segment;
|
||||
StaticSegment.KeyOrderReader<K> reader = staticSegment.keyOrderReader();
|
||||
if (reader.advance())
|
||||
this.readers.add(reader);
|
||||
}
|
||||
}
|
||||
|
||||
public K key()
|
||||
{
|
||||
StaticSegment.KeyOrderReader<K> reader = readers.peek();
|
||||
if (reader == null)
|
||||
return null;
|
||||
return reader.key();
|
||||
}
|
||||
|
||||
public void readAllForKey(K key, RecordConsumer<K> reader)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
StaticSegment.KeyOrderReader<K> next = readers.peek();
|
||||
if (next == null || !next.key().equals(key))
|
||||
break;
|
||||
Invariants.checkState(next == readers.poll());
|
||||
|
||||
reader.accept(next.descriptor.timestamp, next.offset, next.key(), next.record(), next.hosts(), next.descriptor.userVersion);
|
||||
if (next.advance())
|
||||
readers.add(next);
|
||||
}
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
segments.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import java.util.Collection;
|
|||
|
||||
public interface SegmentCompactor<K, V>
|
||||
{
|
||||
SegmentCompactor<?, ?> NOOP = (SegmentCompactor<Object, Object>) (segments, keySupport) -> segments;
|
||||
SegmentCompactor<?, ?> NOOP = (SegmentCompactor<Object, Object>) (segments) -> segments;
|
||||
|
||||
static <K, V> SegmentCompactor<K, V> noop()
|
||||
{
|
||||
|
|
@ -30,5 +30,5 @@ public interface SegmentCompactor<K, V>
|
|||
return (SegmentCompactor<K, V>) NOOP;
|
||||
}
|
||||
|
||||
Collection<StaticSegment<K, V>> compact(Collection<StaticSegment<K, V>> segments, KeySupport<K> keySupport) throws IOException;
|
||||
Collection<StaticSegment<K, V>> compact(Collection<StaticSegment<K, V>> segments) throws IOException;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@ import org.apache.cassandra.io.util.DataOutputPlus;
|
|||
|
||||
public interface ValueSerializer<K, V>
|
||||
{
|
||||
// TODO (required): this is completely unused in Journal
|
||||
int serializedSize(K key, V value, int userVersion);
|
||||
|
||||
void serialize(K key, V value, DataOutputPlus out, int userVersion) throws IOException;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ public class TableId implements Comparable<TableId>
|
|||
return new TableId(new UUID(in.readLong(), in.readLong()));
|
||||
}
|
||||
|
||||
public static <V> TableId deserialize(V src, ValueAccessor<V> accessor, int offset) throws IOException
|
||||
public static <V> TableId deserialize(V src, ValueAccessor<V> accessor, int offset)
|
||||
{
|
||||
return new TableId(new UUID(accessor.getLong(src, offset), accessor.getLong(src, offset + TypeSizes.LONG_SIZE)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ import org.apache.cassandra.schema.TableId;
|
|||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.schema.TableMetadataRef;
|
||||
import org.apache.cassandra.schema.ViewMetadata;
|
||||
import org.apache.cassandra.service.accord.AccordService;
|
||||
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
|
||||
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationTarget;
|
||||
import org.apache.cassandra.service.disk.usage.DiskUsageBroadcaster;
|
||||
|
|
@ -3794,6 +3795,9 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
|
|||
logger.debug(msg);
|
||||
transientMode = Optional.of(Mode.DRAINING);
|
||||
|
||||
if (DatabaseDescriptor.getAccordTransactionsEnabled())
|
||||
AccordService.instance().shutdownAndWait(1, MINUTES);
|
||||
|
||||
try
|
||||
{
|
||||
/* not clear this is reasonable time, but propagated from prior embedded behaviour */
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
package org.apache.cassandra.service.accord;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -32,7 +31,6 @@ import java.util.function.Consumer;
|
|||
import java.util.function.Function;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import accord.api.Key;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -41,22 +39,31 @@ import accord.api.Agent;
|
|||
import accord.api.DataStore;
|
||||
import accord.api.LocalListeners;
|
||||
import accord.api.ProgressLog;
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.impl.TimestampsForKey;
|
||||
import accord.local.Cleanup;
|
||||
import accord.local.Command;
|
||||
import accord.local.CommandStore;
|
||||
import accord.local.CommandStores;
|
||||
import accord.local.Commands;
|
||||
import accord.local.DurableBefore;
|
||||
import accord.local.KeyHistory;
|
||||
import accord.local.NodeTimeService;
|
||||
import accord.local.PreLoadContext;
|
||||
import accord.local.RedundantBefore;
|
||||
import accord.local.SafeCommand;
|
||||
import accord.local.SafeCommandStore;
|
||||
import accord.primitives.Keys;
|
||||
import accord.primitives.Deps;
|
||||
import accord.primitives.Participants;
|
||||
import accord.primitives.Range;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.Routable;
|
||||
import accord.primitives.RoutableKey;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.utils.Invariants;
|
||||
import accord.utils.ReducingRangeMap;
|
||||
import accord.utils.async.AsyncChain;
|
||||
import accord.utils.async.AsyncChains;
|
||||
import org.apache.cassandra.cache.CacheSize;
|
||||
|
|
@ -66,12 +73,21 @@ import org.apache.cassandra.config.CassandraRelevantProperties;
|
|||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.Mutation;
|
||||
import org.apache.cassandra.metrics.AccordStateCacheMetrics;
|
||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
||||
import org.apache.cassandra.service.accord.async.AsyncOperation;
|
||||
import org.apache.cassandra.service.accord.events.CacheEvents;
|
||||
import org.apache.cassandra.utils.Clock;
|
||||
import org.apache.cassandra.utils.concurrent.AsyncPromise;
|
||||
import org.apache.cassandra.utils.concurrent.Promise;
|
||||
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
|
||||
|
||||
import static accord.primitives.SaveStatus.Applying;
|
||||
import static accord.primitives.Status.Committed;
|
||||
import static accord.primitives.Status.Invalidated;
|
||||
import static accord.primitives.Status.PreApplied;
|
||||
import static accord.primitives.Status.Stable;
|
||||
import static accord.primitives.Status.Truncated;
|
||||
import static accord.utils.Invariants.checkState;
|
||||
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
|
||||
|
||||
public class AccordCommandStore extends CommandStore implements CacheSize
|
||||
|
|
@ -103,8 +119,8 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
private final ExecutorService executor;
|
||||
private final AccordStateCache stateCache;
|
||||
private final AccordStateCache.Instance<TxnId, Command, AccordSafeCommand> commandCache;
|
||||
private final AccordStateCache.Instance<Key, TimestampsForKey, AccordSafeTimestampsForKey> timestampsForKeyCache;
|
||||
private final AccordStateCache.Instance<Key, CommandsForKey, AccordSafeCommandsForKey> commandsForKeyCache;
|
||||
private final AccordStateCache.Instance<RoutingKey, TimestampsForKey, AccordSafeTimestampsForKey> timestampsForKeyCache;
|
||||
private final AccordStateCache.Instance<RoutingKey, CommandsForKey, AccordSafeCommandsForKey> commandsForKeyCache;
|
||||
private AsyncOperation<?> currentOperation = null;
|
||||
private AccordSafeCommandStore current = null;
|
||||
private long lastSystemTimestampMicros = Long.MIN_VALUE;
|
||||
|
|
@ -120,7 +136,17 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
IJournal journal,
|
||||
AccordStateCacheMetrics cacheMetrics)
|
||||
{
|
||||
this(id, time, agent, dataStore, progressLogFactory, listenerFactory, epochUpdateHolder, journal, Stage.READ.executor(), Stage.MUTATION.executor(), cacheMetrics);
|
||||
this(id,
|
||||
time,
|
||||
agent,
|
||||
dataStore,
|
||||
progressLogFactory,
|
||||
listenerFactory,
|
||||
epochUpdateHolder,
|
||||
journal,
|
||||
Stage.READ.executor(),
|
||||
Stage.MUTATION.executor(),
|
||||
cacheMetrics);
|
||||
}
|
||||
|
||||
private static <K, V> void registerJfrListener(int id, AccordStateCache.Instance<K, V, ?> instance, String name)
|
||||
|
|
@ -219,7 +245,7 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
AccordObjectSizes::command);
|
||||
registerJfrListener(id, commandCache, "Command");
|
||||
timestampsForKeyCache =
|
||||
stateCache.instance(Key.class,
|
||||
stateCache.instance(RoutingKey.class,
|
||||
AccordSafeTimestampsForKey.class,
|
||||
AccordSafeTimestampsForKey::new,
|
||||
this::loadTimestampsForKey,
|
||||
|
|
@ -228,7 +254,7 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
AccordObjectSizes::timestampsForKey);
|
||||
registerJfrListener(id, timestampsForKeyCache, "TimestampsForKey");
|
||||
commandsForKeyCache =
|
||||
stateCache.instance(Key.class,
|
||||
stateCache.instance(RoutingKey.class,
|
||||
AccordSafeCommandsForKey.class,
|
||||
AccordSafeCommandsForKey::new,
|
||||
this::loadCommandsForKey,
|
||||
|
|
@ -240,20 +266,12 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
|
||||
this.commandsForRangesLoader = new CommandsForRangesLoader(this);
|
||||
|
||||
AccordKeyspace.loadCommandStoreMetadata(id, ((rejectBefore, durableBefore, redundantBefore, bootstrapBeganAt, safeToRead) -> {
|
||||
executor.submit(() -> {
|
||||
if (rejectBefore != null)
|
||||
super.setRejectBefore(rejectBefore);
|
||||
if (durableBefore != null)
|
||||
super.setDurableBefore(durableBefore);
|
||||
if (redundantBefore != null)
|
||||
super.setRedundantBefore(redundantBefore);
|
||||
if (bootstrapBeganAt != null)
|
||||
super.setBootstrapBeganAt(bootstrapBeganAt);
|
||||
if (safeToRead != null)
|
||||
super.setSafeToRead(safeToRead);
|
||||
});
|
||||
}));
|
||||
loadRedundantBefore(journal.loadRedundantBefore(id()));
|
||||
loadDurableBefore(journal.loadDurableBefore(id()));
|
||||
loadBootstrapBeganAt(journal.loadBootstrapBeganAt(id()));
|
||||
loadSafeToRead(journal.loadSafeToRead(id()));
|
||||
loadRangesForEpoch(journal.loadRangesForEpoch(id()));
|
||||
loadHistoricalTransactions(journal.loadHistoricalTransactions(id()));
|
||||
|
||||
executor.execute(() -> CommandStore.register(this));
|
||||
}
|
||||
|
|
@ -269,6 +287,12 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
return commandsForRangesLoader;
|
||||
}
|
||||
|
||||
public void markShardDurable(SafeCommandStore safeStore, TxnId globalSyncId, Ranges ranges)
|
||||
{
|
||||
store.snapshot(ranges, globalSyncId);
|
||||
super.markShardDurable(safeStore, globalSyncId, ranges);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inStore()
|
||||
{
|
||||
|
|
@ -304,14 +328,14 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
|
||||
public void checkInStoreThread()
|
||||
{
|
||||
Invariants.checkState(inStore());
|
||||
checkState(inStore());
|
||||
}
|
||||
|
||||
public void checkNotInStoreThread()
|
||||
{
|
||||
if (!CHECK_THREADS)
|
||||
return;
|
||||
Invariants.checkState(!inStore());
|
||||
checkState(!inStore());
|
||||
}
|
||||
|
||||
public ExecutorService executor()
|
||||
|
|
@ -324,12 +348,12 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
return commandCache;
|
||||
}
|
||||
|
||||
public AccordStateCache.Instance<Key, TimestampsForKey, AccordSafeTimestampsForKey> timestampsForKeyCache()
|
||||
public AccordStateCache.Instance<RoutingKey, TimestampsForKey, AccordSafeTimestampsForKey> timestampsForKeyCache()
|
||||
{
|
||||
return timestampsForKeyCache;
|
||||
}
|
||||
|
||||
public AccordStateCache.Instance<Key, CommandsForKey, AccordSafeCommandsForKey> commandsForKeyCache()
|
||||
public AccordStateCache.Instance<RoutingKey, CommandsForKey, AccordSafeCommandsForKey> commandsForKeyCache()
|
||||
{
|
||||
return commandsForKeyCache;
|
||||
}
|
||||
|
|
@ -338,7 +362,7 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
@VisibleForTesting
|
||||
public Runnable appendToKeyspace(Command before, Command after)
|
||||
{
|
||||
if (after.keysOrRanges() != null && after.keysOrRanges() instanceof Keys)
|
||||
if (after.txnId().is(Routable.Domain.Key))
|
||||
return null;
|
||||
|
||||
Mutation mutation = AccordKeyspace.getCommandMutation(this.id, before, after, nextSystemTimestampMicros());
|
||||
|
|
@ -350,13 +374,17 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
return null;
|
||||
}
|
||||
|
||||
public void persistFieldUpdates(AccordSafeCommandStore.FieldUpdates fieldUpdates, Runnable onFlush)
|
||||
{
|
||||
journal.persistStoreState(id, fieldUpdates, onFlush);
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
@VisibleForTesting
|
||||
public void appendToLog(Command before, Command after, Runnable runnable)
|
||||
public void appendToLog(Command before, Command after, Runnable onFlush)
|
||||
{
|
||||
journal.appendCommand(id,
|
||||
Collections.singletonList(SavedCommand.diffWriter(before, after)),
|
||||
null, runnable);
|
||||
journal.appendCommand(id, SavedCommand.diff(before, after), onFlush);
|
||||
}
|
||||
|
||||
boolean validateCommand(TxnId txnId, Command evicting)
|
||||
|
|
@ -368,23 +396,29 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
return Objects.equals(evicting, reloaded);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void sanityCheckCommand(Command command)
|
||||
{
|
||||
((AccordJournal) journal).sanityCheck(id, command);
|
||||
}
|
||||
|
||||
boolean validateTimestampsForKey(RoutableKey key, TimestampsForKey evicting)
|
||||
{
|
||||
if (!Invariants.isParanoid())
|
||||
return true;
|
||||
|
||||
TimestampsForKey reloaded = AccordKeyspace.unsafeLoadTimestampsForKey(this, (PartitionKey) key);
|
||||
TimestampsForKey reloaded = AccordKeyspace.unsafeLoadTimestampsForKey(this, (TokenKey) key);
|
||||
return Objects.equals(evicting, reloaded);
|
||||
}
|
||||
|
||||
TimestampsForKey loadTimestampsForKey(RoutableKey key)
|
||||
{
|
||||
return AccordKeyspace.loadTimestampsForKey(this, (PartitionKey) key);
|
||||
return AccordKeyspace.loadTimestampsForKey(this, (TokenKey) key);
|
||||
}
|
||||
|
||||
CommandsForKey loadCommandsForKey(RoutableKey key)
|
||||
{
|
||||
return AccordKeyspace.loadCommandsForKey(this, (PartitionKey) key);
|
||||
return AccordKeyspace.loadCommandsForKey(this, (TokenKey) key);
|
||||
}
|
||||
|
||||
boolean validateCommandsForKey(RoutableKey key, CommandsForKey evicting)
|
||||
|
|
@ -392,7 +426,7 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
if (!Invariants.isParanoid())
|
||||
return true;
|
||||
|
||||
CommandsForKey reloaded = AccordKeyspace.loadCommandsForKey(this, (PartitionKey) key);
|
||||
CommandsForKey reloaded = AccordKeyspace.loadCommandsForKey(this, (TokenKey) key);
|
||||
return Objects.equals(evicting, reloaded);
|
||||
}
|
||||
|
||||
|
|
@ -424,19 +458,19 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
|
||||
public void setCurrentOperation(AsyncOperation<?> operation)
|
||||
{
|
||||
Invariants.checkState(currentOperation == null);
|
||||
checkState(currentOperation == null);
|
||||
currentOperation = operation;
|
||||
}
|
||||
|
||||
public AsyncOperation<?> getContext()
|
||||
{
|
||||
Invariants.checkState(currentOperation != null);
|
||||
checkState(currentOperation != null);
|
||||
return currentOperation;
|
||||
}
|
||||
|
||||
public void unsetCurrentOperation(AsyncOperation<?> operation)
|
||||
{
|
||||
Invariants.checkState(currentOperation == operation);
|
||||
checkState(currentOperation == operation);
|
||||
currentOperation = null;
|
||||
}
|
||||
|
||||
|
|
@ -496,11 +530,11 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
|
||||
public AccordSafeCommandStore beginOperation(PreLoadContext preLoadContext,
|
||||
Map<TxnId, AccordSafeCommand> commands,
|
||||
NavigableMap<Key, AccordSafeTimestampsForKey> timestampsForKeys,
|
||||
NavigableMap<Key, AccordSafeCommandsForKey> commandsForKeys,
|
||||
NavigableMap<RoutingKey, AccordSafeTimestampsForKey> timestampsForKeys,
|
||||
NavigableMap<RoutingKey, AccordSafeCommandsForKey> commandsForKeys,
|
||||
@Nullable AccordSafeCommandsForRanges commandsForRanges)
|
||||
{
|
||||
Invariants.checkState(current == null);
|
||||
checkState(current == null);
|
||||
commands.values().forEach(AccordSafeState::preExecute);
|
||||
commandsForKeys.values().forEach(AccordSafeState::preExecute);
|
||||
timestampsForKeys.values().forEach(AccordSafeState::preExecute);
|
||||
|
|
@ -518,7 +552,7 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
|
||||
public void completeOperation(AccordSafeCommandStore store)
|
||||
{
|
||||
Invariants.checkState(current == store);
|
||||
checkState(current == store);
|
||||
try
|
||||
{
|
||||
current.postExecute();
|
||||
|
|
@ -538,50 +572,63 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
public void shutdown()
|
||||
{
|
||||
executor.shutdown();
|
||||
try
|
||||
{
|
||||
executor.awaitTermination(20, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException t)
|
||||
{
|
||||
throw new RuntimeException("Could not shut down command store " + this);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setRejectBefore(ReducingRangeMap<Timestamp> newRejectBefore)
|
||||
public void registerHistoricalTransactions(Deps deps, SafeCommandStore safeStore)
|
||||
{
|
||||
super.setRejectBefore(newRejectBefore);
|
||||
// TODO (required, correctness): rework to persist via journal once available, this can lose updates in some edge cases
|
||||
AccordKeyspace.updateRejectBefore(this, newRejectBefore);
|
||||
if (deps.isEmpty()) return;
|
||||
|
||||
CommandStores.RangesForEpoch ranges = safeStore.ranges();
|
||||
// used in places such as accord.local.CommandStore.fetchMajorityDeps
|
||||
// We find a set of dependencies for a range then update CommandsFor to know about them
|
||||
Ranges allRanges = safeStore.ranges().all();
|
||||
deps.keyDeps.keys().forEach(allRanges, key -> {
|
||||
// TODO (now): batch register to minimise GC
|
||||
deps.keyDeps.forEach(key, (txnId, txnIdx) -> {
|
||||
// TODO (desired, efficiency): this can be made more efficient by batching by epoch
|
||||
if (ranges.coordinates(txnId).contains(key))
|
||||
return; // already coordinates, no need to replicate
|
||||
if (!ranges.allBefore(txnId.epoch()).contains(key))
|
||||
return;
|
||||
|
||||
safeStore.get(key).registerHistorical(safeStore, txnId);
|
||||
});
|
||||
});
|
||||
for (int i = 0; i < deps.rangeDeps.rangeCount(); i++)
|
||||
{
|
||||
Range range = deps.rangeDeps.range(i);
|
||||
if (!allRanges.intersects(range))
|
||||
continue;
|
||||
deps.rangeDeps.forEach(range, txnId -> {
|
||||
// TODO (desired, efficiency): this can be made more efficient by batching by epoch
|
||||
if (ranges.coordinates(txnId).intersects(range))
|
||||
return; // already coordinates, no need to replicate
|
||||
if (!ranges.allBefore(txnId.epoch()).intersects(range))
|
||||
return;
|
||||
|
||||
diskCommandsForRanges().mergeHistoricalTransaction(txnId, Ranges.single(range).slice(allRanges), Ranges::with);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected void setBootstrapBeganAt(NavigableMap<TxnId, Ranges> newBootstrapBeganAt)
|
||||
{
|
||||
super.setBootstrapBeganAt(newBootstrapBeganAt);
|
||||
// TODO (required, correctness): rework to persist via journal once available, this can lose updates in some edge cases
|
||||
AccordKeyspace.updateBootstrapBeganAt(this, newBootstrapBeganAt);
|
||||
}
|
||||
|
||||
protected void setSafeToRead(NavigableMap<Timestamp, Ranges> newSafeToRead)
|
||||
{
|
||||
super.setSafeToRead(newSafeToRead);
|
||||
// TODO (required, correctness): rework to persist via journal once available, this can lose updates in some edge cases
|
||||
AccordKeyspace.updateSafeToRead(this, newSafeToRead);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDurableBefore(DurableBefore newDurableBefore)
|
||||
{
|
||||
super.setDurableBefore(newDurableBefore);
|
||||
AccordKeyspace.updateDurableBefore(this, newDurableBefore);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setRedundantBefore(RedundantBefore newRedundantBefore)
|
||||
{
|
||||
super.setRedundantBefore(newRedundantBefore);
|
||||
// TODO (required): this needs to be synchronous, or at least needs to take effect before we rely upon it
|
||||
AccordKeyspace.updateRedundantBefore(this, newRedundantBefore);
|
||||
}
|
||||
|
||||
public NavigableMap<TxnId, Ranges> bootstrapBeganAt() { return super.bootstrapBeganAt(); }
|
||||
public NavigableMap<Timestamp, Ranges> safeToRead() { return super.safeToRead(); }
|
||||
|
||||
public void appendCommands(List<SavedCommand.Writer<TxnId>> commands, List<Command> sanityCheck, Runnable onFlush)
|
||||
public void appendCommands(List<SavedCommand.DiffWriter> diffs, Runnable onFlush)
|
||||
{
|
||||
journal.appendCommand(id, commands, sanityCheck, onFlush);
|
||||
for (int i = 0; i < diffs.size(); i++)
|
||||
{
|
||||
boolean isLast = i == diffs.size() - 1;
|
||||
SavedCommand.DiffWriter writer = diffs.get(i);
|
||||
journal.appendCommand(id, writer, isLast ? onFlush : null);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
|
@ -589,4 +636,146 @@ public class AccordCommandStore extends CommandStore implements CacheSize
|
|||
{
|
||||
return journal.loadCommand(id, txnId);
|
||||
}
|
||||
|
||||
public interface Loader
|
||||
{
|
||||
Promise<?> load(Command next);
|
||||
Promise<?> apply(Command next);
|
||||
}
|
||||
|
||||
public Loader loader()
|
||||
{
|
||||
return new Loader()
|
||||
{
|
||||
private PreLoadContext context(Command command, KeyHistory keyHistory)
|
||||
{
|
||||
TxnId txnId = command.txnId();
|
||||
Participants<?> keys = null;
|
||||
List<TxnId> deps = null;
|
||||
if (CommandsForKey.manages(txnId))
|
||||
keys = command.hasBeen(Committed) ? command.participants().hasTouched() : command.participants().touches();
|
||||
else if (!CommandsForKey.managesExecution(txnId) && command.hasBeen(Status.Stable) && !command.hasBeen(Status.Truncated))
|
||||
keys = command.asCommitted().waitingOn.keys;
|
||||
|
||||
if (command.partialDeps() != null)
|
||||
deps = command.partialDeps().txnIds();
|
||||
|
||||
if (keys != null)
|
||||
{
|
||||
if (deps != null)
|
||||
return PreLoadContext.contextFor(txnId, deps, keys, keyHistory);
|
||||
|
||||
return PreLoadContext.contextFor(txnId, keys, keyHistory);
|
||||
}
|
||||
|
||||
return PreLoadContext.contextFor(txnId);
|
||||
}
|
||||
|
||||
public Promise<?> load(Command command)
|
||||
{
|
||||
TxnId txnId = command.txnId();
|
||||
|
||||
AsyncPromise<?> future = new AsyncPromise<>();
|
||||
execute(context(command, KeyHistory.COMMANDS),
|
||||
safeStore -> {
|
||||
Command local = command;
|
||||
if (local.status() != Truncated && local.status() != Invalidated)
|
||||
{
|
||||
Cleanup cleanup = Cleanup.shouldCleanup(AccordCommandStore.this, local, local.participants());
|
||||
switch (cleanup)
|
||||
{
|
||||
case NO:
|
||||
break;
|
||||
case INVALIDATE:
|
||||
case TRUNCATE_WITH_OUTCOME:
|
||||
case TRUNCATE:
|
||||
case ERASE:
|
||||
local = Commands.purge(local, local.participants(), cleanup);
|
||||
}
|
||||
}
|
||||
|
||||
local = safeStore.unsafeGet(txnId).update(safeStore, local);
|
||||
if (local.status() == Truncated)
|
||||
safeStore.progressLog().clear(local.txnId());
|
||||
})
|
||||
.begin((unused, throwable) -> {
|
||||
if (throwable != null)
|
||||
future.setFailure(throwable);
|
||||
else
|
||||
future.setSuccess(null);
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
public Promise<?> apply(Command command)
|
||||
{
|
||||
TxnId txnId = command.txnId();
|
||||
|
||||
AsyncPromise<?> future = new AsyncPromise<>();
|
||||
PreLoadContext context = context(command, KeyHistory.TIMESTAMPS);
|
||||
execute(context,
|
||||
safeStore -> {
|
||||
SafeCommand safeCommand = safeStore.unsafeGet(txnId);
|
||||
Command local = safeCommand.current();
|
||||
if (local.is(Stable) || local.is(PreApplied))
|
||||
Commands.maybeExecute(safeStore, safeCommand, local, true, true);
|
||||
else if (local.saveStatus().compareTo(Applying) >= 0 && !local.hasBeen(Truncated))
|
||||
Commands.applyWrites(safeStore, context, local).begin(agent);
|
||||
})
|
||||
.begin((unused, throwable) -> {
|
||||
if (throwable != null)
|
||||
future.setFailure(throwable);
|
||||
else
|
||||
future.setSuccess(null);
|
||||
});
|
||||
return future;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Replay/state reloading
|
||||
*/
|
||||
|
||||
void loadRedundantBefore(RedundantBefore redundantBefore)
|
||||
{
|
||||
if (redundantBefore != null)
|
||||
unsafeSetRedundantBefore(redundantBefore);
|
||||
}
|
||||
|
||||
void loadDurableBefore(DurableBefore durableBefore)
|
||||
{
|
||||
if (durableBefore != null)
|
||||
unsafeSetDurableBefore(durableBefore);
|
||||
}
|
||||
|
||||
void loadBootstrapBeganAt(NavigableMap<TxnId, Ranges> bootstrapBeganAt)
|
||||
{
|
||||
if (bootstrapBeganAt != null)
|
||||
unsafeSetBootstrapBeganAt(bootstrapBeganAt);
|
||||
}
|
||||
|
||||
void loadSafeToRead(NavigableMap<Timestamp, Ranges> safeToRead)
|
||||
{
|
||||
if (safeToRead != null)
|
||||
unsafeSetSafeToRead(safeToRead);
|
||||
}
|
||||
|
||||
void loadRangesForEpoch(CommandStores.RangesForEpoch.Snapshot rangesForEpoch)
|
||||
{
|
||||
if (rangesForEpoch != null)
|
||||
unsafeSetRangesForEpoch(new CommandStores.RangesForEpoch(rangesForEpoch.epochs, rangesForEpoch.ranges, this));
|
||||
}
|
||||
|
||||
void loadHistoricalTransactions(List<Deps> deps)
|
||||
{
|
||||
if (deps != null)
|
||||
{
|
||||
execute(PreLoadContext.empty(),
|
||||
safeStore -> {
|
||||
for (Deps dep : deps)
|
||||
registerHistoricalTransactions(dep, safeStore);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ import javax.annotation.Nullable;
|
|||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import accord.impl.AbstractConfigurationService;
|
||||
import accord.local.Node;
|
||||
import accord.primitives.Ranges;
|
||||
|
|
@ -50,7 +53,10 @@ import org.apache.cassandra.net.MessagingService;
|
|||
import org.apache.cassandra.service.accord.AccordKeyspace.EpochDiskState;
|
||||
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||
import org.apache.cassandra.tcm.ClusterMetadataService;
|
||||
import org.apache.cassandra.tcm.Epoch;
|
||||
import org.apache.cassandra.tcm.listeners.ChangeListener;
|
||||
import org.apache.cassandra.tcm.membership.NodeId;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.Simulate;
|
||||
import org.apache.cassandra.utils.concurrent.AsyncPromise;
|
||||
import org.apache.cassandra.utils.concurrent.Future;
|
||||
|
|
@ -61,6 +67,8 @@ import static org.apache.cassandra.utils.Simulate.With.MONITORS;
|
|||
@Simulate(with=MONITORS)
|
||||
public class AccordConfigurationService extends AbstractConfigurationService<AccordConfigurationService.EpochState, AccordConfigurationService.EpochHistory> implements ChangeListener, AccordEndpointMapper, AccordSyncPropagator.Listener, Shutdownable
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(AccordConfigurationService.class);
|
||||
|
||||
private final AccordSyncPropagator syncPropagator;
|
||||
private final DiskStateManager diskStateManager;
|
||||
|
||||
|
|
@ -393,8 +401,16 @@ public class AccordConfigurationService extends AbstractConfigurationService<Acc
|
|||
@Override
|
||||
protected void fetchTopologyInternal(long epoch)
|
||||
{
|
||||
// TODO: need a non-blocking way to inform CMS of an unknown epoch
|
||||
// ClusterMetadataService.instance().maybeCatchup(Epoch.create(epoch));
|
||||
ClusterMetadata metadata = ClusterMetadata.current();
|
||||
if (metadata.directory.peerIds().size() < 2)
|
||||
return; // just let CMS handle it when it's ready
|
||||
|
||||
// TODO (desired): randomise
|
||||
NodeId first = metadata.directory.peerIds().first();
|
||||
InetAddressAndPort peer = metadata.directory.getNodeAddresses(first).broadcastAddress;
|
||||
if (FBUtilities.getBroadcastAddressAndPort().equals(peer))
|
||||
peer = metadata.directory.getNodeAddresses(metadata.directory.peerIds().higher(first)).broadcastAddress;;
|
||||
ClusterMetadataService.instance().fetchLogFromPeerOrCMSAsync(metadata, peer, Epoch.create(epoch));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -18,11 +18,41 @@
|
|||
|
||||
package org.apache.cassandra.service.accord;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import accord.api.DataStore;
|
||||
import accord.local.Node;
|
||||
import accord.local.SafeCommandStore;
|
||||
import accord.primitives.Range;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.SyncPoint;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.utils.async.AsyncResult;
|
||||
import accord.utils.async.AsyncResults;
|
||||
import org.agrona.collections.Object2ObjectHashMap;
|
||||
import org.apache.cassandra.concurrent.ScheduledExecutors;
|
||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||
import org.apache.cassandra.db.DataRange;
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.db.commitlog.CommitLogPosition;
|
||||
import org.apache.cassandra.db.filter.ColumnFilter;
|
||||
import org.apache.cassandra.db.lifecycle.View;
|
||||
import org.apache.cassandra.db.memtable.Memtable;
|
||||
import org.apache.cassandra.db.memtable.TrieMemtable;
|
||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.io.sstable.SSTableReadsListener;
|
||||
import org.apache.cassandra.schema.TableId;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||
import org.apache.cassandra.utils.concurrent.Future;
|
||||
import org.apache.cassandra.utils.concurrent.FutureCombiner;
|
||||
|
||||
import static accord.utils.Invariants.checkState;
|
||||
import static org.apache.cassandra.db.ColumnFamilyStore.FlushReason.ACCORD_TXN_GC;
|
||||
|
||||
public class AccordDataStore implements DataStore
|
||||
{
|
||||
|
|
@ -33,4 +63,112 @@ public class AccordDataStore implements DataStore
|
|||
coordinator.start();
|
||||
return coordinator.result();
|
||||
}
|
||||
|
||||
static class SnapshotBounds
|
||||
{
|
||||
final List<org.apache.cassandra.dht.Range<Token>> ranges = new ArrayList<>();
|
||||
CommitLogPosition position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncResult<Void> snapshot(Ranges ranges, TxnId before) // TODO: does this have to go to journal, too?
|
||||
{
|
||||
AsyncResults.SettableResult<Void> result = new AsyncResults.SettableResult<>();
|
||||
// TODO: maintain a list of Accord tables, perhaps in ClusterMetadata?
|
||||
ClusterMetadata metadata = ClusterMetadata.current();
|
||||
Object2ObjectHashMap<TableId, SnapshotBounds> tables = new Object2ObjectHashMap<>();
|
||||
for (Range range : ranges)
|
||||
{
|
||||
tables.computeIfAbsent(((TokenRange)range).table(), ignore -> new SnapshotBounds())
|
||||
.ranges.add(((TokenRange) range).toKeyspaceRange());
|
||||
}
|
||||
|
||||
for (Map.Entry<TableId, SnapshotBounds> e : tables.entrySet())
|
||||
{
|
||||
TableMetadata tableMetadata = metadata.schema.getTableMetadata(e.getKey());
|
||||
if (!tableMetadata.isAccordEnabled())
|
||||
continue;
|
||||
|
||||
ColumnFamilyStore cfs = Keyspace.openAndGetStoreIfExists(tableMetadata);
|
||||
// TODO (required): when we can safely map TxnId.hlc() -> local timestamp, consult Memtable timestamps
|
||||
e.getValue().position = cfs.getCurrentMemtable().getCommitLogLowerBound();
|
||||
}
|
||||
|
||||
ScheduledExecutors.scheduledTasks.schedule(() -> {
|
||||
List<Future<?>> futures = new ArrayList<>();
|
||||
for (Map.Entry<TableId, SnapshotBounds> e : tables.entrySet())
|
||||
{
|
||||
TableMetadata tableMetadata = metadata.schema.getTableMetadata(e.getKey());
|
||||
SnapshotBounds bounds = e.getValue();
|
||||
ColumnFamilyStore cfs = Keyspace.openAndGetStoreIfExists(tableMetadata);
|
||||
View view = cfs.getTracker().getView();
|
||||
for (Memtable memtable : view.getAllMemtables())
|
||||
{
|
||||
if (memtable.getCommitLogLowerBound().compareTo(bounds.position) > 0) continue;
|
||||
if (!intersects(cfs, memtable, bounds.ranges)) continue;
|
||||
|
||||
futures.add(cfs.forceFlush(ACCORD_TXN_GC));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FutureCombiner.allOf(futures).addCallback((objects, throwable) -> {
|
||||
if (throwable != null)
|
||||
result.setFailure(throwable);
|
||||
else
|
||||
result.setSuccess(null);
|
||||
});
|
||||
}, 5L, TimeUnit.MINUTES);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean intersects(ColumnFamilyStore cfs, Memtable memtable, List<org.apache.cassandra.dht.Range<Token>> tableRanges)
|
||||
{
|
||||
boolean intersects = false;
|
||||
// TrieMemtable doesn't support reverse iteration so can't find the last token
|
||||
if (memtable instanceof TrieMemtable)
|
||||
intersects = true;
|
||||
else
|
||||
{
|
||||
Token firstToken = null;
|
||||
try (UnfilteredPartitionIterator iterator = memtable.partitionIterator(ColumnFilter.all(cfs.metadata()), DataRange.allData(cfs.getPartitioner()), SSTableReadsListener.NOOP_LISTENER))
|
||||
{
|
||||
if (iterator.hasNext())
|
||||
firstToken = iterator.next().partitionKey().getToken();
|
||||
}
|
||||
Token lastToken = memtable.lastToken();
|
||||
|
||||
if (firstToken != null)
|
||||
{
|
||||
checkState(lastToken != null);
|
||||
if (firstToken.equals(lastToken))
|
||||
{
|
||||
for (org.apache.cassandra.dht.Range<Token> tableRange : tableRanges)
|
||||
{
|
||||
if (tableRange.contains(firstToken))
|
||||
{
|
||||
intersects = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
checkState(firstToken.compareTo(lastToken) < 0);
|
||||
org.apache.cassandra.dht.Range<Token> memtableRange = new org.apache.cassandra.dht.Range<>(firstToken, lastToken);
|
||||
for (org.apache.cassandra.dht.Range<Token> tableRange : tableRanges)
|
||||
{
|
||||
if (tableRange.intersects(memtableRange))
|
||||
{
|
||||
intersects = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return intersects;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ public class AccordFetchCoordinator extends AbstractFetchCoordinator implements
|
|||
public Read slice(Ranges ranges) { return new StreamingRead(to, this.ranges.slice(ranges)); }
|
||||
|
||||
@Override
|
||||
public Read intersecting(Participants<?> participants) { return new StreamingRead(to, this.ranges.intersecting(ranges)); }
|
||||
public Read intersecting(Participants<?> participants) { return new StreamingRead(to, this.ranges.slice(ranges)); }
|
||||
|
||||
@Override
|
||||
public Read merge(Read other) { throw new UnsupportedOperationException(); }
|
||||
|
|
|
|||
|
|
@ -20,31 +20,33 @@ package org.apache.cassandra.service.accord;
|
|||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import accord.coordinate.Timeout;
|
||||
import accord.local.Command;
|
||||
import accord.local.CommandStores;
|
||||
import accord.local.CommandStores.RangesForEpoch;
|
||||
import accord.local.DurableBefore;
|
||||
import accord.local.Node;
|
||||
import accord.messages.ReplyContext;
|
||||
import accord.messages.Request;
|
||||
import accord.local.RedundantBefore;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Deps;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.utils.Invariants;
|
||||
import org.agrona.collections.Long2ObjectHashMap;
|
||||
import org.agrona.collections.LongArrayList;
|
||||
import org.apache.cassandra.concurrent.InfiniteLoopExecutor;
|
||||
import org.apache.cassandra.concurrent.Interruptible;
|
||||
import org.apache.cassandra.concurrent.ManyToOneConcurrentLinkedQueue;
|
||||
import org.apache.cassandra.concurrent.Shutdownable;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.io.util.DataInputBuffer;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.io.util.File;
|
||||
|
|
@ -52,19 +54,20 @@ import org.apache.cassandra.journal.Journal;
|
|||
import org.apache.cassandra.journal.Params;
|
||||
import org.apache.cassandra.journal.RecordPointer;
|
||||
import org.apache.cassandra.journal.ValueSerializer;
|
||||
import org.apache.cassandra.locator.InetAddressAndPort;
|
||||
import org.apache.cassandra.net.MessagingService;
|
||||
import org.apache.cassandra.net.ResponseContext;
|
||||
import org.apache.cassandra.net.Verb;
|
||||
import org.apache.cassandra.service.accord.AccordJournalValueSerializers.HistoricalTransactionsAccumulator;
|
||||
import org.apache.cassandra.service.accord.AccordJournalValueSerializers.IdentityAccumulator;
|
||||
import org.apache.cassandra.utils.ExecutorUtils;
|
||||
import org.apache.cassandra.utils.concurrent.Condition;
|
||||
|
||||
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
|
||||
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe.SAFE;
|
||||
import static org.apache.cassandra.concurrent.Interruptible.State.NORMAL;
|
||||
import static accord.primitives.Status.Truncated;
|
||||
import static org.apache.cassandra.service.accord.AccordJournalValueSerializers.DurableBeforeAccumulator;
|
||||
import static org.apache.cassandra.service.accord.AccordJournalValueSerializers.RedundantBeforeAccumulator;
|
||||
|
||||
public class AccordJournal implements IJournal, Shutdownable
|
||||
{
|
||||
|
||||
private final AtomicBoolean isReplay = new AtomicBoolean(false);
|
||||
|
||||
static
|
||||
{
|
||||
// make noise early if we forget to update our version mappings
|
||||
|
|
@ -75,13 +78,10 @@ public class AccordJournal implements IJournal, Shutdownable
|
|||
|
||||
private static final Set<Integer> SENTINEL_HOSTS = Collections.singleton(0);
|
||||
|
||||
static final ThreadLocal<byte[]> keyCRCBytes = ThreadLocal.withInitial(() -> new byte[22]);
|
||||
static final ThreadLocal<byte[]> keyCRCBytes = ThreadLocal.withInitial(() -> new byte[23]);
|
||||
|
||||
private final Journal<JournalKey, Object> journal;
|
||||
private final AccordJournalTable<JournalKey, Object> journalTable;
|
||||
private final AccordEndpointMapper endpointMapper;
|
||||
|
||||
private final DelayedRequestProcessor delayedRequestProcessor = new DelayedRequestProcessor();
|
||||
|
||||
Node node;
|
||||
|
||||
|
|
@ -89,30 +89,26 @@ public class AccordJournal implements IJournal, Shutdownable
|
|||
private volatile Status status = Status.INITIALIZED;
|
||||
|
||||
@VisibleForTesting
|
||||
public AccordJournal(AccordEndpointMapper endpointMapper, Params params)
|
||||
public AccordJournal(Params params)
|
||||
{
|
||||
File directory = new File(DatabaseDescriptor.getAccordJournalDirectory());
|
||||
this.journal = new Journal<>("AccordJournal", directory, params, JournalKey.SUPPORT,
|
||||
// In Accord, we are using streaming serialization, i.e. Reader/Writer interfaces instead of materializing objects
|
||||
new ValueSerializer<>()
|
||||
{
|
||||
public int serializedSize(JournalKey key, Object value, int userVersion)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(JournalKey key, Object value, DataOutputPlus out, int userVersion)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(JournalKey key, DataInputPlus in, int userVersion)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
},
|
||||
new AccordSegmentCompactor<>());
|
||||
this.endpointMapper = endpointMapper;
|
||||
new AccordSegmentCompactor<>(JournalKey.SUPPORT, params.userVersion()));
|
||||
this.journalTable = new AccordJournalTable<>(journal, JournalKey.SUPPORT, params.userVersion());
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +118,6 @@ public class AccordJournal implements IJournal, Shutdownable
|
|||
this.node = node;
|
||||
status = Status.STARTING;
|
||||
journal.start();
|
||||
delayedRequestProcessor.start();
|
||||
status = Status.STARTED;
|
||||
return this;
|
||||
}
|
||||
|
|
@ -138,7 +133,6 @@ public class AccordJournal implements IJournal, Shutdownable
|
|||
{
|
||||
Invariants.checkState(status == Status.STARTED);
|
||||
status = Status.TERMINATING;
|
||||
delayedRequestProcessor.shutdown();
|
||||
journal.shutdown();
|
||||
status = Status.TERMINATED;
|
||||
}
|
||||
|
|
@ -164,67 +158,120 @@ public class AccordJournal implements IJournal, Shutdownable
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accord protocol messages originating from remote nodes.
|
||||
*/
|
||||
public void processRemoteRequest(Request request, ResponseContext context)
|
||||
{
|
||||
RemoteRequestContext requestContext = RemoteRequestContext.forLive(request, context);
|
||||
if (node.topology().hasEpoch(request.waitForEpoch()))
|
||||
requestContext.process(node, endpointMapper);
|
||||
else
|
||||
delayedRequestProcessor.delay(requestContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command loadCommand(int commandStoreId, TxnId txnId)
|
||||
{
|
||||
try
|
||||
return loadDiffs(commandStoreId, txnId).construct();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public RedundantBefore loadRedundantBefore(int store)
|
||||
{
|
||||
RedundantBeforeAccumulator accumulator = readAll(new JournalKey(Timestamp.NONE, JournalKey.Type.REDUNDANT_BEFORE, store));
|
||||
return accumulator.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DurableBefore loadDurableBefore(int store)
|
||||
{
|
||||
DurableBeforeAccumulator accumulator = readAll(new JournalKey(Timestamp.NONE, JournalKey.Type.DURABLE_BEFORE, store));
|
||||
return accumulator.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableMap<TxnId, Ranges> loadBootstrapBeganAt(int store)
|
||||
{
|
||||
IdentityAccumulator<NavigableMap<TxnId, Ranges>> accumulator = readAll(new JournalKey(Timestamp.NONE, JournalKey.Type.BOOTSTRAP_BEGAN_AT, store));
|
||||
return accumulator.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableMap<Timestamp, Ranges> loadSafeToRead(int store)
|
||||
{
|
||||
IdentityAccumulator<NavigableMap<Timestamp, Ranges>> accumulator = readAll(new JournalKey(Timestamp.NONE, JournalKey.Type.SAFE_TO_READ, store));
|
||||
return accumulator.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandStores.RangesForEpoch.Snapshot loadRangesForEpoch(int store)
|
||||
{
|
||||
IdentityAccumulator<RangesForEpoch.Snapshot> accumulator = readAll(new JournalKey(Timestamp.NONE, JournalKey.Type.RANGES_FOR_EPOCH, store));
|
||||
return accumulator.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Deps> loadHistoricalTransactions(int store)
|
||||
{
|
||||
HistoricalTransactionsAccumulator accumulator = readAll(new JournalKey(Timestamp.NONE, JournalKey.Type.HISTORICAL_TRANSACTIONS, store));
|
||||
return accumulator.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendCommand(int store, SavedCommand.DiffWriter value, Runnable onFlush)
|
||||
{
|
||||
if (value == null || isReplay.get())
|
||||
{
|
||||
return loadDiffs(commandStoreId, txnId).construct();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// can only throw if serializer is buggy
|
||||
throw new RuntimeException(e);
|
||||
if (onFlush != null)
|
||||
onFlush.run();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: use same API for commands as for the other states?
|
||||
JournalKey key = new JournalKey(value.key(), JournalKey.Type.COMMAND_DIFF, store);
|
||||
RecordPointer pointer = journal.asyncWrite(key, value, SENTINEL_HOSTS);
|
||||
if (onFlush != null)
|
||||
journal.onFlush(pointer, onFlush);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void persistStoreState(int store, AccordSafeCommandStore.FieldUpdates fieldUpdates, Runnable onFlush)
|
||||
{
|
||||
RecordPointer pointer = null;
|
||||
// TODO: avoid allocating keys
|
||||
if (fieldUpdates.redundantBefore != null)
|
||||
pointer = appendInternal(new JournalKey(Timestamp.NONE, JournalKey.Type.REDUNDANT_BEFORE, store), fieldUpdates.redundantBefore);
|
||||
if (fieldUpdates.durableBefore != null)
|
||||
pointer = appendInternal(new JournalKey(Timestamp.NONE, JournalKey.Type.DURABLE_BEFORE, store), fieldUpdates.durableBefore);
|
||||
if (fieldUpdates.bootstrapBeganAt != null)
|
||||
pointer = appendInternal(new JournalKey(Timestamp.NONE, JournalKey.Type.BOOTSTRAP_BEGAN_AT, store), fieldUpdates.bootstrapBeganAt);
|
||||
if (fieldUpdates.safeToRead != null)
|
||||
pointer = appendInternal(new JournalKey(Timestamp.NONE, JournalKey.Type.SAFE_TO_READ, store), fieldUpdates.safeToRead);
|
||||
if (fieldUpdates.rangesForEpoch != null)
|
||||
pointer = appendInternal(new JournalKey(Timestamp.NONE, JournalKey.Type.RANGES_FOR_EPOCH, store), fieldUpdates.rangesForEpoch);
|
||||
if (fieldUpdates.historicalTransactions != null)
|
||||
pointer = appendInternal(new JournalKey(Timestamp.NONE, JournalKey.Type.HISTORICAL_TRANSACTIONS, store), fieldUpdates.historicalTransactions);
|
||||
|
||||
if (onFlush == null)
|
||||
return;
|
||||
|
||||
if (pointer != null)
|
||||
journal.onFlush(pointer, onFlush);
|
||||
else
|
||||
onFlush.run();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public SavedCommand.Builder loadDiffs(int commandStoreId, TxnId txnId)
|
||||
{
|
||||
JournalKey key = new JournalKey(txnId, commandStoreId);
|
||||
JournalKey key = new JournalKey(txnId, JournalKey.Type.COMMAND_DIFF, commandStoreId);
|
||||
SavedCommand.Builder builder = new SavedCommand.Builder();
|
||||
journalTable.readAll(key, (ignore, in, userVersion) -> builder.deserializeNext(in, userVersion));
|
||||
journalTable.readAll(key, builder::deserializeNext);
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendCommand(int commandStoreId, List<SavedCommand.Writer<TxnId>> outcomes, List<Command> sanityCheck, Runnable onFlush)
|
||||
private <BUILDER> BUILDER readAll(JournalKey key)
|
||||
{
|
||||
RecordPointer pointer = null;
|
||||
for (SavedCommand.Writer<TxnId> outcome : outcomes)
|
||||
{
|
||||
JournalKey key = new JournalKey(outcome.key(), commandStoreId);
|
||||
pointer = journal.asyncWrite(key, outcome, SENTINEL_HOSTS);
|
||||
}
|
||||
BUILDER builder = (BUILDER) key.type.serializer.mergerFor(key);
|
||||
// TODO: this can be further improved to avoid allocating lambdas
|
||||
AccordJournalValueSerializers.FlyweightSerializer<?, BUILDER> serializer = (AccordJournalValueSerializers.FlyweightSerializer<?, BUILDER>) key.type.serializer;
|
||||
journalTable.readAll(key, (in, userVersion) -> serializer.deserialize(key, builder, in, userVersion));
|
||||
return builder;
|
||||
}
|
||||
|
||||
// If we need to perform sanity check, we can only rely on blocking flushes. Otherwise, we may see into the future.
|
||||
if (sanityCheck != null)
|
||||
{
|
||||
Condition condition = Condition.newOneTimeCondition();
|
||||
journal.onFlush(pointer, condition::signal);
|
||||
condition.awaitUninterruptibly();
|
||||
|
||||
for (Command check : sanityCheck)
|
||||
sanityCheck(commandStoreId, check);
|
||||
|
||||
onFlush.run();
|
||||
}
|
||||
else
|
||||
{
|
||||
journal.onFlush(pointer, onFlush);
|
||||
}
|
||||
private RecordPointer appendInternal(JournalKey key, Object write)
|
||||
{
|
||||
AccordJournalValueSerializers.FlyweightSerializer<Object, ?> serializer = (AccordJournalValueSerializers.FlyweightSerializer<Object, ?>) key.type.serializer;
|
||||
return journal.asyncWrite(key, (out, userVersion) -> serializer.serialize(key, write, out, userVersion), SENTINEL_HOSTS);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
|
@ -235,234 +282,15 @@ public class AccordJournal implements IJournal, Shutdownable
|
|||
|
||||
public void sanityCheck(int commandStoreId, Command orig)
|
||||
{
|
||||
try
|
||||
{
|
||||
SavedCommand.Builder diffs = loadDiffs(commandStoreId, orig.txnId());
|
||||
diffs.forceResult(orig.result());
|
||||
// We can only use strict equality if we supply result.
|
||||
Command reconstructed = diffs.construct();
|
||||
Invariants.checkState(orig.equals(reconstructed),
|
||||
'\n' +
|
||||
"Original: %s\n" +
|
||||
"Reconstructed: %s\n" +
|
||||
"Diffs: %s", orig, reconstructed, diffs);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// can only throw if serializer is buggy
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Context necessary to process log records
|
||||
*/
|
||||
|
||||
static abstract class RequestContext implements ReplyContext
|
||||
{
|
||||
final long waitForEpoch;
|
||||
|
||||
RequestContext(long waitForEpoch)
|
||||
{
|
||||
this.waitForEpoch = waitForEpoch;
|
||||
}
|
||||
|
||||
public abstract void process(Node node, AccordEndpointMapper endpointMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Barebones response context not holding a reference to the entire message
|
||||
*/
|
||||
private abstract static class RemoteRequestContext extends RequestContext implements ResponseContext
|
||||
{
|
||||
private final Request request;
|
||||
|
||||
RemoteRequestContext(long waitForEpoch, Request request)
|
||||
{
|
||||
super(waitForEpoch);
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
static LiveRemoteRequestContext forLive(Request request, ResponseContext context)
|
||||
{
|
||||
return new LiveRemoteRequestContext(request, context.id(), context.from(), context.verb(), context.expiresAtNanos());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Node node, AccordEndpointMapper endpointMapper)
|
||||
{
|
||||
this.request.process(node, endpointMapper.mappedId(from()), this);
|
||||
}
|
||||
|
||||
@Override public abstract long id();
|
||||
@Override public abstract InetAddressAndPort from();
|
||||
@Override public abstract Verb verb();
|
||||
@Override public abstract long expiresAtNanos();
|
||||
}
|
||||
|
||||
// TODO: avoid distinguishing between live and non live
|
||||
private static class LiveRemoteRequestContext extends RemoteRequestContext
|
||||
{
|
||||
private final long id;
|
||||
private final InetAddressAndPort from;
|
||||
private final Verb verb;
|
||||
private final long expiresAtNanos;
|
||||
|
||||
LiveRemoteRequestContext(Request request, long id, InetAddressAndPort from, Verb verb, long expiresAtNanos)
|
||||
{
|
||||
super(request.waitForEpoch(), request);
|
||||
this.id = id;
|
||||
this.from = from;
|
||||
this.verb = verb;
|
||||
this.expiresAtNanos = expiresAtNanos;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long id()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
@Override
|
||||
public InetAddressAndPort from()
|
||||
{
|
||||
return from;
|
||||
}
|
||||
@Override
|
||||
public Verb verb()
|
||||
{
|
||||
return verb;
|
||||
}
|
||||
@Override
|
||||
public long expiresAtNanos()
|
||||
{
|
||||
return expiresAtNanos;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handling topology changes / epoch shift
|
||||
*/
|
||||
|
||||
private class DelayedRequestProcessor implements Interruptible.Task
|
||||
{
|
||||
private final ManyToOneConcurrentLinkedQueue<RequestContext> delayedRequests = new ManyToOneConcurrentLinkedQueue<>();
|
||||
private final LongArrayList waitForEpochs = new LongArrayList();
|
||||
private final Long2ObjectHashMap<List<RequestContext>> byEpoch = new Long2ObjectHashMap<>();
|
||||
private final AtomicReference<Condition> signal = new AtomicReference<>(Condition.newOneTimeCondition());
|
||||
private volatile Interruptible executor;
|
||||
|
||||
public void start()
|
||||
{
|
||||
executor = executorFactory().infiniteLoop("AccordJournal-delayed-request-processor", this, SAFE, InfiniteLoopExecutor.Daemon.NON_DAEMON, InfiniteLoopExecutor.Interrupts.SYNCHRONIZED);
|
||||
}
|
||||
|
||||
private void delay(RequestContext requestContext)
|
||||
{
|
||||
delayedRequests.add(requestContext);
|
||||
runOnce();
|
||||
}
|
||||
|
||||
private void runOnce()
|
||||
{
|
||||
signal.get().signal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(Interruptible.State state)
|
||||
{
|
||||
if (state != NORMAL || Thread.currentThread().isInterrupted() || !isRunnable(status))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Condition signal = Condition.newOneTimeCondition();
|
||||
this.signal.set(signal);
|
||||
// First, poll delayed requests, put them into by epoch
|
||||
while (!delayedRequests.isEmpty())
|
||||
{
|
||||
RequestContext context = delayedRequests.poll();
|
||||
long waitForEpoch = context.waitForEpoch;
|
||||
|
||||
List<RequestContext> l = byEpoch.computeIfAbsent(waitForEpoch, (ignore) -> new ArrayList<>());
|
||||
if (l.isEmpty())
|
||||
waitForEpochs.pushLong(waitForEpoch);
|
||||
l.add(context);
|
||||
BiConsumer<Void, Throwable> withEpochCallback = new BiConsumer<>()
|
||||
{
|
||||
@Override
|
||||
public void accept(Void unused, Throwable withEpochFailure)
|
||||
{
|
||||
if (withEpochFailure != null)
|
||||
{
|
||||
// Nothing to do but keep waiting
|
||||
if (withEpochFailure instanceof Timeout)
|
||||
{
|
||||
node.withEpoch(waitForEpoch, this);
|
||||
return;
|
||||
}
|
||||
else
|
||||
throw new RuntimeException(withEpochFailure);
|
||||
}
|
||||
runOnce();
|
||||
}
|
||||
};
|
||||
node.withEpoch(waitForEpoch, withEpochCallback);
|
||||
}
|
||||
|
||||
// Next, process all delayed epochs
|
||||
for (int i = 0; i < waitForEpochs.size(); i++)
|
||||
{
|
||||
long epoch = waitForEpochs.getLong(i);
|
||||
if (node.topology().hasEpoch(epoch))
|
||||
{
|
||||
List<RequestContext> requests = byEpoch.remove(epoch);
|
||||
assert requests != null : String.format("%s %s (%d)", byEpoch, waitForEpochs, epoch);
|
||||
for (RequestContext request : requests)
|
||||
{
|
||||
try
|
||||
{
|
||||
request.process(node, endpointMapper);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
logger.error("Caught an exception while processing a delayed request {}", request, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
waitForEpochs.removeIfLong(epoch -> !byEpoch.containsKey(epoch));
|
||||
|
||||
signal.await();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
logger.info("Delayed request processor thread interrupted. Shutting down.");
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
logger.error("Caught an exception in delayed processor", t);
|
||||
}
|
||||
}
|
||||
|
||||
private void shutdown()
|
||||
{
|
||||
executor.shutdown();
|
||||
try
|
||||
{
|
||||
executor.awaitTermination(1, TimeUnit.MINUTES);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRunnable(Status status)
|
||||
{
|
||||
return status != Status.TERMINATING && status != Status.TERMINATED;
|
||||
SavedCommand.Builder diffs = loadDiffs(commandStoreId, orig.txnId());
|
||||
diffs.forceResult(orig.result());
|
||||
// We can only use strict equality if we supply result.
|
||||
Command reconstructed = diffs.construct();
|
||||
Invariants.checkState(orig.equals(reconstructed),
|
||||
'\n' +
|
||||
"Original: %s\n" +
|
||||
"Reconstructed: %s\n" +
|
||||
"Diffs: %s", orig, reconstructed, diffs);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
|
@ -470,4 +298,83 @@ public class AccordJournal implements IJournal, Shutdownable
|
|||
{
|
||||
journal.truncateForTesting();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void runCompactorForTesting()
|
||||
{
|
||||
journal.runCompactorForTesting();
|
||||
}
|
||||
|
||||
public void replay()
|
||||
{
|
||||
// TODO: optimize replay memory footprint
|
||||
class ToApply
|
||||
{
|
||||
final JournalKey key;
|
||||
final Command command;
|
||||
|
||||
ToApply(JournalKey key, Command command)
|
||||
{
|
||||
this.key = key;
|
||||
this.command = command;
|
||||
}
|
||||
}
|
||||
|
||||
List<ToApply> toApply = new ArrayList<>();
|
||||
try (AccordJournalTable.KeyOrderIterator<JournalKey> iter = journalTable.readAll())
|
||||
{
|
||||
isReplay.set(true);
|
||||
|
||||
JournalKey key = null;
|
||||
SavedCommand.Builder builder = new SavedCommand.Builder();
|
||||
while ((key = iter.key()) != null)
|
||||
{
|
||||
builder.clear();
|
||||
if (key.type != JournalKey.Type.COMMAND_DIFF)
|
||||
{
|
||||
// TODO (required): add "skip" for the key to avoid getting stuck
|
||||
iter.readAllForKey(key, (segment, position, key1, buffer, hosts, userVersion) -> {});
|
||||
continue;
|
||||
}
|
||||
|
||||
JournalKey finalKey = key;
|
||||
iter.readAllForKey(key, (segment, position, local, buffer, hosts, userVersion) -> {
|
||||
Invariants.checkState(finalKey.equals(local));
|
||||
try (DataInputBuffer in = new DataInputBuffer(buffer, false))
|
||||
{
|
||||
builder.deserializeNext(in, userVersion);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// can only throw if serializer is buggy
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
if (builder.nextCalled)
|
||||
{
|
||||
Command command = builder.construct();
|
||||
AccordCommandStore commandStore = (AccordCommandStore) node.commandStores().forId(key.commandStoreId);
|
||||
commandStore.loader().load(command).get();
|
||||
if (command.saveStatus().compareTo(SaveStatus.Stable) >= 0 && !command.hasBeen(Truncated))
|
||||
toApply.add(new ToApply(key, command));
|
||||
}
|
||||
}
|
||||
|
||||
toApply.sort(Comparator.comparing(v -> v.command.executeAt()));
|
||||
for (ToApply apply : toApply)
|
||||
{
|
||||
AccordCommandStore commandStore = (AccordCommandStore) node.commandStores().forId(apply.key.commandStoreId);
|
||||
commandStore.loader().apply(apply.command);
|
||||
}
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new RuntimeException("Can not replay journal.", t);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isReplay.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
package org.apache.cassandra.service.accord;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -29,6 +30,7 @@ import org.apache.cassandra.cql3.ColumnIdentifier;
|
|||
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||
import org.apache.cassandra.db.ColumnFamilyStore.RefViewFragment;
|
||||
import org.apache.cassandra.db.DecoratedKey;
|
||||
import org.apache.cassandra.db.EmptyIterators;
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.db.Slices;
|
||||
import org.apache.cassandra.db.StorageHook;
|
||||
|
|
@ -37,10 +39,13 @@ import org.apache.cassandra.db.lifecycle.SSTableSet;
|
|||
import org.apache.cassandra.db.lifecycle.View;
|
||||
import org.apache.cassandra.db.marshal.Int32Type;
|
||||
import org.apache.cassandra.db.marshal.LongType;
|
||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.db.rows.Unfiltered;
|
||||
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
|
||||
import org.apache.cassandra.db.rows.UnfilteredRowIterators;
|
||||
import org.apache.cassandra.io.sstable.ISSTableScanner;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||
import org.apache.cassandra.io.util.DataInputBuffer;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
|
|
@ -76,16 +81,16 @@ public class AccordJournalTable<K, V>
|
|||
this.accordJournalVersion = accordJournalVersion;
|
||||
}
|
||||
|
||||
public interface Reader<K>
|
||||
public interface Reader
|
||||
{
|
||||
void read(K key, DataInputPlus input, int userVersion) throws IOException;
|
||||
void read(DataInputPlus input, int userVersion) throws IOException;
|
||||
}
|
||||
|
||||
private abstract class AbstractRecordConsumer implements RecordConsumer<K>
|
||||
{
|
||||
protected final Reader<K> reader;
|
||||
protected final Reader reader;
|
||||
|
||||
AbstractRecordConsumer(Reader<K> reader)
|
||||
AbstractRecordConsumer(Reader reader)
|
||||
{
|
||||
this.reader = reader;
|
||||
}
|
||||
|
|
@ -93,15 +98,7 @@ public class AccordJournalTable<K, V>
|
|||
@Override
|
||||
public void accept(long segment, int position, K key, ByteBuffer buffer, IntHashSet hosts, int userVersion)
|
||||
{
|
||||
try (DataInputBuffer in = new DataInputBuffer(buffer, false))
|
||||
{
|
||||
reader.read(key, in, userVersion);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// can only throw if serializer is buggy
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
readBuffer(buffer, reader, userVersion);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +106,7 @@ public class AccordJournalTable<K, V>
|
|||
{
|
||||
protected LongHashSet visited = null;
|
||||
|
||||
TableRecordConsumer(Reader<K> reader)
|
||||
TableRecordConsumer(Reader reader)
|
||||
{
|
||||
super(reader);
|
||||
}
|
||||
|
|
@ -139,7 +136,7 @@ public class AccordJournalTable<K, V>
|
|||
private final K key;
|
||||
private final TableRecordConsumer tableRecordConsumer;
|
||||
|
||||
JournalAndTableRecordConsumer(K key, Reader<K> reader)
|
||||
JournalAndTableRecordConsumer(K key, Reader reader)
|
||||
{
|
||||
super(reader);
|
||||
this.key = key;
|
||||
|
|
@ -165,7 +162,7 @@ public class AccordJournalTable<K, V>
|
|||
* <p>
|
||||
* When reading from journal segments, skip descriptors that were read from the table.
|
||||
*/
|
||||
public void readAll(K key, Reader<K> reader)
|
||||
public void readAll(K key, Reader reader)
|
||||
{
|
||||
journal.readAll(key, new JournalAndTableRecordConsumer(key, reader));
|
||||
}
|
||||
|
|
@ -195,20 +192,6 @@ public class AccordJournalTable<K, V>
|
|||
}
|
||||
}
|
||||
|
||||
public static <K> DecoratedKey makePartitionKey(ColumnFamilyStore cfs, K key, KeySupport<K> keySupport, int version)
|
||||
{
|
||||
try (DataOutputBuffer out = new DataOutputBuffer(keySupport.serializedSize(version)))
|
||||
{
|
||||
keySupport.serialize(key, out, version);
|
||||
return cfs.decorateKey(out.buffer(false));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// can only throw if (key) serializer is buggy
|
||||
throw new RuntimeException("Could not serialize key " + key + ", this shouldn't be possible", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void readRow(K key, Unfiltered unfiltered, EntryHolder<K> into, RecordConsumer<K> onEntry)
|
||||
{
|
||||
Invariants.checkState(unfiltered.isRow());
|
||||
|
|
@ -224,4 +207,167 @@ public class AccordJournalTable<K, V>
|
|||
|
||||
onEntry.accept(descriptor, position, into.key, into.value, into.hosts, into.userVersion);
|
||||
}
|
||||
|
||||
public static <K> DecoratedKey makePartitionKey(ColumnFamilyStore cfs, K key, KeySupport<K> keySupport, int version)
|
||||
{
|
||||
try (DataOutputBuffer out = new DataOutputBuffer(keySupport.serializedSize(version)))
|
||||
{
|
||||
keySupport.serialize(key, out, version);
|
||||
return cfs.decorateKey(out.buffer(false));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// can only throw if (key) serializer is buggy
|
||||
throw new RuntimeException("Could not serialize key " + key + ", this shouldn't be possible", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource") // Auto-closeable iterator will release related resources
|
||||
public KeyOrderIterator<K> readAll()
|
||||
{
|
||||
return new JournalAndTableKeyIterator();
|
||||
}
|
||||
|
||||
private class TableIterator implements Closeable
|
||||
{
|
||||
private final UnfilteredPartitionIterator mergeIterator;
|
||||
private final RefViewFragment view;
|
||||
|
||||
private UnfilteredRowIterator partition;
|
||||
private LongHashSet visited = null;
|
||||
|
||||
private TableIterator()
|
||||
{
|
||||
view = cfs.selectAndReference(v -> v.select(SSTableSet.LIVE));
|
||||
List<ISSTableScanner> scanners = new ArrayList<>();
|
||||
for (SSTableReader sstable : view.sstables)
|
||||
scanners.add(sstable.getScanner());
|
||||
|
||||
mergeIterator = view.sstables.isEmpty()
|
||||
? EmptyIterators.unfilteredPartition(cfs.metadata())
|
||||
: UnfilteredPartitionIterators.merge(scanners, UnfilteredPartitionIterators.MergeListener.NOOP);
|
||||
}
|
||||
|
||||
public K key()
|
||||
{
|
||||
if (partition == null)
|
||||
{
|
||||
if (mergeIterator.hasNext())
|
||||
partition = mergeIterator.next();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
return keySupport.deserialize(partition.partitionKey().getKey(), 0, accordJournalVersion);
|
||||
}
|
||||
|
||||
protected void readAllForKey(K key, RecordConsumer<K> recordConsumer)
|
||||
{
|
||||
while (partition.hasNext())
|
||||
{
|
||||
EntryHolder<K> into = new EntryHolder<>();
|
||||
// TODO: use flyweight to avoid allocating extra lambdas?
|
||||
readRow(key, partition.next(), into, (segment, position, key1, buffer, hosts, userVersion) -> {
|
||||
visit(segment);
|
||||
recordConsumer.accept(segment, position, key1, buffer, hosts, userVersion);
|
||||
});
|
||||
}
|
||||
|
||||
partition = null;
|
||||
}
|
||||
|
||||
void visit(long segment)
|
||||
{
|
||||
if (visited == null)
|
||||
visited = new LongHashSet();
|
||||
visited.add(segment);
|
||||
}
|
||||
|
||||
boolean visited(long segment)
|
||||
{
|
||||
return visited != null && visited.contains(segment);
|
||||
}
|
||||
|
||||
|
||||
void clear()
|
||||
{
|
||||
visited = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
mergeIterator.close();
|
||||
view.close();
|
||||
}
|
||||
}
|
||||
|
||||
private class JournalAndTableKeyIterator implements KeyOrderIterator<K>
|
||||
{
|
||||
final TableIterator tableIterator;
|
||||
final Journal<K, V>.StaticSegmentIterator staticSegmentIterator;
|
||||
|
||||
private JournalAndTableKeyIterator()
|
||||
{
|
||||
this.tableIterator = new TableIterator();
|
||||
this.staticSegmentIterator = journal.staticSegmentIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public K key()
|
||||
{
|
||||
K tableKey = tableIterator.key();
|
||||
K journalKey = staticSegmentIterator.key();
|
||||
if (tableKey == null)
|
||||
return journalKey;
|
||||
if (journalKey == null || keySupport.compare(tableKey, journalKey) > 0)
|
||||
return journalKey;
|
||||
|
||||
return tableKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAllForKey(K key, RecordConsumer<K> reader)
|
||||
{
|
||||
K tableKey = tableIterator.key();
|
||||
K journalKey = staticSegmentIterator.key();
|
||||
if (tableKey != null && keySupport.compare(tableKey, key) == 0)
|
||||
tableIterator.readAllForKey(key, reader);
|
||||
|
||||
if (journalKey != null && keySupport.compare(journalKey, key) == 0)
|
||||
staticSegmentIterator.readAllForKey(key, (segment, position, key1, buffer, hosts, userVersion) -> {
|
||||
if (!tableIterator.visited(segment))
|
||||
reader.accept(segment, position, key1, buffer, hosts, userVersion);
|
||||
});
|
||||
|
||||
tableIterator.clear();
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
tableIterator.close();
|
||||
staticSegmentIterator.close();
|
||||
}
|
||||
}
|
||||
|
||||
public interface KeyOrderIterator<K> extends Closeable
|
||||
{
|
||||
K key();
|
||||
void readAllForKey(K key, RecordConsumer<K> reader);
|
||||
void close();
|
||||
}
|
||||
|
||||
public static void readBuffer(ByteBuffer buffer, Reader reader, int userVersion)
|
||||
{
|
||||
try (DataInputBuffer in = new DataInputBuffer(buffer, false))
|
||||
{
|
||||
reader.read(in, userVersion);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// can only throw if serializer is buggy
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,383 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.service.accord;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.NavigableMap;
|
||||
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
|
||||
import accord.local.DurableBefore;
|
||||
import accord.local.RedundantBefore;
|
||||
import accord.primitives.Deps;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.net.MessagingService;
|
||||
import org.apache.cassandra.service.accord.serializers.CommandStoreSerializers;
|
||||
import org.apache.cassandra.service.accord.serializers.KeySerializers;
|
||||
|
||||
import static accord.local.CommandStores.RangesForEpoch;
|
||||
import static org.apache.cassandra.service.accord.serializers.DepsSerializer.deps;
|
||||
|
||||
// TODO (required): test with large collection values, and perhaps split out some fields if they have a tendency to grow larger
|
||||
// TODO (required): alert on metadata size
|
||||
// TODO (required): versioning
|
||||
public class AccordJournalValueSerializers
|
||||
{
|
||||
private static final int messagingVersion = MessagingService.VERSION_40;
|
||||
public interface FlyweightSerializer<ENTRY, IMAGE>
|
||||
{
|
||||
IMAGE mergerFor(JournalKey key);
|
||||
|
||||
void serialize(JournalKey key, ENTRY from, DataOutputPlus out, int userVersion) throws IOException;
|
||||
|
||||
void reserialize(JournalKey key, IMAGE from, DataOutputPlus out, int userVersion) throws IOException;
|
||||
|
||||
void deserialize(JournalKey key, IMAGE into, DataInputPlus in, int userVersion) throws IOException;
|
||||
}
|
||||
|
||||
public static class CommandDiffSerializer
|
||||
implements FlyweightSerializer<SavedCommand.DiffWriter, SavedCommand.Builder>
|
||||
{
|
||||
@Override
|
||||
public SavedCommand.Builder mergerFor(JournalKey journalKey)
|
||||
{
|
||||
return new SavedCommand.Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(JournalKey key, SavedCommand.DiffWriter writer, DataOutputPlus out, int userVersion)
|
||||
{
|
||||
try
|
||||
{
|
||||
writer.write(out, userVersion);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reserialize(JournalKey key, SavedCommand.Builder from, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
from.serialize(out, userVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserialize(JournalKey journalKey, SavedCommand.Builder into, DataInputPlus in, int userVersion) throws IOException
|
||||
{
|
||||
into.deserializeNext(in, userVersion);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class Accumulator<A, V>
|
||||
{
|
||||
protected A accumulated;
|
||||
|
||||
public Accumulator(A initial)
|
||||
{
|
||||
this.accumulated = initial;
|
||||
}
|
||||
|
||||
protected void update(V newValue)
|
||||
{
|
||||
accumulated = accumulate(accumulated, newValue);
|
||||
}
|
||||
|
||||
protected abstract A accumulate(A oldValue, V newValue);
|
||||
|
||||
public A get()
|
||||
{
|
||||
return accumulated;
|
||||
}
|
||||
}
|
||||
|
||||
public static class IdentityAccumulator<T> extends Accumulator<T, T>
|
||||
{
|
||||
public IdentityAccumulator(T initial)
|
||||
{
|
||||
super(initial);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T accumulate(T oldValue, T newValue)
|
||||
{
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RedundantBeforeAccumulator extends Accumulator<RedundantBefore, RedundantBefore>
|
||||
{
|
||||
public RedundantBeforeAccumulator()
|
||||
{
|
||||
super(RedundantBefore.EMPTY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RedundantBefore accumulate(RedundantBefore oldValue, RedundantBefore newValue)
|
||||
{
|
||||
return RedundantBefore.merge(oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RedundantBeforeSerializer
|
||||
implements FlyweightSerializer<RedundantBefore, RedundantBeforeAccumulator>
|
||||
{
|
||||
@Override
|
||||
public RedundantBeforeAccumulator mergerFor(JournalKey journalKey)
|
||||
{
|
||||
return new RedundantBeforeAccumulator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(JournalKey key, RedundantBefore entry, DataOutputPlus out, int userVersion)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (entry == RedundantBefore.EMPTY)
|
||||
{
|
||||
out.writeInt(0);
|
||||
return;
|
||||
}
|
||||
out.writeInt(1);
|
||||
CommandStoreSerializers.redundantBefore.serialize(entry, out, messagingVersion);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reserialize(JournalKey key, RedundantBeforeAccumulator from, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
serialize(key, from.get(), out, userVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserialize(JournalKey journalKey, RedundantBeforeAccumulator into, DataInputPlus in, int userVersion) throws IOException
|
||||
{
|
||||
if (in.readInt() == 0)
|
||||
{
|
||||
into.update(RedundantBefore.EMPTY);
|
||||
return;
|
||||
}
|
||||
into.update(CommandStoreSerializers.redundantBefore.deserialize(in, messagingVersion));
|
||||
}
|
||||
}
|
||||
|
||||
public static class DurableBeforeAccumulator extends Accumulator<DurableBefore, DurableBefore>
|
||||
{
|
||||
public DurableBeforeAccumulator()
|
||||
{
|
||||
super(DurableBefore.EMPTY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DurableBefore accumulate(DurableBefore oldValue, DurableBefore newValue)
|
||||
{
|
||||
return DurableBefore.merge(oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DurableBeforeSerializer implements FlyweightSerializer<DurableBefore, DurableBeforeAccumulator>
|
||||
{
|
||||
public DurableBeforeAccumulator mergerFor(JournalKey journalKey)
|
||||
{
|
||||
return new DurableBeforeAccumulator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(JournalKey key, DurableBefore entry, DataOutputPlus out, int userVersion)
|
||||
{
|
||||
try
|
||||
{
|
||||
CommandStoreSerializers.durableBefore.serialize(entry, out, messagingVersion);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reserialize(JournalKey key, DurableBeforeAccumulator from, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
serialize(key, from.get(), out, userVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserialize(JournalKey journalKey, DurableBeforeAccumulator into, DataInputPlus in, int userVersion) throws IOException
|
||||
{
|
||||
// TODO: maybe using local serializer is not the best call here, but how do we distinguish
|
||||
// between messaging and disk versioning?
|
||||
into.update(CommandStoreSerializers.durableBefore.deserialize(in, messagingVersion));
|
||||
}
|
||||
}
|
||||
|
||||
public static class BootstrapBeganAtSerializer
|
||||
implements FlyweightSerializer<NavigableMap<TxnId, Ranges>, IdentityAccumulator<NavigableMap<TxnId, Ranges>>>
|
||||
{
|
||||
@Override
|
||||
public IdentityAccumulator<NavigableMap<TxnId, Ranges>> mergerFor(JournalKey key)
|
||||
{
|
||||
return new IdentityAccumulator<>(ImmutableSortedMap.of(TxnId.NONE, Ranges.EMPTY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(JournalKey key, NavigableMap<TxnId, Ranges> entry, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
CommandStoreSerializers.bootstrapBeganAt.serialize(entry, out, messagingVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reserialize(JournalKey key, IdentityAccumulator<NavigableMap<TxnId, Ranges>> image, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
serialize(key, image.get(), out, userVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserialize(JournalKey key, IdentityAccumulator<NavigableMap<TxnId, Ranges>> into, DataInputPlus in, int userVersion) throws IOException
|
||||
{
|
||||
into.update(CommandStoreSerializers.bootstrapBeganAt.deserialize(in, messagingVersion));
|
||||
}
|
||||
}
|
||||
|
||||
public static class SafeToReadSerializer implements FlyweightSerializer<NavigableMap<Timestamp, Ranges>, IdentityAccumulator<NavigableMap<Timestamp, Ranges>>>
|
||||
{
|
||||
@Override
|
||||
public IdentityAccumulator<NavigableMap<Timestamp, Ranges>> mergerFor(JournalKey key)
|
||||
{
|
||||
return new IdentityAccumulator<>(ImmutableSortedMap.of(Timestamp.NONE, Ranges.EMPTY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(JournalKey key, NavigableMap<Timestamp, Ranges> from, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
CommandStoreSerializers.safeToRead.serialize(from, out, messagingVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reserialize(JournalKey key, IdentityAccumulator<NavigableMap<Timestamp, Ranges>> from, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
serialize(key, from.get(), out, userVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserialize(JournalKey key, IdentityAccumulator<NavigableMap<Timestamp, Ranges>> into, DataInputPlus in, int userVersion) throws IOException
|
||||
{
|
||||
into.update(CommandStoreSerializers.safeToRead.deserialize(in, messagingVersion));
|
||||
}
|
||||
}
|
||||
|
||||
public static class RangesForEpochSerializer
|
||||
implements FlyweightSerializer<RangesForEpoch.Snapshot, IdentityAccumulator<RangesForEpoch.Snapshot>>
|
||||
{
|
||||
|
||||
public IdentityAccumulator<RangesForEpoch.Snapshot> mergerFor(JournalKey key)
|
||||
{
|
||||
return new IdentityAccumulator<>(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(JournalKey key, RangesForEpoch.Snapshot from, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
out.writeUnsignedVInt32(from.ranges.length);
|
||||
for (Ranges ranges : from.ranges)
|
||||
KeySerializers.ranges.serialize(ranges, out, messagingVersion);
|
||||
|
||||
out.writeUnsignedVInt32(from.epochs.length);
|
||||
for (long epoch : from.epochs)
|
||||
out.writeLong(epoch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reserialize(JournalKey key, IdentityAccumulator<RangesForEpoch.Snapshot> from, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
serialize(key, from.get(), out, messagingVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserialize(JournalKey key, IdentityAccumulator<RangesForEpoch.Snapshot> into, DataInputPlus in, int userVersion) throws IOException
|
||||
{
|
||||
Ranges[] ranges = new Ranges[in.readUnsignedVInt32()];
|
||||
for (int i = 0; i < ranges.length; i++)
|
||||
ranges[i] = KeySerializers.ranges.deserialize(in, messagingVersion);
|
||||
|
||||
long[] epochs = new long[in.readUnsignedVInt32()];
|
||||
for (int i = 0; i < epochs.length; i++)
|
||||
epochs[i] = in.readLong(); // TODO: assert lengths equal?
|
||||
|
||||
into.update(new RangesForEpoch.Snapshot(epochs, ranges));
|
||||
}
|
||||
}
|
||||
|
||||
public static class HistoricalTransactionsAccumulator extends Accumulator<List<Deps>, Deps>
|
||||
{
|
||||
public HistoricalTransactionsAccumulator()
|
||||
{
|
||||
super(new ArrayList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Deps> accumulate(List<Deps> oldValue, Deps deps)
|
||||
{
|
||||
accumulated.add(deps); // we can keep it mutable
|
||||
return accumulated;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HistoricalTransactionsSerializer implements FlyweightSerializer<Deps, HistoricalTransactionsAccumulator>
|
||||
{
|
||||
@Override
|
||||
public HistoricalTransactionsAccumulator mergerFor(JournalKey key)
|
||||
{
|
||||
return new HistoricalTransactionsAccumulator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(JournalKey key, Deps from, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
out.writeUnsignedVInt32(1);
|
||||
deps.serialize(from, out, messagingVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reserialize(JournalKey key, HistoricalTransactionsAccumulator from, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
out.writeUnsignedVInt32(from.get().size());
|
||||
for (Deps d : from.get())
|
||||
deps.serialize(d, out, messagingVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserialize(JournalKey key, HistoricalTransactionsAccumulator into, DataInputPlus in, int userVersion) throws IOException
|
||||
{
|
||||
int count = in.readUnsignedVInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
into.update(deps.deserialize(in, messagingVersion));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,14 +24,11 @@ import java.nio.ByteBuffer;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Nullable;
|
||||
|
|
@ -39,36 +36,31 @@ import javax.annotation.Nullable;
|
|||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import accord.api.Key;
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.StoreParticipants;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.impl.TimestampsForKey;
|
||||
import accord.local.Command;
|
||||
import accord.local.CommandStore;
|
||||
import accord.local.DurableBefore;
|
||||
import accord.local.Node;
|
||||
import accord.local.RedundantBefore;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.local.Status;
|
||||
import accord.local.Status.Durability;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.Status.Durability;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.topology.Topology;
|
||||
import accord.utils.Invariants;
|
||||
import accord.utils.ReducingRangeMap;
|
||||
import accord.utils.async.Observable;
|
||||
import org.apache.cassandra.concurrent.Stage;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.QueryOptions;
|
||||
import org.apache.cassandra.cql3.QueryProcessor;
|
||||
import org.apache.cassandra.cql3.UntypedResultSet;
|
||||
import org.apache.cassandra.cql3.statements.ModificationStatement;
|
||||
import org.apache.cassandra.cql3.statements.schema.CreateTableStatement;
|
||||
import org.apache.cassandra.db.Clustering;
|
||||
import org.apache.cassandra.db.ClusteringComparator;
|
||||
|
|
@ -76,7 +68,6 @@ import org.apache.cassandra.db.ColumnFamilyStore;
|
|||
import org.apache.cassandra.db.Columns;
|
||||
import org.apache.cassandra.db.DecoratedKey;
|
||||
import org.apache.cassandra.db.DeletionTime;
|
||||
import org.apache.cassandra.db.IMutation;
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.db.LivenessInfo;
|
||||
import org.apache.cassandra.db.Mutation;
|
||||
|
|
@ -121,6 +112,7 @@ import org.apache.cassandra.dht.Token;
|
|||
import org.apache.cassandra.index.accord.RouteIndex;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
import org.apache.cassandra.io.LocalVersionedSerializer;
|
||||
import org.apache.cassandra.io.MessageVersionProvider;
|
||||
import org.apache.cassandra.io.util.DataInputBuffer;
|
||||
import org.apache.cassandra.io.util.DataOutputBuffer;
|
||||
import org.apache.cassandra.schema.ColumnMetadata;
|
||||
|
|
@ -138,17 +130,14 @@ import org.apache.cassandra.schema.Types;
|
|||
import org.apache.cassandra.schema.UserFunctions;
|
||||
import org.apache.cassandra.schema.Views;
|
||||
import org.apache.cassandra.serializers.UUIDSerializer;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.service.accord.AccordConfigurationService.SyncStatus;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
|
||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
||||
import org.apache.cassandra.service.accord.serializers.AccordRoutingKeyByteSource;
|
||||
import org.apache.cassandra.service.accord.serializers.CommandSerializers;
|
||||
import org.apache.cassandra.service.accord.serializers.CommandStoreSerializers;
|
||||
import org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer;
|
||||
import org.apache.cassandra.service.accord.serializers.KeySerializers;
|
||||
import org.apache.cassandra.service.accord.serializers.TopologySerializers;
|
||||
import org.apache.cassandra.transport.Dispatcher;
|
||||
import org.apache.cassandra.utils.Clock.Global;
|
||||
import org.apache.cassandra.utils.CloseableIterator;
|
||||
import org.apache.cassandra.utils.btree.BTree;
|
||||
|
|
@ -160,12 +149,12 @@ import static accord.utils.Invariants.checkArgument;
|
|||
import static accord.utils.Invariants.checkState;
|
||||
import static java.lang.String.format;
|
||||
import static org.apache.cassandra.cql3.QueryProcessor.executeInternal;
|
||||
import static org.apache.cassandra.cql3.QueryProcessor.executeOnceInternal;
|
||||
import static org.apache.cassandra.db.partitions.PartitionUpdate.singleRowUpdate;
|
||||
import static org.apache.cassandra.db.rows.BTreeRow.singleCellRow;
|
||||
import static org.apache.cassandra.db.rows.BufferCell.live;
|
||||
import static org.apache.cassandra.db.rows.BufferCell.tombstone;
|
||||
import static org.apache.cassandra.schema.SchemaConstants.ACCORD_KEYSPACE_NAME;
|
||||
import static org.apache.cassandra.service.accord.serializers.AccordRoutingKeyByteSource.currentVersion;
|
||||
import static org.apache.cassandra.service.accord.serializers.KeySerializers.blobMapToRanges;
|
||||
import static org.apache.cassandra.utils.ByteBufferUtil.EMPTY_BYTE_BUFFER;
|
||||
import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
|
||||
|
|
@ -180,11 +169,9 @@ public class AccordKeyspace
|
|||
public static final String COMMANDS_FOR_KEY = "commands_for_key";
|
||||
public static final String TOPOLOGIES = "topologies";
|
||||
public static final String EPOCH_METADATA = "epoch_metadata";
|
||||
public static final String COMMAND_STORE_METADATA = "command_store_metadata";
|
||||
|
||||
public static final Set<String> TABLE_NAMES = ImmutableSet.of(COMMANDS, TIMESTAMPS_FOR_KEY, COMMANDS_FOR_KEY,
|
||||
TOPOLOGIES, EPOCH_METADATA,
|
||||
COMMAND_STORE_METADATA,
|
||||
JOURNAL);
|
||||
|
||||
private static final TupleType TIMESTAMP_TYPE = new TupleType(Lists.newArrayList(LongType.instance, LongType.instance, Int32Type.instance));
|
||||
|
|
@ -262,27 +249,22 @@ public class AccordKeyspace
|
|||
+ "domain int," // this is stored as part of txn_id, used currently for cheaper scans of the table
|
||||
+ format("txn_id %s,", TIMESTAMP_TUPLE)
|
||||
+ "status int,"
|
||||
+ "route blob,"
|
||||
+ "participants blob,"
|
||||
+ "durability int,"
|
||||
+ format("execute_at %s,", TIMESTAMP_TUPLE)
|
||||
+ "PRIMARY KEY((store_id, domain, txn_id))"
|
||||
+ ')')
|
||||
.partitioner(new LocalPartitioner(CompositeType.getInstance(Int32Type.instance, Int32Type.instance, TIMESTAMP_TYPE)))
|
||||
.indexes(Indexes.builder()
|
||||
.add(IndexMetadata.fromSchemaMetadata("route", IndexMetadata.Kind.CUSTOM, ImmutableMap.of("class_name", RouteIndex.class.getCanonicalName(), "target", "route")))
|
||||
.add(IndexMetadata.fromSchemaMetadata("route", IndexMetadata.Kind.CUSTOM, ImmutableMap.of("class_name", RouteIndex.class.getCanonicalName(), "target", "participants")))
|
||||
.build())
|
||||
.build();
|
||||
|
||||
// TODO: naming is not very clearly distinct from the base serializers
|
||||
public static class LocalVersionedSerializers
|
||||
{
|
||||
static final LocalVersionedSerializer<Route<?>> route = localSerializer(KeySerializers.route);
|
||||
static final LocalVersionedSerializer<StoreParticipants> participants = localSerializer(CommandSerializers.participants);
|
||||
static final LocalVersionedSerializer<Topology> topology = localSerializer(TopologySerializers.topology);
|
||||
static final LocalVersionedSerializer<ReducingRangeMap<Timestamp>> rejectBefore = localSerializer(CommandStoreSerializers.rejectBefore);
|
||||
static final LocalVersionedSerializer<DurableBefore> durableBefore = localSerializer(CommandStoreSerializers.durableBefore);
|
||||
static final LocalVersionedSerializer<RedundantBefore> redundantBefore = localSerializer(CommandStoreSerializers.redundantBefore);
|
||||
static final LocalVersionedSerializer<NavigableMap<TxnId, Ranges>> bootstrapBeganAt = localSerializer(CommandStoreSerializers.bootstrapBeganAt);
|
||||
static final LocalVersionedSerializer<NavigableMap<Timestamp, Ranges>> safeToRead = localSerializer(CommandStoreSerializers.safeToRead);
|
||||
|
||||
private static <T> LocalVersionedSerializer<T> localSerializer(IVersionedSerializer<T> serializer)
|
||||
{
|
||||
|
|
@ -305,12 +287,12 @@ public class AccordKeyspace
|
|||
public static final ColumnMetadata txn_id = getColumn(Commands, "txn_id");
|
||||
public static final ColumnMetadata store_id = getColumn(Commands, "store_id");
|
||||
public static final ColumnMetadata status = getColumn(Commands, "status");
|
||||
public static final ColumnMetadata route = getColumn(Commands, "route");
|
||||
public static final ColumnMetadata participants = getColumn(Commands, "participants");
|
||||
public static final ColumnMetadata durability = getColumn(Commands, "durability");
|
||||
public static final ColumnMetadata execute_at = getColumn(Commands, "execute_at");
|
||||
|
||||
public static final ColumnMetadata[] TRUNCATE_FIELDS = new ColumnMetadata[] { durability, execute_at, route, status };
|
||||
public static final ColumnMetadata[] INVALIDATE_FIELDS = new ColumnMetadata[] { status };
|
||||
public static final ColumnMetadata[] TRUNCATE_FIELDS = new ColumnMetadata[] { durability, execute_at, participants, status };
|
||||
public static final ColumnMetadata[] SAVE_STATUS_ONLY_FIELDS = new ColumnMetadata[] { status };
|
||||
|
||||
static
|
||||
{
|
||||
|
|
@ -374,33 +356,67 @@ public class AccordKeyspace
|
|||
@Nullable
|
||||
public static Route<?> getRoute(Row row)
|
||||
{
|
||||
Cell<?> cell = row.getCell(route);
|
||||
return deserializeRouteOrNull(cell);
|
||||
Cell<?> cell = row.getCell(participants);
|
||||
StoreParticipants participants = deserializeParticipantsOrNull(cell);
|
||||
return participants == null ? null : participants.route();
|
||||
}
|
||||
|
||||
private static Object[] truncatedApplyLeaf(long newTimestamp, SaveStatus newSaveStatus, Cell<?> durabilityCell, Cell<?> executeAtCell, Cell<?> routeCell, boolean updateTimestamps)
|
||||
private static Object[] truncatedApplyLeaf(long newTimestamp, SaveStatus newSaveStatus, @Nullable Cell<?> durabilityCell, @Nullable Cell<?> executeAtCell, @Nullable Cell<?> participantsCell, boolean updateTimestamps)
|
||||
{
|
||||
checkArgument(durabilityCell.column() == CommandsColumns.durability);
|
||||
checkArgument(executeAtCell.column() == CommandsColumns.execute_at);
|
||||
checkArgument(routeCell.column() == CommandsColumns.route);
|
||||
Object[] newLeaf = BTree.unsafeAllocateNonEmptyLeaf(TRUNCATE_FIELDS.length);
|
||||
int count = 1 + (durabilityCell != null ? 1 : 0) + (executeAtCell != null ? 1 : 0) + (participantsCell != null ? 1 : 0);
|
||||
Object[] newLeaf = BTree.unsafeAllocateNonEmptyLeaf(count);
|
||||
int colIndex = 0;
|
||||
newLeaf[colIndex++] = updateTimestamps ? durabilityCell.withUpdatedTimestamp(newTimestamp) : durabilityCell;
|
||||
newLeaf[colIndex++] = updateTimestamps ? executeAtCell.withUpdatedTimestamp(newTimestamp) : executeAtCell;
|
||||
newLeaf[colIndex++] = updateTimestamps ? routeCell.withUpdatedTimestamp(newTimestamp) : routeCell;
|
||||
// Status always needs to use the new timestamp since we are replacing the existing value
|
||||
// All the other columns are being retained unmodified with at most updated timestamps to accomdate deletion
|
||||
//noinspection UnusedAssignment
|
||||
newLeaf[colIndex++] = BufferCell.live(status, newTimestamp, ByteBufferAccessor.instance.valueOf(newSaveStatus.ordinal()));
|
||||
if (durabilityCell != null)
|
||||
{
|
||||
checkArgument(durabilityCell.column() == CommandsColumns.durability);
|
||||
newLeaf[colIndex++] = updateTimestamps ? durabilityCell.withUpdatedTimestamp(newTimestamp) : durabilityCell;
|
||||
}
|
||||
if (executeAtCell != null)
|
||||
{
|
||||
checkArgument(executeAtCell.column() == CommandsColumns.execute_at);
|
||||
newLeaf[colIndex++] = updateTimestamps ? executeAtCell.withUpdatedTimestamp(newTimestamp) : executeAtCell;
|
||||
}
|
||||
if (participantsCell != null)
|
||||
{
|
||||
checkArgument(participantsCell.column() == CommandsColumns.participants);
|
||||
newLeaf[colIndex++] = updateTimestamps ? participantsCell.withUpdatedTimestamp(newTimestamp) : participantsCell;
|
||||
}
|
||||
newLeaf[colIndex] = BufferCell.live(status, newTimestamp, ByteBufferAccessor.instance.valueOf(newSaveStatus.ordinal()));
|
||||
return newLeaf;
|
||||
}
|
||||
|
||||
public static Row invalidated(SaveStatus newSaveStatus, Row row, long nowInSec)
|
||||
private static Object[] expungePartialLeaf(Cell<?> durabilityCell, Cell<?> executeAtCell, Cell<?> participantsCell)
|
||||
{
|
||||
int count = (durabilityCell != null ? 1 : 0) + (executeAtCell != null ? 1 : 0) + (participantsCell != null ? 1 : 0);
|
||||
if (count == 0)
|
||||
return null;
|
||||
|
||||
Object[] newLeaf = BTree.unsafeAllocateNonEmptyLeaf(count);
|
||||
int colIndex = 0;
|
||||
if (durabilityCell != null)
|
||||
{
|
||||
checkArgument(durabilityCell.column() == CommandsColumns.durability);
|
||||
newLeaf[colIndex++] = durabilityCell;
|
||||
}
|
||||
if (executeAtCell != null)
|
||||
{
|
||||
checkArgument(executeAtCell.column() == CommandsColumns.execute_at);
|
||||
newLeaf[colIndex++] = executeAtCell;
|
||||
}
|
||||
if (participantsCell != null)
|
||||
{
|
||||
checkArgument(participantsCell.column() == CommandsColumns.participants);
|
||||
newLeaf[colIndex] = participantsCell;
|
||||
}
|
||||
return newLeaf;
|
||||
}
|
||||
|
||||
public static Row saveStatusOnly(SaveStatus newSaveStatus, Row row, long nowInSec)
|
||||
{
|
||||
long oldTimestamp = row.primaryKeyLivenessInfo().timestamp();
|
||||
long newTimestamp = oldTimestamp + 1;
|
||||
|
||||
Object[] newLeaf = invalidatedLeaf(newTimestamp, newSaveStatus);
|
||||
Object[] newLeaf = saveStatusOnlyLeaf(newTimestamp, newSaveStatus);
|
||||
|
||||
// Including a deletion allows future compactions to drop data before it gets to the purger
|
||||
// but it is pretty optional because maybeDropTruncatedCommandColumns will drop the extra columns
|
||||
|
|
@ -409,9 +425,9 @@ public class AccordKeyspace
|
|||
return BTreeRow.create(row.clustering(), LivenessInfo.create(newTimestamp, nowInSec), deletion, newLeaf);
|
||||
}
|
||||
|
||||
private static Object[] invalidatedLeaf(long newTimestamp, SaveStatus newSaveStatus)
|
||||
private static Object[] saveStatusOnlyLeaf(long newTimestamp, SaveStatus newSaveStatus)
|
||||
{
|
||||
Object[] newLeaf = BTree.unsafeAllocateNonEmptyLeaf(INVALIDATE_FIELDS.length);
|
||||
Object[] newLeaf = BTree.unsafeAllocateNonEmptyLeaf(SAVE_STATUS_ONLY_FIELDS.length);
|
||||
int colIndex = 0;
|
||||
// Status always needs to use the new timestamp since we are replacing the existing value
|
||||
// All the other columns are being retained unmodified with at most updated timestamps to accomdate deletion
|
||||
|
|
@ -420,17 +436,10 @@ public class AccordKeyspace
|
|||
return newLeaf;
|
||||
}
|
||||
|
||||
public static Row truncatedApply(SaveStatus newSaveStatus, Row row, long nowInSec, Durability durability, Cell<?> durabilityCell, Cell<?> executeAtCell, Cell<?> routeCell, boolean withOutcome)
|
||||
public static Row truncatedApply(SaveStatus newSaveStatus, Row row, long nowInSec, Durability durability, @Nullable Cell<?> durabilityCell, @Nullable Cell<?> executeAtCell, @Nullable Cell<?> participantsCell, boolean withOutcome)
|
||||
{
|
||||
checkArgument(durabilityCell.column() == CommandsColumns.durability);
|
||||
checkArgument(executeAtCell.column() == CommandsColumns.execute_at);
|
||||
checkArgument(routeCell.column() == CommandsColumns.route);
|
||||
long oldTimestamp = row.primaryKeyLivenessInfo().timestamp();
|
||||
long newTimestamp = oldTimestamp + 1;
|
||||
// If durability is not universal we don't want to delete older versions of the row that might have recorded
|
||||
// a higher durability value. maybeDropTruncatedCommandColumns will take care of dropping things even if we don't drop via tombstones.
|
||||
// durability should be the only column that could have an older value that is insufficient for propagating forward
|
||||
// TODO (now): with UniversalOrInvalidated should this change?
|
||||
boolean doDeletion = durability == Durability.Universal;
|
||||
|
||||
// We may not have what we need to generate a deletion and include the outcome in the truncated row
|
||||
|
|
@ -438,7 +447,7 @@ public class AccordKeyspace
|
|||
if (withOutcome)
|
||||
doDeletion = false;
|
||||
|
||||
Object[] newLeaf = truncatedApplyLeaf(newTimestamp, newSaveStatus, durabilityCell, executeAtCell, routeCell, doDeletion);
|
||||
Object[] newLeaf = truncatedApplyLeaf(newTimestamp, newSaveStatus, durabilityCell, executeAtCell, participantsCell, doDeletion);
|
||||
|
||||
// Including a deletion allows future compactions to drop data before it gets to the purger
|
||||
// but it is pretty optional because maybeDropTruncatedCommandColumns will drop the extra columns
|
||||
|
|
@ -447,47 +456,26 @@ public class AccordKeyspace
|
|||
return BTreeRow.create(row.clustering(), LivenessInfo.create(newTimestamp, nowInSec), deletion, newLeaf);
|
||||
}
|
||||
|
||||
public static Row maybeDropTruncatedCommandColumns(Row row, Cell<?> durabilityCell, Cell<?> executeAtCell, Cell<?> routeCell, Cell<?> statusCell)
|
||||
public static Row expungePartial(Row row, @Nullable Cell<?> durabilityCell, @Nullable Cell<?> executeAtCell, @Nullable Cell<?> participantsCell)
|
||||
{
|
||||
checkArgument(durabilityCell.column() == CommandsColumns.durability);
|
||||
checkArgument(executeAtCell.column() == CommandsColumns.execute_at);
|
||||
checkArgument(routeCell.column() == CommandsColumns.route);
|
||||
checkArgument(statusCell.column() == CommandsColumns.status);
|
||||
int colCount = row.columnCount();
|
||||
// If it's the exact length of the post truncate column count without outcome fields
|
||||
// then it is exactly the columns needed for getting this far and withOutcome doesn't matter since
|
||||
// nothing additional is available to include anyway
|
||||
if (colCount == TRUNCATE_FIELDS.length)
|
||||
return row;
|
||||
|
||||
// Construct a replacement with just the available columns that are still needed
|
||||
Object[] newLeaf = BTree.unsafeAllocateNonEmptyLeaf(TRUNCATE_FIELDS.length);
|
||||
int colIndex = 0;
|
||||
newLeaf[colIndex++] = durabilityCell;
|
||||
newLeaf[colIndex++] = executeAtCell;
|
||||
newLeaf[colIndex++] = routeCell;
|
||||
//noinspection UnusedAssignment
|
||||
newLeaf[colIndex++] = statusCell;
|
||||
|
||||
return BTreeRow.create(row.clustering(), row.primaryKeyLivenessInfo(), row.deletion(), newLeaf);
|
||||
Object[] newLeaf = expungePartialLeaf(durabilityCell, executeAtCell, participantsCell);
|
||||
return BTreeRow.create(row.clustering(), row.primaryKeyLivenessInfo(), Deletion.LIVE, newLeaf);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO (now, performance): do we actually care about the sort ordering? We don't do range scans on this table
|
||||
//TODO (now, performance): should we remove key_token? We don't need it so its just added space
|
||||
private static final TableMetadata TimestampsForKeys =
|
||||
parse(TIMESTAMPS_FOR_KEY,
|
||||
"accord timestamps per key",
|
||||
"CREATE TABLE %s ("
|
||||
+ "store_id int, "
|
||||
+ "key_token blob, " // can't use "token" as this is restricted word in CQL
|
||||
+ format("key %s, ", KEY_TUPLE)
|
||||
+ "routing_key blob, " // can't use "token" as this is restricted word in CQL
|
||||
+ format("last_executed_timestamp %s, ", TIMESTAMP_TUPLE)
|
||||
+ "last_executed_micros bigint, "
|
||||
+ format("last_write_id %s, ", TIMESTAMP_TUPLE)
|
||||
+ format("last_write_timestamp %s, ", TIMESTAMP_TUPLE)
|
||||
+ "PRIMARY KEY((store_id, key_token, key))"
|
||||
+ "PRIMARY KEY((store_id, routing_key))"
|
||||
+ ')')
|
||||
.partitioner(new LocalPartitioner(CompositeType.getInstance(Int32Type.instance, BytesType.instance, KEY_TYPE)))
|
||||
.partitioner(new LocalPartitioner(CompositeType.getInstance(Int32Type.instance, BytesType.instance)))
|
||||
.build();
|
||||
|
||||
public static class TimestampsForKeyColumns
|
||||
|
|
@ -495,23 +483,22 @@ public class AccordKeyspace
|
|||
static final ClusteringComparator keyComparator = TimestampsForKeys.partitionKeyAsClusteringComparator();
|
||||
static final CompositeType partitionKeyType = (CompositeType) TimestampsForKeys.partitionKeyType;
|
||||
static final ColumnMetadata store_id = getColumn(TimestampsForKeys, "store_id");
|
||||
static final ColumnMetadata key_token = getColumn(TimestampsForKeys, "key_token");
|
||||
static final ColumnMetadata key = getColumn(TimestampsForKeys, "key");
|
||||
static final ColumnMetadata routing_key = getColumn(TimestampsForKeys, "routing_key");
|
||||
public static final ColumnMetadata last_executed_timestamp = getColumn(TimestampsForKeys, "last_executed_timestamp");
|
||||
public static final ColumnMetadata last_executed_micros = getColumn(TimestampsForKeys, "last_executed_micros");
|
||||
public static final ColumnMetadata last_write_timestamp = getColumn(TimestampsForKeys, "last_write_timestamp");
|
||||
|
||||
static final Columns columns = Columns.from(Lists.newArrayList(last_executed_timestamp, last_executed_micros, last_write_timestamp));
|
||||
|
||||
static ByteBuffer makePartitionKey(int storeId, Key key)
|
||||
static ByteBuffer makeKey(int storeId, RoutingKey key)
|
||||
{
|
||||
PartitionKey pk = (PartitionKey) key;
|
||||
return keyComparator.make(storeId, serializeToken(pk.token()), serializeKey(pk)).serializeAsPartitionKey();
|
||||
TokenKey pk = (TokenKey) key;
|
||||
return keyComparator.make(storeId, serializeRoutingKey(pk)).serializeAsPartitionKey();
|
||||
}
|
||||
|
||||
static ByteBuffer makePartitionKey(int storeId, TimestampsForKey timestamps)
|
||||
static ByteBuffer makeKey(int storeId, TimestampsForKey timestamps)
|
||||
{
|
||||
return makePartitionKey(storeId, timestamps.key());
|
||||
return makeKey(storeId, timestamps.key());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -527,9 +514,9 @@ public class AccordKeyspace
|
|||
return Int32Type.instance.compose(partitionKeyComponents[store_id.position()]);
|
||||
}
|
||||
|
||||
public static PartitionKey getKey(ByteBuffer[] partitionKeyComponents)
|
||||
public static TokenKey getKey(ByteBuffer[] partitionKeyComponents)
|
||||
{
|
||||
return deserializeKey(partitionKeyComponents[key.position()]);
|
||||
return (TokenKey) deserializeRoutingKey(partitionKeyComponents[routing_key.position()]);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
|
@ -596,18 +583,17 @@ public class AccordKeyspace
|
|||
private static TableMetadata commandsForKeysTable(String tableName)
|
||||
{
|
||||
return parse(tableName,
|
||||
"accord commands per key",
|
||||
"CREATE TABLE %s ("
|
||||
+ "store_id int, "
|
||||
+ "table_id uuid, "
|
||||
+ "key_token blob, " // can't use "token" as this is restricted word in CQL
|
||||
+ "key blob, "
|
||||
+ "data blob, "
|
||||
+ "PRIMARY KEY((store_id, table_id, key_token, key))"
|
||||
+ ')'
|
||||
+ " WITH compression = {'class':'NoopCompressor'};")
|
||||
.partitioner(CFKPartitioner)
|
||||
.build();
|
||||
"accord commands per key",
|
||||
"CREATE TABLE %s ("
|
||||
+ "store_id int, "
|
||||
+ "table_id uuid, "
|
||||
+ "key_token blob, " // can't use "token" as this is restricted word in CQL
|
||||
+ "data blob, "
|
||||
+ "PRIMARY KEY((store_id, table_id, key_token))"
|
||||
+ ')'
|
||||
+ " WITH compression = {'class':'NoopCompressor'};")
|
||||
.partitioner(CFKPartitioner)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static class CommandsForKeyAccessor
|
||||
|
|
@ -619,7 +605,6 @@ public class AccordKeyspace
|
|||
final ColumnMetadata store_id;
|
||||
final ColumnMetadata table_id;
|
||||
final ColumnMetadata key_token;
|
||||
final ColumnMetadata key;
|
||||
final ColumnMetadata data;
|
||||
|
||||
final RegularAndStaticColumns columns;
|
||||
|
|
@ -633,7 +618,6 @@ public class AccordKeyspace
|
|||
this.store_id = getColumn(table, "store_id");
|
||||
this.table_id = getColumn(table, "table_id");
|
||||
this.key_token = getColumn(table, "key_token");
|
||||
this.key = getColumn(table, "key");
|
||||
this.data = getColumn(table, "data");
|
||||
this.columns = new RegularAndStaticColumns(Columns.NONE, Columns.from(Lists.newArrayList(data)));
|
||||
}
|
||||
|
|
@ -653,22 +637,18 @@ public class AccordKeyspace
|
|||
return TableId.fromUUID(UUIDType.instance.compose(partitionKeyComponents[table_id.position()]));
|
||||
}
|
||||
|
||||
public PartitionKey getKey(DecoratedKey key)
|
||||
public TokenKey getKey(DecoratedKey key)
|
||||
{
|
||||
return getKey(splitPartitionKey(key));
|
||||
}
|
||||
|
||||
public PartitionKey getKey(ByteBuffer[] partitionKeyComponents)
|
||||
public TokenKey getKey(ByteBuffer[] partitionKeyComponents)
|
||||
{
|
||||
TableId tableId = TableId.fromUUID(UUIDSerializer.instance.deserialize(partitionKeyComponents[table_id.position()]));
|
||||
ByteBuffer keyBytes = partitionKeyComponents[key.position()];
|
||||
IPartitioner partitioner = SchemaHolder.schema.getTablePartitioner(tableId);
|
||||
if (partitioner == null)
|
||||
throw new IllegalStateException("Table with id " + tableId + " could not be found; was it deleted?");
|
||||
return new PartitionKey(tableId, partitioner.decorateKey(keyBytes));
|
||||
return deserializeTokenKeySeparateTable(tableId, partitionKeyComponents[key_token.position()]);
|
||||
}
|
||||
|
||||
public CommandsForKey getCommandsForKey(PartitionKey key, Row row)
|
||||
public CommandsForKey getCommandsForKey(TokenKey key, Row row)
|
||||
{
|
||||
Cell<?> cell = row.getCell(data);
|
||||
if (cell == null)
|
||||
|
|
@ -685,7 +665,7 @@ public class AccordKeyspace
|
|||
}
|
||||
|
||||
// TODO (expected): garbage-free filtering, reusing encoding
|
||||
public Row withoutRedundantCommands(PartitionKey key, Row row, RedundantBefore.Entry redundantBefore)
|
||||
public Row withoutRedundantCommands(TokenKey key, Row row, RedundantBefore.Entry redundantBefore)
|
||||
{
|
||||
Invariants.checkState(row.columnCount() == 1);
|
||||
Cell<?> cell = row.getCell(data);
|
||||
|
|
@ -745,21 +725,6 @@ public class AccordKeyspace
|
|||
"max_epoch bigint " +
|
||||
')').build();
|
||||
|
||||
private static final TableMetadata CommandStoreMetadata =
|
||||
parse(COMMAND_STORE_METADATA,
|
||||
"command store state",
|
||||
"CREATE TABLE %s (" +
|
||||
"store_id int, " +
|
||||
"reject_before blob, " +
|
||||
"bootstrap_began_at blob, " +
|
||||
"safe_to_read blob, " +
|
||||
"redundant_before blob, " +
|
||||
"durable_before blob, " +
|
||||
"PRIMARY KEY(store_id)" +
|
||||
')').build();
|
||||
|
||||
private static final AtomicLong commandStoreMetadataTimestamp = new AtomicLong();
|
||||
|
||||
private static TableMetadata.Builder parse(String name, String description, String cql)
|
||||
{
|
||||
return CreateTableStatement.parse(format(cql, name), ACCORD_KEYSPACE_NAME)
|
||||
|
|
@ -780,7 +745,7 @@ public class AccordKeyspace
|
|||
|
||||
public static Tables tables()
|
||||
{
|
||||
return Tables.of(Commands, TimestampsForKeys, CommandsForKeys, Topologies, EpochMetadata, CommandStoreMetadata, Journal);
|
||||
return Tables.of(Commands, TimestampsForKeys, CommandsForKeys, Topologies, EpochMetadata, Journal);
|
||||
}
|
||||
|
||||
private static <T> ByteBuffer serialize(T obj, LocalVersionedSerializer<T> serializer) throws IOException
|
||||
|
|
@ -863,7 +828,7 @@ public class AccordKeyspace
|
|||
builder.addPrimaryKeyLivenessInfo(LivenessInfo.create(timestampMicros, nowInSeconds));
|
||||
|
||||
addEnumCellIfModified(CommandsColumns.durability, Command::durability, builder, timestampMicros, nowInSeconds, original, command);
|
||||
addCellIfModified(CommandsColumns.route, Command::route, LocalVersionedSerializers.route, builder, timestampMicros, nowInSeconds, original, command);
|
||||
addCellIfModified(CommandsColumns.participants, Command::participants, LocalVersionedSerializers.participants, builder, timestampMicros, nowInSeconds, original, command);
|
||||
addEnumCellIfModified(CommandsColumns.status, Command::saveStatus, builder, timestampMicros, nowInSeconds, original, command);
|
||||
addCellIfModified(CommandsColumns.execute_at, Command::executeAt, AccordKeyspace::serializeTimestamp, builder, timestampMicros, nowInSeconds, original, command);
|
||||
|
||||
|
|
@ -888,6 +853,13 @@ public class AccordKeyspace
|
|||
return serializeToken(token, ByteBufferAccessor.instance);
|
||||
}
|
||||
|
||||
public static ByteBuffer serializeTableId(TableId tableId)
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocate(tableId.serializedSize());
|
||||
tableId.serialize(buffer, ByteBufferAccessor.instance, 0);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private static <V> V serializeToken(Token token, ValueAccessor<V> accessor)
|
||||
{
|
||||
TokenType type = TokenType.valueOf(token);
|
||||
|
|
@ -898,12 +870,6 @@ public class AccordKeyspace
|
|||
return value;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static ByteBuffer serializeKey(PartitionKey key)
|
||||
{
|
||||
return KEY_TYPE.pack(UUIDSerializer.instance.serialize(key.table().asUUID()), key.partitionKey().getKey());
|
||||
}
|
||||
|
||||
public static ByteBuffer serializeTimestamp(Timestamp timestamp)
|
||||
{
|
||||
return TIMESTAMP_TYPE.pack(bytes(timestamp.msb), bytes(timestamp.lsb), bytes(timestamp.node.id));
|
||||
|
|
@ -991,7 +957,7 @@ public class AccordKeyspace
|
|||
public static void findAllKeysBetween(int commandStore,
|
||||
AccordRoutingKey start, boolean startInclusive,
|
||||
AccordRoutingKey end, boolean endInclusive,
|
||||
Observable<PartitionKey> callback)
|
||||
Observable<TokenKey> callback)
|
||||
{
|
||||
|
||||
Token startToken = CommandsForKeysAccessor.getPrefixToken(commandStore, start);
|
||||
|
|
@ -1026,7 +992,7 @@ public class AccordKeyspace
|
|||
{
|
||||
while (iter.hasNext())
|
||||
{
|
||||
PartitionKey pk = CommandsForKeysAccessor.getKey(iter.next());
|
||||
TokenKey pk = CommandsForKeysAccessor.getKey(iter.next());
|
||||
callback.onNext(pk);
|
||||
}
|
||||
callback.onCompleted();
|
||||
|
|
@ -1062,29 +1028,37 @@ public class AccordKeyspace
|
|||
return Status.Durability.values()[row.getInt("durability", 0)];
|
||||
}
|
||||
|
||||
public static Route<?> deserializeRouteOrNull(ByteBuffer bytes) throws IOException
|
||||
public static StoreParticipants deserializeParticipantsOrNull(ByteBuffer bytes) throws IOException
|
||||
{
|
||||
return bytes != null && !ByteBufferAccessor.instance.isEmpty(bytes) ? deserialize(bytes, LocalVersionedSerializers.route) : null;
|
||||
return bytes != null && !ByteBufferAccessor.instance.isEmpty(bytes) ? deserialize(bytes, LocalVersionedSerializers.participants) : null;
|
||||
}
|
||||
|
||||
public static ByteBuffer serializeRoute(Route<?> route) throws IOException
|
||||
public static Route<?> deserializeParticipantsRouteOnlyOrNull(ByteBuffer bytes) throws IOException
|
||||
{
|
||||
return serialize(route, LocalVersionedSerializers.route);
|
||||
if (bytes == null ||ByteBufferAccessor.instance.isEmpty(bytes))
|
||||
return null;
|
||||
|
||||
try (DataInputBuffer in = new DataInputBuffer(bytes, true))
|
||||
{
|
||||
MessageVersionProvider versionProvider = LocalVersionedSerializers.participants.deserializeVersion(in);
|
||||
return CommandSerializers.participants.deserializeRouteOnly(in, versionProvider.messageVersion());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static Route<?> deserializeRouteOrNull(UntypedResultSet.Row row) throws IOException
|
||||
public static ByteBuffer serializeParticipants(StoreParticipants participants) throws IOException
|
||||
{
|
||||
return deserializeRouteOrNull(row.getBlob("route"));
|
||||
return serialize(participants, LocalVersionedSerializers.participants);
|
||||
}
|
||||
|
||||
public static Route<?> deserializeRouteOrNull(Cell<?> cell)
|
||||
public static StoreParticipants deserializeParticipantsOrNull(Cell<?> cell)
|
||||
{
|
||||
if (cell == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return deserializeRouteOrNull(cell.buffer());
|
||||
return deserializeParticipantsOrNull(cell.buffer());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
|
@ -1092,21 +1066,9 @@ public class AccordKeyspace
|
|||
}
|
||||
}
|
||||
|
||||
public static PartitionKey deserializeKey(ByteBuffer buffer)
|
||||
public static TokenKey deserializeTokenKeySeparateTable(TableId tableId, ByteBuffer tokenBytes)
|
||||
{
|
||||
List<ByteBuffer> split = KEY_TYPE.unpack(buffer, ByteBufferAccessor.instance);
|
||||
TableId tableId = TableId.fromUUID(UUIDSerializer.instance.deserialize(split.get(0)));
|
||||
ByteBuffer key = split.get(1);
|
||||
|
||||
IPartitioner partitioner = SchemaHolder.schema.getTablePartitioner(tableId);
|
||||
if (partitioner == null)
|
||||
throw new IllegalStateException("Table with id " + tableId + " could not be found; was it deleted?");
|
||||
return new PartitionKey(tableId, partitioner.decorateKey(key));
|
||||
}
|
||||
|
||||
public static PartitionKey deserializeKey(UntypedResultSet.Row row)
|
||||
{
|
||||
return deserializeKey(row.getBytes("key"));
|
||||
return (TokenKey) AccordRoutingKeyByteSource.Serializer.fromComparableBytes(ByteBufferAccessor.instance, tokenBytes, tableId, currentVersion, null);
|
||||
}
|
||||
|
||||
public static Mutation getTimestampsForKeyMutation(int storeId, TimestampsForKey original, TimestampsForKey current, long timestampMicros)
|
||||
|
|
@ -1130,7 +1092,7 @@ public class AccordKeyspace
|
|||
if (row.columnCount() == 0)
|
||||
return null;
|
||||
|
||||
ByteBuffer key = TimestampsForKeyColumns.makePartitionKey(storeId, current.key());
|
||||
ByteBuffer key = TimestampsForKeyColumns.makeKey(storeId, current.key());
|
||||
PartitionUpdate update = singleRowUpdate(TimestampsForKeys, key, row);
|
||||
return new Mutation(update);
|
||||
}
|
||||
|
|
@ -1145,26 +1107,22 @@ public class AccordKeyspace
|
|||
return getTimestampsForKeyMutation(commandStore.id(), liveTimestamps.original(), liveTimestamps.current(), timestampMicros);
|
||||
}
|
||||
|
||||
public static UntypedResultSet loadTimestampsForKeyRow(CommandStore commandStore, PartitionKey key)
|
||||
public static UntypedResultSet loadTimestampsForKeyRow(CommandStore commandStore, TokenKey key)
|
||||
{
|
||||
String cql = "SELECT * FROM " + ACCORD_KEYSPACE_NAME + '.' + TIMESTAMPS_FOR_KEY + ' ' +
|
||||
"WHERE store_id = ? " +
|
||||
"AND key_token = ? " +
|
||||
"AND key=(?, ?)";
|
||||
"AND routing_key = ?";
|
||||
|
||||
return executeInternal(cql,
|
||||
commandStore.id(),
|
||||
serializeToken(key.token()),
|
||||
key.table().asUUID(), key.partitionKey().getKey());
|
||||
return executeInternal(cql, commandStore.id(), serializeRoutingKey(key));
|
||||
}
|
||||
|
||||
public static TimestampsForKey loadTimestampsForKey(AccordCommandStore commandStore, PartitionKey key)
|
||||
public static TimestampsForKey loadTimestampsForKey(AccordCommandStore commandStore, TokenKey key)
|
||||
{
|
||||
commandStore.checkNotInStoreThread();
|
||||
return unsafeLoadTimestampsForKey(commandStore, key);
|
||||
}
|
||||
|
||||
public static TimestampsForKey unsafeLoadTimestampsForKey(AccordCommandStore commandStore, PartitionKey key)
|
||||
public static TimestampsForKey unsafeLoadTimestampsForKey(AccordCommandStore commandStore, TokenKey key)
|
||||
{
|
||||
UntypedResultSet rows = loadTimestampsForKeyRow(commandStore, key);
|
||||
|
||||
|
|
@ -1174,21 +1132,22 @@ public class AccordKeyspace
|
|||
}
|
||||
|
||||
UntypedResultSet.Row row = rows.one();
|
||||
checkState(deserializeKey(row).equals(key));
|
||||
TokenKey checkKey = (TokenKey) deserializeRoutingKey(row.getBytes("routing_key"));
|
||||
checkState(checkKey.equals(key));
|
||||
|
||||
Timestamp lastExecutedTimestamp = deserializeTimestampOrDefault(row, "last_executed_timestamp", Timestamp::fromBits, Timestamp.NONE);
|
||||
long lastExecutedMicros = row.has("last_executed_micros") ? row.getLong("last_executed_micros") : 0;
|
||||
TxnId lastWriteId = deserializeTimestampOrDefault(row, "last_write_id", TxnId::fromBits, TxnId.NONE);
|
||||
Timestamp lastWriteTimestamp = deserializeTimestampOrDefault(row, "last_write_timestamp", Timestamp::fromBits, Timestamp.NONE);
|
||||
|
||||
return TimestampsForKey.SerializerSupport.create(key, lastExecutedTimestamp, lastExecutedMicros, lastWriteTimestamp);
|
||||
return TimestampsForKey.SerializerSupport.create(key, lastExecutedTimestamp, lastExecutedMicros, lastWriteId, lastWriteTimestamp);
|
||||
}
|
||||
|
||||
private static DecoratedKey makeKey(CommandsForKeyAccessor accessor, int storeId, PartitionKey key)
|
||||
private static DecoratedKey makeKeySeparateTable(CommandsForKeyAccessor accessor, int storeId, TokenKey key)
|
||||
{
|
||||
ByteBuffer pk = accessor.keyComparator.make(storeId,
|
||||
UUIDSerializer.instance.serialize(key.table().asUUID()),
|
||||
serializeRoutingKey(key.toUnseekable()),
|
||||
key.partitionKey().getKey()).serializeAsPartitionKey();
|
||||
serializeRoutingKeyNoTable(key)).serializeAsPartitionKey();
|
||||
return accessor.table.partitioner.decorateKey(pk);
|
||||
}
|
||||
|
||||
|
|
@ -1204,23 +1163,44 @@ public class AccordKeyspace
|
|||
return CommandsForKeysAccessor.serializeKeyNoTable(key);
|
||||
}
|
||||
|
||||
private static PartitionUpdate getCommandsForKeyPartitionUpdate(int storeId, PartitionKey key, CommandsForKey commandsForKey, long timestampMicros)
|
||||
public static AccordRoutingKey deserializeRoutingKey(ByteBuffer buffer)
|
||||
{
|
||||
return AccordRoutingKeyByteSource.Serializer.fromComparableBytes(ByteBufferAccessor.instance, buffer, currentVersion, null);
|
||||
}
|
||||
|
||||
private static AccordRoutingKeyByteSource.Serializer serializer(AccordRoutingKey routingKey)
|
||||
{
|
||||
return serializer(routingKey.table());
|
||||
}
|
||||
|
||||
public static AccordRoutingKeyByteSource.Serializer serializer(TableId tableId)
|
||||
{
|
||||
return TABLE_SERIALIZERS.computeIfAbsent(tableId, id -> AccordRoutingKeyByteSource.variableLength(partitioner(tableId)));
|
||||
}
|
||||
|
||||
public static IPartitioner partitioner(TableId tableId)
|
||||
{
|
||||
|
||||
return SchemaHolder.schema.getTablePartitioner(tableId);
|
||||
}
|
||||
|
||||
private static PartitionUpdate getCommandsForKeyPartitionUpdate(int storeId, TokenKey key, CommandsForKey commandsForKey, long timestampMicros)
|
||||
{
|
||||
ByteBuffer bytes = CommandsForKeySerializer.toBytesWithoutKey(commandsForKey);
|
||||
return getCommandsForKeyPartitionUpdate(storeId, key, timestampMicros, bytes);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static PartitionUpdate getCommandsForKeyPartitionUpdate(int storeId, PartitionKey key, long timestampMicros, ByteBuffer bytes)
|
||||
public static PartitionUpdate getCommandsForKeyPartitionUpdate(int storeId, TokenKey key, long timestampMicros, ByteBuffer bytes)
|
||||
{
|
||||
return singleRowUpdate(CommandsForKeysAccessor.table,
|
||||
makeKey(CommandsForKeysAccessor, storeId, key),
|
||||
makeKeySeparateTable(CommandsForKeysAccessor, storeId, key),
|
||||
singleCellRow(Clustering.EMPTY, BufferCell.live(CommandsForKeysAccessor.data, timestampMicros, bytes)));
|
||||
}
|
||||
|
||||
public static Mutation getCommandsForKeyMutation(int storeId, CommandsForKey update, long timestampMicros)
|
||||
{
|
||||
return new Mutation(getCommandsForKeyPartitionUpdate(storeId, (PartitionKey) update.key(), update, timestampMicros));
|
||||
return new Mutation(getCommandsForKeyPartitionUpdate(storeId, (TokenKey)update.key(), update, timestampMicros));
|
||||
}
|
||||
|
||||
private static <T> ByteBuffer cellValue(Cell<T> cell)
|
||||
|
|
@ -1240,22 +1220,22 @@ public class AccordKeyspace
|
|||
return clustering.accessor().toBuffer(clustering.get(idx));
|
||||
}
|
||||
|
||||
private static SinglePartitionReadCommand getCommandsForKeyRead(CommandsForKeyAccessor accessor, int storeId, PartitionKey key, long nowInSeconds)
|
||||
private static SinglePartitionReadCommand getCommandsForKeyRead(CommandsForKeyAccessor accessor, int storeId, TokenKey key, long nowInSeconds)
|
||||
{
|
||||
return SinglePartitionReadCommand.create(accessor.table, nowInSeconds,
|
||||
accessor.allColumns,
|
||||
RowFilter.none(),
|
||||
DataLimits.NONE,
|
||||
makeKey(accessor, storeId, key),
|
||||
makeKeySeparateTable(accessor, storeId, key),
|
||||
FULL_PARTITION);
|
||||
}
|
||||
|
||||
public static SinglePartitionReadCommand getCommandsForKeyRead(int storeId, PartitionKey key, int nowInSeconds)
|
||||
public static SinglePartitionReadCommand getCommandsForKeyRead(int storeId, TokenKey key, int nowInSeconds)
|
||||
{
|
||||
return getCommandsForKeyRead(CommandsForKeysAccessor, storeId, key, nowInSeconds);
|
||||
}
|
||||
|
||||
static CommandsForKey unsafeLoadCommandsForKey(CommandsForKeyAccessor accessor, AccordCommandStore commandStore, PartitionKey key)
|
||||
static CommandsForKey unsafeLoadCommandsForKey(CommandsForKeyAccessor accessor, AccordCommandStore commandStore, TokenKey key)
|
||||
{
|
||||
long timestampMicros = TimeUnit.MILLISECONDS.toMicros(Global.currentTimeMillis());
|
||||
int nowInSeconds = (int) TimeUnit.MICROSECONDS.toSeconds(timestampMicros);
|
||||
|
|
@ -1283,12 +1263,12 @@ public class AccordKeyspace
|
|||
}
|
||||
}
|
||||
|
||||
public static CommandsForKey unsafeLoadCommandsForKey(AccordCommandStore commandStore, PartitionKey key)
|
||||
public static CommandsForKey unsafeLoadCommandsForKey(AccordCommandStore commandStore, TokenKey key)
|
||||
{
|
||||
return unsafeLoadCommandsForKey(CommandsForKeysAccessor, commandStore, key);
|
||||
}
|
||||
|
||||
public static CommandsForKey loadCommandsForKey(AccordCommandStore commandStore, PartitionKey key)
|
||||
public static CommandsForKey loadCommandsForKey(AccordCommandStore commandStore, TokenKey key)
|
||||
{
|
||||
commandStore.checkNotInStoreThread();
|
||||
return unsafeLoadCommandsForKey(CommandsForKeysAccessor, commandStore, key);
|
||||
|
|
@ -1557,104 +1537,6 @@ public class AccordKeyspace
|
|||
}
|
||||
}
|
||||
|
||||
private static IMutation getCommandStoreMetadataMutation(String cql, ByteBuffer... values)
|
||||
{
|
||||
ClientState clientState = ClientState.forInternalCalls();
|
||||
ModificationStatement statement = (ModificationStatement) QueryProcessor.parseStatement(cql).prepare(ClientState.forInternalCalls());
|
||||
QueryOptions options = QueryOptions.forInternalCalls(Arrays.asList(values));
|
||||
|
||||
long tsMicros = TimeUnit.MILLISECONDS.toMicros(Global.currentTimeMillis());
|
||||
|
||||
while (true)
|
||||
{
|
||||
long prev = commandStoreMetadataTimestamp.get();
|
||||
if (prev >= tsMicros)
|
||||
tsMicros = prev + 1;
|
||||
|
||||
if (commandStoreMetadataTimestamp.compareAndSet(prev, tsMicros))
|
||||
break;
|
||||
}
|
||||
|
||||
return Iterables.getOnlyElement(statement.getMutations(clientState, options, true, tsMicros, (int) TimeUnit.MICROSECONDS.toSeconds(tsMicros), Dispatcher.RequestTime.forImmediateExecution(), false));
|
||||
}
|
||||
|
||||
|
||||
private static <T> Future<?> updateCommandStoreMetadata(CommandStore commandStore, String column, T value, LocalVersionedSerializer<T> serializer)
|
||||
{
|
||||
String cql = format("UPDATE %s.%s SET %s=? WHERE store_id=?", ACCORD_KEYSPACE_NAME, COMMAND_STORE_METADATA, column);
|
||||
try
|
||||
{
|
||||
IMutation mutation = getCommandStoreMetadataMutation(cql, serialize(value, serializer), bytes(commandStore.id()));
|
||||
return Stage.MUTATION.submit(mutation::apply);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Future<?> updateRejectBefore(CommandStore commandStore, ReducingRangeMap<Timestamp> rejectBefore)
|
||||
{
|
||||
return updateCommandStoreMetadata(commandStore, "reject_before", rejectBefore, LocalVersionedSerializers.rejectBefore);
|
||||
}
|
||||
|
||||
public static Future<?> updateDurableBefore(CommandStore commandStore, DurableBefore durableBefore)
|
||||
{
|
||||
return updateCommandStoreMetadata(commandStore, "durable_before", durableBefore, LocalVersionedSerializers.durableBefore);
|
||||
}
|
||||
|
||||
public static Future<?> updateRedundantBefore(CommandStore commandStore, RedundantBefore redundantBefore)
|
||||
{
|
||||
return updateCommandStoreMetadata(commandStore, "redundant_before", redundantBefore, LocalVersionedSerializers.redundantBefore);
|
||||
}
|
||||
|
||||
public static Future<?> updateBootstrapBeganAt(CommandStore commandStore, NavigableMap<TxnId, Ranges> bootstrapBeganAt)
|
||||
{
|
||||
return updateCommandStoreMetadata(commandStore, "bootstrap_began_at", bootstrapBeganAt, LocalVersionedSerializers.bootstrapBeganAt);
|
||||
}
|
||||
|
||||
public static Future<?> updateSafeToRead(CommandStore commandStore, NavigableMap<Timestamp, Ranges> safeToRead)
|
||||
{
|
||||
return updateCommandStoreMetadata(commandStore, "safe_to_read", safeToRead, LocalVersionedSerializers.safeToRead);
|
||||
}
|
||||
|
||||
public interface CommandStoreMetadataConsumer
|
||||
{
|
||||
void accept(ReducingRangeMap<Timestamp> rejectBefore, DurableBefore durableBefore, RedundantBefore redundantBefore, NavigableMap<TxnId, Ranges> bootstrapBeganAt, NavigableMap<Timestamp, Ranges> safeToRead);
|
||||
}
|
||||
|
||||
public static void loadCommandStoreMetadata(int id, CommandStoreMetadataConsumer consumer)
|
||||
{
|
||||
UntypedResultSet result = executeOnceInternal(format("SELECT * FROM %s.%s WHERE store_id=?", ACCORD_KEYSPACE_NAME, COMMAND_STORE_METADATA), id);
|
||||
ReducingRangeMap<Timestamp> rejectBefore = null;
|
||||
DurableBefore durableBefore = null;
|
||||
RedundantBefore redundantBefore = null;
|
||||
NavigableMap<TxnId, Ranges> bootstrapBeganAt = null;
|
||||
NavigableMap<Timestamp, Ranges> safeToRead = null;
|
||||
if (!result.isEmpty())
|
||||
{
|
||||
UntypedResultSet.Row row = Iterables.getOnlyElement(result);
|
||||
try
|
||||
{
|
||||
if (row.has("reject_before"))
|
||||
rejectBefore = deserialize(row.getBlob("reject_before"), LocalVersionedSerializers.rejectBefore);
|
||||
if (row.has("durable_before"))
|
||||
durableBefore = deserialize(row.getBlob("durable_before"), LocalVersionedSerializers.durableBefore);
|
||||
if (row.has("redundant_before"))
|
||||
redundantBefore = deserialize(row.getBlob("redundant_before"), LocalVersionedSerializers.redundantBefore);
|
||||
if (row.has("bootstrap_began_at"))
|
||||
bootstrapBeganAt = deserialize(row.getBlob("bootstrap_began_at"), LocalVersionedSerializers.bootstrapBeganAt);
|
||||
if (row.has("safe_to_read"))
|
||||
safeToRead = deserialize(row.getBlob("safe_to_read"), LocalVersionedSerializers.safeToRead);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
consumer.accept(rejectBefore, durableBefore, redundantBefore, bootstrapBeganAt, safeToRead);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void unsafeSetSchema(SchemaProvider provider)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import java.util.function.ToLongFunction;
|
|||
import accord.api.Key;
|
||||
import accord.api.Result;
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.StoreParticipants;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.local.cfk.CommandsForKey.TxnInfo;
|
||||
import accord.impl.TimestampsForKey;
|
||||
|
|
@ -32,8 +33,8 @@ import accord.local.Command.WaitingOn;
|
|||
import accord.local.cfk.CommandsForKey.TxnInfoExtra;
|
||||
import accord.local.CommonAttributes;
|
||||
import accord.local.Node;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.local.Status;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.AbstractKeys;
|
||||
import accord.primitives.AbstractRanges;
|
||||
import accord.primitives.Ballot;
|
||||
|
|
@ -69,6 +70,7 @@ import org.apache.cassandra.service.accord.txn.TxnResult;
|
|||
import org.apache.cassandra.service.accord.txn.TxnWrite;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
|
||||
import static accord.local.cfk.CommandsForKey.InternalStatus.ACCEPTED;
|
||||
import static accord.primitives.TxnId.NO_TXNIDS;
|
||||
import static org.apache.cassandra.utils.ObjectSizes.measure;
|
||||
|
||||
|
|
@ -235,7 +237,7 @@ public class AccordObjectSizes
|
|||
// doesn't account for txnIdToKeys, txnIdToRanges, and searchable fields;
|
||||
// fix to accunt for, in case caching isn't redone
|
||||
long size = EMPTY_DEPS_SIZE - EMPTY_KEYS_SIZE - ObjectSizes.sizeOfReferenceArray(0);
|
||||
size += keys(dependencies.keyDeps.keys());
|
||||
size += routingKeys(dependencies.keyDeps.keys());
|
||||
for (int i = 0 ; i < dependencies.rangeDeps.rangeCount() ; ++i)
|
||||
size += range(dependencies.rangeDeps.range(i));
|
||||
size += ObjectSizes.sizeOfReferenceArray(dependencies.rangeDeps.rangeCount());
|
||||
|
|
@ -273,7 +275,9 @@ public class AccordObjectSizes
|
|||
|
||||
private static CommonAttributes attrs(boolean hasDeps, boolean hasTxn)
|
||||
{
|
||||
CommonAttributes.Mutable attrs = new CommonAttributes.Mutable(EMPTY_TXNID).route(new FullKeyRoute(EMPTY_KEY, new RoutingKey[]{ EMPTY_KEY }));
|
||||
FullKeyRoute route = new FullKeyRoute(EMPTY_KEY, new RoutingKey[]{ EMPTY_KEY });
|
||||
CommonAttributes.Mutable attrs = new CommonAttributes.Mutable(EMPTY_TXNID)
|
||||
.setParticipants(StoreParticipants.empty(EMPTY_TXNID, route));
|
||||
attrs.durability(Status.Durability.NotDurable);
|
||||
if (hasDeps)
|
||||
attrs.partialDeps(PartialDeps.NONE);
|
||||
|
|
@ -284,14 +288,14 @@ public class AccordObjectSizes
|
|||
return attrs;
|
||||
}
|
||||
|
||||
private static final Writes EMPTY_WRITES = new Writes(EMPTY_TXNID, EMPTY_TXNID, Keys.EMPTY, (key, safeStore, executeAt, store, txn) -> null);
|
||||
private static final Writes EMPTY_WRITES = new Writes(EMPTY_TXNID, EMPTY_TXNID, Keys.EMPTY, (key, safeStore, txnId, executeAt, store, txn) -> null);
|
||||
private static final Result EMPTY_RESULT = new Result() {};
|
||||
|
||||
final static long NOT_DEFINED = measure(Command.SerializerSupport.notDefined(attrs(false, false), Ballot.ZERO));
|
||||
final static long PREACCEPTED = measure(Command.SerializerSupport.preaccepted(attrs(false, true), EMPTY_TXNID, null));;
|
||||
final static long ACCEPTED = measure(Command.SerializerSupport.accepted(attrs(true, false), SaveStatus.Accepted, EMPTY_TXNID, Ballot.ZERO, Ballot.ZERO));
|
||||
final static long COMMITTED = measure(Command.SerializerSupport.committed(attrs(true, true), SaveStatus.Committed, EMPTY_TXNID, Ballot.ZERO, Ballot.ZERO, null));
|
||||
final static long EXECUTED = measure(Command.SerializerSupport.executed(attrs(true, true), SaveStatus.Applied, EMPTY_TXNID, Ballot.ZERO, Ballot.ZERO, WaitingOn.empty(EMPTY_TXNID.domain()), EMPTY_WRITES, EMPTY_RESULT));
|
||||
final static long EXECUTED = measure(Command.SerializerSupport.executed(attrs(true, true), SaveStatus.Applied, EMPTY_TXNID, Ballot.ZERO, Ballot.ZERO, WaitingOn.empty(Domain.Key), EMPTY_WRITES, EMPTY_RESULT));
|
||||
final static long TRUNCATED = measure(Command.SerializerSupport.truncatedApply(attrs(false, false), SaveStatus.TruncatedApply, EMPTY_TXNID, null, null));
|
||||
final static long INVALIDATED = measure(Command.SerializerSupport.invalidated(EMPTY_TXNID));
|
||||
|
||||
|
|
@ -353,7 +357,7 @@ public class AccordObjectSizes
|
|||
return size;
|
||||
}
|
||||
|
||||
private static long EMPTY_TFK_SIZE = measure(TimestampsForKey.SerializerSupport.create(null, null, 0, null));
|
||||
private static long EMPTY_TFK_SIZE = measure(TimestampsForKey.SerializerSupport.create(null, null, 0, null, null));
|
||||
|
||||
public static long timestampsForKey(TimestampsForKey timestamps)
|
||||
{
|
||||
|
|
@ -364,8 +368,8 @@ public class AccordObjectSizes
|
|||
}
|
||||
|
||||
private static long EMPTY_CFK_SIZE = measure(new CommandsForKey(null));
|
||||
private static long EMPTY_INFO_SIZE = measure(TxnInfo.createMock(TxnId.NONE, null, null, NO_TXNIDS, Ballot.ZERO));
|
||||
private static long EMPTY_INFO_EXTRA_ADDITIONAL_SIZE = EMPTY_INFO_SIZE - measure(TxnInfo.createMock(TxnId.NONE, null, null, null, null));
|
||||
private static long EMPTY_INFO_SIZE = measure(CommandsForKey.NO_INFO);
|
||||
private static long EMPTY_INFO_EXTRA_ADDITIONAL_SIZE = measure(TxnInfo.create(TxnId.NONE, ACCEPTED, false, TxnId.NONE, NO_TXNIDS, Ballot.MAX)) - EMPTY_INFO_SIZE;
|
||||
public static long commandsForKey(CommandsForKey cfk)
|
||||
{
|
||||
long size = EMPTY_CFK_SIZE;
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ public class AccordSafeCommand extends SafeCommand implements AccordSafeState<Tx
|
|||
return original;
|
||||
}
|
||||
|
||||
public SavedCommand.Writer<TxnId> diff()
|
||||
public SavedCommand.DiffWriter diff()
|
||||
{
|
||||
return SavedCommand.diff(original, current);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,36 +30,40 @@ import accord.api.Agent;
|
|||
import accord.api.DataStore;
|
||||
import accord.api.Key;
|
||||
import accord.api.ProgressLog;
|
||||
import accord.api.RoutingKey;
|
||||
import accord.impl.AbstractSafeCommandStore;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.impl.CommandsSummary;
|
||||
import accord.local.CommandStores;
|
||||
import accord.local.CommandStores.RangesForEpoch;
|
||||
import accord.local.DurableBefore;
|
||||
import accord.local.NodeTimeService;
|
||||
import accord.local.PreLoadContext;
|
||||
import accord.local.RedundantBefore;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.primitives.AbstractKeys;
|
||||
import accord.primitives.AbstractRanges;
|
||||
import accord.primitives.Deps;
|
||||
import accord.primitives.Range;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.Routables;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.Txn;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.primitives.Unseekables;
|
||||
|
||||
public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeCommand, AccordSafeTimestampsForKey, AccordSafeCommandsForKey>
|
||||
{
|
||||
private final Map<TxnId, AccordSafeCommand> commands;
|
||||
private final NavigableMap<Key, AccordSafeCommandsForKey> commandsForKeys;
|
||||
private final NavigableMap<Key, AccordSafeTimestampsForKey> timestampsForKeys;
|
||||
private final NavigableMap<RoutingKey, AccordSafeCommandsForKey> commandsForKeys;
|
||||
private final NavigableMap<RoutingKey, AccordSafeTimestampsForKey> timestampsForKeys;
|
||||
private final @Nullable AccordSafeCommandsForRanges commandsForRanges;
|
||||
private final AccordCommandStore commandStore;
|
||||
private final RangesForEpoch ranges;
|
||||
private RangesForEpoch ranges;
|
||||
private FieldUpdates fieldUpdates;
|
||||
|
||||
private AccordSafeCommandStore(PreLoadContext context,
|
||||
Map<TxnId, AccordSafeCommand> commands,
|
||||
NavigableMap<Key, AccordSafeTimestampsForKey> timestampsForKey,
|
||||
NavigableMap<Key, AccordSafeCommandsForKey> commandsForKey,
|
||||
NavigableMap<RoutingKey, AccordSafeTimestampsForKey> timestampsForKey,
|
||||
NavigableMap<RoutingKey, AccordSafeCommandsForKey> commandsForKey,
|
||||
@Nullable AccordSafeCommandsForRanges commandsForRanges,
|
||||
AccordCommandStore commandStore)
|
||||
{
|
||||
|
|
@ -69,13 +73,15 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
this.commandsForKeys = commandsForKey;
|
||||
this.commandsForRanges = commandsForRanges;
|
||||
this.commandStore = commandStore;
|
||||
this.ranges = commandStore.updateRangesForEpoch();
|
||||
commandStore.updateRangesForEpoch(this);
|
||||
if (this.ranges == null)
|
||||
this.ranges = commandStore.unsafeRangesForEpoch();
|
||||
}
|
||||
|
||||
public static AccordSafeCommandStore create(PreLoadContext preLoadContext,
|
||||
Map<TxnId, AccordSafeCommand> commands,
|
||||
NavigableMap<Key, AccordSafeTimestampsForKey> timestampsForKey,
|
||||
NavigableMap<Key, AccordSafeCommandsForKey> commandsForKey,
|
||||
NavigableMap<RoutingKey, AccordSafeTimestampsForKey> timestampsForKey,
|
||||
NavigableMap<RoutingKey, AccordSafeCommandsForKey> commandsForKey,
|
||||
@Nullable AccordSafeCommandsForRanges commandsForRanges,
|
||||
AccordCommandStore commandStore)
|
||||
{
|
||||
|
|
@ -83,7 +89,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Set<Key> commandsForKeysKeys()
|
||||
public Set<RoutingKey> commandsForKeysKeys()
|
||||
{
|
||||
return commandsForKeys.keySet();
|
||||
}
|
||||
|
|
@ -109,7 +115,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
}
|
||||
|
||||
@Override
|
||||
protected AccordSafeCommandsForKey getCommandsForKeyInternal(Key key)
|
||||
protected AccordSafeCommandsForKey getCommandsForKeyInternal(RoutingKey key)
|
||||
{
|
||||
return commandsForKeys.get(key);
|
||||
}
|
||||
|
|
@ -121,7 +127,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
}
|
||||
|
||||
@Override
|
||||
protected AccordSafeCommandsForKey getCommandsForKeyIfLoaded(Key key)
|
||||
protected AccordSafeCommandsForKey getCommandsForKeyIfLoaded(RoutingKey key)
|
||||
{
|
||||
AccordSafeCommandsForKey cfk = commandStore.commandsForKeyCache().acquireIfLoaded(key);
|
||||
if (cfk != null) cfk.preExecute();
|
||||
|
|
@ -129,7 +135,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
}
|
||||
|
||||
@Override
|
||||
protected AccordSafeTimestampsForKey getTimestampsForKeyInternal(Key key)
|
||||
protected AccordSafeTimestampsForKey getTimestampsForKeyInternal(RoutingKey key)
|
||||
{
|
||||
return timestampsForKeys.get(key);
|
||||
}
|
||||
|
|
@ -141,7 +147,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
}
|
||||
|
||||
@Override
|
||||
protected AccordSafeTimestampsForKey getTimestampsForKeyIfLoaded(Key key)
|
||||
protected AccordSafeTimestampsForKey getTimestampsForKeyIfLoaded(RoutingKey key)
|
||||
{
|
||||
AccordSafeTimestampsForKey cfk = commandStore.timestampsForKeyCache().acquireIfLoaded(key);
|
||||
if (cfk != null) cfk.preExecute();
|
||||
|
|
@ -182,68 +188,32 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
@Override
|
||||
public RangesForEpoch ranges()
|
||||
{
|
||||
return commandStore().unsafeRangesForEpoch();
|
||||
return ranges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerHistoricalTransactions(Deps deps)
|
||||
private <O> O mapReduce(Routables<?> keysOrRanges, BiFunction<CommandsSummary, O, O> map, O accumulate)
|
||||
{
|
||||
if (deps.isEmpty()) return;
|
||||
// used in places such as accord.local.CommandStore.fetchMajorityDeps
|
||||
// We find a set of dependencies for a range then update CommandsFor to know about them
|
||||
Ranges allRanges = ranges.all();
|
||||
deps.keyDeps.keys().forEach(allRanges, key -> {
|
||||
// TODO (now): batch register to minimise GC
|
||||
deps.keyDeps.forEach(key, (txnId, txnIdx) -> {
|
||||
// TODO (desired, efficiency): this can be made more efficient by batching by epoch
|
||||
if (ranges.coordinates(txnId).contains(key))
|
||||
return; // already coordinates, no need to replicate
|
||||
if (!ranges.allBefore(txnId.epoch()).contains(key))
|
||||
return;
|
||||
|
||||
get(key).registerHistorical(this, txnId);
|
||||
});
|
||||
});
|
||||
for (int i = 0; i < deps.rangeDeps.rangeCount(); i++)
|
||||
{
|
||||
Range range = deps.rangeDeps.range(i);
|
||||
if (!allRanges.intersects(range))
|
||||
continue;
|
||||
deps.rangeDeps.forEach(range, txnId -> {
|
||||
// TODO (desired, efficiency): this can be made more efficient by batching by epoch
|
||||
if (ranges.coordinates(txnId).intersects(range))
|
||||
return; // already coordinates, no need to replicate
|
||||
if (!ranges.allBefore(txnId.epoch()).intersects(range))
|
||||
return;
|
||||
|
||||
commandStore.diskCommandsForRanges().mergeHistoricalTransaction(txnId, Ranges.single(range).slice(allRanges), Ranges::with);
|
||||
});
|
||||
}
|
||||
accumulate = mapReduceForRange(keysOrRanges, map, accumulate);
|
||||
return mapReduceForKey(keysOrRanges, map, accumulate);
|
||||
}
|
||||
|
||||
private <O> O mapReduce(Routables<?> keysOrRanges, Ranges slice, BiFunction<CommandsSummary, O, O> map, O accumulate)
|
||||
{
|
||||
accumulate = mapReduceForRange(keysOrRanges, slice, map, accumulate);
|
||||
return mapReduceForKey(keysOrRanges, slice, map, accumulate);
|
||||
}
|
||||
|
||||
private <O> O mapReduceForRange(Routables<?> keysOrRanges, Ranges slice, BiFunction<CommandsSummary, O, O> map, O accumulate)
|
||||
private <O> O mapReduceForRange(Routables<?> keysOrRanges, BiFunction<CommandsSummary, O, O> map, O accumulate)
|
||||
{
|
||||
if (commandsForRanges == null)
|
||||
return accumulate;
|
||||
CommandsForRanges cfr = commandsForRanges.current().slice(slice);
|
||||
CommandsForRanges cfr = commandsForRanges.current();
|
||||
switch (keysOrRanges.domain())
|
||||
{
|
||||
case Key:
|
||||
{
|
||||
AbstractKeys<Key> keys = (AbstractKeys<Key>) keysOrRanges.slice(slice, Routables.Slice.Minimal);
|
||||
AbstractKeys<Key> keys = (AbstractKeys<Key>) keysOrRanges;
|
||||
if (!cfr.ranges.intersects(keys))
|
||||
return accumulate;
|
||||
}
|
||||
break;
|
||||
case Range:
|
||||
{
|
||||
AbstractRanges ranges = (AbstractRanges) keysOrRanges.slice(slice, Routables.Slice.Minimal);
|
||||
AbstractRanges ranges = (AbstractRanges) keysOrRanges;
|
||||
if (!cfr.ranges.intersects(ranges))
|
||||
return accumulate;
|
||||
}
|
||||
|
|
@ -254,7 +224,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
return map.apply(cfr, accumulate);
|
||||
}
|
||||
|
||||
private <O> O mapReduceForKey(Routables<?> keysOrRanges, Ranges slice, BiFunction<CommandsSummary, O, O> map, O accumulate)
|
||||
private <O> O mapReduceForKey(Routables<?> keysOrRanges, BiFunction<CommandsSummary, O, O> map, O accumulate)
|
||||
{
|
||||
switch (keysOrRanges.domain())
|
||||
{
|
||||
|
|
@ -263,10 +233,9 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
case Key:
|
||||
{
|
||||
// TODO: efficiency
|
||||
AbstractKeys<Key> keys = (AbstractKeys<Key>) keysOrRanges;
|
||||
for (Key key : keys)
|
||||
AbstractKeys<RoutingKey> keys = (AbstractKeys<RoutingKey>) keysOrRanges;
|
||||
for (RoutingKey key : keys)
|
||||
{
|
||||
if (!slice.contains(key)) continue;
|
||||
CommandsForKey commands = get(key).current();
|
||||
accumulate = map.apply(commands, accumulate);
|
||||
}
|
||||
|
|
@ -276,13 +245,11 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
{
|
||||
// Assuming the range provided is in the PreLoadContext, then AsyncLoader has populated commandsForKeys with keys that
|
||||
// are contained within the ranges... so walk all keys found in commandsForKeys
|
||||
Routables<?> sliced = keysOrRanges.slice(slice, Routables.Slice.Minimal);
|
||||
if (!context.keys().slice(slice, Routables.Slice.Minimal).containsAll(sliced))
|
||||
if (!context.keys().containsAll(keysOrRanges))
|
||||
throw new AssertionError("Range(s) detected not present in the PreLoadContext: expected " + context.keys() + " but given " + keysOrRanges);
|
||||
for (Key key : commandsForKeys.keySet())
|
||||
for (RoutingKey key : commandsForKeys.keySet())
|
||||
{
|
||||
//TODO (duplicate code): this is a repeat of Key... only change is checking contains in range
|
||||
if (!sliced.contains(key)) continue;
|
||||
CommandsForKey commands = get(key).current();
|
||||
accumulate = map.apply(commands, accumulate);
|
||||
}
|
||||
|
|
@ -293,17 +260,17 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
}
|
||||
|
||||
@Override
|
||||
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)
|
||||
public <P1, T> T mapReduceActive(Unseekables<?> keysOrRanges, @Nullable Timestamp withLowerTxnId, Txn.Kind.Kinds testKind, CommandFunction<P1, T, T> map, P1 p1, T accumulate)
|
||||
{
|
||||
return mapReduce(keysOrRanges, slice, (summary, in) -> {
|
||||
return mapReduce(keysOrRanges, (summary, in) -> {
|
||||
return summary.mapReduceActive(withLowerTxnId, testKind, map, p1, in);
|
||||
}, accumulate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <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)
|
||||
public <P1, T> T mapReduceFull(Unseekables<?> keysOrRanges, TxnId testTxnId, Txn.Kind.Kinds testKind, TestStartedAt testStartedAt, TestDep testDep, TestStatus testStatus, CommandFunction<P1, T, T> map, P1 p1, T accumulate)
|
||||
{
|
||||
return mapReduce(keysOrRanges, slice, (summary, in) -> {
|
||||
return mapReduce(keysOrRanges, (summary, in) -> {
|
||||
return summary.mapReduceFull(testTxnId, testKind, testStartedAt, testDep, testStatus, map, p1, in);
|
||||
}, accumulate);
|
||||
}
|
||||
|
|
@ -313,4 +280,70 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
|||
{
|
||||
return "AccordSafeCommandStore(id=" + commandStore().id() + ")";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upsertRedundantBefore(RedundantBefore addRedundantBefore)
|
||||
{
|
||||
// TODO (now): this is a temporary measure, see comment on AccordJournalValueSerializers; upsert instead
|
||||
// when modifying, only modify together with AccordJournalValueSerializers
|
||||
ensureFieldUpdates().redundantBefore = RedundantBefore.merge(commandStore.redundantBefore(), addRedundantBefore);
|
||||
super.upsertRedundantBefore(addRedundantBefore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBootstrapBeganAt(NavigableMap<TxnId, Ranges> newBootstrapBeganAt)
|
||||
{
|
||||
ensureFieldUpdates().bootstrapBeganAt = newBootstrapBeganAt;
|
||||
super.setBootstrapBeganAt(newBootstrapBeganAt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upsertDurableBefore(DurableBefore addDurableBefore)
|
||||
{
|
||||
ensureFieldUpdates().durableBefore = addDurableBefore;
|
||||
super.upsertDurableBefore(addDurableBefore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSafeToRead(NavigableMap<Timestamp, Ranges> newSafeToRead)
|
||||
{
|
||||
ensureFieldUpdates().safeToRead = newSafeToRead;
|
||||
super.setSafeToRead(newSafeToRead);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRangesForEpoch(CommandStores.RangesForEpoch rangesForEpoch)
|
||||
{
|
||||
ensureFieldUpdates().rangesForEpoch = rangesForEpoch.snapshot();
|
||||
super.setRangesForEpoch(rangesForEpoch);
|
||||
ranges = rangesForEpoch;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerHistoricalTransactions(Deps deps)
|
||||
{
|
||||
ensureFieldUpdates().historicalTransactions = deps;
|
||||
super.registerHistoricalTransactions(deps);
|
||||
}
|
||||
|
||||
private FieldUpdates ensureFieldUpdates()
|
||||
{
|
||||
if (fieldUpdates == null) fieldUpdates = new FieldUpdates();
|
||||
return fieldUpdates;
|
||||
}
|
||||
|
||||
public FieldUpdates fieldUpdates()
|
||||
{
|
||||
return fieldUpdates;
|
||||
}
|
||||
|
||||
public static class FieldUpdates
|
||||
{
|
||||
public RedundantBefore redundantBefore;
|
||||
public DurableBefore durableBefore;
|
||||
public NavigableMap<TxnId, Ranges> bootstrapBeganAt;
|
||||
public NavigableMap<Timestamp, Ranges> safeToRead;
|
||||
public RangesForEpoch.Snapshot rangesForEpoch;
|
||||
public Deps historicalTransactions;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,18 +22,18 @@ import java.util.Objects;
|
|||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import accord.api.Key;
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.local.cfk.SafeCommandsForKey;
|
||||
|
||||
public class AccordSafeCommandsForKey extends SafeCommandsForKey implements AccordSafeState<Key, CommandsForKey>
|
||||
public class AccordSafeCommandsForKey extends SafeCommandsForKey implements AccordSafeState<RoutingKey, CommandsForKey>
|
||||
{
|
||||
private boolean invalidated;
|
||||
private final AccordCachingState<Key, CommandsForKey> global;
|
||||
private final AccordCachingState<RoutingKey, CommandsForKey> global;
|
||||
private CommandsForKey original;
|
||||
private CommandsForKey current;
|
||||
|
||||
public AccordSafeCommandsForKey(AccordCachingState<Key, CommandsForKey> global)
|
||||
public AccordSafeCommandsForKey(AccordCachingState<RoutingKey, CommandsForKey> global)
|
||||
{
|
||||
super(global.key());
|
||||
this.global = global;
|
||||
|
|
@ -82,7 +82,7 @@ public class AccordSafeCommandsForKey extends SafeCommandsForKey implements Acco
|
|||
}
|
||||
|
||||
@Override
|
||||
public AccordCachingState<Key, CommandsForKey> global()
|
||||
public AccordCachingState<RoutingKey, CommandsForKey> global()
|
||||
{
|
||||
checkNotInvalidated();
|
||||
return global;
|
||||
|
|
|
|||
|
|
@ -23,21 +23,21 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import accord.api.Key;
|
||||
import accord.api.RoutingKey;
|
||||
import accord.impl.SafeTimestampsForKey;
|
||||
import accord.impl.TimestampsForKey;
|
||||
import accord.primitives.Timestamp;
|
||||
|
||||
public class AccordSafeTimestampsForKey extends SafeTimestampsForKey implements AccordSafeState<Key, TimestampsForKey>
|
||||
public class AccordSafeTimestampsForKey extends SafeTimestampsForKey implements AccordSafeState<RoutingKey, TimestampsForKey>
|
||||
{
|
||||
private boolean invalidated;
|
||||
private final AccordCachingState<Key, TimestampsForKey> global;
|
||||
private final AccordCachingState<RoutingKey, TimestampsForKey> global;
|
||||
private TimestampsForKey original;
|
||||
private TimestampsForKey current;
|
||||
|
||||
public AccordSafeTimestampsForKey(AccordCachingState<Key, TimestampsForKey> global)
|
||||
public AccordSafeTimestampsForKey(AccordCachingState<RoutingKey, TimestampsForKey> global)
|
||||
{
|
||||
super((Key) global.key());
|
||||
super(global.key());
|
||||
this.global = global;
|
||||
this.original = null;
|
||||
this.current = null;
|
||||
|
|
@ -70,7 +70,7 @@ public class AccordSafeTimestampsForKey extends SafeTimestampsForKey implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public AccordCachingState<Key, TimestampsForKey> global()
|
||||
public AccordCachingState<RoutingKey, TimestampsForKey> global()
|
||||
{
|
||||
checkNotInvalidated();
|
||||
return global;
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@
|
|||
*/
|
||||
package org.apache.cassandra.service.accord;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.PriorityQueue;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -30,31 +30,43 @@ import org.apache.cassandra.db.ColumnFamilyStore;
|
|||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.db.SerializationHeader;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate.SimpleBuilder;
|
||||
import org.apache.cassandra.db.rows.EncodingStats;
|
||||
import org.apache.cassandra.io.sstable.Descriptor;
|
||||
import org.apache.cassandra.io.sstable.SSTableTxnWriter;
|
||||
import org.apache.cassandra.io.util.DataInputBuffer;
|
||||
import org.apache.cassandra.io.util.DataOutputBuffer;
|
||||
import org.apache.cassandra.journal.KeySupport;
|
||||
import org.apache.cassandra.journal.SegmentCompactor;
|
||||
import org.apache.cassandra.journal.StaticSegment;
|
||||
import org.apache.cassandra.journal.StaticSegment.KeyOrderReader;
|
||||
import org.apache.cassandra.service.accord.AccordJournalValueSerializers.FlyweightSerializer;
|
||||
|
||||
/**
|
||||
* Segment compactor: takes static segments and compacts them into a single SSTable.
|
||||
*/
|
||||
public class AccordSegmentCompactor<K, V> implements SegmentCompactor<K, V>
|
||||
public class AccordSegmentCompactor<V> implements SegmentCompactor<JournalKey, V>
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(AccordSegmentCompactor.class);
|
||||
private final int userVersion;
|
||||
private final KeySupport<JournalKey> keySupport;
|
||||
|
||||
public AccordSegmentCompactor(KeySupport<JournalKey> keySupport, int userVersion)
|
||||
{
|
||||
this.userVersion = userVersion;
|
||||
this.keySupport = keySupport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<StaticSegment<K, V>> compact(Collection<StaticSegment<K, V>> segments, KeySupport<K> keySupport)
|
||||
public Collection<StaticSegment<JournalKey, V>> compact(Collection<StaticSegment<JournalKey, V>> segments)
|
||||
{
|
||||
Invariants.checkState(segments.size() >= 2, () -> String.format("Can only compact 2 or more segments, but got %d", segments.size()));
|
||||
logger.info("Compacting {} static segments: {}", segments.size(), segments);
|
||||
|
||||
PriorityQueue<KeyOrderReader<K>> readers = new PriorityQueue<>();
|
||||
for (StaticSegment<K, V> segment : segments)
|
||||
PriorityQueue<KeyOrderReader<JournalKey>> readers = new PriorityQueue<>();
|
||||
for (StaticSegment<JournalKey, V> segment : segments)
|
||||
{
|
||||
KeyOrderReader<K> reader = segment.keyOrderReader();
|
||||
KeyOrderReader<JournalKey> reader = segment.keyOrderReader();
|
||||
if (reader.advance())
|
||||
readers.add(reader);
|
||||
}
|
||||
|
|
@ -62,7 +74,7 @@ public class AccordSegmentCompactor<K, V> implements SegmentCompactor<K, V>
|
|||
// nothing to compact (all segments empty, should never happen, but it is theoretically possible?) - exit early
|
||||
// TODO: investigate how this comes to be, check if there is a cleanup issue
|
||||
if (readers.isEmpty())
|
||||
return segments;
|
||||
return null;
|
||||
|
||||
ColumnFamilyStore cfs = Keyspace.open(AccordKeyspace.metadata().name).getColumnFamilyStore(AccordKeyspace.JOURNAL);
|
||||
Descriptor descriptor = cfs.newSSTableDescriptor(cfs.getDirectories().getDirectoryForNewSSTables());
|
||||
|
|
@ -70,50 +82,67 @@ public class AccordSegmentCompactor<K, V> implements SegmentCompactor<K, V>
|
|||
|
||||
try (SSTableTxnWriter writer = SSTableTxnWriter.create(cfs, descriptor, 0, 0, null, false, header))
|
||||
{
|
||||
K key = null;
|
||||
PartitionUpdate.SimpleBuilder partitionBuilder = null;
|
||||
|
||||
JournalKey key = null;
|
||||
Object builder = null;
|
||||
FlyweightSerializer<Object, Object> serializer = null;
|
||||
long lastDescriptor = -1;
|
||||
int lastOffset = -1;
|
||||
try
|
||||
{
|
||||
KeyOrderReader<K> reader;
|
||||
KeyOrderReader<JournalKey> reader;
|
||||
while ((reader = readers.poll()) != null)
|
||||
{
|
||||
if (!reader.key().equals(key)) // first ever - or new - key
|
||||
if (key == null || !reader.key().equals(key))
|
||||
{
|
||||
if (partitionBuilder != null) // append previous partition if any
|
||||
writer.append(partitionBuilder.build().unfilteredIterator());
|
||||
maybeWritePartition(cfs, writer, key, builder, serializer, lastDescriptor, lastOffset);
|
||||
|
||||
key = reader.key();
|
||||
partitionBuilder = PartitionUpdate.simpleBuilder(
|
||||
AccordKeyspace.Journal, AccordJournalTable.makePartitionKey(cfs, key, keySupport, reader.descriptor.userVersion)
|
||||
);
|
||||
serializer = (FlyweightSerializer<Object, Object>) key.type.serializer;
|
||||
builder = serializer.mergerFor(key);
|
||||
}
|
||||
|
||||
boolean advanced;
|
||||
do
|
||||
{
|
||||
partitionBuilder.row(reader.descriptor.timestamp, reader.offset())
|
||||
.add("record", reader.record())
|
||||
.add("user_version", reader.descriptor.userVersion);
|
||||
try (DataInputBuffer in = new DataInputBuffer(reader.record(), false))
|
||||
{
|
||||
serializer.deserialize(key, builder, in, reader.descriptor.userVersion);
|
||||
lastDescriptor = reader.descriptor.timestamp;
|
||||
lastOffset = reader.offset();
|
||||
}
|
||||
}
|
||||
while ((advanced = reader.advance()) && reader.key().equals(key));
|
||||
|
||||
if (advanced) readers.offer(reader); // there is more to this reader, but not with this key
|
||||
}
|
||||
|
||||
//noinspection DataFlowIssue
|
||||
writer.append(partitionBuilder.build().unfilteredIterator()); // append the last partition
|
||||
maybeWritePartition(cfs, writer, key, builder, serializer, lastDescriptor, lastOffset);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
Throwable accumulate = writer.abort(t);
|
||||
Throwables.throwIfUnchecked(accumulate);
|
||||
throw new RuntimeException(accumulate);
|
||||
throw new RuntimeException(String.format("Caught exception while serializing. Last seen key: %s", key), accumulate);
|
||||
}
|
||||
|
||||
cfs.addSSTables(writer.finish(true));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeWritePartition(ColumnFamilyStore cfs, SSTableTxnWriter writer, JournalKey key, Object builder, FlyweightSerializer<Object, Object> serializer, long descriptor, int offset) throws IOException
|
||||
{
|
||||
if (builder != null)
|
||||
{
|
||||
SimpleBuilder partitionBuilder = PartitionUpdate.simpleBuilder(AccordKeyspace.Journal, AccordJournalTable.makePartitionKey(cfs, key, keySupport, userVersion));
|
||||
try (DataOutputBuffer out = DataOutputBuffer.scratchBuffer.get())
|
||||
{
|
||||
serializer.reserialize(key, builder, out, userVersion);
|
||||
partitionBuilder.row(descriptor, offset)
|
||||
.add("record", out.asNewBuffer())
|
||||
.add("user_version", userVersion);
|
||||
}
|
||||
writer.append(partitionBuilder.build().unfilteredIterator());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ import org.slf4j.LoggerFactory;
|
|||
import accord.api.BarrierType;
|
||||
import accord.api.LocalConfig;
|
||||
import accord.api.Result;
|
||||
import accord.coordinate.Barrier.AsyncSyncPoint;
|
||||
import accord.coordinate.CoordinationAdapter.Adapters.SyncPointAdapter;
|
||||
import accord.coordinate.CoordinationFailed;
|
||||
import accord.coordinate.Preempted;
|
||||
import accord.coordinate.Timeout;
|
||||
|
|
@ -93,15 +95,14 @@ import accord.impl.SizeOfIntersectionSorter;
|
|||
import accord.impl.progresslog.DefaultProgressLogs;
|
||||
import accord.local.CommandStore;
|
||||
import accord.local.CommandStores;
|
||||
import accord.local.CommandStores.RangesForEpoch;
|
||||
import accord.local.DurableBefore;
|
||||
import accord.local.KeyHistory;
|
||||
import accord.local.Node;
|
||||
import accord.local.Node.Id;
|
||||
import accord.local.NodeTimeService;
|
||||
import accord.local.RedundantBefore;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.local.ShardDistributor.EvenSplit;
|
||||
import accord.local.Status;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.messages.Callback;
|
||||
import accord.messages.ReadData;
|
||||
|
|
@ -110,6 +111,10 @@ import accord.messages.WaitUntilApplied;
|
|||
import accord.primitives.Keys;
|
||||
import accord.primitives.Seekable;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.FullRoute;
|
||||
import accord.primitives.RoutingKeys;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.Txn;
|
||||
import accord.primitives.Txn.Kind;
|
||||
|
|
@ -142,6 +147,7 @@ import org.apache.cassandra.schema.TableId;
|
|||
import org.apache.cassandra.service.accord.AccordSyncPropagator.Notification;
|
||||
import org.apache.cassandra.service.accord.api.AccordAgent;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.KeyspaceSplitter;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
||||
import org.apache.cassandra.service.accord.api.AccordScheduler;
|
||||
import org.apache.cassandra.service.accord.api.AccordTopologySorter;
|
||||
import org.apache.cassandra.service.accord.api.CompositeTopologySorter;
|
||||
|
|
@ -174,6 +180,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
|
|||
import static org.apache.cassandra.config.DatabaseDescriptor.getPartitioner;
|
||||
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.accordReadMetrics;
|
||||
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.accordWriteMetrics;
|
||||
import static org.apache.cassandra.service.consensus.migration.ConsensusKeyMigrationState.maybeSaveAccordKeyMigrationLocally;
|
||||
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
|
||||
|
||||
public class AccordService implements IAccordService, Shutdownable
|
||||
|
|
@ -207,19 +214,19 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
}
|
||||
|
||||
@Override
|
||||
public Seekables barrierWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException
|
||||
public Seekables<?, ?> barrierWithRetries(Seekables<?, ?> keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Seekables barrier(@Nonnull Seekables keysOrRanges, long minEpoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite)
|
||||
public Seekables<?, ?> barrier(@Nonnull Seekables<?, ?> keysOrRanges, long minEpoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite)
|
||||
{
|
||||
throw new UnsupportedOperationException("No accord barriers should be executed when accord.enabled = false in cassandra.yaml");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Seekables repair(@Nonnull Seekables keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints)
|
||||
public Seekables<?, ?> repair(@Nonnull Seekables<?, ?> keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints)
|
||||
{
|
||||
throw new UnsupportedOperationException("No accord repairs should be executed when accord.enabled = false in cassandra.yaml");
|
||||
}
|
||||
|
|
@ -357,6 +364,8 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
as.configurationService().notifyPostCommit(current, current, false);
|
||||
}
|
||||
instance = as;
|
||||
|
||||
as.journal().replay();
|
||||
}
|
||||
|
||||
public static void shutdownServiceAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
|
||||
|
|
@ -393,7 +402,7 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
this.scheduler = new AccordScheduler();
|
||||
this.dataStore = new AccordDataStore();
|
||||
this.configuration = new AccordConfiguration(DatabaseDescriptor.getRawConfig());
|
||||
this.journal = new AccordJournal(configService, DatabaseDescriptor.getAccord().journal);
|
||||
this.journal = new AccordJournal(DatabaseDescriptor.getAccord().journal);
|
||||
this.node = new Node(localId,
|
||||
messageSink,
|
||||
configService,
|
||||
|
|
@ -415,7 +424,7 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
configuration);
|
||||
this.nodeShutdown = toShutdownable(node);
|
||||
this.durabilityScheduling = new CoordinateDurabilityScheduling(node);
|
||||
this.requestHandler = new AccordVerbHandler<>(node, configService, journal);
|
||||
this.requestHandler = new AccordVerbHandler<>(node, configService);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -473,7 +482,7 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
durabilityScheduling.setShardCycleTime(Ints.checkedCast(DatabaseDescriptor.getAccordShardDurabilityCycle(SECONDS)), SECONDS);
|
||||
durabilityScheduling.setTxnIdLag(Ints.checkedCast(DatabaseDescriptor.getAccordScheduleDurabilityTxnIdLag(SECONDS)), TimeUnit.SECONDS);
|
||||
durabilityScheduling.setFrequency(Ints.checkedCast(DatabaseDescriptor.getAccordScheduleDurabilityFrequency(SECONDS)), SECONDS);
|
||||
// durabilityScheduling.start();
|
||||
durabilityScheduling.start();
|
||||
state = State.STARTED;
|
||||
}
|
||||
|
||||
|
|
@ -551,10 +560,10 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
return requestHandler;
|
||||
}
|
||||
|
||||
private <S extends Seekables<?, ?>> Seekables barrier(@Nonnull S keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, BiFunction<Node, S, AsyncResult<SyncPoint<S>>> syncPoint)
|
||||
private Seekables<?, ?> barrier(@Nonnull Seekables<?, ?> keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, BiFunction<Node, FullRoute<?>, AsyncSyncPoint> syncPoint)
|
||||
{
|
||||
Stopwatch sw = Stopwatch.createStarted();
|
||||
keysOrRanges = (S) intersectionWithAccordManagedRanges(keysOrRanges);
|
||||
keysOrRanges = intersectionWithAccordManagedRanges(keysOrRanges);
|
||||
// It's possible none of them were Accord managed and we aren't going to treat that as an error
|
||||
if (keysOrRanges.isEmpty())
|
||||
{
|
||||
|
|
@ -562,15 +571,22 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
return keysOrRanges;
|
||||
}
|
||||
|
||||
FullRoute<?> route = node.computeRoute(epoch, keysOrRanges);
|
||||
AccordClientRequestMetrics metrics = isForWrite ? accordWriteMetrics : accordReadMetrics;
|
||||
try
|
||||
{
|
||||
logger.debug("Starting barrier key: {} epoch: {} barrierType: {} isForWrite {}", keysOrRanges, epoch, barrierType, isForWrite);
|
||||
AsyncResult<TxnId> asyncResult = syncPoint == null
|
||||
? Barrier.barrier(node, keysOrRanges, epoch, barrierType)
|
||||
: Barrier.barrier(node, keysOrRanges, epoch, barrierType, syncPoint);
|
||||
? Barrier.barrier(node, keysOrRanges, route, epoch, barrierType)
|
||||
: Barrier.barrier(node, keysOrRanges, route, epoch, barrierType, syncPoint);
|
||||
if (keysOrRanges.domain() == Key)
|
||||
{
|
||||
PartitionKey key = (PartitionKey)keysOrRanges.get(0);
|
||||
asyncResult.accept(txnId -> maybeSaveAccordKeyMigrationLocally(key, Epoch.create(txnId.epoch())));
|
||||
}
|
||||
long deadlineNanos = requestTime.startedAtNanos() + timeoutNanos;
|
||||
Timestamp barrierExecuteAt = AsyncChains.getBlocking(asyncResult, deadlineNanos - nanoTime(), NANOSECONDS);
|
||||
TxnId txnId = AsyncChains.getBlocking(asyncResult, deadlineNanos - nanoTime(), NANOSECONDS);
|
||||
((AccordAgent) node.agent()).onSuccessfulBarrier(txnId, keysOrRanges);
|
||||
logger.debug("Completed barrier attempt in {}ms, {}ms since attempts start, barrier key: {} epoch: {} barrierType: {} isForWrite {}",
|
||||
sw.elapsed(MILLISECONDS),
|
||||
NANOSECONDS.toMillis(nanoTime() - requestTime.startedAtNanos()),
|
||||
|
|
@ -627,33 +643,37 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
}
|
||||
|
||||
@Override
|
||||
public Seekables barrier(@Nonnull Seekables keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite)
|
||||
public Seekables<?, ?> barrier(@Nonnull Seekables<?, ?> keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite)
|
||||
{
|
||||
return barrier(keysOrRanges, epoch, requestTime, timeoutNanos, barrierType, isForWrite, null);
|
||||
}
|
||||
|
||||
public static <S extends Seekables<?, ?>> BiFunction<Node, S, AsyncResult<SyncPoint<S>>> repairSyncPoint(Set<Node.Id> allNodes)
|
||||
public static BiFunction<Node, FullRoute<?>, AsyncSyncPoint> repairSyncPoint(Set<Node.Id> allNodes)
|
||||
{
|
||||
return (node, seekables) -> CoordinateSyncPoint.coordinate(node, Kind.SyncPoint, seekables, RepairSyncPointAdapter.create(allNodes));
|
||||
return (node, route) -> {
|
||||
TxnId txnId = node.nextTxnId(Kind.SyncPoint, route.domain());
|
||||
AsyncResult<SyncPoint<?>> async = CoordinateSyncPoint.coordinate(node, Kind.SyncPoint, route, (SyncPointAdapter)RepairSyncPointAdapter.create(allNodes));
|
||||
return new AsyncSyncPoint(txnId, async);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Seekables repair(@Nonnull Seekables keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints)
|
||||
public Seekables<?, ?> repair(@Nonnull Seekables<?, ?> keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints)
|
||||
{
|
||||
Set<Node.Id> allNodes = allEndpoints.stream().map(configService::mappedId).collect(Collectors.toUnmodifiableSet());
|
||||
return barrier(keysOrRanges, epoch, requestTime, timeoutNanos, barrierType, isForWrite, repairSyncPoint(allNodes));
|
||||
}
|
||||
|
||||
private static <S extends Seekables<?, ?>> Seekables intersectionWithAccordManagedRanges(Seekables<?, ?> keysOrRanges)
|
||||
private static Seekables<?, ?> intersectionWithAccordManagedRanges(Seekables<?, ?> keysOrRanges)
|
||||
{
|
||||
TableId tableId = null;
|
||||
for (Seekable seekable : keysOrRanges)
|
||||
for (Seekable keyOrRange : keysOrRanges)
|
||||
{
|
||||
TableId newTableId;
|
||||
if (keysOrRanges.domain() == Key)
|
||||
newTableId = ((PartitionKey) seekable).table();
|
||||
newTableId = ((PartitionKey)keyOrRange).table();
|
||||
else if (keysOrRanges.domain() == Range)
|
||||
newTableId = ((TokenRange) seekable).table();
|
||||
newTableId = ((TokenRange) keyOrRange).table();
|
||||
else
|
||||
throw new IllegalStateException("Unexpected domain " + keysOrRanges.domain());
|
||||
|
||||
|
|
@ -780,7 +800,7 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
}
|
||||
|
||||
@Override
|
||||
public Seekables repairWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints) throws InterruptedException
|
||||
public Seekables<?, ?> repairWithRetries(Seekables<?, ?> keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints) throws InterruptedException
|
||||
{
|
||||
return doWithRetries(Blocking.Default.instance, () -> AccordService.instance().repair(keysOrRanges, minEpoch, Dispatcher.RequestTime.forImmediateExecution(), DatabaseDescriptor.getAccordRangeBarrierTimeoutNanos(), barrierType, isForWrite, allEndpoints),
|
||||
DatabaseDescriptor.getAccordBarrierRetryAttempts(),
|
||||
|
|
@ -1030,9 +1050,9 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
return submit.flatMap(Function.identity());
|
||||
}
|
||||
|
||||
private static AsyncChain<Void> populate(CommandStoreTxnBlockedGraph.Builder state, CommandStore commandStore, PartitionKey blockedBy, TxnId txnId, Timestamp executeAt)
|
||||
private static AsyncChain<Void> populate(CommandStoreTxnBlockedGraph.Builder state, CommandStore commandStore, TokenKey blockedBy, TxnId txnId, Timestamp executeAt)
|
||||
{
|
||||
AsyncChain<AsyncChain<Void>> submit = commandStore.submit(PreLoadContext.contextFor(txnId, Keys.of(blockedBy), KeyHistory.COMMANDS), in -> {
|
||||
AsyncChain<AsyncChain<Void>> submit = commandStore.submit(PreLoadContext.contextFor(txnId, RoutingKeys.of(blockedBy.toUnseekable()), KeyHistory.COMMANDS), in -> {
|
||||
AsyncChain<Void> chain = populate(state, (AccordSafeCommandStore) in, blockedBy, txnId, executeAt);
|
||||
return chain == null ? AsyncChains.success(null) : chain;
|
||||
});
|
||||
|
|
@ -1067,7 +1087,7 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
chains.add(populate(state, safeStore.commandStore(), blockedBy));
|
||||
}
|
||||
}
|
||||
for (PartitionKey blockedBy : cmdTxnState.blockedByKey)
|
||||
for (TokenKey blockedBy : cmdTxnState.blockedByKey)
|
||||
{
|
||||
if (state.keys.containsKey(blockedBy)) continue;
|
||||
if (safeStore.getCommandsForKeyIfLoaded(blockedBy) != null)
|
||||
|
|
@ -1087,7 +1107,7 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
return AsyncChains.all(chains).map(ignore -> null);
|
||||
}
|
||||
|
||||
private static AsyncChain<Void> populate(CommandStoreTxnBlockedGraph.Builder state, AccordSafeCommandStore safeStore, PartitionKey pk, TxnId txnId, Timestamp executeAt)
|
||||
private static AsyncChain<Void> populate(CommandStoreTxnBlockedGraph.Builder state, AccordSafeCommandStore safeStore, TokenKey pk, TxnId txnId, Timestamp executeAt)
|
||||
{
|
||||
AccordSafeCommandsForKey commandsForKey = safeStore.getCommandsForKeyIfLoaded(pk);
|
||||
TxnId blocking = commandsForKey.current().blockedOnTxnId(txnId, executeAt);
|
||||
|
|
@ -1115,7 +1135,7 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
else
|
||||
{
|
||||
// blocked on key
|
||||
cmdTxnState.blockedByKey.add((PartitionKey) waitingOn.keys.get(i - waitingOn.txnIdCount()));
|
||||
cmdTxnState.blockedByKey.add((TokenKey) waitingOn.keys.get(i - waitingOn.txnIdCount()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1138,9 +1158,9 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
tryMarkRemoved(ranges, 0).begin(node().agent());
|
||||
}
|
||||
|
||||
private AsyncChain<SyncPoint<Ranges>> tryMarkRemoved(Ranges ranges, int attempt)
|
||||
private AsyncChain<SyncPoint<accord.primitives.Range>> tryMarkRemoved(Ranges ranges, int attempt)
|
||||
{
|
||||
return CoordinateSyncPoint.exclusive(node, ranges)
|
||||
return CoordinateSyncPoint.exclusiveSyncPoint(node, ranges)
|
||||
.recover(t ->
|
||||
//TODO (operability): make this configurable / monitorable?
|
||||
attempt <= 3 && t instanceof Invalidated || t instanceof Preempted || t instanceof Timeout ? tryMarkRemoved(ranges, attempt + 1) : null);
|
||||
|
|
@ -1234,7 +1254,7 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
public CompactionInfo getCompactionInfo()
|
||||
{
|
||||
Int2ObjectHashMap<RedundantBefore> redundantBefores = new Int2ObjectHashMap<>();
|
||||
Int2ObjectHashMap<CommandStores.RangesForEpoch> ranges = new Int2ObjectHashMap<>();
|
||||
Int2ObjectHashMap<RangesForEpoch> ranges = new Int2ObjectHashMap<>();
|
||||
AtomicReference<DurableBefore> durableBefore = new AtomicReference<>(DurableBefore.EMPTY);
|
||||
AsyncChains.getBlockingAndRethrow(node.commandStores().forEach(safeStore -> {
|
||||
synchronized (redundantBefores)
|
||||
|
|
@ -1334,10 +1354,10 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
.flatMap(s -> s == null ? AsyncChains.success(null) : Await.coordinate(node, s));
|
||||
}
|
||||
|
||||
private AsyncChain<SyncPoint<Ranges>> exclusiveSyncPoint(Ranges ranges, int attempt)
|
||||
private AsyncChain<SyncPoint<accord.primitives.Range>> exclusiveSyncPoint(Ranges ranges, int attempt)
|
||||
{
|
||||
//TODO (on merge): CASSANDRA-19769 has the same logic... should this be refactored? Would make it nice so we could split the range on retries?
|
||||
return CoordinateSyncPoint.exclusive(node, ranges)
|
||||
return CoordinateSyncPoint.exclusiveSyncPoint(node, ranges)
|
||||
.recover(t -> {
|
||||
//TODO (operability): make this configurable / monitorable?
|
||||
if (attempt > 3) return null;
|
||||
|
|
@ -1368,21 +1388,21 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
// TODO (duplication): this is 95% of accord.coordinate.CoordinateShardDurable
|
||||
// we already report all this information to EpochState; would be better to use that
|
||||
// Taken from ListStore...
|
||||
private static class Await extends AsyncResults.SettableResult<SyncPoint<Ranges>> implements Callback<ReadData.ReadReply>
|
||||
private static class Await extends AsyncResults.SettableResult<SyncPoint<?>> implements Callback<ReadData.ReadReply>
|
||||
{
|
||||
private final Node node;
|
||||
private final AllTracker tracker;
|
||||
private final SyncPoint<Ranges> exclusiveSyncPoint;
|
||||
private final SyncPoint<?> exclusiveSyncPoint;
|
||||
|
||||
private Await(Node node, SyncPoint<Ranges> exclusiveSyncPoint)
|
||||
private Await(Node node, SyncPoint<?> exclusiveSyncPoint)
|
||||
{
|
||||
Topologies topologies = node.topology().forEpoch(exclusiveSyncPoint.keysOrRanges, exclusiveSyncPoint.sourceEpoch());
|
||||
Topologies topologies = node.topology().forEpoch(exclusiveSyncPoint.route, exclusiveSyncPoint.sourceEpoch());
|
||||
this.node = node;
|
||||
this.tracker = new AllTracker(topologies);
|
||||
this.exclusiveSyncPoint = exclusiveSyncPoint;
|
||||
}
|
||||
|
||||
public static AsyncChain<Void> coordinate(Node node, SyncPoint<Ranges> sp)
|
||||
public static AsyncChain<Void> coordinate(Node node, SyncPoint<?> sp)
|
||||
{
|
||||
return node.withEpoch(sp.sourceEpoch(), () -> {
|
||||
Await coordinate = new Await(node, sp);
|
||||
|
|
@ -1402,7 +1422,7 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
|
||||
private void start()
|
||||
{
|
||||
node.send(tracker.nodes(), to -> new WaitUntilApplied(to, tracker.topologies(), exclusiveSyncPoint.syncId, exclusiveSyncPoint.keysOrRanges, exclusiveSyncPoint.syncId.epoch()), this);
|
||||
node.send(tracker.nodes(), to -> new WaitUntilApplied(to, tracker.topologies(), exclusiveSyncPoint.syncId, exclusiveSyncPoint.route, exclusiveSyncPoint.syncId.epoch()), this);
|
||||
}
|
||||
@Override
|
||||
public void onSuccess(Node.Id from, ReadData.ReadReply reply)
|
||||
|
|
@ -1423,7 +1443,7 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
tryFailure(new ExecuteSyncPoint.SyncPointErased());
|
||||
return;
|
||||
case Invalid:
|
||||
tryFailure(new Invalidated(exclusiveSyncPoint.syncId, exclusiveSyncPoint.homeKey));
|
||||
tryFailure(new Invalidated(exclusiveSyncPoint.syncId, exclusiveSyncPoint.route.homeKey()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1431,7 +1451,7 @@ public class AccordService implements IAccordService, Shutdownable
|
|||
{
|
||||
if (tracker.recordSuccess(from) == RequestStatus.Success)
|
||||
{
|
||||
node.configService().reportEpochRedundant(exclusiveSyncPoint.keysOrRanges, exclusiveSyncPoint.syncId.epoch());
|
||||
node.configService().reportEpochRedundant(exclusiveSyncPoint.route.toRanges(), exclusiveSyncPoint.syncId.epoch());
|
||||
trySuccess(exclusiveSyncPoint);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -379,6 +379,25 @@ public class AccordStateCache extends IntrusiveLinkedList<AccordCachingState<?,?
|
|||
return safeRefFactory.apply(acquireExisting(node, false));
|
||||
}
|
||||
|
||||
|
||||
public void maybeLoad(K key, V initial)
|
||||
{
|
||||
AccordCachingState<K, V> node = (AccordCachingState<K, V>) cache.get(key);
|
||||
if (node == null)
|
||||
{
|
||||
node = nodeFactory.create(key, index);
|
||||
node.initialize(initial);
|
||||
Object prev = cache.put(key, node);
|
||||
Invariants.checkState(prev == null, "%s not absent from cache: %s already present", key, node);
|
||||
if (listeners != null)
|
||||
{
|
||||
AccordCachingState<K, V> finalNode = node;
|
||||
listeners.forEach(l -> l.onAdd(finalNode));
|
||||
}
|
||||
maybeUpdateSize(node, heapEstimator);
|
||||
}
|
||||
}
|
||||
|
||||
public S acquire(K key)
|
||||
{
|
||||
AccordCachingState<K, V> node = acquire(key, false);
|
||||
|
|
|
|||
|
|
@ -23,12 +23,12 @@ import java.util.concurrent.ExecutionException;
|
|||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import accord.local.Node.Id;
|
||||
import accord.primitives.Ranges;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import accord.local.Node;
|
||||
import accord.topology.Shard;
|
||||
import accord.topology.Topology;
|
||||
import accord.utils.Invariants;
|
||||
|
|
@ -58,14 +58,14 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|||
*/
|
||||
public class AccordTopology
|
||||
{
|
||||
public static Node.Id tcmIdToAccord(NodeId nodeId)
|
||||
public static Id tcmIdToAccord(NodeId nodeId)
|
||||
{
|
||||
return new Node.Id(nodeId.id());
|
||||
return new Id(nodeId.id());
|
||||
}
|
||||
|
||||
private static class ShardLookup extends HashMap<accord.primitives.Range, Shard>
|
||||
{
|
||||
private Shard createOrReuse(boolean pendingRemoval, accord.primitives.Range range, SortedArrayList<Node.Id> nodes, Set<Node.Id> fastPathElectorate, Set<Node.Id> joining)
|
||||
private Shard createOrReuse(boolean pendingRemoval, accord.primitives.Range range, SortedArrayList<Id> nodes, Set<Id> fastPathElectorate, Set<Id> joining)
|
||||
{
|
||||
Shard prev = get(range);
|
||||
if (prev != null
|
||||
|
|
@ -83,10 +83,10 @@ public class AccordTopology
|
|||
{
|
||||
private final KeyspaceMetadata keyspace;
|
||||
private final Range<Token> range;
|
||||
private final SortedArrayList<Node.Id> nodes;
|
||||
private final Set<Node.Id> pending;
|
||||
private final SortedArrayList<Id> nodes;
|
||||
private final Set<Id> pending;
|
||||
|
||||
private KeyspaceShard(KeyspaceMetadata keyspace, Range<Token> range, SortedArrayList<Node.Id> nodes, Set<Node.Id> pending)
|
||||
private KeyspaceShard(KeyspaceMetadata keyspace, Range<Token> range, SortedArrayList<Id> nodes, Set<Id> pending)
|
||||
{
|
||||
this.keyspace = keyspace;
|
||||
this.range = range;
|
||||
|
|
@ -100,15 +100,15 @@ public class AccordTopology
|
|||
FastPathStrategy tableStrategy = metadata.params.fastPath;
|
||||
FastPathStrategy strategy = tableStrategy.kind() != FastPathStrategy.Kind.INHERIT_KEYSPACE
|
||||
? tableStrategy : keyspace.params.fastPath;
|
||||
Invariants.checkState(strategy.kind() != FastPathStrategy.Kind.INHERIT_KEYSPACE);;
|
||||
Invariants.checkState(strategy.kind() != FastPathStrategy.Kind.INHERIT_KEYSPACE);
|
||||
return strategy;
|
||||
}
|
||||
|
||||
Shard createForTable(TableMetadata metadata, Set<Node.Id> unavailable, Map<Node.Id, String> dcMap, ShardLookup lookup)
|
||||
Shard createForTable(TableMetadata metadata, Set<Id> unavailable, Map<Id, String> dcMap, ShardLookup lookup)
|
||||
{
|
||||
TokenRange tokenRange = AccordTopology.range(metadata.id, range);
|
||||
|
||||
SortedArrayList<Node.Id> fastPath = strategyFor(metadata).calculateFastPath(nodes, unavailable, dcMap);
|
||||
SortedArrayList<Id> fastPath = strategyFor(metadata).calculateFastPath(nodes, unavailable, dcMap);
|
||||
|
||||
return lookup.createOrReuse(metadata.params.pendingDrop, tokenRange, nodes, fastPath, pending);
|
||||
}
|
||||
|
|
@ -124,14 +124,14 @@ public class AccordTopology
|
|||
Sets.SetView<InetAddressAndPort> readOnly = Sets.difference(readEndpoints, writeEndpoints);
|
||||
Invariants.checkState(readOnly.isEmpty(), "Read only replicas detected: %s", readOnly);
|
||||
|
||||
SortedArrayList<Node.Id> nodes = new SortedArrayList<>(writes.endpoints().stream()
|
||||
.map(directory::peerId)
|
||||
.map(AccordTopology::tcmIdToAccord)
|
||||
.sorted().toArray(Node.Id[]::new));
|
||||
SortedArrayList<Id> nodes = new SortedArrayList<>(writes.endpoints().stream()
|
||||
.map(directory::peerId)
|
||||
.map(AccordTopology::tcmIdToAccord)
|
||||
.sorted().toArray(Id[]::new));
|
||||
|
||||
Set<Node.Id> pending = readEndpoints.equals(writeEndpoints) ?
|
||||
Collections.emptySet() :
|
||||
writeEndpoints.stream()
|
||||
Set<Id> pending = readEndpoints.equals(writeEndpoints) ?
|
||||
Collections.emptySet() :
|
||||
writeEndpoints.stream()
|
||||
.filter(e -> !readEndpoints.contains(e))
|
||||
.map(directory::peerId)
|
||||
.map(AccordTopology::tcmIdToAccord)
|
||||
|
|
@ -156,7 +156,7 @@ public class AccordTopology
|
|||
return shards;
|
||||
}
|
||||
|
||||
public List<Node.Id> nodes()
|
||||
public List<Id> nodes()
|
||||
{
|
||||
return nodes;
|
||||
}
|
||||
|
|
@ -213,9 +213,9 @@ public class AccordTopology
|
|||
return accordRanges;
|
||||
}
|
||||
|
||||
private static Map<Node.Id, String> createDCMap(Directory directory)
|
||||
private static Map<Id, String> createDCMap(Directory directory)
|
||||
{
|
||||
ImmutableMap.Builder<Node.Id, String> builder = ImmutableMap.builder();
|
||||
ImmutableMap.Builder<Id, String> builder = ImmutableMap.builder();
|
||||
directory.knownDatacenters().forEach(dc -> {
|
||||
Set<InetAddressAndPort> dcEndpoints = directory.datacenterEndpoints(dc);
|
||||
// nodes aren't added to the endpointsToDCMap until they've joined
|
||||
|
|
@ -223,7 +223,7 @@ public class AccordTopology
|
|||
return;
|
||||
dcEndpoints.forEach(ep -> {
|
||||
NodeId tid = directory.peerId(ep);
|
||||
Node.Id aid = tcmIdToAccord(tid);
|
||||
Id aid = tcmIdToAccord(tid);
|
||||
builder.put(aid, dc);
|
||||
});
|
||||
});
|
||||
|
|
@ -235,8 +235,8 @@ public class AccordTopology
|
|||
AccordStaleReplicas staleReplicas)
|
||||
{
|
||||
List<Shard> shards = new ArrayList<>();
|
||||
Set<Node.Id> unavailable = accordFastPath.unavailableIds();
|
||||
Map<Node.Id, String> dcMap = createDCMap(directory);
|
||||
Set<Id> unavailable = accordFastPath.unavailableIds();
|
||||
Map<Id, String> dcMap = createDCMap(directory);
|
||||
|
||||
for (KeyspaceMetadata keyspace : schema.getKeyspaces())
|
||||
{
|
||||
|
|
@ -249,7 +249,7 @@ public class AccordTopology
|
|||
|
||||
shards.sort((a, b) -> a.range.compare(b.range));
|
||||
|
||||
return new Topology(epoch.getEpoch(), staleReplicas.ids(), shards.toArray(new Shard[0]));
|
||||
return new Topology(epoch.getEpoch(), SortedArrayList.copyUnsorted(staleReplicas.ids(), Id[]::new), shards.toArray(new Shard[0]));
|
||||
}
|
||||
|
||||
public static Topology createAccordTopology(ClusterMetadata metadata, ShardLookup lookup)
|
||||
|
|
@ -275,7 +275,7 @@ public class AccordTopology
|
|||
|
||||
// There are cases where nodes are removed from the cluster (host replacement, decom, etc.), but inflight events may still be happening;
|
||||
// keep the ids around so pending events do not fail with a mapping error
|
||||
for (Node.Id id : mapping.differenceIds(builder))
|
||||
for (Id id : mapping.differenceIds(builder))
|
||||
builder.add(mapping.mappedEndpoint(id), id);
|
||||
return builder.build();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,13 +33,11 @@ public class AccordVerbHandler<T extends Request> implements IVerbHandler<T>
|
|||
|
||||
private final Node node;
|
||||
private final AccordEndpointMapper endpointMapper;
|
||||
private final AccordJournal journal;
|
||||
|
||||
public AccordVerbHandler(Node node, AccordEndpointMapper endpointMapper, AccordJournal journal)
|
||||
public AccordVerbHandler(Node node, AccordEndpointMapper endpointMapper)
|
||||
{
|
||||
this.node = node;
|
||||
this.endpointMapper = endpointMapper;
|
||||
this.journal = journal;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -50,12 +48,6 @@ public class AccordVerbHandler<T extends Request> implements IVerbHandler<T>
|
|||
logger.trace("Receiving {} from {}", message.payload, message.from());
|
||||
T request = message.payload;
|
||||
|
||||
if (request.type().hasSideEffects())
|
||||
{
|
||||
journal.processRemoteRequest(request, message);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO (desired): messages without side-effects don't go through the journal,
|
||||
* and as such are retained on heap until the node catches up to waitForEpoch,
|
||||
|
|
|
|||
|
|
@ -29,16 +29,16 @@ import com.google.common.collect.ImmutableList;
|
|||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import accord.local.SaveStatus;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
||||
|
||||
public class CommandStoreTxnBlockedGraph
|
||||
{
|
||||
public final int storeId;
|
||||
public final Map<TxnId, TxnState> txns;
|
||||
public final Map<PartitionKey, TxnId> keys;
|
||||
public final Map<TokenKey, TxnId> keys;
|
||||
|
||||
public CommandStoreTxnBlockedGraph(Builder builder)
|
||||
{
|
||||
|
|
@ -53,7 +53,7 @@ public class CommandStoreTxnBlockedGraph
|
|||
public final Timestamp executeAt;
|
||||
public final SaveStatus saveStatus;
|
||||
public final List<TxnId> blockedBy;
|
||||
public final Set<PartitionKey> blockedByKey;
|
||||
public final Set<TokenKey> blockedByKey;
|
||||
|
||||
public TxnState(Builder.TxnBuilder builder)
|
||||
{
|
||||
|
|
@ -79,7 +79,7 @@ public class CommandStoreTxnBlockedGraph
|
|||
{
|
||||
final int storeId;
|
||||
final Map<TxnId, TxnState> txns = new LinkedHashMap<>();
|
||||
final Map<PartitionKey, TxnId> keys = new LinkedHashMap<>();
|
||||
final Map<TokenKey, TxnId> keys = new LinkedHashMap<>();
|
||||
|
||||
public Builder(int storeId)
|
||||
{
|
||||
|
|
@ -107,7 +107,7 @@ public class CommandStoreTxnBlockedGraph
|
|||
final Timestamp executeAt;
|
||||
final SaveStatus saveStatus;
|
||||
List<TxnId> blockedBy = new ArrayList<>();
|
||||
Set<PartitionKey> blockedByKey = new LinkedHashSet<>();
|
||||
Set<TokenKey> blockedByKey = new LinkedHashSet<>();
|
||||
|
||||
public TxnBuilder(TxnId txnId, Timestamp executeAt, SaveStatus saveStatus)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ import accord.local.SafeCommandStore.CommandFunction;
|
|||
import accord.local.SafeCommandStore.TestDep;
|
||||
import accord.local.SafeCommandStore.TestStartedAt;
|
||||
import accord.local.SafeCommandStore.TestStatus;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.primitives.Range;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.Txn;
|
||||
import accord.primitives.TxnId;
|
||||
|
|
@ -44,9 +44,9 @@ import static accord.local.SafeCommandStore.TestDep.ANY_DEPS;
|
|||
import static accord.local.SafeCommandStore.TestDep.WITH;
|
||||
import static accord.local.SafeCommandStore.TestStartedAt.STARTED_BEFORE;
|
||||
import static accord.local.SafeCommandStore.TestStatus.ANY_STATUS;
|
||||
import static accord.local.Status.Stable;
|
||||
import static accord.local.Status.Truncated;
|
||||
import static accord.primitives.Routables.Slice.Minimal;
|
||||
import static accord.primitives.Status.Stable;
|
||||
import static accord.primitives.Status.Truncated;
|
||||
|
||||
public class CommandsForRanges implements CommandsSummary
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@ import com.google.common.collect.ImmutableMap;
|
|||
|
||||
import accord.local.Command;
|
||||
import accord.local.DurableBefore;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.local.Status;
|
||||
import accord.primitives.PartialDeps;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.Range;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.Routable;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import javax.annotation.Nullable;
|
|||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import accord.api.BarrierType;
|
||||
import accord.local.CommandStores;
|
||||
import accord.local.CommandStores.RangesForEpoch;
|
||||
import accord.local.DurableBefore;
|
||||
import accord.local.Node;
|
||||
import accord.local.Node.Id;
|
||||
|
|
@ -69,16 +69,16 @@ public interface IAccordService
|
|||
|
||||
IVerbHandler<? extends Request> verbHandler();
|
||||
|
||||
Seekables barrierWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException;
|
||||
Seekables<?, ?> barrierWithRetries(Seekables<?, ?> keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException;
|
||||
|
||||
Seekables barrier(@Nonnull Seekables keysOrRanges, long minEpoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite);
|
||||
Seekables<?, ?> barrier(@Nonnull Seekables<?, ?> keysOrRanges, long minEpoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite);
|
||||
|
||||
default Seekables repairWithRetries(Seekables keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints) throws InterruptedException
|
||||
default Seekables<?, ?> repairWithRetries(Seekables<?, ?> keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints) throws InterruptedException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
Seekables repair(@Nonnull Seekables keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints);
|
||||
Seekables<?, ?> repair(@Nonnull Seekables<?, ?> keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints);
|
||||
|
||||
default void postStreamReceivingBarrier(ColumnFamilyStore cfs, List<Range<Token>> ranges)
|
||||
{
|
||||
|
|
@ -136,10 +136,10 @@ public interface IAccordService
|
|||
static final Supplier<CompactionInfo> NO_OP = () -> new CompactionInfo(new Int2ObjectHashMap<>(), new Int2ObjectHashMap<>(), DurableBefore.EMPTY);
|
||||
|
||||
public final Int2ObjectHashMap<RedundantBefore> redundantBefores;
|
||||
public final Int2ObjectHashMap<CommandStores.RangesForEpoch> ranges;
|
||||
public final Int2ObjectHashMap<RangesForEpoch> ranges;
|
||||
public final DurableBefore durableBefore;
|
||||
|
||||
public CompactionInfo(Int2ObjectHashMap<RedundantBefore> redundantBefores, Int2ObjectHashMap<CommandStores.RangesForEpoch> ranges, DurableBefore durableBefore)
|
||||
public CompactionInfo(Int2ObjectHashMap<RedundantBefore> redundantBefores, Int2ObjectHashMap<RangesForEpoch> ranges, DurableBefore durableBefore)
|
||||
{
|
||||
this.redundantBefores = redundantBefores;
|
||||
this.ranges = ranges;
|
||||
|
|
|
|||
|
|
@ -19,19 +19,31 @@
|
|||
package org.apache.cassandra.service.accord;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.NavigableMap;
|
||||
|
||||
import accord.local.Command;
|
||||
import accord.local.CommandStores;
|
||||
import accord.local.DurableBefore;
|
||||
import accord.local.RedundantBefore;
|
||||
import accord.primitives.Deps;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
|
||||
public interface IJournal
|
||||
{
|
||||
Command loadCommand(int commandStoreId, TxnId txnId);
|
||||
|
||||
/**
|
||||
* Append outcomes to the log.
|
||||
*/
|
||||
void appendCommand(int commandStoreId,
|
||||
List<SavedCommand.Writer<TxnId>> command,
|
||||
List<Command> sanityCheck,
|
||||
Runnable onFlush);
|
||||
RedundantBefore loadRedundantBefore(int commandStoreId);
|
||||
DurableBefore loadDurableBefore(int commandStoreId);
|
||||
NavigableMap<TxnId, Ranges> loadBootstrapBeganAt(int commandStoreId);
|
||||
NavigableMap<Timestamp, Ranges> loadSafeToRead(int commandStoreId);
|
||||
CommandStores.RangesForEpoch.Snapshot loadRangesForEpoch(int commandStoreId);
|
||||
List<Deps> loadHistoricalTransactions(int store);
|
||||
|
||||
void appendCommand(int store, SavedCommand.DiffWriter value, Runnable onFlush);
|
||||
void persistStoreState(int store,
|
||||
// TODO: this class should not live under ASCS
|
||||
AccordSafeCommandStore.FieldUpdates fieldUpdates,
|
||||
Runnable onFlush);
|
||||
}
|
||||
|
|
@ -23,31 +23,38 @@ import java.nio.ByteBuffer;
|
|||
import java.util.Objects;
|
||||
import java.util.zip.Checksum;
|
||||
|
||||
import accord.local.Node;
|
||||
import accord.local.Node.Id;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.utils.Invariants;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.journal.KeySupport;
|
||||
import org.apache.cassandra.service.accord.AccordJournalValueSerializers.BootstrapBeganAtSerializer;
|
||||
import org.apache.cassandra.service.accord.AccordJournalValueSerializers.CommandDiffSerializer;
|
||||
import org.apache.cassandra.service.accord.AccordJournalValueSerializers.DurableBeforeSerializer;
|
||||
import org.apache.cassandra.service.accord.AccordJournalValueSerializers.FlyweightSerializer;
|
||||
import org.apache.cassandra.service.accord.AccordJournalValueSerializers.HistoricalTransactionsSerializer;
|
||||
import org.apache.cassandra.service.accord.AccordJournalValueSerializers.RedundantBeforeSerializer;
|
||||
import org.apache.cassandra.utils.ByteArrayUtil;
|
||||
|
||||
import static org.apache.cassandra.db.TypeSizes.BYTE_SIZE;
|
||||
import static org.apache.cassandra.db.TypeSizes.INT_SIZE;
|
||||
import static org.apache.cassandra.db.TypeSizes.LONG_SIZE;
|
||||
import static org.apache.cassandra.db.TypeSizes.SHORT_SIZE;
|
||||
import static org.apache.cassandra.service.accord.AccordJournalValueSerializers.RangesForEpochSerializer;
|
||||
import static org.apache.cassandra.service.accord.AccordJournalValueSerializers.SafeToReadSerializer;
|
||||
|
||||
public final class JournalKey
|
||||
{
|
||||
final Type type;
|
||||
public final Timestamp timestamp;
|
||||
// TODO: command store id _before_ timestamp
|
||||
public final int commandStoreId;
|
||||
|
||||
JournalKey(Timestamp timestamp)
|
||||
public JournalKey(Timestamp timestamp, Type type, int commandStoreId)
|
||||
{
|
||||
this(timestamp, -1);
|
||||
}
|
||||
|
||||
JournalKey(Timestamp timestamp, int commandStoreId)
|
||||
{
|
||||
if (timestamp == null) throw new NullPointerException("Null timestamp");
|
||||
Invariants.nonNull(type);
|
||||
Invariants.nonNull(timestamp);
|
||||
this.type = type;
|
||||
this.timestamp = timestamp;
|
||||
this.commandStoreId = commandStoreId;
|
||||
}
|
||||
|
|
@ -65,7 +72,8 @@ public final class JournalKey
|
|||
private static final int HLC_OFFSET = 0;
|
||||
private static final int EPOCH_AND_FLAGS_OFFSET = HLC_OFFSET + LONG_SIZE;
|
||||
private static final int NODE_OFFSET = EPOCH_AND_FLAGS_OFFSET + LONG_SIZE;
|
||||
private static final int CS_ID_OFFSET = NODE_OFFSET + INT_SIZE;
|
||||
private static final int TYPE_OFFSET = NODE_OFFSET + INT_SIZE;
|
||||
private static final int CS_ID_OFFSET = TYPE_OFFSET + BYTE_SIZE;
|
||||
|
||||
@Override
|
||||
public int serializedSize(int userVersion)
|
||||
|
|
@ -74,6 +82,7 @@ public final class JournalKey
|
|||
+ 6 // timestamp.epoch()
|
||||
+ 2 // timestamp.flags()
|
||||
+ INT_SIZE // timestamp.node
|
||||
+ BYTE_SIZE // type
|
||||
+ SHORT_SIZE; // commandStoreId
|
||||
}
|
||||
|
||||
|
|
@ -81,29 +90,33 @@ public final class JournalKey
|
|||
public void serialize(JournalKey key, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
serializeTimestamp(key.timestamp, out);
|
||||
out.writeByte(key.type.id);
|
||||
out.writeShort(key.commandStoreId);
|
||||
}
|
||||
|
||||
private void serialize(JournalKey key, byte[] out)
|
||||
{
|
||||
serializeTimestamp(key.timestamp, out);
|
||||
ByteArrayUtil.putShort(out, 20, (short) key.commandStoreId);
|
||||
out[20] = (byte) (key.type.id & 0xFF);
|
||||
ByteArrayUtil.putShort(out, 21, (short) key.commandStoreId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JournalKey deserialize(DataInputPlus in, int userVersion) throws IOException
|
||||
{
|
||||
Timestamp timestamp = deserializeTimestamp(in);
|
||||
int commandStoreId = in.readShort();
|
||||
return new JournalKey(timestamp, commandStoreId);
|
||||
int type = in.readByte();
|
||||
int commandStoreId = in.readShort();
|
||||
return new JournalKey(timestamp, Type.fromId(type), commandStoreId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JournalKey deserialize(ByteBuffer buffer, int position, int userVersion)
|
||||
{
|
||||
Timestamp timestamp = deserializeTimestamp(buffer, position);
|
||||
int type = buffer.get(position + TYPE_OFFSET);
|
||||
int commandStoreId = buffer.getShort(position + CS_ID_OFFSET);
|
||||
return new JournalKey(timestamp, commandStoreId);
|
||||
return new JournalKey(timestamp, Type.fromId(type), commandStoreId);
|
||||
}
|
||||
|
||||
private void serializeTimestamp(Timestamp timestamp, DataOutputPlus out) throws IOException
|
||||
|
|
@ -118,7 +131,7 @@ public final class JournalKey
|
|||
long hlc = in.readLong();
|
||||
long epochAndFlags = in.readLong();
|
||||
int nodeId = in.readInt();
|
||||
return Timestamp.fromValues(epoch(epochAndFlags), hlc, flags(epochAndFlags), new Node.Id(nodeId));
|
||||
return Timestamp.fromValues(epoch(epochAndFlags), hlc, flags(epochAndFlags), new Id(nodeId));
|
||||
}
|
||||
|
||||
private void serializeTimestamp(Timestamp timestamp, byte[] out)
|
||||
|
|
@ -133,7 +146,7 @@ public final class JournalKey
|
|||
long hlc = buffer.getLong(position + HLC_OFFSET);
|
||||
long epochAndFlags = buffer.getLong(position + EPOCH_AND_FLAGS_OFFSET);
|
||||
int nodeId = buffer.getInt(position + NODE_OFFSET);
|
||||
return Timestamp.fromValues(epoch(epochAndFlags), hlc, flags(epochAndFlags), new Node.Id(nodeId));
|
||||
return Timestamp.fromValues(epoch(epochAndFlags), hlc, flags(epochAndFlags), new Id(nodeId));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -150,6 +163,10 @@ public final class JournalKey
|
|||
int cmp = compareWithTimestampAt(k.timestamp, buffer, position);
|
||||
if (cmp != 0) return cmp;
|
||||
|
||||
byte type = buffer.get(position + TYPE_OFFSET);
|
||||
cmp = Byte.compare((byte) k.type.id, type);
|
||||
if (cmp != 0) return cmp;
|
||||
|
||||
short commandStoreId = buffer.getShort(position + CS_ID_OFFSET);
|
||||
cmp = Short.compare((byte) k.commandStoreId, commandStoreId);
|
||||
return cmp;
|
||||
|
|
@ -174,6 +191,7 @@ public final class JournalKey
|
|||
public int compare(JournalKey k1, JournalKey k2)
|
||||
{
|
||||
int cmp = compare(k1.timestamp, k2.timestamp);
|
||||
if (cmp == 0) cmp = Byte.compare((byte) k1.type.id, (byte) k2.type.id);
|
||||
if (cmp == 0) cmp = Short.compare((short) k1.commandStoreId, (short) k2.commandStoreId);
|
||||
return cmp;
|
||||
}
|
||||
|
|
@ -213,20 +231,75 @@ public final class JournalKey
|
|||
boolean equals(JournalKey other)
|
||||
{
|
||||
return this.timestamp.equals(other.timestamp) &&
|
||||
this.type == other.type &&
|
||||
this.commandStoreId == other.commandStoreId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(timestamp, commandStoreId);
|
||||
return Objects.hash(timestamp, type, commandStoreId);
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return "Key{" +
|
||||
"timestamp=" + timestamp +
|
||||
"type=" + type +
|
||||
", commandStoreId=" + commandStoreId +
|
||||
'}';
|
||||
}
|
||||
|
||||
public enum Type
|
||||
{
|
||||
COMMAND_DIFF (0, new CommandDiffSerializer()),
|
||||
REDUNDANT_BEFORE (1, new RedundantBeforeSerializer()),
|
||||
DURABLE_BEFORE (2, new DurableBeforeSerializer()),
|
||||
SAFE_TO_READ (3, new SafeToReadSerializer()),
|
||||
BOOTSTRAP_BEGAN_AT (4, new BootstrapBeganAtSerializer()),
|
||||
RANGES_FOR_EPOCH (5, new RangesForEpochSerializer()),
|
||||
HISTORICAL_TRANSACTIONS (6, new HistoricalTransactionsSerializer())
|
||||
;
|
||||
|
||||
final int id;
|
||||
final FlyweightSerializer<?, ?> serializer;
|
||||
|
||||
Type(int id, FlyweightSerializer<?, ?> serializer)
|
||||
{
|
||||
this.id = id;
|
||||
this.serializer = serializer;
|
||||
}
|
||||
|
||||
private static final Type[] idToTypeMapping;
|
||||
|
||||
static
|
||||
{
|
||||
Type[] types = values();
|
||||
|
||||
int maxId = -1;
|
||||
for (Type type : types)
|
||||
maxId = Math.max(type.id, maxId);
|
||||
|
||||
Type[] idToType = new Type[maxId + 1];
|
||||
for (Type type : types)
|
||||
{
|
||||
if (null != idToType[type.id])
|
||||
throw new IllegalStateException("Duplicate Type id " + type.id);
|
||||
idToType[type.id] = type;
|
||||
}
|
||||
idToTypeMapping = idToType;
|
||||
}
|
||||
|
||||
static Type fromId(int id)
|
||||
{
|
||||
if (id < 0 || id >= idToTypeMapping.length)
|
||||
throw new IllegalArgumentException("Out or range Type id " + id);
|
||||
Type type = idToTypeMapping[id];
|
||||
if (null == type)
|
||||
throw new IllegalArgumentException("Unknown Type id " + id);
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ package org.apache.cassandra.service.accord;
|
|||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
|
@ -29,13 +28,12 @@ import com.google.common.annotations.VisibleForTesting;
|
|||
import accord.api.Result;
|
||||
import accord.local.Command;
|
||||
import accord.local.CommonAttributes;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.local.Status;
|
||||
import accord.local.StoreParticipants;
|
||||
import accord.primitives.Ballot;
|
||||
import accord.primitives.PartialDeps;
|
||||
import accord.primitives.PartialTxn;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.primitives.Writes;
|
||||
|
|
@ -44,10 +42,12 @@ import org.apache.cassandra.io.util.DataOutputPlus;
|
|||
import org.apache.cassandra.journal.Journal;
|
||||
import org.apache.cassandra.service.accord.serializers.CommandSerializers;
|
||||
import org.apache.cassandra.service.accord.serializers.DepsSerializer;
|
||||
import org.apache.cassandra.service.accord.serializers.KeySerializers;
|
||||
import org.apache.cassandra.service.accord.serializers.WaitingOnSerializer;
|
||||
import org.apache.cassandra.utils.Throwables;
|
||||
|
||||
import static accord.primitives.Known.KnownDeps.DepsErased;
|
||||
import static accord.primitives.Known.KnownDeps.DepsUnknown;
|
||||
import static accord.primitives.Known.KnownDeps.NoDeps;
|
||||
import static accord.utils.Invariants.illegalState;
|
||||
|
||||
public class SavedCommand
|
||||
|
|
@ -62,31 +62,28 @@ public class SavedCommand
|
|||
DURABILITY,
|
||||
ACCEPTED,
|
||||
PROMISED,
|
||||
ROUTE,
|
||||
PARTICIPANTS,
|
||||
PARTIAL_TXN,
|
||||
PARTIAL_DEPS,
|
||||
ADDITIONAL_KEYS,
|
||||
WAITING_ON,
|
||||
WRITES,
|
||||
}
|
||||
|
||||
public interface Writer<K> extends Journal.Writer
|
||||
{
|
||||
void write(DataOutputPlus out, int userVersion) throws IOException;
|
||||
K key();
|
||||
}
|
||||
|
||||
public static class DiffWriter implements Writer<TxnId>
|
||||
// TODO: maybe rename this and enclosing classes?
|
||||
public static class DiffWriter implements Journal.Writer
|
||||
{
|
||||
private final Command before;
|
||||
private final Command after;
|
||||
private final TxnId txnId;
|
||||
|
||||
// TODO: improve encapsulationd
|
||||
@VisibleForTesting
|
||||
public DiffWriter(Command before, Command after)
|
||||
{
|
||||
this(after.txnId(), before, after);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public DiffWriter(TxnId txnId, Command before, Command after)
|
||||
{
|
||||
this.txnId = txnId;
|
||||
|
|
@ -118,21 +115,29 @@ public class SavedCommand
|
|||
}
|
||||
|
||||
@Nullable
|
||||
public static Writer<TxnId> diff(Command original, Command current)
|
||||
public static DiffWriter diff(Command original, Command current)
|
||||
{
|
||||
if (original == current
|
||||
|| current == null
|
||||
|| current.saveStatus() == SaveStatus.Uninitialised)
|
||||
|| current.saveStatus() == SaveStatus.Uninitialised
|
||||
|| !anyFieldChanged(original, current))
|
||||
return null;
|
||||
return new SavedCommand.DiffWriter(original, current);
|
||||
}
|
||||
|
||||
|
||||
public static Writer<TxnId> diffWriter(Command before, Command after)
|
||||
// TODO (required): this is very inefficient
|
||||
private static boolean anyFieldChanged(Command before, Command after)
|
||||
{
|
||||
return new DiffWriter(before, after);
|
||||
}
|
||||
int flags = getFlags(before, after);
|
||||
for (Fields field : Fields.values())
|
||||
{
|
||||
if (getFieldChanged(field, flags))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void serialize(Command before, Command after, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
int flags = getFlags(before, after);
|
||||
|
|
@ -157,14 +162,12 @@ public class SavedCommand
|
|||
if (getFieldChanged(Fields.PROMISED, flags) && after.promised() != null)
|
||||
CommandSerializers.ballot.serialize(after.promised(), out, userVersion);
|
||||
|
||||
if (getFieldChanged(Fields.ROUTE, flags) && after.route() != null)
|
||||
AccordKeyspace.LocalVersionedSerializers.route.serialize(after.route(), out); // TODO (required): user version
|
||||
if (getFieldChanged(Fields.PARTICIPANTS, flags) && after.participants() != null)
|
||||
CommandSerializers.participants.serialize(after.participants(), out, userVersion);
|
||||
if (getFieldChanged(Fields.PARTIAL_TXN, flags) && after.partialTxn() != null)
|
||||
CommandSerializers.partialTxn.serialize(after.partialTxn(), out, userVersion);
|
||||
if (getFieldChanged(Fields.PARTIAL_DEPS, flags) && after.partialDeps() != null)
|
||||
DepsSerializer.partialDeps.serialize(after.partialDeps(), out, userVersion);
|
||||
if (getFieldChanged(Fields.ADDITIONAL_KEYS, flags) && after.additionalKeysOrRanges() != null)
|
||||
KeySerializers.seekables.serialize(after.additionalKeysOrRanges(), out, userVersion);
|
||||
|
||||
Command.WaitingOn waitingOn = getWaitingOn(after);
|
||||
if (getFieldChanged(Fields.WAITING_ON, flags) && waitingOn != null)
|
||||
|
|
@ -193,10 +196,9 @@ public class SavedCommand
|
|||
flags = collectFlags(before, after, Command::acceptedOrCommitted, false, Fields.ACCEPTED, flags);
|
||||
flags = collectFlags(before, after, Command::promised, false, Fields.PROMISED, flags);
|
||||
|
||||
flags = collectFlags(before, after, Command::route, true, Fields.ROUTE, flags);
|
||||
flags = collectFlags(before, after, Command::participants, true, Fields.PARTICIPANTS, flags);
|
||||
flags = collectFlags(before, after, Command::partialTxn, false, Fields.PARTIAL_TXN, flags);
|
||||
flags = collectFlags(before, after, Command::partialDeps, false, Fields.PARTIAL_DEPS, flags);
|
||||
flags = collectFlags(before, after, Command::additionalKeysOrRanges, false, Fields.ADDITIONAL_KEYS, flags);
|
||||
|
||||
flags = collectFlags(before, after, SavedCommand::getWaitingOn, false, Fields.WAITING_ON, flags);
|
||||
|
||||
|
|
@ -259,8 +261,15 @@ public class SavedCommand
|
|||
return oldFlags | (1 << field.ordinal());
|
||||
}
|
||||
|
||||
private static int unsetFieldIsNull(Fields field, int oldFlags)
|
||||
{
|
||||
return oldFlags & ~(1 << field.ordinal());
|
||||
}
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
int flags;
|
||||
|
||||
TxnId txnId;
|
||||
|
||||
Timestamp executeAt;
|
||||
|
|
@ -271,11 +280,11 @@ public class SavedCommand
|
|||
Ballot acceptedOrCommitted;
|
||||
Ballot promised;
|
||||
|
||||
Route<?> route;
|
||||
StoreParticipants participants;
|
||||
PartialTxn partialTxn;
|
||||
PartialDeps partialDeps;
|
||||
Seekables<?, ?> additionalKeysOrRanges;
|
||||
|
||||
byte[] waitingOnBytes;
|
||||
SavedCommand.WaitingOnProvider waitingOn;
|
||||
Writes writes;
|
||||
Result result;
|
||||
|
|
@ -298,6 +307,11 @@ public class SavedCommand
|
|||
return executeAt;
|
||||
}
|
||||
|
||||
public Timestamp executeAtLeast()
|
||||
{
|
||||
return executeAtLeast;
|
||||
}
|
||||
|
||||
public SaveStatus saveStatus()
|
||||
{
|
||||
return saveStatus;
|
||||
|
|
@ -318,9 +332,9 @@ public class SavedCommand
|
|||
return promised;
|
||||
}
|
||||
|
||||
public Route<?> route()
|
||||
public StoreParticipants participants()
|
||||
{
|
||||
return route;
|
||||
return participants;
|
||||
}
|
||||
|
||||
public PartialTxn partialTxn()
|
||||
|
|
@ -333,11 +347,6 @@ public class SavedCommand
|
|||
return partialDeps;
|
||||
}
|
||||
|
||||
public Seekables<?, ?> additionalKeysOrRanges()
|
||||
{
|
||||
return additionalKeysOrRanges;
|
||||
}
|
||||
|
||||
public SavedCommand.WaitingOnProvider waitingOn()
|
||||
{
|
||||
return waitingOn;
|
||||
|
|
@ -355,6 +364,8 @@ public class SavedCommand
|
|||
|
||||
public void clear()
|
||||
{
|
||||
flags = 0;
|
||||
|
||||
txnId = null;
|
||||
|
||||
executeAt = null;
|
||||
|
|
@ -364,10 +375,9 @@ public class SavedCommand
|
|||
acceptedOrCommitted = Ballot.ZERO;
|
||||
promised = null;
|
||||
|
||||
route = null;
|
||||
participants = null;
|
||||
partialTxn = null;
|
||||
partialDeps = null;
|
||||
additionalKeysOrRanges = null;
|
||||
|
||||
waitingOn = (txn, deps) -> null;
|
||||
writes = null;
|
||||
|
|
@ -387,13 +397,65 @@ public class SavedCommand
|
|||
return count;
|
||||
}
|
||||
|
||||
public void serialize(DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
out.writeInt(flags);
|
||||
|
||||
// We encode all changed fields unless their value is null
|
||||
if (getFieldChanged(Fields.TXN_ID, flags) && !getFieldIsNull(Fields.TXN_ID, flags))
|
||||
CommandSerializers.txnId.serialize(txnId(), out, userVersion);
|
||||
if (getFieldChanged(Fields.EXECUTE_AT, flags) && !getFieldIsNull(Fields.EXECUTE_AT, flags))
|
||||
CommandSerializers.timestamp.serialize(executeAt(), out, userVersion);
|
||||
// TODO (desired): check if this can fold into executeAt
|
||||
if (getFieldChanged(Fields.EXECUTES_AT_LEAST, flags) && !getFieldIsNull(Fields.EXECUTES_AT_LEAST, flags))
|
||||
CommandSerializers.timestamp.serialize(executeAtLeast(), out, userVersion);
|
||||
if (getFieldChanged(Fields.SAVE_STATUS, flags) && !getFieldIsNull(Fields.SAVE_STATUS, flags))
|
||||
out.writeInt(saveStatus().ordinal());
|
||||
if (getFieldChanged(Fields.DURABILITY, flags) && !getFieldIsNull(Fields.DURABILITY, flags))
|
||||
out.writeInt(durability().ordinal());
|
||||
|
||||
if (getFieldChanged(Fields.ACCEPTED, flags) && !getFieldIsNull(Fields.ACCEPTED, flags))
|
||||
CommandSerializers.ballot.serialize(acceptedOrCommitted(), out, userVersion);
|
||||
if (getFieldChanged(Fields.PROMISED, flags) && !getFieldIsNull(Fields.PROMISED, flags))
|
||||
CommandSerializers.ballot.serialize(promised(), out, userVersion);
|
||||
|
||||
if (getFieldChanged(Fields.PARTICIPANTS, flags) && !getFieldIsNull(Fields.PARTICIPANTS, flags))
|
||||
CommandSerializers.participants.serialize(participants(), out, userVersion);
|
||||
if (getFieldChanged(Fields.PARTIAL_TXN, flags) && !getFieldIsNull(Fields.PARTIAL_TXN, flags))
|
||||
CommandSerializers.partialTxn.serialize(partialTxn(), out, userVersion);
|
||||
if (getFieldChanged(Fields.PARTIAL_DEPS, flags) && !getFieldIsNull(Fields.PARTIAL_DEPS, flags))
|
||||
DepsSerializer.partialDeps.serialize(partialDeps(), out, userVersion);
|
||||
|
||||
if (getFieldChanged(Fields.WAITING_ON, flags) && !getFieldIsNull(Fields.WAITING_ON, flags))
|
||||
{
|
||||
out.writeInt(waitingOnBytes.length);
|
||||
out.write(waitingOnBytes);
|
||||
}
|
||||
|
||||
if (getFieldChanged(Fields.WRITES, flags) && !getFieldIsNull(Fields.WRITES, flags))
|
||||
CommandSerializers.writes.serialize(writes(), out, userVersion);
|
||||
}
|
||||
|
||||
|
||||
// TODO: we seem to be writing some form of empty transaction
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public void deserializeNext(DataInputPlus in, int userVersion) throws IOException
|
||||
{
|
||||
final int flags = in.readInt();
|
||||
nextCalled = true;
|
||||
count++;
|
||||
|
||||
final int flags = in.readInt();
|
||||
for (Fields field : Fields.values())
|
||||
{
|
||||
if (getFieldChanged(field, flags))
|
||||
{
|
||||
this.flags = setFieldChanged(field, this.flags);
|
||||
if (getFieldIsNull(field, flags))
|
||||
this.flags = setFieldIsNull(field, this.flags);
|
||||
else
|
||||
this.flags = unsetFieldIsNull(field, this.flags);
|
||||
}
|
||||
}
|
||||
|
||||
if (getFieldChanged(Fields.TXN_ID, flags))
|
||||
{
|
||||
|
|
@ -450,12 +512,12 @@ public class SavedCommand
|
|||
promised = CommandSerializers.ballot.deserialize(in, userVersion);
|
||||
}
|
||||
|
||||
if (getFieldChanged(Fields.ROUTE, flags))
|
||||
if (getFieldChanged(Fields.PARTICIPANTS, flags))
|
||||
{
|
||||
if (getFieldIsNull(Fields.ROUTE, flags))
|
||||
route = null;
|
||||
if (getFieldIsNull(Fields.PARTICIPANTS, flags))
|
||||
participants = null;
|
||||
else
|
||||
route = AccordKeyspace.LocalVersionedSerializers.route.deserialize(in);
|
||||
participants = CommandSerializers.participants.deserialize(in, userVersion);
|
||||
}
|
||||
|
||||
if (getFieldChanged(Fields.PARTIAL_TXN, flags))
|
||||
|
|
@ -474,14 +536,6 @@ public class SavedCommand
|
|||
partialDeps = DepsSerializer.partialDeps.deserialize(in, userVersion);
|
||||
}
|
||||
|
||||
if (getFieldChanged(Fields.ADDITIONAL_KEYS, flags))
|
||||
{
|
||||
if (getFieldIsNull(Fields.ADDITIONAL_KEYS, flags))
|
||||
additionalKeysOrRanges = null;
|
||||
else
|
||||
additionalKeysOrRanges = KeySerializers.seekables.deserialize(in, userVersion);
|
||||
}
|
||||
|
||||
if (getFieldChanged(Fields.WAITING_ON, flags))
|
||||
{
|
||||
if (getFieldIsNull(Fields.WAITING_ON, flags))
|
||||
|
|
@ -491,9 +545,9 @@ public class SavedCommand
|
|||
else
|
||||
{
|
||||
int size = in.readInt();
|
||||
byte[] bytes = new byte[size];
|
||||
in.readFully(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
||||
waitingOnBytes = new byte[size];
|
||||
in.readFully(waitingOnBytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(waitingOnBytes);
|
||||
waitingOn = (localTxnId, deps) -> {
|
||||
try
|
||||
{
|
||||
|
|
@ -514,6 +568,7 @@ public class SavedCommand
|
|||
else
|
||||
writes = CommandSerializers.writes.deserialize(in, userVersion);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void forceResult(Result newValue)
|
||||
|
|
@ -521,7 +576,7 @@ public class SavedCommand
|
|||
this.result = newValue;
|
||||
}
|
||||
|
||||
public Command construct() throws IOException
|
||||
public Command construct()
|
||||
{
|
||||
if (!nextCalled)
|
||||
return null;
|
||||
|
|
@ -531,15 +586,13 @@ public class SavedCommand
|
|||
attrs.partialTxn(partialTxn);
|
||||
if (durability != null)
|
||||
attrs.durability(durability);
|
||||
if (route != null)
|
||||
attrs.route(route);
|
||||
if (participants != null)
|
||||
attrs.setParticipants(participants);
|
||||
if (partialDeps != null &&
|
||||
(saveStatus.known.deps != Status.KnownDeps.NoDeps &&
|
||||
saveStatus.known.deps != Status.KnownDeps.DepsErased &&
|
||||
saveStatus.known.deps != Status.KnownDeps.DepsUnknown))
|
||||
(saveStatus.known.deps != NoDeps &&
|
||||
saveStatus.known.deps != DepsErased &&
|
||||
saveStatus.known.deps != DepsUnknown))
|
||||
attrs.partialDeps(partialDeps);
|
||||
if (additionalKeysOrRanges != null)
|
||||
attrs.additionalKeysOrRanges(additionalKeysOrRanges);
|
||||
|
||||
Command.WaitingOn waitingOn = null;
|
||||
if (this.waitingOn != null)
|
||||
|
|
@ -553,14 +606,12 @@ public class SavedCommand
|
|||
case PreAccepted:
|
||||
return Command.PreAccepted.preAccepted(attrs, executeAt, promised);
|
||||
case AcceptedInvalidate:
|
||||
if (saveStatus == SaveStatus.AcceptedInvalidateWithDefinition)
|
||||
return Command.Accepted.accepted(attrs, saveStatus, executeAt, promised, acceptedOrCommitted);
|
||||
else
|
||||
return Command.AcceptedInvalidateWithoutDefinition.acceptedInvalidate(attrs, promised, acceptedOrCommitted);
|
||||
|
||||
case Accepted:
|
||||
case PreCommitted:
|
||||
return Command.Accepted.accepted(attrs, saveStatus, executeAt, promised, acceptedOrCommitted);
|
||||
if (saveStatus == SaveStatus.AcceptedInvalidate)
|
||||
return Command.AcceptedInvalidateWithoutDefinition.acceptedInvalidate(attrs, promised, acceptedOrCommitted);
|
||||
else
|
||||
return Command.Accepted.accepted(attrs, saveStatus, executeAt, promised, acceptedOrCommitted);
|
||||
case Committed:
|
||||
case Stable:
|
||||
return Command.Committed.committed(attrs, saveStatus, executeAt, promised, acceptedOrCommitted, waitingOn);
|
||||
|
|
@ -587,10 +638,10 @@ public class SavedCommand
|
|||
if (attrs.txnId().kind().awaitsOnlyDeps())
|
||||
return Command.Truncated.truncatedApply(attrs, status, executeAt, writes, result, executesAtLeast);
|
||||
return Command.Truncated.truncatedApply(attrs, status, executeAt, writes, result, null);
|
||||
case ErasedOrInvalidOrVestigial:
|
||||
return Command.Truncated.erasedOrInvalidOrVestigial(attrs.txnId(), attrs.durability(), attrs.route());
|
||||
case ErasedOrVestigial:
|
||||
return Command.Truncated.erasedOrInvalidOrVestigial(attrs.txnId(), attrs.durability(), attrs.participants());
|
||||
case Erased:
|
||||
return Command.Truncated.erased(attrs.txnId(), attrs.durability(), attrs.route());
|
||||
return Command.Truncated.erased(attrs.txnId(), attrs.durability(), attrs.participants());
|
||||
case Invalidated:
|
||||
return Command.Truncated.invalidated(attrs.txnId());
|
||||
}
|
||||
|
|
@ -605,10 +656,9 @@ public class SavedCommand
|
|||
", durability=" + durability +
|
||||
", acceptedOrCommitted=" + acceptedOrCommitted +
|
||||
", promised=" + promised +
|
||||
", route=" + route +
|
||||
", participants=" + participants +
|
||||
", partialTxn=" + partialTxn +
|
||||
", partialDeps=" + partialDeps +
|
||||
", additionalKeysOrRanges=" + additionalKeysOrRanges +
|
||||
", waitingOn=" + waitingOn +
|
||||
", writes=" + writes +
|
||||
'}';
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
package org.apache.cassandra.service.accord.api;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
|
|
@ -35,7 +34,9 @@ import accord.local.Node;
|
|||
import accord.local.SafeCommand;
|
||||
import accord.local.SafeCommandStore;
|
||||
import accord.messages.ReplyContext;
|
||||
import accord.primitives.Keys;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.Routable;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.Txn;
|
||||
|
|
@ -52,7 +53,6 @@ import org.apache.cassandra.net.ResponseContext;
|
|||
import org.apache.cassandra.service.accord.AccordService;
|
||||
import org.apache.cassandra.service.accord.txn.TxnQuery;
|
||||
import org.apache.cassandra.service.accord.txn.TxnRead;
|
||||
import org.apache.cassandra.tcm.Epoch;
|
||||
import org.apache.cassandra.utils.Clock;
|
||||
import org.apache.cassandra.utils.JVMStabilityInspector;
|
||||
|
||||
|
|
@ -62,7 +62,6 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|||
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.apache.cassandra.config.DatabaseDescriptor.getReadRpcTimeout;
|
||||
import static org.apache.cassandra.service.consensus.migration.ConsensusKeyMigrationState.maybeSaveAccordKeyMigrationLocally;
|
||||
|
||||
// TODO (expected): merge with AccordService
|
||||
public class AccordAgent implements Agent
|
||||
|
|
@ -100,6 +99,11 @@ public class AccordAgent implements Agent
|
|||
throw error;
|
||||
}
|
||||
|
||||
public void onSuccessfulBarrier(TxnId id, Seekables<?, ?> keysOrRanges)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void onFailedBarrier(TxnId id, Seekables<?, ?> keysOrRanges, Throwable cause)
|
||||
{
|
||||
|
||||
|
|
@ -112,16 +116,6 @@ public class AccordAgent implements Agent
|
|||
AccordService.instance().scheduler().once(retry, retryBootstrapDelayMicros, MICROSECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocalBarrier(@Nonnull Seekables<?, ?> keysOrRanges, @Nonnull TxnId txnId)
|
||||
{
|
||||
if (keysOrRanges.domain() == Key)
|
||||
{
|
||||
PartitionKey key = (PartitionKey)keysOrRanges.get(0);
|
||||
maybeSaveAccordKeyMigrationLocally(key, Epoch.create(txnId.epoch()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStale(Timestamp staleSince, Ranges ranges)
|
||||
{
|
||||
|
|
@ -136,9 +130,10 @@ public class AccordAgent implements Agent
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onHandledException(Throwable t)
|
||||
public void onHandledException(Throwable t, String context)
|
||||
{
|
||||
// TODO: this
|
||||
logger.warn(context, t);
|
||||
JVMStabilityInspector.uncaughtException(Thread.currentThread(), t);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -179,9 +174,9 @@ public class AccordAgent implements Agent
|
|||
* for tests since it skips validation done by regular transactions.
|
||||
*/
|
||||
@Override
|
||||
public Txn emptySystemTxn(Kind kind, Seekables<?, ?> seekables)
|
||||
public Txn emptySystemTxn(Kind kind, Routable.Domain domain)
|
||||
{
|
||||
return new Txn.InMemory(kind, seekables, TxnRead.EMPTY, TxnQuery.UNSAFE_EMPTY, null);
|
||||
return new Txn.InMemory(kind, domain == Key ? Keys.EMPTY : Ranges.EMPTY, TxnRead.EMPTY, TxnQuery.UNSAFE_EMPTY, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import accord.primitives.Range;
|
|||
import accord.primitives.RangeFactory;
|
||||
import accord.primitives.Ranges;
|
||||
import org.apache.cassandra.db.TypeSizes;
|
||||
import org.apache.cassandra.db.marshal.ByteBufferAccessor;
|
||||
import org.apache.cassandra.dht.IPartitioner;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
|
|
@ -79,6 +80,18 @@ public abstract class AccordRoutingKey extends AccordRoutableKey implements Rout
|
|||
return (TokenKey) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoutingKey toUnseekable()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoutingKey asRoutingKey()
|
||||
{
|
||||
return asTokenKey();
|
||||
}
|
||||
|
||||
public static AccordRoutingKey of(Key key)
|
||||
{
|
||||
return (AccordRoutingKey) key;
|
||||
|
|
@ -266,6 +279,25 @@ public abstract class AccordRoutingKey extends AccordRoutableKey implements Rout
|
|||
return new TokenKey(table, token);
|
||||
}
|
||||
|
||||
public TokenKey fromBytes(ByteBuffer bytes, IPartitioner partitioner)
|
||||
{
|
||||
TableId tableId = TableId.deserialize(bytes, ByteBufferAccessor.instance, 0);
|
||||
bytes.position(tableId.serializedSize());
|
||||
Token token = Token.compactSerializer.deserialize(bytes, partitioner);
|
||||
return new TokenKey(tableId, token);
|
||||
}
|
||||
|
||||
public ByteBuffer toBytes(TokenKey tokenKey)
|
||||
{
|
||||
int size = (int) (tokenKey.table.serializedSize() + Token.compactSerializer.serializedSize(tokenKey.token));
|
||||
ByteBuffer out = ByteBuffer.allocate(size);
|
||||
int position = tokenKey.table.serialize(out, ByteBufferAccessor.instance, 0);
|
||||
out.position(position);
|
||||
Token.compactSerializer.serialize(tokenKey.token, out);
|
||||
out.flip();
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedSize(TokenKey key, int version)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
package org.apache.cassandra.service.accord.async;
|
||||
|
||||
import accord.api.Key;
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.local.KeyHistory;
|
||||
import accord.local.PreLoadContext;
|
||||
|
|
@ -32,7 +32,7 @@ import com.google.common.collect.ImmutableSet;
|
|||
import com.google.common.collect.Iterables;
|
||||
import org.apache.cassandra.service.accord.*;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
|
||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
||||
import org.apache.cassandra.utils.NoSpamLogger;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
|
|
@ -61,12 +61,12 @@ public class AsyncLoader
|
|||
private final AccordCommandStore commandStore;
|
||||
|
||||
private final Iterable<TxnId> txnIds;
|
||||
private final Seekables<?, ?> keysOrRanges;
|
||||
private final Unseekables<?> keysOrRanges;
|
||||
private final KeyHistory keyHistory;
|
||||
|
||||
protected AsyncResult<?> readResult;
|
||||
|
||||
public AsyncLoader(AccordCommandStore commandStore, Iterable<TxnId> txnIds, Seekables<?, ?> keysOrRanges, KeyHistory keyHistory)
|
||||
public AsyncLoader(AccordCommandStore commandStore, Iterable<TxnId> txnIds, Unseekables<?> keysOrRanges, KeyHistory keyHistory)
|
||||
{
|
||||
this.commandStore = commandStore;
|
||||
this.txnIds = txnIds;
|
||||
|
|
@ -116,7 +116,7 @@ public class AsyncLoader
|
|||
}
|
||||
}
|
||||
|
||||
private void referenceAndAssembleReadsForKey(Key key,
|
||||
private void referenceAndAssembleReadsForKey(RoutingKey key,
|
||||
AsyncOperation.Context context,
|
||||
List<AsyncChain<?>> listenChains)
|
||||
{
|
||||
|
|
@ -151,7 +151,7 @@ public class AsyncLoader
|
|||
switch (keysOrRanges.domain())
|
||||
{
|
||||
case Key:
|
||||
AbstractKeys<Key> keys = (AbstractKeys<Key>) keysOrRanges;
|
||||
AbstractKeys<RoutingKey> keys = (AbstractKeys<RoutingKey>) keysOrRanges;
|
||||
keys.forEach(key -> referenceAndAssembleReadsForKey(key, context, chains));
|
||||
break;
|
||||
case Range:
|
||||
|
|
@ -166,20 +166,20 @@ public class AsyncLoader
|
|||
|
||||
private AsyncChain<?> referenceAndDispatchReadsForRange(AsyncOperation.Context context)
|
||||
{
|
||||
Ranges ranges = (Ranges) keysOrRanges;
|
||||
Ranges ranges = ((AbstractRanges) keysOrRanges).toRanges();
|
||||
|
||||
List<AsyncChain<?>> root = new ArrayList<>(ranges.size() + 1);
|
||||
class Watcher implements AccordStateCache.Listener<Key, CommandsForKey>
|
||||
class Watcher implements AccordStateCache.Listener<RoutingKey, CommandsForKey>
|
||||
{
|
||||
private final Set<PartitionKey> cached = commandStore.commandsForKeyCache().stream()
|
||||
.map(n -> (PartitionKey) n.key())
|
||||
private final Set<TokenKey> cached = commandStore.commandsForKeyCache().stream()
|
||||
.map(n -> (TokenKey) n.key())
|
||||
.filter(ranges::contains)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
@Override
|
||||
public void onAdd(AccordCachingState<Key, CommandsForKey> state)
|
||||
public void onAdd(AccordCachingState<RoutingKey, CommandsForKey> state)
|
||||
{
|
||||
PartitionKey pk = (PartitionKey) state.key();
|
||||
TokenKey pk = (TokenKey) state.key();
|
||||
if (ranges.contains(pk))
|
||||
cached.add(pk);
|
||||
}
|
||||
|
|
@ -190,7 +190,7 @@ public class AsyncLoader
|
|||
commandStore.commandsForKeyCache().unregister(watcher);
|
||||
if (keys.isEmpty() && watcher.cached.isEmpty())
|
||||
return AsyncChains.success(null);
|
||||
Set<? extends Key> set = ImmutableSet.<Key>builder().addAll(watcher.cached).addAll(keys).build();
|
||||
Set<? extends RoutingKey> set = ImmutableSet.<RoutingKey>builder().addAll(watcher.cached).addAll(keys).build();
|
||||
List<AsyncChain<?>> chains = new ArrayList<>();
|
||||
set.forEach(key -> referenceAndAssembleReadsForKey(key, context, chains));
|
||||
return chains.isEmpty() ? AsyncChains.success(null) : AsyncChains.reduce(chains, (a, b) -> null);
|
||||
|
|
@ -203,7 +203,7 @@ public class AsyncLoader
|
|||
return AsyncChains.all(root);
|
||||
}
|
||||
|
||||
private AsyncChain<List<? extends Key>> findOverlappingKeys(Ranges ranges)
|
||||
private AsyncChain<List<? extends RoutingKey>> findOverlappingKeys(Ranges ranges)
|
||||
{
|
||||
if (ranges.isEmpty())
|
||||
{
|
||||
|
|
@ -211,16 +211,16 @@ public class AsyncLoader
|
|||
return AsyncChains.success(Collections.emptyList());
|
||||
}
|
||||
|
||||
List<AsyncChain<List<PartitionKey>>> chains = new ArrayList<>(ranges.size());
|
||||
List<AsyncChain<List<TokenKey>>> chains = new ArrayList<>(ranges.size());
|
||||
for (Range range : ranges)
|
||||
chains.add(findOverlappingKeys(range));
|
||||
return AsyncChains.reduce(chains, (a, b) -> ImmutableList.<Key>builderWithExpectedSize(a.size() + b.size()).addAll(a).addAll(b).build());
|
||||
return AsyncChains.reduce(chains, (a, b) -> ImmutableList.<RoutingKey>builderWithExpectedSize(a.size() + b.size()).addAll(a).addAll(b).build());
|
||||
}
|
||||
|
||||
private AsyncChain<List<PartitionKey>> findOverlappingKeys(Range range)
|
||||
private AsyncChain<List<TokenKey>> findOverlappingKeys(Range range)
|
||||
{
|
||||
// save to a variable as java gets confused when `.map` is called on the result of asChain
|
||||
AsyncChain<List<PartitionKey>> map = Observable.asChain(callback ->
|
||||
AsyncChain<List<TokenKey>> map = Observable.asChain(callback ->
|
||||
AccordKeyspace.findAllKeysBetween(commandStore.id(),
|
||||
(AccordRoutingKey) range.start(), range.startInclusive(),
|
||||
(AccordRoutingKey) range.end(), range.endInclusive(),
|
||||
|
|
|
|||
|
|
@ -30,13 +30,13 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import accord.api.Key;
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.Command;
|
||||
import accord.local.CommandStore;
|
||||
import accord.local.PreLoadContext;
|
||||
import accord.local.SafeCommandStore;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.primitives.Unseekables;
|
||||
import accord.utils.Invariants;
|
||||
import accord.utils.async.AsyncChains;
|
||||
import org.apache.cassandra.config.CassandraRelevantProperties;
|
||||
|
|
@ -48,6 +48,7 @@ import org.apache.cassandra.service.accord.AccordSafeCommandsForRanges;
|
|||
import org.apache.cassandra.service.accord.AccordSafeState;
|
||||
import org.apache.cassandra.service.accord.AccordSafeTimestampsForKey;
|
||||
import org.apache.cassandra.service.accord.SavedCommand;
|
||||
import org.apache.cassandra.utils.concurrent.Condition;
|
||||
|
||||
import static org.apache.cassandra.service.accord.async.AsyncLoader.txnIds;
|
||||
import static org.apache.cassandra.service.accord.async.AsyncOperation.State.COMPLETING;
|
||||
|
|
@ -71,8 +72,8 @@ public abstract class AsyncOperation<R> extends AsyncChains.Head<R> implements R
|
|||
static class Context
|
||||
{
|
||||
final HashMap<TxnId, AccordSafeCommand> commands = new HashMap<>();
|
||||
final TreeMap<Key, AccordSafeTimestampsForKey> timestampsForKey = new TreeMap<>();
|
||||
final TreeMap<Key, AccordSafeCommandsForKey> commandsForKey = new TreeMap<>();
|
||||
final TreeMap<RoutingKey, AccordSafeTimestampsForKey> timestampsForKey = new TreeMap<>();
|
||||
final TreeMap<RoutingKey, AccordSafeCommandsForKey> commandsForKey = new TreeMap<>();
|
||||
@Nullable
|
||||
AccordSafeCommandsForRanges commandsForRanges = null;
|
||||
|
||||
|
|
@ -189,7 +190,7 @@ public abstract class AsyncOperation<R> extends AsyncChains.Head<R> implements R
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Seekables<?, ?> keys()
|
||||
Unseekables<?> keys()
|
||||
{
|
||||
return preLoadContext.keys();
|
||||
}
|
||||
|
|
@ -251,10 +252,10 @@ public abstract class AsyncOperation<R> extends AsyncChains.Head<R> implements R
|
|||
|
||||
result = apply(safeStore);
|
||||
// TODO (required): currently, we are not very efficient about ensuring that we persist the absolute minimum amount of state. Improve that.
|
||||
List<SavedCommand.Writer<TxnId>> diffs = null;
|
||||
List<SavedCommand.DiffWriter> diffs = null;
|
||||
for (AccordSafeCommand commandState : context.commands.values())
|
||||
{
|
||||
SavedCommand.Writer<TxnId> diff = commandState.diff();
|
||||
SavedCommand.DiffWriter diff = commandState.diff();
|
||||
if (diff == null)
|
||||
continue;
|
||||
if (diffs == null)
|
||||
|
|
@ -269,11 +270,22 @@ public abstract class AsyncOperation<R> extends AsyncChains.Head<R> implements R
|
|||
}
|
||||
|
||||
commandStore.completeOperation(safeStore);
|
||||
|
||||
context.releaseResources(commandStore);
|
||||
state(COMPLETING);
|
||||
if (diffs != null)
|
||||
if (diffs != null || safeStore.fieldUpdates() != null)
|
||||
{
|
||||
this.commandStore.appendCommands(diffs, sanityCheck, () -> finish(result, null));
|
||||
Runnable onFlush = () -> finish(result, null);
|
||||
if (safeStore.fieldUpdates() != null)
|
||||
{
|
||||
if (diffs != null)
|
||||
appendCommands(diffs, null);
|
||||
commandStore.persistFieldUpdates(safeStore.fieldUpdates(), onFlush);
|
||||
}
|
||||
else
|
||||
{
|
||||
appendCommands(diffs, onFlush);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case COMPLETING:
|
||||
|
|
@ -286,6 +298,26 @@ public abstract class AsyncOperation<R> extends AsyncChains.Head<R> implements R
|
|||
return false;
|
||||
}
|
||||
|
||||
private void appendCommands(List<SavedCommand.DiffWriter> diffs, Runnable onFlush)
|
||||
{
|
||||
if (sanityCheck != null)
|
||||
{
|
||||
Invariants.checkState(CassandraRelevantProperties.DTEST_ACCORD_JOURNAL_SANITY_CHECK_ENABLED.getBoolean());
|
||||
Condition condition = Condition.newOneTimeCondition();
|
||||
this.commandStore.appendCommands(diffs, condition::signal);
|
||||
condition.awaitUninterruptibly();
|
||||
|
||||
for (Command check : sanityCheck)
|
||||
this.commandStore.sanityCheckCommand(check);
|
||||
|
||||
if (onFlush != null) onFlush.run();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.commandStore.appendCommands(diffs, onFlush);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import accord.local.Node;
|
|||
import accord.messages.Apply;
|
||||
import accord.primitives.Deps;
|
||||
import accord.primitives.FullRoute;
|
||||
import accord.primitives.Participants;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.Txn;
|
||||
import accord.primitives.TxnId;
|
||||
|
|
@ -84,8 +85,9 @@ public class AccordInteropAdapter extends AbstractTxnAdapter
|
|||
}
|
||||
|
||||
@Override
|
||||
public void persist(Node node, Topologies all, FullRoute<?> route, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, BiConsumer<? super Result, Throwable> callback)
|
||||
public void persist(Node node, Topologies all, FullRoute<?> route, Participants<?> sendTo, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, BiConsumer<? super Result, Throwable> callback)
|
||||
{
|
||||
// TODO (required): we aren't using sendTo
|
||||
if (applyKind == Minimal && doInteropPersist(node, all, route, txnId, txn, executeAt, deps, writes, result, callback))
|
||||
return;
|
||||
|
||||
|
|
|
|||
|
|
@ -27,19 +27,19 @@ import accord.local.Command;
|
|||
import accord.local.Node.Id;
|
||||
import accord.local.SafeCommand;
|
||||
import accord.local.SafeCommandStore;
|
||||
import accord.local.Status;
|
||||
import accord.local.StoreParticipants;
|
||||
import accord.messages.Apply;
|
||||
import accord.messages.MessageType;
|
||||
import accord.primitives.Deps;
|
||||
import accord.primitives.FullRoute;
|
||||
import accord.primitives.Keys;
|
||||
import accord.primitives.PartialDeps;
|
||||
import accord.primitives.PartialTxn;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.Txn;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.primitives.Unseekables;
|
||||
import accord.primitives.Writes;
|
||||
import accord.topology.Topologies;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
|
|
@ -77,9 +77,9 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL
|
|||
public static final IVersionedSerializer<AccordInteropApply> serializer = new ApplySerializer<AccordInteropApply>()
|
||||
{
|
||||
@Override
|
||||
protected AccordInteropApply deserializeApply(TxnId txnId, Route<?> scope, long waitForEpoch, Apply.Kind kind, Seekables<?, ?> keys, Timestamp executeAt, PartialDeps deps, PartialTxn txn, @Nullable FullRoute<?> fullRoute, Writes writes, Result result)
|
||||
protected AccordInteropApply deserializeApply(TxnId txnId, Route<?> scope, long waitForEpoch, Apply.Kind kind, Timestamp executeAt, PartialDeps deps, PartialTxn txn, @Nullable FullRoute<?> fullRoute, Writes writes, Result result)
|
||||
{
|
||||
return new AccordInteropApply(kind, txnId, scope, waitForEpoch, keys, executeAt, deps, txn, fullRoute, writes, result);
|
||||
return new AccordInteropApply(kind, txnId, scope, waitForEpoch, executeAt, deps, txn, fullRoute, writes, result);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -87,9 +87,9 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL
|
|||
transient int waitingOnCount;
|
||||
final MpscChunkedArrayQueue<LocalListeners.Registered> listeners = new MpscChunkedArrayQueue<>(4, 1 << 30);
|
||||
|
||||
private AccordInteropApply(Kind kind, TxnId txnId, Route<?> route, long waitForEpoch, Seekables<?, ?> keys, Timestamp executeAt, PartialDeps deps, @Nullable PartialTxn txn, @Nullable FullRoute<?> fullRoute, Writes writes, Result result)
|
||||
private AccordInteropApply(Kind kind, TxnId txnId, Route<?> route, long waitForEpoch, Timestamp executeAt, PartialDeps deps, @Nullable PartialTxn txn, @Nullable FullRoute<?> fullRoute, Writes writes, Result result)
|
||||
{
|
||||
super(kind, txnId, route, waitForEpoch, keys, executeAt, deps, txn, fullRoute, writes, result);
|
||||
super(kind, txnId, route, waitForEpoch, executeAt, deps, txn, fullRoute, writes, result);
|
||||
}
|
||||
|
||||
private AccordInteropApply(Kind kind, Id to, Topologies participates, TxnId txnId, FullRoute<?> route, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result)
|
||||
|
|
@ -106,7 +106,7 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL
|
|||
|
||||
|
||||
@Override
|
||||
public ApplyReply apply(SafeCommandStore safeStore)
|
||||
public ApplyReply apply(SafeCommandStore safeStore, StoreParticipants participants)
|
||||
{
|
||||
ApplyReply reply = super.apply(safeStore);
|
||||
checkState(reply == ApplyReply.Redundant || reply == ApplyReply.Applied || reply == ApplyReply.Insufficient, "Unexpected ApplyReply");
|
||||
|
|
@ -118,7 +118,7 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL
|
|||
// once the coordinator sends a maximal commit
|
||||
// Applied doesn't actually mean the command is in the Applied state so we still need to check and maybe install
|
||||
// the listener
|
||||
SafeCommand safeCommand = safeStore.get(txnId, executeAt, scope);
|
||||
SafeCommand safeCommand = safeStore.get(txnId, participants);
|
||||
Command current = safeCommand.current();
|
||||
// Don't actually think it is possible for this to reach applied while we are stll running, but just to be safe
|
||||
// check anyways
|
||||
|
|
@ -201,10 +201,9 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL
|
|||
}
|
||||
|
||||
@Override
|
||||
public Seekables<?, ?> keys()
|
||||
public Unseekables<?> keys()
|
||||
{
|
||||
if (txn == null) return Keys.EMPTY;
|
||||
return txn.keys();
|
||||
return scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import accord.primitives.FullRoute;
|
|||
import accord.primitives.PartialDeps;
|
||||
import accord.primitives.PartialTxn;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.Txn;
|
||||
import accord.primitives.TxnId;
|
||||
|
|
@ -43,18 +42,18 @@ import org.apache.cassandra.service.accord.serializers.CommitSerializers.CommitS
|
|||
|
||||
public class AccordInteropCommit extends Commit
|
||||
{
|
||||
public static final IVersionedSerializer<AccordInteropCommit> serializer = new CommitSerializer<AccordInteropCommit, AccordInteropRead>(AccordInteropRead.class, AccordInteropRead.requestSerializer)
|
||||
public static final IVersionedSerializer<AccordInteropCommit> serializer = new CommitSerializer<>(AccordInteropRead.class, AccordInteropRead.requestSerializer)
|
||||
{
|
||||
@Override
|
||||
protected AccordInteropCommit deserializeCommit(TxnId txnId, Route<?> scope, long waitForEpoch, Kind kind, Ballot ballot, Timestamp executeAt, Seekables<?, ?> keys, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute, @Nullable ReadData read)
|
||||
protected AccordInteropCommit deserializeCommit(TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch, Kind kind, Ballot ballot, Timestamp executeAt, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute, @Nullable ReadData read)
|
||||
{
|
||||
return new AccordInteropCommit(kind, txnId, scope, waitForEpoch, ballot, executeAt, keys, partialTxn, partialDeps, fullRoute, read);
|
||||
return new AccordInteropCommit(kind, txnId, scope, waitForEpoch, minEpoch, ballot, executeAt, partialTxn, partialDeps, fullRoute, read);
|
||||
}
|
||||
};
|
||||
|
||||
public AccordInteropCommit(Kind kind, TxnId txnId, Route<?> scope, long waitForEpoch, Ballot ballot, Timestamp executeAt, Seekables<?, ?> keys, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute, @Nonnull ReadData readData)
|
||||
public AccordInteropCommit(Kind kind, TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch, Ballot ballot, Timestamp executeAt, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute, @Nonnull ReadData readData)
|
||||
{
|
||||
super(kind, txnId, scope, waitForEpoch, ballot, executeAt, keys, partialTxn, partialDeps, fullRoute, readData);
|
||||
super(kind, txnId, scope, waitForEpoch, minEpoch, ballot, executeAt, partialTxn, partialDeps, fullRoute, readData);
|
||||
}
|
||||
|
||||
public AccordInteropCommit(Kind kind, Node.Id to, Topology coordinateTopology, Topologies topologies, TxnId txnId, Txn txn, FullRoute<?> route, Timestamp executeAt, Deps deps, AccordInteropRead read)
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ import org.apache.cassandra.service.accord.serializers.KeySerializers;
|
|||
import org.apache.cassandra.service.accord.serializers.ReadDataSerializers;
|
||||
import org.apache.cassandra.service.accord.serializers.ReadDataSerializers.ReadDataSerializer;
|
||||
|
||||
import static accord.local.SaveStatus.PreApplied;
|
||||
import static accord.local.SaveStatus.ReadyToExecute;
|
||||
import static accord.primitives.SaveStatus.PreApplied;
|
||||
import static accord.primitives.SaveStatus.ReadyToExecute;
|
||||
|
||||
public class AccordInteropRead extends ReadData
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@ import javax.annotation.Nullable;
|
|||
import accord.api.Data;
|
||||
import accord.local.Node;
|
||||
import accord.local.SafeCommandStore;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.messages.ReadData;
|
||||
import accord.messages.MessageType;
|
||||
import accord.primitives.PartialTxn;
|
||||
import accord.primitives.Participants;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.topology.Topologies;
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@ import accord.coordinate.ExecuteSyncPoint;
|
|||
import accord.local.Node;
|
||||
import accord.primitives.Deps;
|
||||
import accord.primitives.FullRoute;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.SyncPoint;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.Txn;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.primitives.Unseekable;
|
||||
import accord.primitives.Writes;
|
||||
import accord.topology.Topologies;
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ import accord.topology.Topologies;
|
|||
* adapter requires responses from all of the supplied endpoints before completing. Note that shards only block on the
|
||||
* intersection of the provided replicas and their own endpoints.
|
||||
*/
|
||||
public class RepairSyncPointAdapter<S extends Seekables<?, ?>> extends CoordinationAdapter.Adapters.AbstractSyncPointAdapter<S>
|
||||
public class RepairSyncPointAdapter<U extends Unseekable> extends CoordinationAdapter.Adapters.AbstractInclusiveSyncPointAdapter<U>
|
||||
{
|
||||
private final ImmutableSet<Node.Id> requiredResponses;
|
||||
|
||||
|
|
@ -58,21 +58,27 @@ public class RepairSyncPointAdapter<S extends Seekables<?, ?>> extends Coordinat
|
|||
}
|
||||
|
||||
@Override
|
||||
public void execute(Node node, Topologies all, FullRoute<?> route, ExecutePath path, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super SyncPoint<S>, Throwable> callback)
|
||||
public void execute(Node node, Topologies all, FullRoute<?> route, ExecutePath path, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super SyncPoint<U>, Throwable> callback)
|
||||
{
|
||||
RequiredResponseTracker tracker = new RequiredResponseTracker(requiredResponses, all);
|
||||
ExecuteSyncPoint.ExecuteBlocking<S> execute = new ExecuteSyncPoint.ExecuteBlocking<>(node, tracker, new SyncPoint<>(txnId, deps, (S) txn.keys(), route), executeAt);
|
||||
ExecuteSyncPoint.ExecuteBlocking<U> execute = new ExecuteSyncPoint.ExecuteBlocking<>(node, new SyncPoint<U>(txnId, deps, (FullRoute<U>) route), tracker, executeAt);
|
||||
execute.addCallback(callback);
|
||||
execute.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void persist(Node node, Topologies all, FullRoute<?> route, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, BiConsumer<? super SyncPoint<S>, Throwable> callback)
|
||||
protected void addOrExecuteCallback(ExecuteSyncPoint.ExecuteBlocking<U> execute, BiConsumer<? super SyncPoint<U>, Throwable> callback)
|
||||
{
|
||||
execute.addCallback(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void persist(Node node, Topologies all, FullRoute<?> route, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, BiConsumer<? super SyncPoint<U>, Throwable> callback)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static <S extends Seekables<?, ?>> CoordinationAdapter<SyncPoint<S>> create(Collection<Node.Id> requiredResponses)
|
||||
public static <U extends Unseekable> CoordinationAdapter<SyncPoint<U>> create(Collection<Node.Id> requiredResponses)
|
||||
{
|
||||
return new RepairSyncPointAdapter<>(requiredResponses);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,11 @@ import java.io.IOException;
|
|||
|
||||
import accord.messages.Accept;
|
||||
import accord.messages.Accept.AcceptReply;
|
||||
import accord.primitives.Ballot;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.utils.Invariants;
|
||||
import org.apache.cassandra.db.TypeSizes;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
|
|
@ -35,14 +38,13 @@ public class AcceptSerializers
|
|||
{
|
||||
private AcceptSerializers() {}
|
||||
|
||||
public static final IVersionedSerializer<Accept> request = new TxnRequestSerializer.WithUnsyncedSerializer<Accept>()
|
||||
public static final IVersionedSerializer<Accept> request = new TxnRequestSerializer.WithUnsyncedSerializer<>()
|
||||
{
|
||||
@Override
|
||||
public void serializeBody(Accept accept, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
CommandSerializers.ballot.serialize(accept.ballot, out, version);
|
||||
CommandSerializers.timestamp.serialize(accept.executeAt, out, version);
|
||||
KeySerializers.seekables.serialize(accept.keys, out, version);
|
||||
DepsSerializer.partialDeps.serialize(accept.partialDeps, out, version);
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +54,6 @@ public class AcceptSerializers
|
|||
return create(txnId, scope, waitForEpoch, minEpoch,
|
||||
CommandSerializers.ballot.deserialize(in, version),
|
||||
CommandSerializers.timestamp.deserialize(in, version),
|
||||
KeySerializers.seekables.deserialize(in, version),
|
||||
DepsSerializer.partialDeps.deserialize(in, version));
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +62,6 @@ public class AcceptSerializers
|
|||
{
|
||||
return CommandSerializers.ballot.serializedSize(accept.ballot, version)
|
||||
+ CommandSerializers.timestamp.serializedSize(accept.executeAt, version)
|
||||
+ KeySerializers.seekables.serializedSize(accept.keys, version)
|
||||
+ DepsSerializer.partialDeps.serializedSize(accept.partialDeps, version);
|
||||
}
|
||||
};
|
||||
|
|
@ -105,43 +105,50 @@ public class AcceptSerializers
|
|||
if (reply.deps != null)
|
||||
{
|
||||
out.writeByte(1);
|
||||
DepsSerializer.partialDeps.serialize(reply.deps, out, version);
|
||||
DepsSerializer.deps.serialize(reply.deps, out, version);
|
||||
}
|
||||
else
|
||||
{
|
||||
Invariants.checkState(reply == AcceptReply.ACCEPT_INVALIDATE);
|
||||
out.writeByte(2);
|
||||
}
|
||||
break;
|
||||
case Redundant:
|
||||
case Truncated:
|
||||
out.writeByte(3);
|
||||
break;
|
||||
case RejectedBallot:
|
||||
out.writeByte(4);
|
||||
CommandSerializers.ballot.serialize(reply.supersededBy, out, version);
|
||||
break;
|
||||
case Truncated:
|
||||
out.writeByte(5);
|
||||
break;
|
||||
case Redundant:
|
||||
int flags = 5 | (reply.supersededBy == null ? 0x8 : 0) | (reply.committedExecuteAt == null ? 0x10 : 0);
|
||||
out.writeByte(flags);
|
||||
if (reply.supersededBy != null)
|
||||
CommandSerializers.ballot.serialize(reply.supersededBy, out, version);
|
||||
if (reply.committedExecuteAt != null)
|
||||
CommandSerializers.timestamp.serialize(reply.committedExecuteAt, out, version);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AcceptReply deserialize(DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
int type = in.readByte();
|
||||
switch (type)
|
||||
int flags = in.readByte();
|
||||
switch (flags & 0x7)
|
||||
{
|
||||
default: throw new IllegalStateException("Unexpected AcceptNack type: " + type);
|
||||
default: throw new IllegalStateException("Unexpected AcceptNack type: " + (flags & 0x7));
|
||||
case 1:
|
||||
return new AcceptReply(DepsSerializer.partialDeps.deserialize(in, version));
|
||||
return new AcceptReply(DepsSerializer.deps.deserialize(in, version));
|
||||
case 2:
|
||||
return AcceptReply.ACCEPT_INVALIDATE;
|
||||
case 3:
|
||||
return AcceptReply.REDUNDANT;
|
||||
return AcceptReply.TRUNCATED;
|
||||
case 4:
|
||||
return new AcceptReply(CommandSerializers.ballot.deserialize(in, version));
|
||||
case 5:
|
||||
return AcceptReply.TRUNCATED;
|
||||
Ballot supersededBy = (flags & 0x8) == 0 ? null : CommandSerializers.ballot.deserialize(in, version);
|
||||
Timestamp committedExecuteAt = (flags & 0x10) == 0 ? null : CommandSerializers.timestamp.deserialize(in, version);
|
||||
return new AcceptReply(supersededBy, committedExecuteAt);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -154,13 +161,16 @@ public class AcceptSerializers
|
|||
default: throw new AssertionError();
|
||||
case Success:
|
||||
if (reply.deps != null)
|
||||
size += DepsSerializer.partialDeps.serializedSize(reply.deps, version);
|
||||
size += DepsSerializer.deps.serializedSize(reply.deps, version);
|
||||
break;
|
||||
case Redundant:
|
||||
case Truncated:
|
||||
break;
|
||||
case RejectedBallot:
|
||||
size += CommandSerializers.ballot.serializedSize(reply.supersededBy, version);
|
||||
break;
|
||||
case Redundant:
|
||||
if (reply.supersededBy != null) size += CommandSerializers.ballot.serializedSize(reply.supersededBy, version);
|
||||
if (reply.committedExecuteAt != null) size += CommandSerializers.timestamp.serializedSize(reply.committedExecuteAt, version);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ package org.apache.cassandra.service.accord.serializers;
|
|||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.apache.cassandra.db.marshal.ByteArrayAccessor;
|
||||
import org.apache.cassandra.db.marshal.LongType;
|
||||
|
|
@ -29,6 +31,7 @@ import org.apache.cassandra.db.marshal.ValueAccessor;
|
|||
import org.apache.cassandra.dht.IPartitioner;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.schema.TableId;
|
||||
import org.apache.cassandra.service.accord.AccordKeyspace;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
|
||||
import org.apache.cassandra.utils.ByteArrayUtil;
|
||||
import org.apache.cassandra.utils.bytecomparable.ByteComparable;
|
||||
|
|
@ -39,6 +42,8 @@ import static org.apache.cassandra.service.accord.api.AccordRoutingKey.RoutingKe
|
|||
|
||||
public class AccordRoutingKeyByteSource
|
||||
{
|
||||
public static final ByteComparable.Version currentVersion = ByteComparable.Version.OSS50;
|
||||
|
||||
private static final byte[] MIN_ORDER = { -1 };
|
||||
private static final byte[] TOKEN_ORDER = { 0 };
|
||||
private static final byte[] MAX_ORDER = { 1 };
|
||||
|
|
@ -61,18 +66,18 @@ public class AccordRoutingKeyByteSource
|
|||
public static Serializer create(IPartitioner partitioner)
|
||||
{
|
||||
if (partitioner.isFixedLength())
|
||||
return new FixedLength(partitioner, ByteComparable.Version.OSS50);
|
||||
return new VariableLength(partitioner, ByteComparable.Version.OSS50);
|
||||
return new FixedLength(partitioner, currentVersion);
|
||||
return new VariableLength(partitioner, currentVersion);
|
||||
}
|
||||
|
||||
public static FixedLength fixedLength(IPartitioner partitioner)
|
||||
{
|
||||
return new FixedLength(partitioner, ByteComparable.Version.OSS50);
|
||||
return new FixedLength(partitioner, currentVersion);
|
||||
}
|
||||
|
||||
public static VariableLength variableLength(IPartitioner partitioner)
|
||||
{
|
||||
return new VariableLength(partitioner, ByteComparable.Version.OSS50);
|
||||
return new VariableLength(partitioner, currentVersion);
|
||||
}
|
||||
|
||||
public static abstract class Serializer
|
||||
|
|
@ -148,6 +153,11 @@ public class AccordRoutingKeyByteSource
|
|||
}
|
||||
|
||||
public <V> AccordRoutingKey fromComparableBytes(ValueAccessor<V> accessor, V data) throws IOException
|
||||
{
|
||||
return fromComparableBytes(accessor, data, version, partitioner);
|
||||
}
|
||||
|
||||
public static <V> AccordRoutingKey fromComparableBytes(ValueAccessor<V> accessor, V data, ByteComparable.Version version, @Nullable IPartitioner partitioner)
|
||||
{
|
||||
ByteSource.Peekable bs = ByteSource.peekable(ByteSource.fixedLength(accessor, data));
|
||||
long[] uuidValues = new long[2];
|
||||
|
|
@ -160,47 +170,63 @@ public class AccordRoutingKeyByteSource
|
|||
uuidValues[i] = value;
|
||||
}
|
||||
TableId tableId = TableId.fromUUID(new UUID(uuidValues[0], uuidValues[1]));
|
||||
return fromComparableBytes(bs,
|
||||
isMin -> isMin ? AccordRoutingKey.SentinelKey.min(tableId) : AccordRoutingKey.SentinelKey.max(tableId),
|
||||
token -> new AccordRoutingKey.TokenKey(tableId, token));
|
||||
return fromComparableBytes(bs, tableId, version, partitioner);
|
||||
}
|
||||
|
||||
private AccordRoutingKey fromComparableBytes(ByteSource.Peekable bs,
|
||||
Function<Boolean, AccordRoutingKey> onSentinel,
|
||||
Function<Token, AccordRoutingKey> onToken) throws IOException
|
||||
public static <V> AccordRoutingKey fromComparableBytes(ValueAccessor<V> accessor, V data, TableId tableId, ByteComparable.Version version, @Nullable IPartitioner partitioner)
|
||||
{
|
||||
ByteSource.Peekable bs = ByteSource.peekable(ByteSource.fixedLength(accessor, data));
|
||||
return fromComparableBytes(bs, tableId, version, partitioner);
|
||||
}
|
||||
|
||||
public static <V> AccordRoutingKey fromComparableBytes(ByteSource.Peekable bs, TableId tableId, ByteComparable.Version version, @Nullable IPartitioner partitioner)
|
||||
{
|
||||
if (partitioner == null)
|
||||
partitioner = AccordKeyspace.partitioner(tableId);
|
||||
return fromComparableBytes(bs, tableId,
|
||||
(id, isMin) -> isMin ? AccordRoutingKey.SentinelKey.min(id) : AccordRoutingKey.SentinelKey.max(id),
|
||||
AccordRoutingKey.TokenKey::new,
|
||||
version, partitioner
|
||||
);
|
||||
}
|
||||
|
||||
public static AccordRoutingKey fromComparableBytes(ByteSource.Peekable bs, TableId tableId,
|
||||
BiFunction<TableId, Boolean, AccordRoutingKey> onSentinel,
|
||||
BiFunction<TableId, Token, AccordRoutingKey> onToken,
|
||||
ByteComparable.Version version, IPartitioner partitioner)
|
||||
{
|
||||
if (bs.peek() == ByteSource.TERMINATOR)
|
||||
throw new IOException("Unable to read prefix");
|
||||
throw new IllegalStateException("Unable to read prefix");
|
||||
ByteSource.Peekable component = progress(bs);
|
||||
|
||||
byte[] prefix = ByteSourceInverse.getOptionalSignedFixedLength(ByteArrayAccessor.instance, component, 1);
|
||||
if (prefix == null)
|
||||
throw new IOException("Unable to read prefix; prefix was null");
|
||||
throw new IllegalStateException("Unable to read prefix; prefix was null");
|
||||
if (Arrays.equals(TOKEN_ORDER, prefix))
|
||||
{
|
||||
component = ByteSourceInverse.nextComponentSource(bs);
|
||||
if (component == null)
|
||||
throw new IOException("Unable to read token; component was not found");
|
||||
return onToken.apply(partitioner.getTokenFactory().fromComparableBytes(component, version));
|
||||
throw new IllegalStateException("Unable to read token; component was not found");
|
||||
return onToken.apply(tableId, partitioner.getTokenFactory().fromComparableBytes(component, version));
|
||||
}
|
||||
if (Arrays.equals(MIN_ORDER, prefix))
|
||||
return onSentinel.apply(true);
|
||||
return onSentinel.apply(tableId, true);
|
||||
if (Arrays.equals(MAX_ORDER, prefix))
|
||||
return onSentinel.apply(false);
|
||||
return onSentinel.apply(tableId, false);
|
||||
throw new AssertionError("Unknown prefix");
|
||||
}
|
||||
|
||||
private static ByteSource.Peekable progress(ByteSource.Peekable bs) throws IOException
|
||||
private static ByteSource.Peekable progress(ByteSource.Peekable bs)
|
||||
{
|
||||
ByteSource.Peekable component = ByteSourceInverse.nextComponentSource(bs);
|
||||
if (component == null)
|
||||
throw new IOException("Unable to read prefix; component was not found");
|
||||
throw new IllegalStateException("Unable to read prefix; component was not found");
|
||||
if (component.peek() == ByteSource.NEXT_COMPONENT)
|
||||
{
|
||||
// this came from (table, token_or_sentinel)
|
||||
component = ByteSourceInverse.nextComponentSource(bs);
|
||||
if (component == null)
|
||||
throw new IOException("Unable to read prefix; component was not found");
|
||||
throw new IllegalStateException("Unable to read prefix; component was not found");
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import accord.primitives.FullRoute;
|
|||
import accord.primitives.PartialDeps;
|
||||
import accord.primitives.PartialTxn;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.primitives.Writes;
|
||||
|
|
@ -64,7 +63,6 @@ public class ApplySerializers
|
|||
public void serializeBody(A apply, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
kind.serialize(apply.kind, out, version);
|
||||
KeySerializers.seekables.serialize(apply.keys(), out, version);
|
||||
CommandSerializers.timestamp.serialize(apply.executeAt, out, version);
|
||||
DepsSerializer.partialDeps.serialize(apply.deps, out, version);
|
||||
CommandSerializers.nullablePartialTxn.serialize(apply.txn, out, version);
|
||||
|
|
@ -72,7 +70,7 @@ public class ApplySerializers
|
|||
CommandSerializers.writes.serialize(apply.writes, out, version);
|
||||
}
|
||||
|
||||
protected abstract A deserializeApply(TxnId txnId, Route<?> scope, long waitForEpoch, Apply.Kind kind, Seekables<?, ?> keys,
|
||||
protected abstract A deserializeApply(TxnId txnId, Route<?> scope, long waitForEpoch, Apply.Kind kind,
|
||||
Timestamp executeAt, PartialDeps deps, PartialTxn txn, FullRoute<?> fullRoute, Writes writes, Result result);
|
||||
|
||||
@Override
|
||||
|
|
@ -80,7 +78,6 @@ public class ApplySerializers
|
|||
{
|
||||
return deserializeApply(txnId, scope, waitForEpoch,
|
||||
kind.deserialize(in, version),
|
||||
KeySerializers.seekables.deserialize(in, version),
|
||||
CommandSerializers.timestamp.deserialize(in, version),
|
||||
DepsSerializer.partialDeps.deserialize(in, version),
|
||||
CommandSerializers.nullablePartialTxn.deserialize(in, version),
|
||||
|
|
@ -93,7 +90,6 @@ public class ApplySerializers
|
|||
public long serializedBodySize(A apply, int version)
|
||||
{
|
||||
return kind.serializedSize(apply.kind, version)
|
||||
+ KeySerializers.seekables.serializedSize(apply.keys(), version)
|
||||
+ CommandSerializers.timestamp.serializedSize(apply.executeAt, version)
|
||||
+ DepsSerializer.partialDeps.serializedSize(apply.deps, version)
|
||||
+ CommandSerializers.nullablePartialTxn.serializedSize(apply.txn, version)
|
||||
|
|
@ -102,17 +98,17 @@ public class ApplySerializers
|
|||
}
|
||||
}
|
||||
|
||||
public static final IVersionedSerializer<Apply> request = new ApplySerializer<Apply>()
|
||||
public static final IVersionedSerializer<Apply> request = new ApplySerializer<>()
|
||||
{
|
||||
@Override
|
||||
protected Apply deserializeApply(TxnId txnId, Route<?> scope, long waitForEpoch, Apply.Kind kind, Seekables<?, ?> keys,
|
||||
protected Apply deserializeApply(TxnId txnId, Route<?> scope, long waitForEpoch, Apply.Kind kind,
|
||||
Timestamp executeAt, PartialDeps deps, PartialTxn txn, FullRoute<?> fullRoute, Writes writes, Result result)
|
||||
{
|
||||
return Apply.SerializationSupport.create(txnId, scope, waitForEpoch, kind, keys, executeAt, deps, txn, fullRoute, writes, result);
|
||||
return Apply.SerializationSupport.create(txnId, scope, waitForEpoch, kind, executeAt, deps, txn, fullRoute, writes, result);
|
||||
}
|
||||
};
|
||||
|
||||
public static final IVersionedSerializer<Apply.ApplyReply> reply = new IVersionedSerializer<Apply.ApplyReply>()
|
||||
public static final IVersionedSerializer<Apply.ApplyReply> reply = new IVersionedSerializer<>()
|
||||
{
|
||||
private final Apply.ApplyReply[] replies = Apply.ApplyReply.values();
|
||||
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@ package org.apache.cassandra.service.accord.serializers;
|
|||
import java.io.IOException;
|
||||
|
||||
import accord.api.ProgressLog.BlockedUntil;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.messages.Await;
|
||||
import accord.messages.Await.AsyncAwaitComplete;
|
||||
import accord.messages.Await.AwaitOk;
|
||||
import accord.primitives.Participants;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.utils.Invariants;
|
||||
import org.apache.cassandra.db.TypeSizes;
|
||||
|
|
@ -45,6 +45,7 @@ public class AwaitSerializer
|
|||
CommandSerializers.txnId.serialize(await.txnId, out, version);
|
||||
KeySerializers.participants.serialize(await.scope, out, version);
|
||||
out.writeByte(await.blockedUntil.ordinal());
|
||||
out.writeUnsignedVInt(await.awaitEpoch - await.txnId.epoch());
|
||||
out.writeUnsignedVInt32(await.callbackId + 1);
|
||||
Invariants.checkState(await.callbackId >= -1);
|
||||
}
|
||||
|
|
@ -55,9 +56,10 @@ public class AwaitSerializer
|
|||
TxnId txnId = CommandSerializers.txnId.deserialize(in, version);
|
||||
Participants<?> scope = KeySerializers.participants.deserialize(in, version);
|
||||
BlockedUntil blockedUntil = BlockedUntil.forOrdinal(in.readByte());
|
||||
long awaitEpoch = in.readUnsignedVInt() + txnId.epoch();
|
||||
int callbackId = in.readUnsignedVInt32() - 1;
|
||||
Invariants.checkState(callbackId >= -1);
|
||||
return Await.SerializerSupport.create(txnId, scope, blockedUntil, callbackId);
|
||||
return Await.SerializerSupport.create(txnId, scope, blockedUntil, awaitEpoch, callbackId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -66,6 +68,7 @@ public class AwaitSerializer
|
|||
return CommandSerializers.txnId.serializedSize(await.txnId, version)
|
||||
+ KeySerializers.participants.serializedSize(await.scope, version)
|
||||
+ TypeSizes.BYTE_SIZE
|
||||
+ VIntCoding.computeUnsignedVIntSize(await.awaitEpoch - await.txnId.epoch())
|
||||
+ VIntCoding.computeUnsignedVIntSize(await.callbackId + 1);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,11 +21,12 @@ package org.apache.cassandra.service.accord.serializers;
|
|||
import java.io.IOException;
|
||||
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.messages.BeginInvalidation;
|
||||
import accord.messages.BeginInvalidation.InvalidateReply;
|
||||
import accord.primitives.Ballot;
|
||||
import accord.primitives.Participants;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.SaveStatus;
|
||||
import org.apache.cassandra.db.TypeSizes;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
|
|
@ -39,7 +40,7 @@ public class BeginInvalidationSerializers
|
|||
public void serialize(BeginInvalidation begin, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
CommandSerializers.txnId.serialize(begin.txnId, out, version);
|
||||
KeySerializers.unseekables.serialize(begin.someUnseekables, out, version);
|
||||
KeySerializers.participants.serialize(begin.participants, out, version);
|
||||
CommandSerializers.ballot.serialize(begin.ballot, out, version);
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +48,7 @@ public class BeginInvalidationSerializers
|
|||
public BeginInvalidation deserialize(DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
return new BeginInvalidation(CommandSerializers.txnId.deserialize(in, version),
|
||||
KeySerializers.unseekables.deserialize(in, version),
|
||||
KeySerializers.participants.deserialize(in, version),
|
||||
CommandSerializers.ballot.deserialize(in, version));
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +56,7 @@ public class BeginInvalidationSerializers
|
|||
public long serializedSize(BeginInvalidation begin, int version)
|
||||
{
|
||||
return CommandSerializers.txnId.serializedSize(begin.txnId, version)
|
||||
+ KeySerializers.unseekables.serializedSize(begin.someUnseekables, version)
|
||||
+ KeySerializers.participants.serializedSize(begin.participants, version)
|
||||
+ CommandSerializers.ballot.serializedSize(begin.ballot, version);
|
||||
}
|
||||
};
|
||||
|
|
@ -70,6 +71,7 @@ public class BeginInvalidationSerializers
|
|||
CommandSerializers.saveStatus.serialize(reply.maxStatus, out, version);
|
||||
CommandSerializers.saveStatus.serialize(reply.maxKnowledgeStatus, out, version);
|
||||
out.writeBoolean(reply.acceptedFastPath);
|
||||
KeySerializers.nullableParticipants.serialize(reply.truncated, out, version);
|
||||
KeySerializers.nullableRoute.serialize(reply.route, out, version);
|
||||
KeySerializers.nullableRoutingKey.serialize(reply.homeKey, out, version);
|
||||
}
|
||||
|
|
@ -77,14 +79,16 @@ public class BeginInvalidationSerializers
|
|||
@Override
|
||||
public InvalidateReply deserialize(DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
// TODO (expected): use headers instead of nullable+bool serializers
|
||||
Ballot supersededBy = CommandSerializers.nullableBallot.deserialize(in, version);
|
||||
Ballot accepted = CommandSerializers.ballot.deserialize(in, version);
|
||||
SaveStatus maxStatus = CommandSerializers.saveStatus.deserialize(in, version);
|
||||
SaveStatus maxKnowledgeStatus = CommandSerializers.saveStatus.deserialize(in, version);
|
||||
boolean acceptedFastPath = in.readBoolean();
|
||||
Participants<?> truncated = KeySerializers.nullableParticipants.deserialize(in, version);
|
||||
Route<?> route = KeySerializers.nullableRoute.deserialize(in, version);
|
||||
RoutingKey homeKey = KeySerializers.nullableRoutingKey.deserialize(in, version);
|
||||
return new InvalidateReply(supersededBy, accepted, maxStatus, maxKnowledgeStatus, acceptedFastPath, route, homeKey);
|
||||
return new InvalidateReply(supersededBy, accepted, maxStatus, maxKnowledgeStatus, acceptedFastPath, truncated, route, homeKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -95,6 +99,7 @@ public class BeginInvalidationSerializers
|
|||
+ CommandSerializers.saveStatus.serializedSize(reply.maxStatus, version)
|
||||
+ CommandSerializers.saveStatus.serializedSize(reply.maxKnowledgeStatus, version)
|
||||
+ TypeSizes.BOOL_SIZE
|
||||
+ KeySerializers.nullableParticipants.serializedSize(reply.truncated, version)
|
||||
+ KeySerializers.nullableRoute.serializedSize(reply.route, version)
|
||||
+ KeySerializers.nullableRoutingKey.serializedSize(reply.homeKey, version);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import java.io.IOException;
|
|||
import accord.messages.CalculateDeps;
|
||||
import accord.messages.CalculateDeps.CalculateDepsOk;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
|
|
@ -32,28 +31,25 @@ import org.apache.cassandra.io.util.DataOutputPlus;
|
|||
|
||||
public class CalculateDepsSerializers
|
||||
{
|
||||
public static final IVersionedSerializer<CalculateDeps> request = new TxnRequestSerializer.WithUnsyncedSerializer<CalculateDeps>()
|
||||
public static final IVersionedSerializer<CalculateDeps> request = new TxnRequestSerializer.WithUnsyncedSerializer<>()
|
||||
{
|
||||
@Override
|
||||
public void serializeBody(CalculateDeps msg, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
KeySerializers.seekables.serialize(msg.keys, out, version);
|
||||
CommandSerializers.timestamp.serialize(msg.executeAt, out, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CalculateDeps deserializeBody(DataInputPlus in, int version, TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch) throws IOException
|
||||
{
|
||||
Seekables<?, ?> keys = KeySerializers.seekables.deserialize(in, version);
|
||||
Timestamp executeAt = CommandSerializers.timestamp.deserialize(in, version);
|
||||
return CalculateDeps.SerializationSupport.create(txnId, scope, waitForEpoch, minEpoch, keys, executeAt);
|
||||
return CalculateDeps.SerializationSupport.create(txnId, scope, waitForEpoch, minEpoch, executeAt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedBodySize(CalculateDeps msg, int version)
|
||||
{
|
||||
return KeySerializers.seekables.serializedSize(msg.keys, version)
|
||||
+ CommandSerializers.timestamp.serializedSize(msg.executeAt, version);
|
||||
return CommandSerializers.timestamp.serializedSize(msg.executeAt, version);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -62,19 +58,19 @@ public class CalculateDepsSerializers
|
|||
@Override
|
||||
public void serialize(CalculateDepsOk reply, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
DepsSerializer.partialDeps.serialize(reply.deps, out, version);
|
||||
DepsSerializer.deps.serialize(reply.deps, out, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CalculateDepsOk deserialize(DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
return new CalculateDepsOk(DepsSerializer.partialDeps.deserialize(in, version));
|
||||
return new CalculateDepsOk(DepsSerializer.deps.deserialize(in, version));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedSize(CalculateDepsOk reply, int version)
|
||||
{
|
||||
return DepsSerializer.partialDeps.serializedSize(reply.deps, version);
|
||||
return DepsSerializer.deps.serializedSize(reply.deps, version);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* Licensed to the Apache Software ation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
|
|
@ -23,99 +23,68 @@ import java.io.IOException;
|
|||
import accord.api.Result;
|
||||
import accord.api.RoutingKey;
|
||||
import accord.coordinate.Infer;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.local.Status.Durability;
|
||||
import accord.local.Status.Known;
|
||||
import accord.messages.CheckStatus;
|
||||
import accord.messages.CheckStatus.CheckStatusNack;
|
||||
import accord.messages.CheckStatus.CheckStatusOk;
|
||||
import accord.messages.CheckStatus.CheckStatusOkFull;
|
||||
import accord.messages.CheckStatus.CheckStatusReply;
|
||||
import accord.messages.CheckStatus.FoundKnown;
|
||||
import accord.messages.CheckStatus.FoundKnownMap;
|
||||
import accord.primitives.Ballot;
|
||||
import accord.primitives.Known;
|
||||
import accord.primitives.KnownMap;
|
||||
import accord.primitives.PartialDeps;
|
||||
import accord.primitives.PartialTxn;
|
||||
import accord.primitives.Participants;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Status.Durability;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.primitives.Unseekables;
|
||||
import accord.primitives.Writes;
|
||||
import org.apache.cassandra.db.TypeSizes;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.utils.NullableSerializer;
|
||||
|
||||
import static accord.messages.CheckStatus.SerializationSupport.createOk;
|
||||
import static org.apache.cassandra.service.accord.serializers.CommandSerializers.nullableKnown;
|
||||
|
||||
public class CheckStatusSerializers
|
||||
{
|
||||
public static final IVersionedSerializer<FoundKnown> foundKnown = new IVersionedSerializer<>()
|
||||
public static final IVersionedSerializer<KnownMap> knownMap = new IVersionedSerializer<>()
|
||||
{
|
||||
@Override
|
||||
public void serialize(FoundKnown known, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
CommandSerializers.known.serialize(known, out, version);
|
||||
CommandSerializers.invalidIfNot.serialize(known.invalidIfNot, out, version);
|
||||
CommandSerializers.isPreempted.serialize(known.isPreempted, out, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FoundKnown deserialize(DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
Known known = CommandSerializers.known.deserialize(in, version);
|
||||
Infer.InvalidIfNot invalidIfNot = CommandSerializers.invalidIfNot.deserialize(in, version);
|
||||
Infer.IsPreempted isPreempted = CommandSerializers.isPreempted.deserialize(in, version);
|
||||
return new FoundKnown(known, invalidIfNot, isPreempted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedSize(FoundKnown known, int version)
|
||||
{
|
||||
return CommandSerializers.known.serializedSize(known, version)
|
||||
+ CommandSerializers.invalidIfNot.serializedSize(known.invalidIfNot, version)
|
||||
+ CommandSerializers.isPreempted.serializedSize(known.isPreempted, version);
|
||||
}
|
||||
};
|
||||
|
||||
public static final IVersionedSerializer<FoundKnown> foundKnownNullable = NullableSerializer.wrap(foundKnown);
|
||||
|
||||
public static final IVersionedSerializer<FoundKnownMap> foundKnownMap = new IVersionedSerializer<>()
|
||||
{
|
||||
@Override
|
||||
public void serialize(FoundKnownMap knownMap, DataOutputPlus out, int version) throws IOException
|
||||
public void serialize(KnownMap knownMap, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
int size = knownMap.size();
|
||||
out.writeUnsignedVInt32(size);
|
||||
for (int i = 0 ; i <= size ; ++i)
|
||||
KeySerializers.routingKey.serialize(knownMap.startAt(i), out, version);
|
||||
for (int i = 0 ; i < size ; ++i)
|
||||
foundKnownNullable.serialize(knownMap.valueAt(i), out, version);
|
||||
nullableKnown.serialize(knownMap.valueAt(i), out, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FoundKnownMap deserialize(DataInputPlus in, int version) throws IOException
|
||||
public KnownMap deserialize(DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
int size = in.readUnsignedVInt32();
|
||||
RoutingKey[] starts = new RoutingKey[size + 1];
|
||||
for (int i = 0 ; i <= size ; ++i)
|
||||
starts[i] = KeySerializers.routingKey.deserialize(in, version);
|
||||
FoundKnown[] values = new FoundKnown[size];
|
||||
Known[] values = new Known[size];
|
||||
for (int i = 0 ; i < size ; ++i)
|
||||
values[i] = foundKnownNullable.deserialize(in, version);
|
||||
return FoundKnownMap.SerializerSupport.create(true, starts, values);
|
||||
values[i] = nullableKnown.deserialize(in, version);
|
||||
return KnownMap.SerializerSupport.create(true, starts, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedSize(FoundKnownMap knownMap, int version)
|
||||
public long serializedSize(KnownMap knownMap, int version)
|
||||
{
|
||||
int size = knownMap.size();
|
||||
long result = TypeSizes.sizeofUnsignedVInt(size);
|
||||
for (int i = 0 ; i <= size ; ++i)
|
||||
result += KeySerializers.routingKey.serializedSize(knownMap.startAt(i), version);
|
||||
for (int i = 0 ; i < size ; ++i)
|
||||
result += foundKnownNullable.serializedSize(knownMap.valueAt(i), version);
|
||||
result += nullableKnown.serializedSize(knownMap.valueAt(i), version);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
|
@ -128,7 +97,7 @@ public class CheckStatusSerializers
|
|||
public void serialize(CheckStatus check, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
CommandSerializers.txnId.serialize(check.txnId, out, version);
|
||||
KeySerializers.unseekables.serialize(check.query, out, version);
|
||||
KeySerializers.participants.serialize(check.query, out, version);
|
||||
out.writeUnsignedVInt(check.sourceEpoch);
|
||||
out.writeByte(check.includeInfo.ordinal());
|
||||
}
|
||||
|
|
@ -137,7 +106,7 @@ public class CheckStatusSerializers
|
|||
public CheckStatus deserialize(DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
TxnId txnId = CommandSerializers.txnId.deserialize(in, version);
|
||||
Unseekables<?> query = KeySerializers.unseekables.deserialize(in, version);
|
||||
Participants<?> query = KeySerializers.participants.deserialize(in, version);
|
||||
long sourceEpoch = in.readUnsignedVInt();
|
||||
CheckStatus.IncludeInfo info = infos[in.readByte()];
|
||||
return new CheckStatus(txnId, query, sourceEpoch, info);
|
||||
|
|
@ -147,7 +116,7 @@ public class CheckStatusSerializers
|
|||
public long serializedSize(CheckStatus check, int version)
|
||||
{
|
||||
return CommandSerializers.txnId.serializedSize(check.txnId, version)
|
||||
+ KeySerializers.unseekables.serializedSize(check.query, version)
|
||||
+ KeySerializers.participants.serializedSize(check.query, version)
|
||||
+ TypeSizes.sizeofUnsignedVInt(check.sourceEpoch)
|
||||
+ TypeSizes.BYTE_SIZE;
|
||||
}
|
||||
|
|
@ -170,7 +139,7 @@ public class CheckStatusSerializers
|
|||
|
||||
CheckStatusOk ok = (CheckStatusOk) reply;
|
||||
out.write(reply instanceof CheckStatusOkFull ? FULL : OK);
|
||||
foundKnownMap.serialize(ok.map, out, version);
|
||||
knownMap.serialize(ok.map, out, version);
|
||||
CommandSerializers.saveStatus.serialize(ok.maxKnowledgeSaveStatus, out, version);
|
||||
CommandSerializers.saveStatus.serialize(ok.maxSaveStatus, out, version);
|
||||
CommandSerializers.ballot.serialize(ok.maxPromised, out, version);
|
||||
|
|
@ -181,6 +150,7 @@ public class CheckStatusSerializers
|
|||
CommandSerializers.durability.serialize(ok.durability, out, version);
|
||||
KeySerializers.nullableRoute.serialize(ok.route, out, version);
|
||||
KeySerializers.nullableRoutingKey.serialize(ok.homeKey, out, version);
|
||||
CommandSerializers.invalidIf.serialize(ok.invalidIf, out, version);
|
||||
|
||||
if (!(reply instanceof CheckStatusOkFull))
|
||||
return;
|
||||
|
|
@ -202,7 +172,7 @@ public class CheckStatusSerializers
|
|||
return CheckStatusNack.NotOwned;
|
||||
case OK:
|
||||
case FULL:
|
||||
FoundKnownMap map = foundKnownMap.deserialize(in, version);
|
||||
KnownMap map = knownMap.deserialize(in, version);
|
||||
SaveStatus maxKnowledgeStatus = CommandSerializers.saveStatus.deserialize(in, version);
|
||||
SaveStatus maxStatus = CommandSerializers.saveStatus.deserialize(in, version);
|
||||
Ballot maxPromised = CommandSerializers.ballot.deserialize(in, version);
|
||||
|
|
@ -213,10 +183,11 @@ public class CheckStatusSerializers
|
|||
Durability durability = CommandSerializers.durability.deserialize(in, version);
|
||||
Route<?> route = KeySerializers.nullableRoute.deserialize(in, version);
|
||||
RoutingKey homeKey = KeySerializers.nullableRoutingKey.deserialize(in, version);
|
||||
Infer.InvalidIf invalidIf = CommandSerializers.invalidIf.deserialize(in, version);
|
||||
|
||||
if (kind == OK)
|
||||
return createOk(map, maxKnowledgeStatus, maxStatus, maxPromised, maxAcceptedOrCommitted, acceptedOrCommitted, executeAt,
|
||||
isCoordinating, durability, route, homeKey);
|
||||
isCoordinating, durability, route, homeKey, invalidIf);
|
||||
|
||||
PartialTxn partialTxn = CommandSerializers.nullablePartialTxn.deserialize(in, version);
|
||||
PartialDeps committedDeps = DepsSerializer.nullablePartialDeps.deserialize(in, version);
|
||||
|
|
@ -227,7 +198,7 @@ public class CheckStatusSerializers
|
|||
result = CommandSerializers.APPLIED;
|
||||
|
||||
return createOk(map, maxKnowledgeStatus, maxStatus, maxPromised, maxAcceptedOrCommitted, acceptedOrCommitted, executeAt,
|
||||
isCoordinating, durability, route, homeKey, partialTxn, committedDeps, writes, result);
|
||||
isCoordinating, durability, route, homeKey, invalidIf, partialTxn, committedDeps, writes, result);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -240,7 +211,7 @@ public class CheckStatusSerializers
|
|||
return size;
|
||||
|
||||
CheckStatusOk ok = (CheckStatusOk) reply;
|
||||
size += foundKnownMap.serializedSize(ok.map, version);
|
||||
size += knownMap.serializedSize(ok.map, version);
|
||||
size += CommandSerializers.saveStatus.serializedSize(ok.maxKnowledgeSaveStatus, version);
|
||||
size += CommandSerializers.saveStatus.serializedSize(ok.maxSaveStatus, version);
|
||||
size += CommandSerializers.ballot.serializedSize(ok.maxPromised, version);
|
||||
|
|
@ -249,8 +220,9 @@ public class CheckStatusSerializers
|
|||
size += CommandSerializers.nullableTimestamp.serializedSize(ok.executeAt, version);
|
||||
size += TypeSizes.BOOL_SIZE;
|
||||
size += CommandSerializers.durability.serializedSize(ok.durability, version);
|
||||
size += KeySerializers.nullableRoutingKey.serializedSize(ok.homeKey, version);
|
||||
size += KeySerializers.nullableRoute.serializedSize(ok.route, version);
|
||||
size += KeySerializers.nullableRoutingKey.serializedSize(ok.homeKey, version);
|
||||
size += CommandSerializers.invalidIf.serializedSize(ok.invalidIf, version);
|
||||
|
||||
if (!(reply instanceof CheckStatusOkFull))
|
||||
return size;
|
||||
|
|
|
|||
|
|
@ -29,10 +29,18 @@ import accord.api.Result;
|
|||
import accord.api.Update;
|
||||
import accord.coordinate.Infer;
|
||||
import accord.local.Node;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.local.Status;
|
||||
import accord.local.Status.Durability;
|
||||
import accord.local.Status.Known;
|
||||
import accord.local.StoreParticipants;
|
||||
import accord.primitives.Known.Definition;
|
||||
import accord.primitives.Known.KnownDeps;
|
||||
import accord.primitives.Known.KnownExecuteAt;
|
||||
import accord.primitives.Known.KnownRoute;
|
||||
import accord.primitives.Known.Outcome;
|
||||
import accord.primitives.Participants;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.Status.Durability;
|
||||
import accord.primitives.Known;
|
||||
import accord.primitives.Ballot;
|
||||
import accord.primitives.PartialTxn;
|
||||
import accord.primitives.ProgressToken;
|
||||
|
|
@ -47,7 +55,6 @@ import org.apache.cassandra.io.IVersionedSerializer;
|
|||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.service.accord.serializers.IVersionedWithKeysSerializer.AbstractWithKeysSerializer;
|
||||
import org.apache.cassandra.service.accord.serializers.IVersionedWithKeysSerializer.NullableWithKeysSerializer;
|
||||
import org.apache.cassandra.service.accord.serializers.SmallEnumSerializer.NullableSmallEnumSerializer;
|
||||
import org.apache.cassandra.service.accord.txn.AccordUpdate;
|
||||
import org.apache.cassandra.service.accord.txn.TxnQuery;
|
||||
|
|
@ -71,11 +78,74 @@ public class CommandSerializers
|
|||
};
|
||||
|
||||
public static final TimestampSerializer<TxnId> txnId = new TimestampSerializer<>(TxnId::fromBits);
|
||||
public static final IVersionedSerializer<TxnId> nullableTxnId = NullableSerializer.wrap(txnId);
|
||||
public static final TimestampSerializer<Timestamp> timestamp = new TimestampSerializer<>(Timestamp::fromBits);
|
||||
public static final IVersionedSerializer<Timestamp> nullableTimestamp = NullableSerializer.wrap(timestamp);
|
||||
public static final TimestampSerializer<Ballot> ballot = new TimestampSerializer<>(Ballot::fromBits);
|
||||
public static final IVersionedSerializer<Ballot> nullableBallot = NullableSerializer.wrap(ballot);
|
||||
public static final EnumSerializer<Txn.Kind> kind = new EnumSerializer<>(Txn.Kind.class);
|
||||
public static final StoreParticipantsSerializer participants = new StoreParticipantsSerializer();
|
||||
|
||||
// TODO (expected): optimise using subset serializers (but be careful for range txns, e.g. some collections have differently sliced sub ranges)
|
||||
public static class StoreParticipantsSerializer implements IVersionedSerializer<StoreParticipants>
|
||||
{
|
||||
static final int HAS_ROUTE = 0x1;
|
||||
static final int HAS_TOUCHED_EQUALS_ROUTE = 0x2;
|
||||
static final int TOUCHES_EQUALS_HAS_TOUCHED = 0x4;
|
||||
static final int OWNS_EQUALS_TOUCHES = 0x8;
|
||||
@Override
|
||||
public void serialize(StoreParticipants t, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
boolean hasRoute = t.route() != null;
|
||||
boolean hasTouchedEqualsRoute = t.route() == t.hasTouched();
|
||||
boolean touchesEqualsHasTouched = t.touches() == t.hasTouched();
|
||||
boolean ownsEqualsTouches = t.owns() == t.touches();
|
||||
out.writeByte((hasRoute ? HAS_ROUTE : 0)
|
||||
| (hasTouchedEqualsRoute ? HAS_TOUCHED_EQUALS_ROUTE : 0)
|
||||
| (touchesEqualsHasTouched ? TOUCHES_EQUALS_HAS_TOUCHED : 0)
|
||||
| (ownsEqualsTouches ? OWNS_EQUALS_TOUCHES : 0)
|
||||
);
|
||||
if (hasRoute) KeySerializers.route.serialize(t.route(), out, version);
|
||||
if (!hasTouchedEqualsRoute) KeySerializers.participants.serialize(t.hasTouched(), out, version);
|
||||
if (!touchesEqualsHasTouched) KeySerializers.participants.serialize(t.touches(), out, version);
|
||||
if (!ownsEqualsTouches) KeySerializers.participants.serialize(t.owns(), out, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoreParticipants deserialize(DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
int flags = in.readByte();
|
||||
Route<?> route = 0 == (flags & HAS_ROUTE) ? null : KeySerializers.route.deserialize(in, version);
|
||||
Participants<?> hasTouched = 0 != (flags & HAS_TOUCHED_EQUALS_ROUTE) ? route : KeySerializers.participants.deserialize(in, version);
|
||||
Participants<?> touches = 0 != (flags & TOUCHES_EQUALS_HAS_TOUCHED) ? hasTouched : KeySerializers.participants.deserialize(in, version);
|
||||
Participants<?> owns = 0 != (flags & OWNS_EQUALS_TOUCHES) ? touches : KeySerializers.participants.deserialize(in, version);
|
||||
return StoreParticipants.SerializationSupport.create(route, owns, touches, hasTouched);
|
||||
}
|
||||
|
||||
public Route<?> deserializeRouteOnly(DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
int flags = in.readByte();
|
||||
if (0 == (flags & HAS_ROUTE))
|
||||
return null;
|
||||
|
||||
return KeySerializers.route.deserialize(in, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedSize(StoreParticipants t, int version)
|
||||
{
|
||||
boolean hasRoute = t.route() != null;
|
||||
boolean hasTouchedEqualsRoute = t.route() == t.hasTouched();
|
||||
boolean touchesEqualsHasTouched = t.touches() == t.hasTouched();
|
||||
boolean ownsEqualsTouches = t.owns() == t.touches();
|
||||
long size = 1;
|
||||
if (hasRoute) size += KeySerializers.route.serializedSize(t.route(), version);
|
||||
if (!hasTouchedEqualsRoute) size += KeySerializers.participants.serializedSize(t.hasTouched(), version);
|
||||
if (!touchesEqualsHasTouched) size += KeySerializers.participants.serializedSize(t.touches(), version);
|
||||
if (!ownsEqualsTouches) size += KeySerializers.participants.serializedSize(t.owns(), version);
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TimestampSerializer<T extends Timestamp> implements IVersionedSerializer<T>
|
||||
{
|
||||
|
|
@ -173,7 +243,7 @@ public class CommandSerializers
|
|||
}
|
||||
}
|
||||
|
||||
public static class PartialTxnSerializer extends AbstractWithKeysSerializer implements IVersionedWithKeysSerializer<Seekables<?, ?>, PartialTxn>
|
||||
public static class PartialTxnSerializer extends AbstractWithKeysSerializer implements IVersionedSerializer<PartialTxn>
|
||||
{
|
||||
private final IVersionedSerializer<Read> readSerializer;
|
||||
private final IVersionedSerializer<Query> querySerializer;
|
||||
|
|
@ -208,28 +278,6 @@ public class CommandSerializers
|
|||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Seekables<?, ?> superset, PartialTxn txn, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
serializeSubset(txn.keys(), superset, out);
|
||||
serializeWithoutKeys(txn, out, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PartialTxn deserialize(Seekables<?, ?> superset, DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
Seekables<?, ?> keys = deserializeSubset(superset, in);
|
||||
return deserializeWithoutKeys(keys, in, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedSize(Seekables<?, ?> superset, PartialTxn txn, int version)
|
||||
{
|
||||
long size = serializedSubsetSize(txn.keys(), superset);
|
||||
size += serializedSizeWithoutKeys(txn, version);
|
||||
return size;
|
||||
}
|
||||
|
||||
private void serializeWithoutKeys(PartialTxn txn, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
CommandSerializers.kind.serialize(txn.kind(), out, version);
|
||||
|
|
@ -249,7 +297,6 @@ public class CommandSerializers
|
|||
return new PartialTxn.InMemory(kind, keys, read, query, update);
|
||||
}
|
||||
|
||||
|
||||
private long serializedSizeWithoutKeys(PartialTxn txn, int version)
|
||||
{
|
||||
long size = CommandSerializers.kind.serializedSize(txn.kind(), version);
|
||||
|
|
@ -266,14 +313,14 @@ public class CommandSerializers
|
|||
private static final IVersionedSerializer<Query> query = new CastingSerializer<>(TxnQuery.class, TxnQuery.serializer);
|
||||
private static final IVersionedSerializer<Update> update = new CastingSerializer<>(AccordUpdate.class, AccordUpdate.serializer);
|
||||
|
||||
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 IVersionedSerializer<PartialTxn> partialTxn = new PartialTxnSerializer(read, query, update);
|
||||
public static final IVersionedSerializer<PartialTxn> nullablePartialTxn = NullableSerializer.wrap(partialTxn);
|
||||
|
||||
public static final EnumSerializer<SaveStatus> saveStatus = new EnumSerializer<>(SaveStatus.class);
|
||||
public static final EnumSerializer<Status> status = new EnumSerializer<>(Status.class);
|
||||
public static final EnumSerializer<Durability> durability = new EnumSerializer<>(Durability.class);
|
||||
|
||||
public static final IVersionedSerializer<Writes> writes = new IVersionedSerializer<Writes>()
|
||||
public static final IVersionedSerializer<Writes> writes = new IVersionedSerializer<>()
|
||||
{
|
||||
@Override
|
||||
public void serialize(Writes writes, DataOutputPlus out, int version) throws IOException
|
||||
|
|
@ -311,14 +358,13 @@ public class CommandSerializers
|
|||
|
||||
public static final IVersionedSerializer<Writes> nullableWrites = NullableSerializer.wrap(writes);
|
||||
|
||||
public static final SmallEnumSerializer<Status.KnownRoute> knownRoute = new SmallEnumSerializer<>(Status.KnownRoute.class);
|
||||
public static final SmallEnumSerializer<Status.Definition> definition = new SmallEnumSerializer<>(Status.Definition.class);
|
||||
public static final SmallEnumSerializer<Status.KnownExecuteAt> knownExecuteAt = new SmallEnumSerializer<>(Status.KnownExecuteAt.class);
|
||||
public static final SmallEnumSerializer<Status.KnownDeps> knownDeps = new SmallEnumSerializer<>(Status.KnownDeps.class);
|
||||
public static final NullableSmallEnumSerializer<Status.KnownDeps> nullableKnownDeps = new NullableSmallEnumSerializer<>(knownDeps);
|
||||
public static final SmallEnumSerializer<Status.Outcome> outcome = new SmallEnumSerializer<>(Status.Outcome.class);
|
||||
public static final SmallEnumSerializer<Infer.InvalidIfNot> invalidIfNot = new SmallEnumSerializer<>(Infer.InvalidIfNot.class);
|
||||
public static final SmallEnumSerializer<Infer.IsPreempted> isPreempted = new SmallEnumSerializer<>(Infer.IsPreempted.class);
|
||||
public static final SmallEnumSerializer<KnownRoute> knownRoute = new SmallEnumSerializer<>(KnownRoute.class);
|
||||
public static final SmallEnumSerializer<Definition> definition = new SmallEnumSerializer<>(Definition.class);
|
||||
public static final SmallEnumSerializer<KnownExecuteAt> knownExecuteAt = new SmallEnumSerializer<>(KnownExecuteAt.class);
|
||||
public static final SmallEnumSerializer<KnownDeps> knownDeps = new SmallEnumSerializer<>(KnownDeps.class);
|
||||
public static final NullableSmallEnumSerializer<KnownDeps> nullableKnownDeps = new NullableSmallEnumSerializer<>(knownDeps);
|
||||
public static final SmallEnumSerializer<Outcome> outcome = new SmallEnumSerializer<>(Outcome.class);
|
||||
public static final SmallEnumSerializer<Infer.InvalidIf> invalidIf = new SmallEnumSerializer<>(Infer.InvalidIf.class);
|
||||
|
||||
public static final IVersionedSerializer<Known> known = new IVersionedSerializer<>()
|
||||
{
|
||||
|
|
@ -352,4 +398,6 @@ public class CommandSerializers
|
|||
+ outcome.serializedSize(known.outcome, version);
|
||||
}
|
||||
};
|
||||
|
||||
public static final IVersionedSerializer<Known> nullableKnown = NullableSerializer.wrap(known);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import java.util.function.IntFunction;
|
|||
import accord.api.RoutingKey;
|
||||
import accord.local.DurableBefore;
|
||||
import accord.local.RedundantBefore;
|
||||
import accord.local.RejectBefore;
|
||||
import accord.primitives.Range;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.Timestamp;
|
||||
|
|
@ -61,29 +62,31 @@ public class CommandStoreSerializers
|
|||
public void serialize(R map, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
out.writeBoolean(map.inclusiveEnds());
|
||||
int size = map.size();
|
||||
out.writeUnsignedVInt32(size);
|
||||
int mapSize = map.size();
|
||||
out.writeUnsignedVInt32(mapSize);
|
||||
|
||||
for (int i=0; i<size; i++)
|
||||
for (int i=0; i<mapSize; i++)
|
||||
{
|
||||
KeySerializers.routingKey.serialize(map.startAt(i), out, version);
|
||||
valueSerializer.serialize(map.valueAt(i), out, version);
|
||||
}
|
||||
KeySerializers.routingKey.serialize(map.startAt(size), out, version);
|
||||
if (mapSize > 0)
|
||||
KeySerializers.routingKey.serialize(map.startAt(mapSize), out, version);
|
||||
}
|
||||
|
||||
public R deserialize(DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
boolean inclusiveEnds = in.readBoolean();
|
||||
int size = in.readUnsignedVInt32();
|
||||
RoutingKey[] keys = new RoutingKey[size + 1];
|
||||
T[] values = newValueArray.apply(size);
|
||||
for (int i=0; i<size; i++)
|
||||
int mapSize = in.readUnsignedVInt32();
|
||||
RoutingKey[] keys = new RoutingKey[mapSize + 1];
|
||||
T[] values = newValueArray.apply(mapSize);
|
||||
for (int i=0; i<mapSize; i++)
|
||||
{
|
||||
keys[i] = KeySerializers.routingKey.deserialize(in, version);
|
||||
values[i] = valueSerializer.deserialize(in, version);
|
||||
}
|
||||
keys[size] = KeySerializers.routingKey.deserialize(in, version);
|
||||
if (mapSize > 0)
|
||||
keys[mapSize] = KeySerializers.routingKey.deserialize(in, version);
|
||||
return constructor.apply(inclusiveEnds, keys, values);
|
||||
}
|
||||
|
||||
|
|
@ -97,14 +100,15 @@ public class CommandStoreSerializers
|
|||
size += KeySerializers.routingKey.serializedSize(map.startAt(i), version);
|
||||
size += valueSerializer.serializedSize(map.valueAt(i), version);
|
||||
}
|
||||
size += KeySerializers.routingKey.serializedSize(map.startAt(mapSize), version);
|
||||
if (mapSize > 0)
|
||||
size += KeySerializers.routingKey.serializedSize(map.startAt(mapSize), version);
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
public static IVersionedSerializer<ReducingRangeMap<Timestamp>> rejectBefore = new ReducingRangeMapSerializer<>(CommandSerializers.nullableTimestamp, Timestamp[]::new, ReducingRangeMap.SerializerSupport::create);
|
||||
public static IVersionedSerializer<DurableBefore> durableBefore = new ReducingRangeMapSerializer<>(NullableSerializer.wrap(new IVersionedSerializer<DurableBefore.Entry>()
|
||||
public static IVersionedSerializer<RejectBefore> rejectBefore = new ReducingRangeMapSerializer<>(CommandSerializers.nullableTxnId, TxnId[]::new, RejectBefore.SerializerSupport::create);
|
||||
public static IVersionedSerializer<DurableBefore> durableBefore = new ReducingRangeMapSerializer<>(NullableSerializer.wrap(new IVersionedSerializer<>()
|
||||
{
|
||||
@Override
|
||||
public void serialize(DurableBefore.Entry t, DataOutputPlus out, int version) throws IOException
|
||||
|
|
@ -135,12 +139,15 @@ public class CommandStoreSerializers
|
|||
public void serialize(RedundantBefore.Entry t, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
TokenRange.serializer.serialize((TokenRange) t.range, out, version);
|
||||
Invariants.checkState(t.startEpoch <= t.endEpoch);
|
||||
out.writeUnsignedVInt(t.startEpoch);
|
||||
if (t.endEpoch == Long.MAX_VALUE) out.writeUnsignedVInt(0L);
|
||||
else out.writeUnsignedVInt(1 + t.endEpoch - t.startEpoch);
|
||||
Invariants.checkState(t.startOwnershipEpoch <= t.endOwnershipEpoch);
|
||||
out.writeUnsignedVInt(t.startOwnershipEpoch);
|
||||
if (t.endOwnershipEpoch == Long.MAX_VALUE) out.writeUnsignedVInt(0L);
|
||||
else out.writeUnsignedVInt(1 + t.endOwnershipEpoch - t.startOwnershipEpoch);
|
||||
CommandSerializers.txnId.serialize(t.locallyAppliedOrInvalidatedBefore, out, version);
|
||||
CommandSerializers.txnId.serialize(t.locallyDecidedAndAppliedOrInvalidatedBefore, out, version);
|
||||
CommandSerializers.txnId.serialize(t.shardAppliedOrInvalidatedBefore, out, version);
|
||||
CommandSerializers.txnId.serialize(t.shardOnlyAppliedOrInvalidatedBefore, out, version);
|
||||
CommandSerializers.txnId.serialize(t.gcBefore, out, version);
|
||||
CommandSerializers.txnId.serialize(t.bootstrappedAt, out, version);
|
||||
CommandSerializers.nullableTimestamp.serialize(t.staleUntilAtLeast, out, version);
|
||||
}
|
||||
|
|
@ -154,20 +161,26 @@ public class CommandStoreSerializers
|
|||
if (endEpoch == 0) endEpoch = Long.MAX_VALUE;
|
||||
else endEpoch = endEpoch - 1 + startEpoch;
|
||||
TxnId locallyAppliedOrInvalidatedBefore = CommandSerializers.txnId.deserialize(in, version);
|
||||
TxnId locallyDecidedAndAppliedOrInvalidatedBefore = CommandSerializers.txnId.deserialize(in, version);
|
||||
TxnId shardAppliedOrInvalidatedBefore = CommandSerializers.txnId.deserialize(in, version);
|
||||
TxnId shardOnlyAppliedOrInvalidatedBefore = CommandSerializers.txnId.deserialize(in, version);
|
||||
TxnId gcBefore = CommandSerializers.txnId.deserialize(in, version);
|
||||
TxnId bootstrappedAt = CommandSerializers.txnId.deserialize(in, version);
|
||||
Timestamp staleUntilAtLeast = CommandSerializers.nullableTimestamp.deserialize(in, version);
|
||||
return new RedundantBefore.Entry(range, startEpoch, endEpoch, locallyAppliedOrInvalidatedBefore, shardAppliedOrInvalidatedBefore, bootstrappedAt, staleUntilAtLeast);
|
||||
return new RedundantBefore.Entry(range, startEpoch, endEpoch, locallyAppliedOrInvalidatedBefore, locallyDecidedAndAppliedOrInvalidatedBefore, shardAppliedOrInvalidatedBefore, shardOnlyAppliedOrInvalidatedBefore, gcBefore, bootstrappedAt, staleUntilAtLeast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedSize(RedundantBefore.Entry t, int version)
|
||||
{
|
||||
long size = TokenRange.serializer.serializedSize((TokenRange) t.range, version);
|
||||
size += TypeSizes.sizeofUnsignedVInt(t.startEpoch);
|
||||
size += TypeSizes.sizeofUnsignedVInt(t.endEpoch == Long.MAX_VALUE ? 0 : 1 + t.endEpoch - t.startEpoch);
|
||||
size += TypeSizes.sizeofUnsignedVInt(t.startOwnershipEpoch);
|
||||
size += TypeSizes.sizeofUnsignedVInt(t.endOwnershipEpoch == Long.MAX_VALUE ? 0 : 1 + t.endOwnershipEpoch - t.startOwnershipEpoch);
|
||||
size += CommandSerializers.txnId.serializedSize(t.locallyAppliedOrInvalidatedBefore, version);
|
||||
size += CommandSerializers.txnId.serializedSize(t.locallyDecidedAndAppliedOrInvalidatedBefore, version);
|
||||
size += CommandSerializers.txnId.serializedSize(t.shardAppliedOrInvalidatedBefore, version);
|
||||
size += CommandSerializers.txnId.serializedSize(t.shardOnlyAppliedOrInvalidatedBefore, version);
|
||||
size += CommandSerializers.txnId.serializedSize(t.gcBefore, version);
|
||||
size += CommandSerializers.txnId.serializedSize(t.bootstrappedAt, version);
|
||||
size += CommandSerializers.nullableTimestamp.serializedSize(t.staleUntilAtLeast, version);
|
||||
return size;
|
||||
|
|
|
|||
|
|
@ -21,9 +21,12 @@ package org.apache.cassandra.service.accord.serializers;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
import accord.api.Key;
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.RedundantBefore;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.local.cfk.CommandsForKey.TxnInfo;
|
||||
import accord.local.cfk.CommandsForKey.InternalStatus;
|
||||
|
|
@ -40,18 +43,16 @@ import org.apache.cassandra.db.TypeSizes;
|
|||
import org.apache.cassandra.db.marshal.ByteBufferAccessor;
|
||||
import org.apache.cassandra.utils.vint.VIntCoding;
|
||||
|
||||
import static accord.local.cfk.CommandsForKey.NO_BOUNDS_INFO;
|
||||
import static accord.local.cfk.CommandsForKey.NO_PENDING_UNMANAGED;
|
||||
import static accord.primitives.Txn.Kind.ExclusiveSyncPoint;
|
||||
import static accord.primitives.TxnId.NO_TXNIDS;
|
||||
import static accord.primitives.Txn.Kind.Read;
|
||||
import static accord.primitives.Txn.Kind.SyncPoint;
|
||||
import static accord.primitives.Txn.Kind.Write;
|
||||
import static accord.utils.ArrayBuffers.cachedInts;
|
||||
import static accord.utils.ArrayBuffers.cachedTxnIds;
|
||||
import static org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer.TxnIdFlags.EXTENDED;
|
||||
import static org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer.TxnIdFlags.EXTENDED_BITS;
|
||||
import static org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer.TxnIdFlags.RAW;
|
||||
import static org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer.TxnIdFlags.RAW_BITS;
|
||||
import static org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer.TxnIdFlags.STANDARD;
|
||||
import static org.apache.cassandra.utils.ByteBufferUtil.readLeastSignificantBytes;
|
||||
import static org.apache.cassandra.utils.ByteBufferUtil.writeLeastSignificantBytes;
|
||||
import static org.apache.cassandra.utils.ByteBufferUtil.writeMostSignificantBytes;
|
||||
|
|
@ -63,7 +64,8 @@ public class CommandsForKeySerializer
|
|||
private static final int HAS_MISSING_DEPS_HEADER_BIT = 0x1;
|
||||
private static final int HAS_EXECUTE_AT_HEADER_BIT = 0x2;
|
||||
private static final int HAS_BALLOT_HEADER_BIT = 0x4;
|
||||
private static final int HAS_NON_STANDARD_FLAGS = 0x8;
|
||||
private static final int HAS_STATUS_OVERRIDES = 0x8;
|
||||
private static final int HAS_NON_STANDARD_FLAGS = 0x10;
|
||||
|
||||
/**
|
||||
* We read/write a fixed number of intial bytes for each command, with an initial flexible number of flag bits
|
||||
|
|
@ -77,8 +79,9 @@ public class CommandsForKeySerializer
|
|||
* bit 0 is set if there are any missing ids;
|
||||
* bit 1 is set if there are any executeAt specified
|
||||
* bit 2 is set if there are any ballots specified
|
||||
* bit 3 is set if there are any queries present besides reads/writes
|
||||
* bits 4-5 number of header bytes to read for each command
|
||||
* bit 3 is set if there are any non-standard TxnId.Kind present
|
||||
* bit 4 is set if there are any queries with override flags
|
||||
* bits 6-7 number of header bytes to read for each command
|
||||
* bits 8-9: level 0 extra hlc bytes to read
|
||||
* bits 10-11: level 1 extra hlc bytes to read (+ 1 + level 0)
|
||||
* bits 12-13: level 2 extra hlc bytes to read (+ 1 + level 1)
|
||||
|
|
@ -86,9 +89,10 @@ public class CommandsForKeySerializer
|
|||
*
|
||||
* In order, for each command, we consume:
|
||||
* 3 bits for the InternalStatus of the command
|
||||
* 1 optional bit: if any command has override flags; 2 bits more to read if this bit is set
|
||||
* 1 optional bit: if the status encodes an executeAt, indicating if the executeAt is not the TxnId
|
||||
* 1 optional bit: if the status encodes any dependencies and there are non-zero missing ids, indicating if there are any missing for this command
|
||||
* 1 or 2 bits for the kind of the TxnId: 0=key read, 1=key write, 2=exclusive sync point,3=read 16 bits
|
||||
* 2 or 3 bits for the kind of the TxnId
|
||||
* 1 bit encoding if the epoch has changed
|
||||
* 2 optional bits: if the prior bit is set, indicating how many bits should be read for the epoch increment: 0=none (increment by 1); 1=4, 2=8, 3=32
|
||||
* 4 option bits: if prior bits=01, epoch delta
|
||||
|
|
@ -124,7 +128,7 @@ public class CommandsForKeySerializer
|
|||
// whether we have any missing transactions to encode, any executeAt that are not equal to their TxnId
|
||||
// and whether there are any non-standard flag bits to encode
|
||||
boolean hasNonStandardFlags = false;
|
||||
int nodeIdCount, missingIdCount = 0, executeAtCount = 0, ballotCount = 0;
|
||||
int nodeIdCount, missingIdCount = 0, executeAtCount = 0, ballotCount = 0, overrideCount = 0;
|
||||
int bitsPerExecuteAtEpoch = 0, bitsPerExecuteAtFlags = 0, bitsPerExecuteAtHlc = 1; // to permit us to use full 64 bits and encode in 5 bits we force at least one hlc bit
|
||||
{
|
||||
nodeIds[0] = cfk.redundantBefore().node.id;
|
||||
|
|
@ -139,13 +143,13 @@ public class CommandsForKeySerializer
|
|||
}
|
||||
|
||||
TxnInfo txn = cfk.get(i);
|
||||
|
||||
hasNonStandardFlags |= txnIdFlags(txn) != STANDARD;
|
||||
overrideCount += txn.statusOverrides() > 0 ? 1 : 0;
|
||||
hasNonStandardFlags |= hasNonStandardFlags(txn);
|
||||
nodeIds[nodeIdCount++] = txn.node.id;
|
||||
|
||||
if (txn.executeAt != txn)
|
||||
{
|
||||
Invariants.checkState(txn.status.hasExecuteAtOrDeps);
|
||||
Invariants.checkState(txn.status().hasExecuteAtOrDeps);
|
||||
nodeIds[nodeIdCount++] = txn.executeAt.node.id;
|
||||
bitsPerExecuteAtEpoch = Math.max(bitsPerExecuteAtEpoch, numberOfBitsToRepresent(txn.executeAt.epoch() - txn.epoch()));
|
||||
bitsPerExecuteAtHlc = Math.max(bitsPerExecuteAtHlc, numberOfBitsToRepresent(txn.executeAt.hlc() - txn.hlc()));
|
||||
|
|
@ -159,7 +163,7 @@ public class CommandsForKeySerializer
|
|||
missingIdCount += extra.missing.length;
|
||||
if (extra.ballot != Ballot.ZERO)
|
||||
{
|
||||
Invariants.checkArgument(txn.status.hasBallot);
|
||||
Invariants.checkArgument(txn.status().hasBallot);
|
||||
nodeIds[nodeIdCount++] = extra.ballot.node.id;
|
||||
ballotCount += 1;
|
||||
}
|
||||
|
|
@ -172,7 +176,7 @@ public class CommandsForKeySerializer
|
|||
// We can now use this information to calculate the fixed header size, compute the amount
|
||||
// of additional space we'll need to store the TxnId and its basic info
|
||||
int bitsPerNodeId = numberOfBitsToRepresent(nodeIdCount);
|
||||
int minHeaderBits = 7 + bitsPerNodeId + (hasNonStandardFlags ? 1 : 0);
|
||||
int minHeaderBits = 8 + bitsPerNodeId + (hasNonStandardFlags ? 1 : 0) + (overrideCount > 0 ? 1 : 0);
|
||||
int infoHeaderBits = (executeAtCount > 0 ? 1 : 0) + (missingIdCount > 0 ? 1 : 0);
|
||||
int ballotHeaderBits = (ballotCount > 0 ? 1 : 0);
|
||||
int maxHeaderBits = minHeaderBits;
|
||||
|
|
@ -216,14 +220,16 @@ public class CommandsForKeySerializer
|
|||
prevHlc = hlc;
|
||||
}
|
||||
|
||||
if (hasNonStandardFlags && txnIdFlags(txnId) == RAW)
|
||||
if (txnIdFlagsBits(txnId, hasNonStandardFlags) == RAW_BITS)
|
||||
totalBytes += 2;
|
||||
|
||||
TxnInfo info = cfk.get(i);
|
||||
if (info.status.hasExecuteAtOrDeps)
|
||||
if (info.status().hasExecuteAtOrDeps)
|
||||
headerBits += infoHeaderBits;
|
||||
if (info.status.hasBallot)
|
||||
if (info.status().hasBallot)
|
||||
headerBits += ballotHeaderBits;
|
||||
if (info.statusOverrides() != 0)
|
||||
headerBits += 2;
|
||||
maxHeaderBits = Math.max(headerBits, maxHeaderBits);
|
||||
int basicBytes = (headerBits + payloadBits + 7)/8;
|
||||
bytesHistogram[basicBytes]++;
|
||||
|
|
@ -242,10 +248,11 @@ public class CommandsForKeySerializer
|
|||
int flags = (missingIdCount > 0 ? HAS_MISSING_DEPS_HEADER_BIT : 0)
|
||||
| (executeAtCount > 0 ? HAS_EXECUTE_AT_HEADER_BIT : 0)
|
||||
| (ballotCount > 0 ? HAS_BALLOT_HEADER_BIT : 0)
|
||||
| (hasNonStandardFlags ? HAS_NON_STANDARD_FLAGS : 0);
|
||||
| (hasNonStandardFlags ? HAS_NON_STANDARD_FLAGS : 0)
|
||||
| (overrideCount > 0 ? HAS_STATUS_OVERRIDES : 0);
|
||||
|
||||
int headerBytes = (maxHeaderBits+7)/8;
|
||||
flags |= Invariants.checkArgument(headerBytes - 1, headerBytes <= 4) << 4;
|
||||
flags |= Invariants.checkArgument(headerBytes - 1, headerBytes <= 4) << 6;
|
||||
|
||||
int hlcBytesLookup;
|
||||
{ // 2bits per size, first value may be zero and remainder may be increments of 1-4;
|
||||
|
|
@ -281,7 +288,14 @@ public class CommandsForKeySerializer
|
|||
|
||||
prevEpoch = cfk.redundantBefore().epoch();
|
||||
prevHlc = cfk.redundantBefore().hlc();
|
||||
totalBytes += TypeSizes.sizeofUnsignedVInt(prevEpoch);
|
||||
{
|
||||
RedundantBefore.Entry boundsInfo = cfk.boundsInfo();
|
||||
long start = boundsInfo.startOwnershipEpoch;
|
||||
long end = boundsInfo.endOwnershipEpoch;
|
||||
totalBytes += VIntCoding.computeUnsignedVIntSize(start);
|
||||
totalBytes += VIntCoding.computeUnsignedVIntSize(end == Long.MAX_VALUE ? 0 : (1 + end - start));
|
||||
totalBytes += VIntCoding.computeVIntSize(prevEpoch - start);
|
||||
}
|
||||
totalBytes += TypeSizes.sizeofUnsignedVInt(prevHlc);
|
||||
totalBytes += TypeSizes.sizeofUnsignedVInt(cfk.redundantBefore().flags());
|
||||
totalBytes += TypeSizes.sizeofUnsignedVInt(Arrays.binarySearch(nodeIds, 0, nodeIdCount, cfk.redundantBefore().node.id));
|
||||
|
|
@ -297,7 +311,7 @@ public class CommandsForKeySerializer
|
|||
{
|
||||
TxnInfo txn = cfk.get(i);
|
||||
if (txn.getClass() != TxnInfoExtra.class) continue;
|
||||
if (!txn.status.hasBallot) continue;
|
||||
if (!txn.status().hasBallot) continue;
|
||||
TxnInfoExtra extra = (TxnInfoExtra) txn;
|
||||
if (extra.ballot == Ballot.ZERO) continue;
|
||||
if (prevBallot != null)
|
||||
|
|
@ -353,7 +367,14 @@ public class CommandsForKeySerializer
|
|||
out.putShort((short)flags);
|
||||
|
||||
|
||||
VIntCoding.writeUnsignedVInt(prevEpoch, out);
|
||||
{
|
||||
RedundantBefore.Entry boundsInfo = cfk.boundsInfo();
|
||||
long start = boundsInfo.startOwnershipEpoch;
|
||||
long end = boundsInfo.endOwnershipEpoch;
|
||||
VIntCoding.writeUnsignedVInt(start, out);
|
||||
VIntCoding.writeUnsignedVInt(end == Long.MAX_VALUE ? 0 : (1 + end - start), out);
|
||||
VIntCoding.writeVInt(prevEpoch - start, out);
|
||||
}
|
||||
VIntCoding.writeUnsignedVInt(prevHlc, out);
|
||||
VIntCoding.writeUnsignedVInt32(cfk.redundantBefore().flags(), out);
|
||||
VIntCoding.writeUnsignedVInt32(Arrays.binarySearch(nodeIds, 0, nodeIdCount, cfk.redundantBefore().node.id), out);
|
||||
|
|
@ -362,32 +383,37 @@ public class CommandsForKeySerializer
|
|||
int executeAtMask = executeAtCount > 0 ? 1 : 0;
|
||||
int missingDepsMask = missingIdCount > 0 ? 1 : 0;
|
||||
int ballotMask = ballotCount > 0 ? 1 : 0;
|
||||
int flagsIncrement = hasNonStandardFlags ? 2 : 1;
|
||||
int noOverrideIncrement = overrideCount > 0 ? 1 : 0;
|
||||
int flagsIncrement = hasNonStandardFlags ? 3 : 2;
|
||||
// TODO (desired): check this loop compiles correctly to only branch on epoch case, for binarySearch and flushing
|
||||
for (int i = 0 ; i < commandCount ; ++i)
|
||||
{
|
||||
TxnId txnId = cfk.txnId(i);
|
||||
TxnInfo info = cfk.get(i);
|
||||
InternalStatus status = info.status;
|
||||
TxnInfo txn = cfk.get(i);
|
||||
InternalStatus status = txn.status();
|
||||
|
||||
long bits = status.ordinal();
|
||||
int bitIndex = 3;
|
||||
|
||||
int statusHasInfo = status.hasExecuteAtOrDeps ? 1 : 0;
|
||||
int statusHasBallot = status.hasBallot ? 1 : 0;
|
||||
long hasExecuteAt = info.executeAt != txnId ? 1 : 0;
|
||||
long hasExecuteAt = txn.executeAt != txn ? 1 : 0;
|
||||
bits |= hasExecuteAt << bitIndex;
|
||||
bitIndex += statusHasInfo & executeAtMask;
|
||||
|
||||
long hasMissingIds = info.getClass() == TxnInfoExtra.class && ((TxnInfoExtra)info).missing != NO_TXNIDS ? 1 : 0;
|
||||
long hasMissingIds = txn.getClass() == TxnInfoExtra.class && ((TxnInfoExtra)txn).missing != NO_TXNIDS ? 1 : 0;
|
||||
bits |= hasMissingIds << bitIndex;
|
||||
bitIndex += statusHasInfo & missingDepsMask;
|
||||
|
||||
long hasBallot = info.getClass() == TxnInfoExtra.class && ((TxnInfoExtra)info).ballot != Ballot.ZERO ? 1 : 0;
|
||||
long hasBallot = txn.getClass() == TxnInfoExtra.class && ((TxnInfoExtra)txn).ballot != Ballot.ZERO ? 1 : 0;
|
||||
bits |= hasBallot << bitIndex;
|
||||
bitIndex += statusHasBallot & ballotMask;
|
||||
|
||||
long flagBits = txnIdFlagsBits(txnId);
|
||||
long statusOverrides = (long) txn.statusOverrides() << 1;
|
||||
statusOverrides |= statusOverrides != 0 ? 1 : 0;
|
||||
bits |= statusOverrides << bitIndex;
|
||||
bitIndex += statusOverrides != 0 ? 3 : noOverrideIncrement;
|
||||
|
||||
long flagBits = txnIdFlagsBits(txn, hasNonStandardFlags);
|
||||
boolean writeFullFlags = flagBits == RAW_BITS;
|
||||
bits |= flagBits << bitIndex;
|
||||
bitIndex += flagsIncrement;
|
||||
|
|
@ -395,9 +421,9 @@ public class CommandsForKeySerializer
|
|||
long hlcBits;
|
||||
int extraEpochDeltaBytes = 0;
|
||||
{
|
||||
long epoch = txnId.epoch();
|
||||
long epoch = txn.epoch();
|
||||
long delta = epoch - prevEpoch;
|
||||
long hlc = txnId.hlc();
|
||||
long hlc = txn.hlc();
|
||||
hlcBits = hlc - prevHlc;
|
||||
if (delta == 0)
|
||||
{
|
||||
|
|
@ -432,7 +458,7 @@ public class CommandsForKeySerializer
|
|||
prevHlc = hlc;
|
||||
}
|
||||
|
||||
bits |= ((long)Arrays.binarySearch(nodeIds, 0, nodeIdCount, txnId.node.id)) << bitIndex;
|
||||
bits |= ((long)Arrays.binarySearch(nodeIds, 0, nodeIdCount, txn.node.id)) << bitIndex;
|
||||
bitIndex += bitsPerNodeId;
|
||||
|
||||
bits |= hlcBits << (bitIndex + 2);
|
||||
|
|
@ -444,7 +470,7 @@ public class CommandsForKeySerializer
|
|||
writeLeastSignificantBytes(hlcBits, getHlcBytes(hlcBytesLookup, hlcFlag), out);
|
||||
|
||||
if (writeFullFlags)
|
||||
out.putShort((short)txnId.flags());
|
||||
out.putShort((short)txn.flags());
|
||||
|
||||
if (extraEpochDeltaBytes > 0)
|
||||
{
|
||||
|
|
@ -608,7 +634,7 @@ public class CommandsForKeySerializer
|
|||
}
|
||||
}
|
||||
|
||||
public static CommandsForKey fromBytes(Key key, ByteBuffer in)
|
||||
public static CommandsForKey fromBytes(RoutingKey key, ByteBuffer in)
|
||||
{
|
||||
if (!in.hasRemaining())
|
||||
return null;
|
||||
|
|
@ -632,19 +658,26 @@ public class CommandsForKeySerializer
|
|||
nodeIds[i] = new Node.Id(prev += VIntCoding.readUnsignedVInt32(in));
|
||||
}
|
||||
|
||||
int missingDepsMasks, executeAtMasks, ballotMasks, txnIdFlagsMask;
|
||||
int missingDepsMasks, executeAtMasks, ballotMasks, txnIdFlagsMask, overrideMask;
|
||||
int headerByteCount, hlcBytesLookup;
|
||||
{
|
||||
int flags = in.getShort();
|
||||
missingDepsMasks = 0 != (flags & HAS_MISSING_DEPS_HEADER_BIT) ? 1 : 0;
|
||||
executeAtMasks = 0 != (flags & HAS_EXECUTE_AT_HEADER_BIT) ? 1 : 0;
|
||||
ballotMasks = 0 != (flags & HAS_BALLOT_HEADER_BIT) ? 1 : 0;
|
||||
txnIdFlagsMask = 0 != (flags & HAS_NON_STANDARD_FLAGS) ? 3 : 1;
|
||||
headerByteCount = 1 + ((flags >>> 4) & 0x3);
|
||||
overrideMask = 0 != (flags & HAS_STATUS_OVERRIDES) ? 1 : 0;
|
||||
txnIdFlagsMask = 0 != (flags & HAS_NON_STANDARD_FLAGS) ? 7 : 3;
|
||||
headerByteCount = 1 + ((flags >>> 6) & 0x3);
|
||||
hlcBytesLookup = setHlcByteDeltas((flags >>> 8) & 0x3, (flags >>> 10) & 0x3, (flags >>> 12) & 0x3, (flags >>> 14) & 0x3);
|
||||
}
|
||||
|
||||
long prevEpoch = VIntCoding.readUnsignedVInt(in);
|
||||
long minEpoch = VIntCoding.readUnsignedVInt(in);
|
||||
long maxEpoch; {
|
||||
long offset = VIntCoding.readUnsignedVInt(in);
|
||||
maxEpoch = offset == 0 ? Long.MAX_VALUE : minEpoch + offset - 1;
|
||||
}
|
||||
RedundantBefore.Entry boundsInfo = NO_BOUNDS_INFO.withEpochs(minEpoch, maxEpoch);
|
||||
long prevEpoch = minEpoch + VIntCoding.readVInt(in);
|
||||
long prevHlc = VIntCoding.readUnsignedVInt(in);
|
||||
TxnId redundantBefore;
|
||||
{
|
||||
|
|
@ -661,7 +694,7 @@ public class CommandsForKeySerializer
|
|||
int commandDecodeFlags = (int)(header & 0x7);
|
||||
InternalStatus status = InternalStatus.get(commandDecodeFlags);
|
||||
header >>>= 3;
|
||||
commandDecodeFlags <<= 3;
|
||||
commandDecodeFlags <<= 6;
|
||||
|
||||
{
|
||||
int infoMask = status.hasExecuteAtOrDeps ? 1 : 0;
|
||||
|
|
@ -669,15 +702,21 @@ public class CommandsForKeySerializer
|
|||
int missingDepsMask = infoMask & missingDepsMasks;
|
||||
commandDecodeFlags |= ((int)header & executeAtMask) << 1;
|
||||
header >>>= executeAtMask;
|
||||
commandDecodeFlags |= (int)header & missingDepsMask;
|
||||
commandDecodeFlags |= ((int)header & missingDepsMask);
|
||||
header >>>= missingDepsMask;
|
||||
int ballotMask = status.hasBallot ? ballotMasks : 0;
|
||||
commandDecodeFlags |= ((int)header & ballotMask) << 2;
|
||||
header >>>= ballotMask;
|
||||
commandDecodeFlags |= (header & 0x7) << 3;
|
||||
header >>= (header & overrideMask) == 0 ? overrideMask : 3;
|
||||
decodeFlags[i] = commandDecodeFlags;
|
||||
}
|
||||
|
||||
Txn.Kind kind = TXN_ID_FLAG_BITS_KIND_LOOKUP[((int)header & txnIdFlagsMask)];
|
||||
Txn.Kind kind; Domain domain; {
|
||||
int flags = (int)header & txnIdFlagsMask;
|
||||
kind = kindLookup(flags);
|
||||
domain = domainLookup(flags);
|
||||
}
|
||||
header >>>= Integer.bitCount(txnIdFlagsMask);
|
||||
|
||||
boolean hlcIsNegative = false;
|
||||
|
|
@ -725,7 +764,7 @@ public class CommandsForKeySerializer
|
|||
if (readEpochBytes > 0)
|
||||
epoch += readEpochBytes == 1 ? (in.get() & 0xff) : in.getInt();
|
||||
|
||||
txnIds[i] = kind != null ? new TxnId(epoch, hlc, kind, Domain.Key, node)
|
||||
txnIds[i] = kind != null ? new TxnId(epoch, hlc, kind, domain, node)
|
||||
: TxnId.fromValues(epoch, hlc, flags, node);
|
||||
|
||||
prevEpoch = epoch;
|
||||
|
|
@ -880,7 +919,9 @@ public class CommandsForKeySerializer
|
|||
prevBallot = ballot;
|
||||
}
|
||||
|
||||
txns[i] = TxnInfo.create(txnId, InternalStatus.get(commandDecodeFlags >>> 3), executeAt, missing, ballot);
|
||||
InternalStatus status = InternalStatus.get(commandDecodeFlags >>> 6);
|
||||
int statusOverrides = ((commandDecodeFlags >>> 3) & overrideMask) == 0 ? 0 : commandDecodeFlags >>> 4;
|
||||
txns[i] = create(boundsInfo, txnId, status, statusOverrides, executeAt, missing, ballot);
|
||||
}
|
||||
|
||||
cachedTxnIds().forceDiscard(missingIdBuffer, maxIdBufferCount);
|
||||
|
|
@ -888,13 +929,25 @@ public class CommandsForKeySerializer
|
|||
else
|
||||
{
|
||||
for (int i = 0 ; i < commandCount ; ++i)
|
||||
txns[i] = TxnInfo.create(txnIds[i], InternalStatus.get(decodeFlags[i] >>> 3), txnIds[i], Ballot.ZERO);
|
||||
{
|
||||
int commandDecodeFlags = decodeFlags[i];
|
||||
InternalStatus status = InternalStatus.get(commandDecodeFlags >>> 6);
|
||||
int statusOverrides = ((commandDecodeFlags >>> 3) & overrideMask) == 0 ? 0 : commandDecodeFlags >>> 4;
|
||||
txns[i] = create(boundsInfo, txnIds[i], status, statusOverrides, txnIds[i], NO_TXNIDS, Ballot.ZERO);
|
||||
}
|
||||
}
|
||||
cachedTxnIds().forceDiscard(txnIds, commandCount);
|
||||
|
||||
return CommandsForKey.SerializerSupport.create(key, txns, unmanageds, redundantBefore, prunedBeforeIndex == -1 ? TxnId.NONE : txns[prunedBeforeIndex]);
|
||||
}
|
||||
|
||||
private static TxnInfo create(RedundantBefore.Entry boundsInfo, @Nonnull TxnId txnId, InternalStatus status, int statusOverrides, @Nonnull Timestamp executeAt, @Nonnull TxnId[] missing, @Nonnull Ballot ballot)
|
||||
{
|
||||
boolean mayExecute = status.isCommittedToExecute() ? CommandsForKey.executes(boundsInfo, txnId, executeAt)
|
||||
: CommandsForKey.mayExecute(boundsInfo, txnId);
|
||||
return TxnInfo.create(txnId, status, mayExecute, statusOverrides, executeAt, missing, ballot);
|
||||
}
|
||||
|
||||
private static int getHlcBytes(int lookup, int index)
|
||||
{
|
||||
return (lookup >>> (index * 4)) & 0xf;
|
||||
|
|
@ -998,40 +1051,48 @@ public class CommandsForKeySerializer
|
|||
enum TxnIdFlags
|
||||
{
|
||||
STANDARD, EXTENDED, RAW;
|
||||
static final int EXTENDED_BITS = 0x2;
|
||||
static final int RAW_BITS = 0x3;
|
||||
static final int RAW_BITS = 0;
|
||||
}
|
||||
|
||||
private static TxnIdFlags txnIdFlags(TxnId txnId)
|
||||
private static boolean hasNonStandardFlags(TxnId txnId)
|
||||
{
|
||||
if (txnId.flags() > Timestamp.IDENTITY_FLAGS || txnId.domain() != Domain.Key)
|
||||
return RAW;
|
||||
switch (txnId.kind())
|
||||
if (txnId.flags() > Timestamp.IDENTITY_FLAGS)
|
||||
return false;
|
||||
|
||||
int flagBits = txnIdFlagsBits(txnId, true);
|
||||
return flagBits > 3;
|
||||
}
|
||||
|
||||
private static int txnIdFlagsBits(TxnId txnId, boolean permitNonStandardFlags)
|
||||
{
|
||||
Txn.Kind kind = txnId.kind();
|
||||
Domain domain = txnId.domain();
|
||||
if (!permitNonStandardFlags && domain == Domain.Range)
|
||||
return 0;
|
||||
|
||||
int offset = domain == Domain.Range ? 3 : 0;
|
||||
switch (kind)
|
||||
{
|
||||
default: throw new AssertionError("Unhandled Kind: " + txnId.kind());
|
||||
case Read:
|
||||
case Write:
|
||||
return STANDARD;
|
||||
case SyncPoint:
|
||||
return EXTENDED;
|
||||
case Read: return offset + 1;
|
||||
case Write: return offset + 2;
|
||||
case SyncPoint: return offset + 3;
|
||||
case ExclusiveSyncPoint:
|
||||
case LocalOnly:
|
||||
case EphemeralRead:
|
||||
return RAW;
|
||||
if (domain == Domain.Range)
|
||||
return 7;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static long txnIdFlagsBits(TxnId txnId)
|
||||
private static Domain domainLookup(int flags)
|
||||
{
|
||||
switch (txnIdFlags(txnId))
|
||||
{
|
||||
default: throw new AssertionError("Unhandled TxnIdFlag: " + txnIdFlags(txnId));
|
||||
case RAW: return RAW_BITS;
|
||||
case EXTENDED: return EXTENDED_BITS;
|
||||
case STANDARD:
|
||||
return txnId.kind() == Read ? 0 : 1;
|
||||
}
|
||||
return flags <= 4 ? Domain.Key : Domain.Range;
|
||||
}
|
||||
|
||||
private static final Txn.Kind[] TXN_ID_FLAG_BITS_KIND_LOOKUP = new Txn.Kind[] { Read, Write, SyncPoint, null };
|
||||
private static Txn.Kind kindLookup(int flags)
|
||||
{
|
||||
return TXN_ID_FLAG_BITS_KIND_LOOKUP[flags];
|
||||
}
|
||||
|
||||
private static final Txn.Kind[] TXN_ID_FLAG_BITS_KIND_LOOKUP = new Txn.Kind[] { null, Read, Write, SyncPoint, Read, Write, SyncPoint, ExclusiveSyncPoint };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,11 +27,10 @@ import accord.primitives.Ballot;
|
|||
import accord.primitives.FullRoute;
|
||||
import accord.primitives.PartialDeps;
|
||||
import accord.primitives.PartialTxn;
|
||||
import accord.primitives.Participants;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.primitives.Unseekables;
|
||||
import org.apache.cassandra.db.TypeSizes;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
|
|
@ -46,7 +45,7 @@ public class CommitSerializers
|
|||
{
|
||||
private static final IVersionedSerializer<Commit.Kind> kind = new EnumSerializer<>(Commit.Kind.class);
|
||||
|
||||
public abstract static class CommitSerializer<C extends Commit, R extends ReadData> extends TxnRequestSerializer<C>
|
||||
public abstract static class CommitSerializer<C extends Commit, R extends ReadData> extends TxnRequestSerializer.WithUnsyncedSerializer<C>
|
||||
{
|
||||
private final IVersionedSerializer<ReadData> read;
|
||||
|
||||
|
|
@ -61,30 +60,28 @@ public class CommitSerializers
|
|||
kind.serialize(msg.kind, out, version);
|
||||
CommandSerializers.ballot.serialize(msg.ballot, out, version);
|
||||
CommandSerializers.timestamp.serialize(msg.executeAt, out, version);
|
||||
KeySerializers.seekables.serialize(msg.keys, out, version);
|
||||
CommandSerializers.nullablePartialTxn.serialize(msg.keys, msg.partialTxn, out, version);
|
||||
DepsSerializer.partialDeps.serialize(msg.keys, msg.partialDeps, out, version);
|
||||
CommandSerializers.nullablePartialTxn.serialize(msg.partialTxn, out, version);
|
||||
DepsSerializer.partialDeps.serialize(msg.scope, msg.partialDeps, out, version);
|
||||
serializeNullable(msg.route, out, version, KeySerializers.fullRoute);
|
||||
serializeNullable(msg.readData, out, version, read);
|
||||
}
|
||||
|
||||
protected abstract C deserializeCommit(TxnId txnId, Route<?> scope, long waitForEpoch, Commit.Kind kind,
|
||||
protected abstract C deserializeCommit(TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch, Commit.Kind kind,
|
||||
Ballot ballot, Timestamp executeAt,
|
||||
Seekables<?, ?> keys, @Nullable PartialTxn partialTxn, PartialDeps partialDeps,
|
||||
@Nullable PartialTxn partialTxn, PartialDeps partialDeps,
|
||||
@Nullable FullRoute<?> fullRoute, @Nullable ReadData read);
|
||||
|
||||
@Override
|
||||
public C deserializeBody(DataInputPlus in, int version, TxnId txnId, Route<?> scope, long waitForEpoch) throws IOException
|
||||
public C deserializeBody(DataInputPlus in, int version, TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch) throws IOException
|
||||
{
|
||||
Commit.Kind kind = CommitSerializers.kind.deserialize(in, version);
|
||||
Ballot ballot = CommandSerializers.ballot.deserialize(in, version);
|
||||
Timestamp executeAt = CommandSerializers.timestamp.deserialize(in, version);
|
||||
Seekables<?, ?> keys = KeySerializers.seekables.deserialize(in, version);
|
||||
PartialTxn txn = CommandSerializers.nullablePartialTxn.deserialize(keys, in, version);
|
||||
PartialDeps deps = DepsSerializer.partialDeps.deserialize(keys, in, version);
|
||||
PartialTxn txn = CommandSerializers.nullablePartialTxn.deserialize(in, version);
|
||||
PartialDeps deps = DepsSerializer.partialDeps.deserialize(scope, in, version);
|
||||
FullRoute<?> route = deserializeNullable(in, version, KeySerializers.fullRoute);
|
||||
ReadData read = deserializeNullable(in, version, this.read);
|
||||
return deserializeCommit(txnId, scope, waitForEpoch, kind, ballot, executeAt, keys, txn, deps, route, read);
|
||||
return deserializeCommit(txnId, scope, waitForEpoch, minEpoch, kind, ballot, executeAt, txn, deps, route, read);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -93,9 +90,8 @@ public class CommitSerializers
|
|||
return kind.serializedSize(msg.kind, version)
|
||||
+ CommandSerializers.ballot.serializedSize(msg.ballot, version)
|
||||
+ CommandSerializers.timestamp.serializedSize(msg.executeAt, version)
|
||||
+ KeySerializers.seekables.serializedSize(msg.keys, version)
|
||||
+ CommandSerializers.nullablePartialTxn.serializedSize(msg.keys, msg.partialTxn, version)
|
||||
+ DepsSerializer.partialDeps.serializedSize(msg.keys, msg.partialDeps, version)
|
||||
+ CommandSerializers.nullablePartialTxn.serializedSize(msg.partialTxn, version)
|
||||
+ DepsSerializer.partialDeps.serializedSize(msg.scope, msg.partialDeps, version)
|
||||
+ serializedNullableSize(msg.route, version, KeySerializers.fullRoute)
|
||||
+ serializedNullableSize(msg.readData, version, read);
|
||||
}
|
||||
|
|
@ -104,9 +100,9 @@ public class CommitSerializers
|
|||
public static final IVersionedSerializer<Commit> request = new CommitSerializer<Commit, ReadData>(ReadData.class, ReadDataSerializers.readData)
|
||||
{
|
||||
@Override
|
||||
protected Commit deserializeCommit(TxnId txnId, Route<?> scope, long waitForEpoch, Commit.Kind kind, Ballot ballot, Timestamp executeAt, Seekables<?, ?> keys, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute, @Nullable ReadData read)
|
||||
protected Commit deserializeCommit(TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch, Commit.Kind kind, Ballot ballot, Timestamp executeAt, @Nullable PartialTxn partialTxn, PartialDeps partialDeps, @Nullable FullRoute<?> fullRoute, @Nullable ReadData read)
|
||||
{
|
||||
return Commit.SerializerSupport.create(txnId, scope, waitForEpoch, kind, ballot, executeAt, keys, partialTxn, partialDeps, fullRoute, read);
|
||||
return Commit.SerializerSupport.create(txnId, scope, waitForEpoch, minEpoch, kind, ballot, executeAt, partialTxn, partialDeps, fullRoute, read);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -116,7 +112,7 @@ public class CommitSerializers
|
|||
public void serialize(Commit.Invalidate invalidate, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
CommandSerializers.txnId.serialize(invalidate.txnId, out, version);
|
||||
KeySerializers.unseekables.serialize(invalidate.scope, out, version);
|
||||
KeySerializers.participants.serialize(invalidate.scope, out, version);
|
||||
out.writeUnsignedVInt(invalidate.waitForEpoch);
|
||||
out.writeUnsignedVInt(invalidate.invalidateUntilEpoch - invalidate.waitForEpoch);
|
||||
}
|
||||
|
|
@ -125,7 +121,7 @@ public class CommitSerializers
|
|||
public Commit.Invalidate deserialize(DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
TxnId txnId = CommandSerializers.txnId.deserialize(in, version);
|
||||
Unseekables<?> scope = KeySerializers.unseekables.deserialize(in, version);
|
||||
Participants<?> scope = KeySerializers.participants.deserialize(in, version);
|
||||
long waitForEpoch = in.readUnsignedVInt();
|
||||
long invalidateUntilEpoch = in.readUnsignedVInt() + waitForEpoch;
|
||||
return Commit.Invalidate.SerializerSupport.create(txnId, scope, waitForEpoch, invalidateUntilEpoch);
|
||||
|
|
@ -135,7 +131,7 @@ public class CommitSerializers
|
|||
public long serializedSize(Commit.Invalidate invalidate, int version)
|
||||
{
|
||||
return CommandSerializers.txnId.serializedSize(invalidate.txnId, version)
|
||||
+ KeySerializers.unseekables.serializedSize(invalidate.scope, version)
|
||||
+ KeySerializers.participants.serializedSize(invalidate.scope, version)
|
||||
+ TypeSizes.sizeofUnsignedVInt(invalidate.waitForEpoch)
|
||||
+ TypeSizes.sizeofUnsignedVInt(invalidate.invalidateUntilEpoch - invalidate.waitForEpoch);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,15 +21,16 @@ import java.io.IOException;
|
|||
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
import accord.primitives.AbstractUnseekableKeys;
|
||||
import accord.primitives.Deps;
|
||||
import accord.primitives.KeyDeps;
|
||||
import accord.primitives.Keys;
|
||||
import accord.primitives.PartialDeps;
|
||||
import accord.primitives.Participants;
|
||||
import accord.primitives.Range;
|
||||
import accord.primitives.RangeDeps;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.RoutingKeys;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.primitives.Unseekables;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
|
|
@ -43,7 +44,7 @@ import static accord.primitives.RangeDeps.SerializerSupport.rangesToTxnIdsCount;
|
|||
import static accord.primitives.Routable.Domain.Key;
|
||||
import static org.apache.cassandra.db.TypeSizes.sizeofUnsignedVInt;
|
||||
|
||||
public abstract class DepsSerializer<D extends Deps> extends IVersionedWithKeysSerializer.AbstractWithKeysSerializer implements IVersionedWithKeysSerializer<Seekables<?, ?>, D>
|
||||
public abstract class DepsSerializer<D extends Deps> extends IVersionedWithKeysSerializer.AbstractWithKeysSerializer implements IVersionedWithKeysSerializer<Unseekables<?>, D>
|
||||
{
|
||||
public static final DepsSerializer<Deps> deps = new DepsSerializer<>()
|
||||
{
|
||||
|
|
@ -72,7 +73,7 @@ public abstract class DepsSerializer<D extends Deps> extends IVersionedWithKeysS
|
|||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Seekables<?, ?> superset, PartialDeps partialDeps, DataOutputPlus out, int version) throws IOException
|
||||
public void serialize(Unseekables<?> superset, PartialDeps partialDeps, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
super.serialize(superset, partialDeps, out, version);
|
||||
KeySerializers.participants.serialize(partialDeps.covering, out, version);
|
||||
|
|
@ -86,7 +87,7 @@ public abstract class DepsSerializer<D extends Deps> extends IVersionedWithKeysS
|
|||
}
|
||||
|
||||
@Override
|
||||
public long serializedSize(Seekables<?, ?> keys, PartialDeps partialDeps, int version)
|
||||
public long serializedSize(Unseekables<?> keys, PartialDeps partialDeps, int version)
|
||||
{
|
||||
return super.serializedSize(keys, partialDeps, version)
|
||||
+ KeySerializers.participants.serializedSize(partialDeps.covering, version);
|
||||
|
|
@ -100,48 +101,48 @@ public abstract class DepsSerializer<D extends Deps> extends IVersionedWithKeysS
|
|||
@Override
|
||||
public void serialize(D deps, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
KeySerializers.keys.serialize(deps.keyDeps.keys(), out, version);
|
||||
KeySerializers.routingKeys.serialize(deps.keyDeps.keys(), out, version);
|
||||
serializeWithoutKeys(deps, out, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Seekables<?, ?> superset, D deps, DataOutputPlus out, int version) throws IOException
|
||||
public void serialize(Unseekables<?> superset, D deps, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
if (superset.domain() == Key) serializeSubset(deps.keyDeps.keys(), superset, out);
|
||||
else KeySerializers.keys.serialize(deps.keyDeps.keys(), out, version);
|
||||
else KeySerializers.routingKeys.serialize(deps.keyDeps.keys(), out, version);
|
||||
serializeWithoutKeys(deps, out, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public D deserialize(DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
Keys keys = KeySerializers.keys.deserialize(in, version);
|
||||
RoutingKeys keys = KeySerializers.routingKeys.deserialize(in, version);
|
||||
return deserializeWithoutKeys(keys, in, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public D deserialize(Seekables<?, ?> superset, DataInputPlus in, int version) throws IOException
|
||||
public D deserialize(Unseekables<?> superset, DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
Keys keys;
|
||||
if (superset.domain() == Key) keys = (Keys)deserializeSubset(superset, in);
|
||||
else keys = KeySerializers.keys.deserialize(in, version);
|
||||
RoutingKeys keys;
|
||||
if (superset.domain() == Key) keys = ((AbstractUnseekableKeys)deserializeSubset(superset, in)).toParticipants();
|
||||
else keys = KeySerializers.routingKeys.deserialize(in, version);
|
||||
return deserializeWithoutKeys(keys, in, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedSize(D deps, int version)
|
||||
{
|
||||
long size = KeySerializers.keys.serializedSize(deps.keyDeps.keys(), version);
|
||||
long size = KeySerializers.routingKeys.serializedSize(deps.keyDeps.keys(), version);
|
||||
size += serializedSizeWithoutKeys(deps, version);
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedSize(Seekables<?, ?> keys, D deps, int version)
|
||||
public long serializedSize(Unseekables<?> keys, D deps, int version)
|
||||
{
|
||||
long size;
|
||||
if (keys.domain() == Key) size = serializedSubsetSize(deps.keyDeps.keys(), keys);
|
||||
else size = KeySerializers.keys.serializedSize(deps.keyDeps.keys(), version);
|
||||
else size = KeySerializers.routingKeys.serializedSize(deps.keyDeps.keys(), version);
|
||||
size += serializedSizeWithoutKeys(deps, version);
|
||||
return size;
|
||||
}
|
||||
|
|
@ -169,11 +170,11 @@ public abstract class DepsSerializer<D extends Deps> extends IVersionedWithKeysS
|
|||
}
|
||||
|
||||
{
|
||||
Keys keys = deps.directKeyDeps.keys();
|
||||
RoutingKeys keys = deps.directKeyDeps.keys();
|
||||
boolean isSubset = isSubset(keys, deps.keyDeps.keys());
|
||||
out.writeBoolean(isSubset);
|
||||
if (isSubset) serializeSubset(keys, deps.keyDeps.keys(), out);
|
||||
else KeySerializers.keys.serialize(keys, out, version);
|
||||
else KeySerializers.routingKeys.serialize(keys, out, version);
|
||||
|
||||
serializeKeyDepsWithoutKeys(deps.directKeyDeps, out, version);
|
||||
}
|
||||
|
|
@ -192,7 +193,7 @@ public abstract class DepsSerializer<D extends Deps> extends IVersionedWithKeysS
|
|||
out.writeUnsignedVInt32(keysToTxnIds(keyDeps, i));
|
||||
}
|
||||
|
||||
private D deserializeWithoutKeys(Keys keys, DataInputPlus in, int version) throws IOException
|
||||
private D deserializeWithoutKeys(RoutingKeys keys, DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
KeyDeps keyDeps = deserializeKeyDeps(keys, in, version);
|
||||
|
||||
|
|
@ -219,14 +220,14 @@ public abstract class DepsSerializer<D extends Deps> extends IVersionedWithKeysS
|
|||
KeyDeps directKeyDeps;
|
||||
{
|
||||
boolean isSubset = in.readBoolean();
|
||||
Keys directKeys = isSubset ? (Keys)deserializeSubset(keys, in) : KeySerializers.keys.deserialize(in, version);
|
||||
RoutingKeys directKeys = isSubset ? (RoutingKeys)deserializeSubset(keys, in) : KeySerializers.routingKeys.deserialize(in, version);
|
||||
directKeyDeps = deserializeKeyDeps(directKeys, in, version);
|
||||
}
|
||||
|
||||
return deserialize(keyDeps, rangeDeps, directKeyDeps, in, version);
|
||||
}
|
||||
|
||||
private static KeyDeps deserializeKeyDeps(Keys keys, DataInputPlus in, int version) throws IOException
|
||||
private static KeyDeps deserializeKeyDeps(RoutingKeys keys, DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
int txnIdCount = in.readUnsignedVInt32();
|
||||
TxnId[] txnIds = new TxnId[txnIdCount];
|
||||
|
|
@ -266,7 +267,7 @@ public abstract class DepsSerializer<D extends Deps> extends IVersionedWithKeysS
|
|||
{
|
||||
boolean isSubset = isSubset(deps.directKeyDeps.keys(), deps.keyDeps.keys());
|
||||
size += 1;
|
||||
size += isSubset ? serializedSubsetSize(deps.directKeyDeps.keys(), deps.keyDeps.keys()) : KeySerializers.keys.serializedSize(deps.directKeyDeps.keys(), version);
|
||||
size += isSubset ? serializedSubsetSize(deps.directKeyDeps.keys(), deps.keyDeps.keys()) : KeySerializers.routingKeys.serializedSize(deps.directKeyDeps.keys(), version);
|
||||
size += serializedSizeOfKeyDepsWithoutKeys(deps.directKeyDeps, version);
|
||||
}
|
||||
return size;
|
||||
|
|
@ -286,7 +287,7 @@ public abstract class DepsSerializer<D extends Deps> extends IVersionedWithKeysS
|
|||
return size;
|
||||
}
|
||||
|
||||
private static boolean isSubset(Keys test, Keys superset)
|
||||
private static boolean isSubset(RoutingKeys test, RoutingKeys superset)
|
||||
{
|
||||
return test.foldl(superset, (k, p, v, i) -> v + 1, 0, 0, 0) == test.size();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,8 @@ import java.io.IOException;
|
|||
|
||||
import accord.messages.GetEphemeralReadDeps;
|
||||
import accord.messages.GetEphemeralReadDeps.GetEphemeralReadDepsOk;
|
||||
import accord.primitives.PartialDeps;
|
||||
import accord.primitives.Deps;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.TxnId;
|
||||
import org.apache.cassandra.db.TypeSizes;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
|
|
@ -38,23 +37,20 @@ public class GetEphmrlReadDepsSerializers
|
|||
@Override
|
||||
public void serializeBody(GetEphemeralReadDeps msg, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
KeySerializers.seekables.serialize(msg.keys, out, version);
|
||||
out.writeUnsignedVInt(msg.executionEpoch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GetEphemeralReadDeps deserializeBody(DataInputPlus in, int version, TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch) throws IOException
|
||||
{
|
||||
Seekables<?, ?> keys = KeySerializers.seekables.deserialize(in, version);
|
||||
long executionEpoch = in.readUnsignedVInt();
|
||||
return GetEphemeralReadDeps.SerializationSupport.create(txnId, scope, waitForEpoch, minEpoch, keys, executionEpoch);
|
||||
return GetEphemeralReadDeps.SerializationSupport.create(txnId, scope, waitForEpoch, minEpoch, executionEpoch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedBodySize(GetEphemeralReadDeps msg, int version)
|
||||
{
|
||||
return KeySerializers.seekables.serializedSize(msg.keys, version)
|
||||
+ TypeSizes.sizeofUnsignedVInt(msg.executionEpoch);
|
||||
return TypeSizes.sizeofUnsignedVInt(msg.executionEpoch);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -63,14 +59,14 @@ public class GetEphmrlReadDepsSerializers
|
|||
@Override
|
||||
public void serialize(GetEphemeralReadDepsOk reply, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
DepsSerializer.partialDeps.serialize(reply.deps, out, version);
|
||||
DepsSerializer.deps.serialize(reply.deps, out, version);
|
||||
out.writeUnsignedVInt(reply.latestEpoch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GetEphemeralReadDepsOk deserialize(DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
PartialDeps deps = DepsSerializer.partialDeps.deserialize(in, version);
|
||||
Deps deps = DepsSerializer.deps.deserialize(in, version);
|
||||
long latestEpoch = in.readUnsignedVInt();
|
||||
return new GetEphemeralReadDepsOk(deps, latestEpoch);
|
||||
}
|
||||
|
|
@ -78,7 +74,7 @@ public class GetEphmrlReadDepsSerializers
|
|||
@Override
|
||||
public long serializedSize(GetEphemeralReadDepsOk reply, int version)
|
||||
{
|
||||
return DepsSerializer.partialDeps.serializedSize(reply.deps, version)
|
||||
return DepsSerializer.deps.serializedSize(reply.deps, version)
|
||||
+ TypeSizes.sizeofUnsignedVInt(reply.latestEpoch);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import java.io.IOException;
|
|||
import accord.messages.GetMaxConflict;
|
||||
import accord.messages.GetMaxConflict.GetMaxConflictOk;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import org.apache.cassandra.db.TypeSizes;
|
||||
|
|
@ -38,23 +37,20 @@ public class GetMaxConflictSerializers
|
|||
@Override
|
||||
public void serializeBody(GetMaxConflict msg, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
KeySerializers.seekables.serialize(msg.keys, out, version);
|
||||
out.writeUnsignedVInt(msg.executionEpoch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GetMaxConflict deserializeBody(DataInputPlus in, int version, TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch) throws IOException
|
||||
{
|
||||
Seekables<?, ?> keys = KeySerializers.seekables.deserialize(in, version);
|
||||
long executionEpoch = in.readUnsignedVInt();
|
||||
return GetMaxConflict.SerializationSupport.create(scope, waitForEpoch, minEpoch, keys, executionEpoch);
|
||||
return GetMaxConflict.SerializationSupport.create(scope, waitForEpoch, minEpoch, executionEpoch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedBodySize(GetMaxConflict msg, int version)
|
||||
{
|
||||
return KeySerializers.seekables.serializedSize(msg.keys, version)
|
||||
+ TypeSizes.sizeofUnsignedVInt(msg.executionEpoch);
|
||||
return TypeSizes.sizeofUnsignedVInt(msg.executionEpoch);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -20,14 +20,16 @@ package org.apache.cassandra.service.accord.serializers;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import accord.api.Key;
|
||||
import accord.api.RoutingKey;
|
||||
import accord.primitives.AbstractKeys;
|
||||
import accord.primitives.Keys;
|
||||
import accord.primitives.AbstractRanges;
|
||||
import accord.primitives.AbstractUnseekableKeys;
|
||||
import accord.primitives.Range;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.RoutableKey;
|
||||
import accord.primitives.Routables;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.RoutingKeys;
|
||||
import accord.primitives.Unseekables;
|
||||
import net.nicoulaj.compilecommand.annotations.DontInline;
|
||||
import org.apache.cassandra.db.TypeSizes;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
|
|
@ -123,7 +125,7 @@ public interface IVersionedWithKeysSerializer<K extends Routables<?>, T> extends
|
|||
* If both ends have a pre-shared superset of the columns we are serializing, we can send them much
|
||||
* more efficiently. Both ends must provide the identically same set of columns.
|
||||
*/
|
||||
protected void serializeSubset(Seekables<?, ?> serialize, Seekables<?, ?> superset, DataOutputPlus out) throws IOException
|
||||
protected void serializeSubset(Routables<?> serialize, Routables<?> superset, DataOutputPlus out) throws IOException
|
||||
{
|
||||
/**
|
||||
* We weight this towards small sets, and sets where the majority of items are present, since
|
||||
|
|
@ -149,10 +151,10 @@ public interface IVersionedWithKeysSerializer<K extends Routables<?>, T> extends
|
|||
{
|
||||
default: throw new AssertionError("Unhandled domain: " + serialize.domain());
|
||||
case Key:
|
||||
out.writeUnsignedVInt(encodeBitmap((Keys)serialize, (Keys)superset, supersetCount));
|
||||
out.writeUnsignedVInt(encodeBitmap((AbstractUnseekableKeys)serialize, (AbstractUnseekableKeys)superset, supersetCount));
|
||||
break;
|
||||
case Range:
|
||||
out.writeUnsignedVInt(encodeBitmap((Ranges)serialize, (Ranges)superset, supersetCount));
|
||||
out.writeUnsignedVInt(encodeBitmap((AbstractRanges)serialize, (AbstractRanges)superset, supersetCount));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -162,16 +164,16 @@ public interface IVersionedWithKeysSerializer<K extends Routables<?>, T> extends
|
|||
{
|
||||
default: throw new AssertionError("Unhandled domain: " + serialize.domain());
|
||||
case Key:
|
||||
serializeLargeSubset((Keys)serialize, serializeCount, (Keys)superset, supersetCount, out);
|
||||
serializeLargeSubset((AbstractUnseekableKeys)serialize, serializeCount, (AbstractUnseekableKeys)superset, supersetCount, out);
|
||||
break;
|
||||
case Range:
|
||||
serializeLargeSubset((Ranges)serialize, serializeCount, (Ranges)superset, supersetCount, out);
|
||||
serializeLargeSubset((AbstractRanges)serialize, serializeCount, (AbstractRanges)superset, supersetCount, out);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long serializedSubsetSize(Seekables<?, ?> serialize, Seekables<?, ?> superset)
|
||||
public long serializedSubsetSize(Routables<?> serialize, Routables<?> superset)
|
||||
{
|
||||
int columnCount = serialize.size();
|
||||
int supersetCount = superset.size();
|
||||
|
|
@ -185,9 +187,9 @@ public interface IVersionedWithKeysSerializer<K extends Routables<?>, T> extends
|
|||
{
|
||||
default: throw new AssertionError("Unhandled domain: " + serialize.domain());
|
||||
case Key:
|
||||
return TypeSizes.sizeofUnsignedVInt(encodeBitmap((Keys)serialize, (Keys)superset, supersetCount));
|
||||
return TypeSizes.sizeofUnsignedVInt(encodeBitmap((AbstractUnseekableKeys)serialize, (AbstractUnseekableKeys)superset, supersetCount));
|
||||
case Range:
|
||||
return TypeSizes.sizeofUnsignedVInt(encodeBitmap((Ranges)serialize, (Ranges)superset, supersetCount));
|
||||
return TypeSizes.sizeofUnsignedVInt(encodeBitmap((AbstractRanges)serialize, (AbstractRanges)superset, supersetCount));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -196,14 +198,14 @@ public interface IVersionedWithKeysSerializer<K extends Routables<?>, T> extends
|
|||
{
|
||||
default: throw new AssertionError("Unhandled domain: " + serialize.domain());
|
||||
case Key:
|
||||
return serializeLargeSubsetSize((Keys)serialize, columnCount, (Keys)superset, supersetCount);
|
||||
return serializeLargeSubsetSize((AbstractUnseekableKeys)serialize, columnCount, (AbstractUnseekableKeys)superset, supersetCount);
|
||||
case Range:
|
||||
return serializeLargeSubsetSize((Ranges)serialize, columnCount, (Ranges)superset, supersetCount);
|
||||
return serializeLargeSubsetSize((AbstractRanges)serialize, columnCount, (AbstractRanges)superset, supersetCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Seekables<?, ?> deserializeSubset(Seekables<?, ?> superset, DataInputPlus in) throws IOException
|
||||
public Unseekables<?> deserializeSubset(Unseekables<?> superset, DataInputPlus in) throws IOException
|
||||
{
|
||||
long encoded = in.readUnsignedVInt();
|
||||
int supersetCount = superset.size();
|
||||
|
|
@ -224,8 +226,8 @@ public interface IVersionedWithKeysSerializer<K extends Routables<?>, T> extends
|
|||
default: throw new AssertionError("Unhandled domain: " + superset.domain());
|
||||
case Key:
|
||||
{
|
||||
Keys keys = (Keys)superset;
|
||||
Key[] out = new Key[deserializeCount];
|
||||
AbstractUnseekableKeys keys = (AbstractUnseekableKeys) superset;
|
||||
RoutingKey[] out = new RoutingKey[deserializeCount];
|
||||
int count = 0;
|
||||
while (encoded != 0)
|
||||
{
|
||||
|
|
@ -233,11 +235,11 @@ public interface IVersionedWithKeysSerializer<K extends Routables<?>, T> extends
|
|||
out[count++] = keys.get(Long.numberOfTrailingZeros(lowestBit));
|
||||
encoded ^= lowestBit;
|
||||
}
|
||||
return Keys.ofSortedUnique(out);
|
||||
return RoutingKeys.ofSortedUnique(out);
|
||||
}
|
||||
case Range:
|
||||
{
|
||||
Ranges ranges = (Ranges)superset;
|
||||
AbstractRanges ranges = (AbstractRanges)superset;
|
||||
Range[] out = new Range[deserializeCount];
|
||||
int count = 0;
|
||||
while (encoded != 0)
|
||||
|
|
@ -264,7 +266,7 @@ public interface IVersionedWithKeysSerializer<K extends Routables<?>, T> extends
|
|||
return bitmap;
|
||||
}
|
||||
|
||||
private static long encodeBitmap(Ranges serialize, Ranges superset, int supersetCount)
|
||||
private static long encodeBitmap(AbstractRanges serialize, AbstractRanges superset, int supersetCount)
|
||||
{
|
||||
// the index we would encounter next if all columns are present
|
||||
long bitmap = superset.foldl(serialize, (k, p1, v, i) -> {
|
||||
|
|
@ -299,7 +301,7 @@ public interface IVersionedWithKeysSerializer<K extends Routables<?>, T> extends
|
|||
}
|
||||
|
||||
@DontInline
|
||||
private void serializeLargeSubset(Ranges serialize, int serializeCount, Ranges superset, int supersetCount, DataOutputPlus out) throws IOException
|
||||
private void serializeLargeSubset(AbstractRanges serialize, int serializeCount, AbstractRanges superset, int supersetCount, DataOutputPlus out) throws IOException
|
||||
{
|
||||
out.writeUnsignedVInt32(supersetCount - serializeCount);
|
||||
int serializeIndex = 0, supersetIndex = 0;
|
||||
|
|
@ -323,7 +325,7 @@ public interface IVersionedWithKeysSerializer<K extends Routables<?>, T> extends
|
|||
}
|
||||
|
||||
@DontInline
|
||||
private Seekables<?, ?> deserializeLargeSubset(DataInputPlus in, Seekables<?, ?> superset, int supersetCount, int delta) throws IOException
|
||||
private Unseekables<?> deserializeLargeSubset(DataInputPlus in, Unseekables<?> superset, int supersetCount, int delta) throws IOException
|
||||
{
|
||||
int deserializeCount = supersetCount - delta;
|
||||
switch (superset.domain())
|
||||
|
|
@ -331,8 +333,8 @@ public interface IVersionedWithKeysSerializer<K extends Routables<?>, T> extends
|
|||
default: throw new AssertionError("Unhandled domain: " + superset.domain());
|
||||
case Key:
|
||||
{
|
||||
Keys keys = (Keys)superset;
|
||||
Key[] out = new Key[deserializeCount];
|
||||
RoutingKeys keys = (RoutingKeys) superset;
|
||||
RoutingKey[] out = new RoutingKey[deserializeCount];
|
||||
int supersetIndex = 0;
|
||||
int count = 0;
|
||||
while (count < deserializeCount)
|
||||
|
|
@ -341,11 +343,11 @@ public interface IVersionedWithKeysSerializer<K extends Routables<?>, T> extends
|
|||
while (takeCount-- > 0) out[count++] = keys.get(supersetIndex++);
|
||||
supersetIndex += in.readUnsignedVInt32();
|
||||
}
|
||||
return Keys.ofSortedUnique(out);
|
||||
return RoutingKeys.ofSortedUnique(out);
|
||||
}
|
||||
case Range:
|
||||
{
|
||||
Ranges ranges = (Ranges)superset;
|
||||
AbstractRanges ranges = (AbstractRanges)superset;
|
||||
Range[] out = new Range[deserializeCount];
|
||||
int supersetIndex = 0;
|
||||
int count = 0;
|
||||
|
|
@ -386,7 +388,7 @@ public interface IVersionedWithKeysSerializer<K extends Routables<?>, T> extends
|
|||
}
|
||||
|
||||
@DontInline
|
||||
private long serializeLargeSubsetSize(Ranges serialize, int serializeCount, Ranges superset, int supersetCount)
|
||||
private long serializeLargeSubsetSize(AbstractRanges serialize, int serializeCount, AbstractRanges superset, int supersetCount)
|
||||
{
|
||||
long size = TypeSizes.sizeofUnsignedVInt(supersetCount - serializeCount);
|
||||
int serializeIndex = 0, supersetIndex = 0;
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ package org.apache.cassandra.service.accord.serializers;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import accord.local.Status;
|
||||
import accord.messages.InformDurable;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
|
|
|
|||
|
|
@ -203,6 +203,8 @@ public class KeySerializers
|
|||
EnumSet.allOf(UnseekablesKind.class)
|
||||
);
|
||||
|
||||
public static final IVersionedSerializer<Participants<?>> nullableParticipants = NullableSerializer.wrap(participants);
|
||||
|
||||
static class AbstractRoutablesSerializer<RS extends Unseekables<?>> implements IVersionedSerializer<RS>
|
||||
{
|
||||
final EnumSet<UnseekablesKind> permitted;
|
||||
|
|
@ -337,6 +339,7 @@ public class KeySerializers
|
|||
};
|
||||
|
||||
public static final IVersionedSerializer<Seekables<?, ?>> nullableSeekables = NullableSerializer.wrap(seekables);
|
||||
public static final IVersionedSerializer<Unseekables<?>> nullableUnseekables = NullableSerializer.wrap(unseekables);
|
||||
|
||||
public static abstract class AbstractKeysSerializer<K extends RoutableKey, KS extends AbstractKeys<K>> implements IVersionedSerializer<KS>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -42,14 +42,14 @@ public class PreacceptSerializers
|
|||
{
|
||||
private PreacceptSerializers() {}
|
||||
|
||||
public static final IVersionedSerializer<PreAccept> request = new WithUnsyncedSerializer<PreAccept>()
|
||||
public static final IVersionedSerializer<PreAccept> request = new WithUnsyncedSerializer<>()
|
||||
{
|
||||
@Override
|
||||
public void serializeBody(PreAccept msg, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
CommandSerializers.partialTxn.serialize(msg.partialTxn, out, version);
|
||||
serializeNullable(msg.route, out, version, KeySerializers.fullRoute);
|
||||
out.writeUnsignedVInt(msg.maxEpoch - msg.minEpoch);
|
||||
out.writeUnsignedVInt(msg.acceptEpoch - msg.minEpoch);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -57,9 +57,9 @@ public class PreacceptSerializers
|
|||
{
|
||||
PartialTxn partialTxn = CommandSerializers.partialTxn.deserialize(in, version);
|
||||
@Nullable FullRoute<?> fullRoute = deserializeNullable(in, version, KeySerializers.fullRoute);
|
||||
long maxEpoch = in.readUnsignedVInt() + minEpoch;
|
||||
long acceptEpoch = in.readUnsignedVInt() + minEpoch;
|
||||
return PreAccept.SerializerSupport.create(txnId, scope, waitForEpoch, minEpoch,
|
||||
maxEpoch, partialTxn, fullRoute);
|
||||
acceptEpoch, partialTxn, fullRoute);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -67,11 +67,11 @@ public class PreacceptSerializers
|
|||
{
|
||||
return CommandSerializers.partialTxn.serializedSize(msg.partialTxn, version)
|
||||
+ serializedNullableSize(msg.route, version, KeySerializers.fullRoute)
|
||||
+ TypeSizes.sizeofUnsignedVInt(msg.maxEpoch - msg.minEpoch);
|
||||
+ TypeSizes.sizeofUnsignedVInt(msg.acceptEpoch - msg.minEpoch);
|
||||
}
|
||||
};
|
||||
|
||||
public static final IVersionedSerializer<PreAcceptReply> reply = new IVersionedSerializer<PreAcceptReply>()
|
||||
public static final IVersionedSerializer<PreAcceptReply> reply = new IVersionedSerializer<>()
|
||||
{
|
||||
@Override
|
||||
public void serialize(PreAcceptReply reply, DataOutputPlus out, int version) throws IOException
|
||||
|
|
@ -83,7 +83,7 @@ public class PreacceptSerializers
|
|||
PreAcceptOk preAcceptOk = (PreAcceptOk) reply;
|
||||
CommandSerializers.txnId.serialize(preAcceptOk.txnId, out, version);
|
||||
CommandSerializers.timestamp.serialize(preAcceptOk.witnessedAt, out, version);
|
||||
DepsSerializer.partialDeps.serialize(preAcceptOk.deps, out, version);
|
||||
DepsSerializer.deps.serialize(preAcceptOk.deps, out, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -94,7 +94,7 @@ public class PreacceptSerializers
|
|||
|
||||
return new PreAcceptOk(CommandSerializers.txnId.deserialize(in, version),
|
||||
CommandSerializers.timestamp.deserialize(in, version),
|
||||
DepsSerializer.partialDeps.deserialize(in, version));
|
||||
DepsSerializer.deps.deserialize(in, version));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -107,7 +107,7 @@ public class PreacceptSerializers
|
|||
PreAcceptOk preAcceptOk = (PreAcceptOk) reply;
|
||||
size += CommandSerializers.txnId.serializedSize(preAcceptOk.txnId, version);
|
||||
size += CommandSerializers.timestamp.serializedSize(preAcceptOk.witnessedAt, version);
|
||||
size += DepsSerializer.partialDeps.serializedSize(preAcceptOk.deps, version);
|
||||
size += DepsSerializer.deps.serializedSize(preAcceptOk.deps, version);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,6 @@ public class ReadDataSerializers
|
|||
DepsSerializer.partialDeps.serialize(msg.deps, out, version);
|
||||
CommandSerializers.writes.serialize(msg.writes, out, version);
|
||||
TxnResult.serializer.serialize((TxnResult) msg.result, out, version);
|
||||
KeySerializers.nullableSeekables.serialize(msg.notify, out, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -101,8 +100,7 @@ public class ReadDataSerializers
|
|||
CommandSerializers.partialTxn.deserialize(in, version),
|
||||
DepsSerializer.partialDeps.deserialize(in, version),
|
||||
CommandSerializers.writes.deserialize(in, version),
|
||||
TxnResult.serializer.deserialize(in, version),
|
||||
KeySerializers.nullableSeekables.deserialize(in, version));
|
||||
TxnResult.serializer.deserialize(in, version));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -115,8 +113,7 @@ public class ReadDataSerializers
|
|||
+ CommandSerializers.partialTxn.serializedSize(msg.txn, version)
|
||||
+ DepsSerializer.partialDeps.serializedSize(msg.deps, version)
|
||||
+ CommandSerializers.writes.serializedSize(msg.writes, version)
|
||||
+ TxnResult.serializer.serializedSize((TxnData)msg.result, version)
|
||||
+ KeySerializers.nullableSeekables.serializedSize(msg.notify, version);
|
||||
+ TxnResult.serializer.serializedSize((TxnData)msg.result, version);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import javax.annotation.Nullable;
|
|||
|
||||
import accord.api.Result;
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.Status;
|
||||
import accord.messages.BeginRecovery;
|
||||
import accord.messages.BeginRecovery.RecoverNack;
|
||||
import accord.messages.BeginRecovery.RecoverOk;
|
||||
|
|
@ -32,9 +31,11 @@ import accord.messages.BeginRecovery.RecoverReply;
|
|||
import accord.primitives.Ballot;
|
||||
import accord.primitives.Deps;
|
||||
import accord.primitives.FullRoute;
|
||||
import accord.primitives.Known.KnownDeps;
|
||||
import accord.primitives.LatestDeps;
|
||||
import accord.primitives.PartialTxn;
|
||||
import accord.primitives.Route;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.primitives.Writes;
|
||||
|
|
@ -42,6 +43,7 @@ import org.apache.cassandra.db.TypeSizes;
|
|||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.service.accord.serializers.TxnRequestSerializer.WithUnsyncedSerializer;
|
||||
|
||||
import static org.apache.cassandra.utils.NullableSerializer.deserializeNullable;
|
||||
import static org.apache.cassandra.utils.NullableSerializer.serializeNullable;
|
||||
|
|
@ -49,7 +51,7 @@ import static org.apache.cassandra.utils.NullableSerializer.serializedNullableSi
|
|||
|
||||
public class RecoverySerializers
|
||||
{
|
||||
public static final IVersionedSerializer<BeginRecovery> request = new TxnRequestSerializer<BeginRecovery>()
|
||||
public static final IVersionedSerializer<BeginRecovery> request = new WithUnsyncedSerializer<BeginRecovery>()
|
||||
{
|
||||
@Override
|
||||
public void serializeBody(BeginRecovery recover, DataOutputPlus out, int version) throws IOException
|
||||
|
|
@ -57,15 +59,17 @@ public class RecoverySerializers
|
|||
CommandSerializers.partialTxn.serialize(recover.partialTxn, out, version);
|
||||
CommandSerializers.ballot.serialize(recover.ballot, out, version);
|
||||
serializeNullable(recover.route, out, version, KeySerializers.fullRoute);
|
||||
out.writeUnsignedVInt(recover.executeAtOrTxnIdEpoch - recover.txnId.epoch());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeginRecovery deserializeBody(DataInputPlus in, int version, TxnId txnId, Route<?> scope, long waitForEpoch) throws IOException
|
||||
public BeginRecovery deserializeBody(DataInputPlus in, int version, TxnId txnId, Route<?> scope, long waitForEpoch, long minEpoch) throws IOException
|
||||
{
|
||||
PartialTxn partialTxn = CommandSerializers.partialTxn.deserialize(in, version);
|
||||
Ballot ballot = CommandSerializers.ballot.deserialize(in, version);
|
||||
@Nullable FullRoute<?> route = deserializeNullable(in, version, KeySerializers.fullRoute);
|
||||
return BeginRecovery.SerializationSupport.create(txnId, scope, waitForEpoch, partialTxn, ballot, route);
|
||||
long executeAtOrTxnIdEpoch = in.readUnsignedVInt32() + txnId.epoch();
|
||||
return BeginRecovery.SerializationSupport.create(txnId, scope, waitForEpoch, minEpoch, partialTxn, ballot, route, executeAtOrTxnIdEpoch);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -73,7 +77,8 @@ public class RecoverySerializers
|
|||
{
|
||||
return CommandSerializers.partialTxn.serializedSize(recover.partialTxn, version)
|
||||
+ CommandSerializers.ballot.serializedSize(recover.ballot, version)
|
||||
+ serializedNullableSize(recover.route, version, KeySerializers.fullRoute);
|
||||
+ serializedNullableSize(recover.route, version, KeySerializers.fullRoute)
|
||||
+ TypeSizes.sizeofUnsignedVInt(recover.executeAtOrTxnIdEpoch - recover.txnId.epoch());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -93,6 +98,7 @@ public class RecoverySerializers
|
|||
latestDeps.serialize(recoverOk.deps, out, version);
|
||||
DepsSerializer.deps.serialize(recoverOk.earlierCommittedWitness, out, version);
|
||||
DepsSerializer.deps.serialize(recoverOk.earlierAcceptedNoWitness, out, version);
|
||||
out.writeBoolean(recoverOk.acceptsFastPath);
|
||||
out.writeBoolean(recoverOk.rejectsFastPath);
|
||||
CommandSerializers.nullableWrites.serialize(recoverOk.writes, out, version);
|
||||
}
|
||||
|
|
@ -112,9 +118,9 @@ public class RecoverySerializers
|
|||
return new RecoverNack(supersededBy);
|
||||
}
|
||||
|
||||
RecoverOk deserializeOk(TxnId txnId, Status status, Ballot accepted, Timestamp executeAt, @Nonnull LatestDeps deps, Deps earlierCommittedWitness, Deps earlierAcceptedNoWitness, boolean rejectsFastPath, Writes writes, Result result, DataInputPlus in, int version)
|
||||
RecoverOk deserializeOk(TxnId txnId, Status status, Ballot accepted, Timestamp executeAt, @Nonnull LatestDeps deps, Deps earlierCommittedWitness, Deps earlierAcceptedNoWitness, boolean acceptsFastPath, boolean rejectsFastPath, Writes writes, Result result, DataInputPlus in, int version)
|
||||
{
|
||||
return new RecoverOk(txnId, status, accepted, executeAt, deps, earlierCommittedWitness, earlierAcceptedNoWitness, rejectsFastPath, writes, result);
|
||||
return new RecoverOk(txnId, status, accepted, executeAt, deps, earlierCommittedWitness, earlierAcceptedNoWitness, acceptsFastPath, rejectsFastPath, writes, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -139,6 +145,7 @@ public class RecoverySerializers
|
|||
DepsSerializer.deps.deserialize(in, version),
|
||||
DepsSerializer.deps.deserialize(in, version),
|
||||
in.readBoolean(),
|
||||
in.readBoolean(),
|
||||
CommandSerializers.nullableWrites.deserialize(in, version),
|
||||
result,
|
||||
in,
|
||||
|
|
@ -159,6 +166,7 @@ public class RecoverySerializers
|
|||
size += latestDeps.serializedSize(recoverOk.deps, version);
|
||||
size += DepsSerializer.deps.serializedSize(recoverOk.earlierCommittedWitness, version);
|
||||
size += DepsSerializer.deps.serializedSize(recoverOk.earlierAcceptedNoWitness, version);
|
||||
size += TypeSizes.sizeof(recoverOk.acceptsFastPath);
|
||||
size += TypeSizes.sizeof(recoverOk.rejectsFastPath);
|
||||
size += CommandSerializers.nullableWrites.serializedSize(recoverOk.writes, version);
|
||||
return size;
|
||||
|
|
@ -207,7 +215,7 @@ public class RecoverySerializers
|
|||
for (int i = 0 ; i < size ; ++i)
|
||||
{
|
||||
starts[i] = KeySerializers.routingKey.deserialize(in, version);
|
||||
Status.KnownDeps knownDeps = CommandSerializers.nullableKnownDeps.deserialize(in, version);
|
||||
KnownDeps knownDeps = CommandSerializers.nullableKnownDeps.deserialize(in, version);
|
||||
if (knownDeps == null)
|
||||
continue;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,11 +19,10 @@ package org.apache.cassandra.service.accord.serializers;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import accord.api.RoutingKey;
|
||||
import accord.messages.SetGloballyDurable;
|
||||
import accord.messages.SetShardDurable;
|
||||
import accord.primitives.Deps;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.FullRoute;
|
||||
import accord.primitives.SyncPoint;
|
||||
import accord.primitives.TxnId;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
|
|
@ -58,21 +57,19 @@ public class SetDurableSerializers
|
|||
@Override
|
||||
public void serialize(SetGloballyDurable msg, DataOutputPlus out, int version) throws IOException
|
||||
{
|
||||
CommandSerializers.txnId.serialize(msg.txnId, out, version);
|
||||
CommandStoreSerializers.durableBefore.serialize(msg.durableBefore, out, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SetGloballyDurable deserialize(DataInputPlus in, int version) throws IOException
|
||||
{
|
||||
return new SetGloballyDurable(CommandSerializers.txnId.deserialize(in, version), CommandStoreSerializers.durableBefore.deserialize(in, version));
|
||||
return new SetGloballyDurable(CommandStoreSerializers.durableBefore.deserialize(in, version));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedSize(SetGloballyDurable msg, int version)
|
||||
{
|
||||
return CommandSerializers.txnId.serializedSize(msg.txnId, version)
|
||||
+ CommandStoreSerializers.durableBefore.serializedSize(msg.durableBefore, version);
|
||||
return CommandStoreSerializers.durableBefore.serializedSize(msg.durableBefore, version);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -83,8 +80,7 @@ public class SetDurableSerializers
|
|||
{
|
||||
CommandSerializers.txnId.serialize(sp.syncId, out, version);
|
||||
DepsSerializer.deps.serialize(sp.waitFor, out, version);
|
||||
KeySerializers.seekables.serialize(sp.keysOrRanges, out, version);
|
||||
KeySerializers.routingKey.serialize(sp.homeKey, out, version);
|
||||
KeySerializers.fullRoute.serialize(sp.route, out, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -92,9 +88,8 @@ public class SetDurableSerializers
|
|||
{
|
||||
TxnId syncId = CommandSerializers.txnId.deserialize(in, version);
|
||||
Deps waitFor = DepsSerializer.deps.deserialize(in, version);
|
||||
Seekables<?, ?> keysOrRanges = KeySerializers.seekables.deserialize(in, version);
|
||||
RoutingKey homeKey = KeySerializers.routingKey.deserialize(in, version);
|
||||
return SyncPoint.SerializationSupport.construct(syncId, waitFor, keysOrRanges, homeKey);
|
||||
FullRoute<?> route = KeySerializers.fullRoute.deserialize(in, version);
|
||||
return SyncPoint.SerializationSupport.construct(syncId, waitFor, route);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -102,8 +97,7 @@ public class SetDurableSerializers
|
|||
{
|
||||
return CommandSerializers.txnId.serializedSize(sp.syncId, version)
|
||||
+ DepsSerializer.deps.serializedSize(sp.waitFor, version)
|
||||
+ KeySerializers.seekables.serializedSize(sp.keysOrRanges, version)
|
||||
+ KeySerializers.routingKey.serializedSize(sp.homeKey, version);
|
||||
+ KeySerializers.fullRoute.serializedSize(sp.route, version);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ public class TopologySerializers
|
|||
}
|
||||
};
|
||||
|
||||
public static final IVersionedSerializer<Topology> topology = new IVersionedSerializer<Topology>()
|
||||
public static final IVersionedSerializer<Topology> topology = new IVersionedSerializer<>()
|
||||
{
|
||||
@Override
|
||||
public void serialize(Topology topology, DataOutputPlus out, int version) throws IOException
|
||||
|
|
@ -167,7 +167,7 @@ public class TopologySerializers
|
|||
{
|
||||
long epoch = in.readLong();
|
||||
Shard[] shards = ArraySerializers.deserializeArray(in, version, shard, Shard[]::new);
|
||||
Set<Node.Id> staleIds = CollectionSerializers.deserializeSet(in, version, TopologySerializers.nodeId);
|
||||
SortedArrayList<Node.Id> staleIds = CollectionSerializers.deserializeSortedArrayList(in, version, TopologySerializers.nodeId, Node.Id[]::new);
|
||||
return new Topology(epoch, staleIds, shards);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ import java.nio.ByteBuffer;
|
|||
|
||||
import accord.local.Command.WaitingOn;
|
||||
import accord.primitives.KeyDeps;
|
||||
import accord.primitives.Keys;
|
||||
import accord.primitives.RangeDeps;
|
||||
import accord.primitives.Routable;
|
||||
import accord.primitives.RoutingKeys;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.utils.ImmutableBitSet;
|
||||
import accord.utils.Invariants;
|
||||
|
|
@ -84,7 +84,7 @@ public class WaitingOnSerializer
|
|||
out.putLong(bits[i]);
|
||||
}
|
||||
|
||||
public static WaitingOn deserialize(TxnId txnId, Keys keys, RangeDeps directRangeDeps, KeyDeps directKeyDeps, ByteBuffer in) throws IOException
|
||||
public static WaitingOn deserialize(TxnId txnId, RoutingKeys keys, RangeDeps directRangeDeps, KeyDeps directKeyDeps, ByteBuffer in) throws IOException
|
||||
{
|
||||
int txnIdCount = directRangeDeps.txnIdCount() + directKeyDeps.txnIdCount();
|
||||
int waitingOnLength = (txnIdCount + keys.size() + 63) / 64;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import accord.primitives.PartialTxn;
|
|||
import accord.primitives.RoutableKey;
|
||||
import accord.primitives.Seekable;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.primitives.Writes;
|
||||
import accord.utils.async.AsyncChain;
|
||||
import accord.utils.async.AsyncChains;
|
||||
|
|
@ -144,7 +145,7 @@ public class TxnWrite extends AbstractKeySorted<TxnWrite.Update> implements Writ
|
|||
if (!preserveTimestamps)
|
||||
update = new PartitionUpdate.Builder(get(), 0).updateTimesAndPathsForAccord(cellToMaybeNewListPath, timestamp, nowInSeconds).build();
|
||||
Mutation mutation = new Mutation(update, true);
|
||||
return AsyncChains.ofRunnable(Stage.MUTATION.executor(), mutation::apply);
|
||||
return AsyncChains.ofRunnable(Stage.MUTATION.executor(), mutation::applyUnsafe);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -373,12 +374,12 @@ public class TxnWrite extends AbstractKeySorted<TxnWrite.Update> implements Writ
|
|||
}
|
||||
|
||||
@Override
|
||||
public AsyncChain<Void> apply(Seekable key, SafeCommandStore safeStore, Timestamp executeAt, DataStore store, PartialTxn txn)
|
||||
public AsyncChain<Void> apply(Seekable key, SafeCommandStore safeStore, TxnId txnId, Timestamp executeAt, DataStore store, PartialTxn txn)
|
||||
{
|
||||
// TODO (expected, efficiency): 99.9999% of the time we can just use executeAt.hlc(), so can avoid bringing
|
||||
// cfk into memory by retaining at all times in memory key ranges that are dirty and must use this logic;
|
||||
// any that aren't can just use executeAt.hlc
|
||||
TimestampsForKey cfk = TimestampsForKeys.updateLastExecutionTimestamps((AbstractSafeCommandStore<?,?,?>) safeStore, (Key) key, executeAt, true);
|
||||
TimestampsForKey cfk = TimestampsForKeys.updateLastExecutionTimestamps((AbstractSafeCommandStore<?,?,?>) safeStore, ((Key) key).toUnseekable(), txnId, executeAt, true);
|
||||
long timestamp = AccordSafeTimestampsForKey.timestampMicrosFor(cfk, executeAt, true);
|
||||
// TODO (low priority - do we need to compute nowInSeconds, or can we just use executeAt?)
|
||||
int nowInSeconds = AccordSafeTimestampsForKey.nowInSecondsFor(cfk, executeAt, true);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import javax.annotation.Nullable;
|
|||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
|
|
@ -360,9 +359,9 @@ public class Directory implements MetadataValue<Directory>
|
|||
return ImmutableList.copyOf(peers.values());
|
||||
}
|
||||
|
||||
public ImmutableSet<NodeId> peerIds()
|
||||
public NavigableSet<NodeId> peerIds()
|
||||
{
|
||||
return ImmutableSet.copyOf(peers.keySet());
|
||||
return peers.keySet();
|
||||
}
|
||||
|
||||
public NodeAddresses getNodeAddresses(NodeId id)
|
||||
|
|
|
|||
|
|
@ -65,11 +65,13 @@ import com.google.common.base.Joiner;
|
|||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.vdurmont.semver4j.Semver;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import accord.utils.async.AsyncResult;
|
||||
import accord.utils.async.AsyncResults;
|
||||
import com.vdurmont.semver4j.Semver;
|
||||
import org.apache.cassandra.audit.IAuditLogger;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.DecoratedKey;
|
||||
|
|
@ -1458,4 +1460,16 @@ public class FBUtilities
|
|||
if (rc == 0) return Order.EQ;
|
||||
return Order.GT;
|
||||
}
|
||||
|
||||
public static <T> AsyncResult<T> futureToAsyncResult(org.apache.cassandra.utils.concurrent.Future<T> future)
|
||||
{
|
||||
AsyncResult.Settable<T> adapter = AsyncResults.settable();
|
||||
future.addCallback((value, failure) -> {
|
||||
if (failure != null)
|
||||
adapter.tryFailure(failure);
|
||||
else
|
||||
adapter.trySuccess(value);
|
||||
});
|
||||
return adapter;
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import java.util.Collection;
|
|||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.Iterators;
|
||||
|
|
@ -98,9 +99,9 @@ public abstract class AbstractBTreeMap<K, V> extends AbstractMap<K, V>
|
|||
return null;
|
||||
}
|
||||
|
||||
private Set<K> keySet = null;
|
||||
private NavigableSet<K> keySet = null;
|
||||
@Override
|
||||
public Set<K> keySet()
|
||||
public NavigableSet<K> keySet()
|
||||
{
|
||||
if (keySet == null)
|
||||
keySet = BTreeSet.wrap(BTree.transformAndFilter(tree, (entry) -> ((Map.Entry<K, V>)entry).getKey()), comparator.keyComparator);
|
||||
|
|
|
|||
|
|
@ -331,6 +331,11 @@ public class VIntCoding
|
|||
return decodeZigZag64(readUnsignedVInt(input));
|
||||
}
|
||||
|
||||
public static long readVInt(ByteBuffer input)
|
||||
{
|
||||
return decodeZigZag64(readUnsignedVInt(input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read up to a signed 32-bit integer back.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -453,7 +453,7 @@ public class AccordBootstrapTest extends TestBaseImpl
|
|||
|
||||
PartitionKey partitionKey = new PartitionKey(tableId, dk);
|
||||
|
||||
awaitUninterruptiblyAndRethrow(service().node().commandStores().forEach(PreLoadContext.contextFor(partitionKey),
|
||||
awaitUninterruptiblyAndRethrow(service().node().commandStores().forEach(PreLoadContext.contextFor(partitionKey.toUnseekable()),
|
||||
partitionKey.toUnseekable(), moveMax, moveMax,
|
||||
safeStore -> {
|
||||
if (!safeStore.ranges().allAt(preMove).contains(partitionKey))
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import java.util.UUID;
|
|||
|
||||
import com.google.common.base.Throwables;
|
||||
|
||||
import accord.api.Key;
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.CommandStores;
|
||||
import accord.local.KeyHistory;
|
||||
import accord.local.PreLoadContext;
|
||||
|
|
@ -45,8 +45,6 @@ import org.apache.cassandra.service.accord.AccordService;
|
|||
import org.apache.cassandra.service.accord.TokenRange;
|
||||
import org.assertj.core.api.Assertions;
|
||||
|
||||
import static org.apache.cassandra.service.accord.AccordTestUtils.wrapInTxn;
|
||||
|
||||
public class AccordDropTableBase extends TestBaseImpl
|
||||
{
|
||||
protected static void addChaos(Cluster cluster, int example)
|
||||
|
|
@ -137,7 +135,7 @@ public class AccordDropTableBase extends TestBaseImpl
|
|||
AccordCommandStore store = (AccordCommandStore) stores.forId(storeId);
|
||||
AsyncChains.getUnchecked(store.submit(ctx, input -> {
|
||||
AccordSafeCommandStore safe = (AccordSafeCommandStore) input;
|
||||
for (Key key : safe.commandsForKeysKeys())
|
||||
for (RoutingKey key : safe.commandsForKeysKeys())
|
||||
{
|
||||
AccordSafeCommandsForKey safeCFK = safe.maybeCommandsForKey(key);
|
||||
if (safeCFK == null) // we read and found a key, but its null at load time... so ignore it
|
||||
|
|
|
|||
|
|
@ -40,9 +40,11 @@ import accord.impl.progresslog.DefaultProgressLogs;
|
|||
import accord.local.Node;
|
||||
import accord.local.PreLoadContext;
|
||||
import accord.local.SafeCommand;
|
||||
import accord.local.Status;
|
||||
import accord.local.StoreParticipants;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.local.cfk.SafeCommandsForKey;
|
||||
import accord.primitives.Seekables;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.utils.async.AsyncChains;
|
||||
|
|
@ -61,13 +63,14 @@ import org.apache.cassandra.schema.TableMetadata;
|
|||
import org.apache.cassandra.service.accord.AccordSafeCommandStore;
|
||||
import org.apache.cassandra.service.accord.AccordService;
|
||||
import org.apache.cassandra.service.accord.api.AccordAgent;
|
||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
||||
import org.apache.cassandra.service.consensus.TransactionalMode;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.Clock;
|
||||
import org.apache.cassandra.utils.concurrent.Future;
|
||||
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
|
||||
|
||||
import static accord.local.KeyHistory.COMMANDS;
|
||||
import static java.lang.String.format;
|
||||
|
||||
public class AccordIncrementalRepairTest extends AccordTestBase
|
||||
|
|
@ -100,9 +103,9 @@ public class AccordIncrementalRepairTest extends AccordTestBase
|
|||
private final List<ExecutedBarrier> barriers = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onLocalBarrier(@Nonnull Seekables<?, ?> keysOrRanges, @Nonnull TxnId txnId)
|
||||
public void onSuccessfulBarrier(@Nonnull TxnId txnId, @Nonnull Seekables<?, ?> keysOrRanges)
|
||||
{
|
||||
super.onLocalBarrier(keysOrRanges, txnId);
|
||||
super.onSuccessfulBarrier(txnId, keysOrRanges);
|
||||
synchronized (barriers)
|
||||
{
|
||||
barriers.add(new ExecutedBarrier(keysOrRanges, txnId));
|
||||
|
|
@ -215,19 +218,27 @@ public class AccordIncrementalRepairTest extends AccordTestBase
|
|||
return getUninterruptibly(future, 1, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private static TxnId awaitLocalApplyOnKey(PartitionKey key)
|
||||
private static TxnId awaitLocalApplyOnKey(TableMetadata metadata, int k)
|
||||
{
|
||||
return awaitLocalApplyOnKey(new TokenKey(metadata.id, metadata.partitioner.decorateKey(ByteBufferUtil.bytes(k)).getToken()));
|
||||
}
|
||||
|
||||
private static TxnId awaitLocalApplyOnKey(TokenKey key)
|
||||
{
|
||||
Node node = accordService().node();
|
||||
AtomicReference<TxnId> waitFor = new AtomicReference<>(null);
|
||||
AsyncChains.awaitUninterruptibly(node.commandStores().ifLocal(PreLoadContext.contextFor(key), key.toUnseekable(), 0, Long.MAX_VALUE, safeStore -> {
|
||||
AsyncChains.awaitUninterruptibly(node.commandStores().ifLocal(PreLoadContext.contextFor(key, COMMANDS), key.toUnseekable(), 0, Long.MAX_VALUE, safeStore -> {
|
||||
AccordSafeCommandStore store = (AccordSafeCommandStore) safeStore;
|
||||
CommandsForKey commands = store.maybeCommandsForKey(key).current();
|
||||
int size = commands.size();
|
||||
SafeCommandsForKey safeCfk = store.maybeCommandsForKey(key);
|
||||
if (safeCfk == null)
|
||||
return;
|
||||
CommandsForKey cfk = safeCfk.current();
|
||||
int size = cfk.size();
|
||||
if (size < 1)
|
||||
return;
|
||||
// if txnId is an instance of CommandsForKey.TxnInfo, copying it into a
|
||||
// new txnId instance will prevent any issues related to TxnInfo#hashCode
|
||||
waitFor.set(new TxnId(commands.txnId(size - 1)));
|
||||
waitFor.set(new TxnId(cfk.txnId(size - 1)));
|
||||
}));
|
||||
Assert.assertNotNull(waitFor.get());
|
||||
TxnId txnId = waitFor.get();
|
||||
|
|
@ -239,7 +250,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase
|
|||
if (now - start > TimeUnit.MINUTES.toMillis(1))
|
||||
throw new AssertionError("Timeout");
|
||||
AsyncChains.awaitUninterruptibly(node.commandStores().ifLocal(PreLoadContext.contextFor(txnId), key.toUnseekable(), 0, Long.MAX_VALUE, safeStore -> {
|
||||
SafeCommand command = safeStore.get(txnId, key.toUnseekable());
|
||||
SafeCommand command = safeStore.get(txnId, StoreParticipants.empty(txnId));
|
||||
Assert.assertNotNull(command.current());
|
||||
if (command.current().status().hasBeen(Status.Applied))
|
||||
applied.set(true);
|
||||
|
|
@ -267,7 +278,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase
|
|||
|
||||
SHARED_CLUSTER.get(1, 2).forEach(instance -> instance.runOnInstance(() -> {
|
||||
TableMetadata metadata = Schema.instance.getTableMetadata(keyspace, table);
|
||||
awaitLocalApplyOnKey(new PartitionKey(metadata.id, metadata.partitioner.decorateKey(ByteBufferUtil.bytes(1))));
|
||||
awaitLocalApplyOnKey(metadata, 1);
|
||||
}));
|
||||
|
||||
SHARED_CLUSTER.forEach(instance -> instance.runOnInstance(() -> agent().reset()));
|
||||
|
|
@ -299,14 +310,12 @@ public class AccordIncrementalRepairTest extends AccordTestBase
|
|||
awaitEndpointUp(SHARED_CLUSTER.get(1), SHARED_CLUSTER.get(3));
|
||||
nodetool(SHARED_CLUSTER.get(1), "repair", KEYSPACE);
|
||||
|
||||
SHARED_CLUSTER.forEach(instance -> {
|
||||
instance.runOnInstance(() -> {
|
||||
Assert.assertFalse( agent().executedBarriers().isEmpty());
|
||||
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
|
||||
Assert.assertFalse(cfs.getLiveSSTables().isEmpty());
|
||||
cfs.getLiveSSTables().forEach(sstable -> {
|
||||
Assert.assertTrue(sstable.isRepaired() || sstable.isPendingRepair());
|
||||
});
|
||||
SHARED_CLUSTER.get(1).runOnInstance(() -> {
|
||||
Assert.assertFalse( agent().executedBarriers().isEmpty());
|
||||
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
|
||||
Assert.assertFalse(cfs.getLiveSSTables().isEmpty());
|
||||
cfs.getLiveSSTables().forEach(sstable -> {
|
||||
Assert.assertTrue(sstable.isRepaired() || sstable.isPendingRepair());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -351,7 +360,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase
|
|||
}));
|
||||
|
||||
nodetool(SHARED_CLUSTER.get(1), "repair", KEYSPACE);
|
||||
SHARED_CLUSTER.forEach(instance -> instance.runOnInstance(() -> {
|
||||
SHARED_CLUSTER.get(1).runOnInstance(() -> {
|
||||
Assert.assertFalse( agent().executedBarriers().isEmpty());
|
||||
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
|
||||
Assert.assertFalse(cfs.getLiveSSTables().isEmpty());
|
||||
|
|
@ -364,7 +373,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase
|
|||
UntypedResultSet.Row row = Iterables.getOnlyElement(result);
|
||||
Assert.assertEquals(1, row.getInt("k"));
|
||||
Assert.assertEquals(2, row.getInt("v"));
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -402,7 +411,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase
|
|||
|
||||
SHARED_CLUSTER.get(1, 2).forEach(instance -> instance.runOnInstance(() -> {
|
||||
TableMetadata metadata = Schema.instance.getTableMetadata(keyspace, table);
|
||||
awaitLocalApplyOnKey(new PartitionKey(metadata.id, metadata.partitioner.decorateKey(ByteBufferUtil.bytes(1))));
|
||||
awaitLocalApplyOnKey(metadata, 1);
|
||||
}));
|
||||
|
||||
SHARED_CLUSTER.forEach(instance -> instance.runOnInstance(() -> agent().reset()));
|
||||
|
|
@ -411,11 +420,8 @@ public class AccordIncrementalRepairTest extends AccordTestBase
|
|||
awaitEndpointUp(SHARED_CLUSTER.get(1), SHARED_CLUSTER.get(3));
|
||||
nodetool(SHARED_CLUSTER.get(1), "repair", "--accord-only", KEYSPACE);
|
||||
|
||||
SHARED_CLUSTER.forEach(instance -> {
|
||||
logger().info("checking instance {}", instance.broadcastAddress());
|
||||
instance.runOnInstance(() -> {
|
||||
Assert.assertFalse( agent().executedBarriers().isEmpty());
|
||||
});
|
||||
SHARED_CLUSTER.get(1).runOnInstance(() -> {
|
||||
Assert.assertFalse( agent().executedBarriers().isEmpty());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,16 +19,20 @@
|
|||
package org.apache.cassandra.distributed.test.accord;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.config.CassandraRelevantProperties;
|
||||
import org.apache.cassandra.distributed.Cluster;
|
||||
import org.apache.cassandra.distributed.api.ConsistencyLevel;
|
||||
import org.apache.cassandra.distributed.shared.ClusterUtils;
|
||||
import org.apache.cassandra.distributed.shared.WithProperties;
|
||||
import org.apache.cassandra.distributed.test.TestBaseImpl;
|
||||
import org.apache.cassandra.service.accord.AccordService;
|
||||
import org.apache.cassandra.utils.concurrent.CountDownLatch;
|
||||
|
||||
public class AccordJournalIntegrationTest extends TestBaseImpl
|
||||
|
|
@ -36,14 +40,9 @@ public class AccordJournalIntegrationTest extends TestBaseImpl
|
|||
@Test
|
||||
public void saveLoadSanityCheck() throws Throwable
|
||||
{
|
||||
String timeout = "10s";
|
||||
try (WithProperties wp = new WithProperties().set(CassandraRelevantProperties.DTEST_ACCORD_JOURNAL_SANITY_CHECK_ENABLED, "true");
|
||||
Cluster cluster = init(Cluster.build(1)
|
||||
.withoutVNodes()
|
||||
.withConfig(c -> c
|
||||
.set("read_request_timeout", timeout)
|
||||
.set("transaction_timeout", timeout)
|
||||
)
|
||||
.start()))
|
||||
{
|
||||
final String TABLE = KEYSPACE + ".test_table";
|
||||
|
|
@ -86,4 +85,41 @@ public class AccordJournalIntegrationTest extends TestBaseImpl
|
|||
cluster.coordinator(1).execute("SELECT * FROM " + TABLE + " WHERE k = ?;", ConsistencyLevel.SERIAL, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void memtableStateReloadingTest() throws Throwable
|
||||
{
|
||||
try (Cluster cluster = Cluster.build(1)
|
||||
.withoutVNodes()
|
||||
.start())
|
||||
{
|
||||
cluster.schemaChange("CREATE KEYSPACE " + KEYSPACE + " WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};");
|
||||
final String TABLE = KEYSPACE + ".test_table";
|
||||
cluster.schemaChange("CREATE TABLE " + TABLE + " (k int, c int, v int, primary key (k, c)) WITH transactional_mode='full'");
|
||||
|
||||
for (int j = 0; j < 1_000; j++)
|
||||
{
|
||||
cluster.coordinator(1).execute("BEGIN TRANSACTION\n" +
|
||||
"INSERT INTO " + TABLE + "(k, c, v) VALUES (?, ?, ?);\n" +
|
||||
"COMMIT TRANSACTION",
|
||||
ConsistencyLevel.ALL,
|
||||
j, j, 1
|
||||
);
|
||||
}
|
||||
|
||||
Object[][] before = cluster.coordinator(1).execute("SELECT * FROM " + TABLE + " WHERE k = ?;", ConsistencyLevel.SERIAL, 1);
|
||||
|
||||
cluster.get(1).runOnInstance(() -> {
|
||||
((AccordService) AccordService.instance()).journal().closeCurrentSegmentForTesting();
|
||||
});
|
||||
ClusterUtils.stopUnchecked(cluster.get(1));
|
||||
cluster.get(1).startup();
|
||||
|
||||
Object[][] after = cluster.coordinator(1).execute("SELECT * FROM " + TABLE + " WHERE k = ?;", ConsistencyLevel.SERIAL, 1);
|
||||
for (int i = 0; i < before.length; i++)
|
||||
{
|
||||
Assert.assertTrue(Arrays.equals(before[i], after[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -337,7 +337,8 @@ public abstract class AccordTestBase extends TestBaseImpl
|
|||
.withConfig(c -> c.with(Feature.GOSSIP)
|
||||
.set("write_request_timeout", "10s")
|
||||
.set("transaction_timeout", "15s")
|
||||
.set("native_transport_timeout", "30s"))
|
||||
.set("native_transport_timeout", "30s")
|
||||
.set("accord.shard_count", "2"))
|
||||
.withInstanceInitializer(EnforceUpdateDoesNotPerformRead::install);
|
||||
builder = options.apply(builder);
|
||||
return init(builder.start());
|
||||
|
|
|
|||
|
|
@ -1,137 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.journal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.ServerTestUtils;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.io.util.File;
|
||||
import org.apache.cassandra.service.accord.AccordJournalTable;
|
||||
import org.apache.cassandra.service.accord.AccordSegmentCompactor;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.TimeUUID;
|
||||
|
||||
import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUID;
|
||||
|
||||
public class AccordJournalCompactionTest
|
||||
{
|
||||
private static final Set<Integer> SENTINEL_HOSTS = Collections.singleton(0);
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp()
|
||||
{
|
||||
DatabaseDescriptor.daemonInitialization();
|
||||
ServerTestUtils.prepareServer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void segmentMergeTest() throws IOException
|
||||
{
|
||||
File directory = new File(Files.createTempDirectory(null));
|
||||
directory.deleteOnExit();
|
||||
|
||||
Journal<TimeUUID, ByteBuffer> journal = journal(directory);
|
||||
AccordJournalTable<TimeUUID, ByteBuffer> journalTable = new AccordJournalTable<>(journal, journal.keySupport, journal.params.userVersion());
|
||||
journal.start();
|
||||
|
||||
Map<TimeUUID, List<ByteBuffer >> uuids = new HashMap<>();
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < 1024 * 5; i++)
|
||||
{
|
||||
TimeUUID uuid = nextTimeUUID();
|
||||
for (long j = 0; j < 5; j++)
|
||||
{
|
||||
ByteBuffer buf = ByteBuffer.allocate(1024);
|
||||
for (int k = 0; k < 1024; k++)
|
||||
buf.put((byte) count);
|
||||
count++;
|
||||
buf.rewind();
|
||||
uuids.computeIfAbsent(uuid, (k) -> new ArrayList<>())
|
||||
.add(buf);
|
||||
journal.asyncWrite(uuid, buf, SENTINEL_HOSTS);
|
||||
}
|
||||
}
|
||||
|
||||
journal.closeCurrentSegmentForTesting();
|
||||
Runnable checkAll = () -> {
|
||||
for (Map.Entry<TimeUUID, List<ByteBuffer>> e : uuids.entrySet())
|
||||
{
|
||||
List<ByteBuffer> expected = e.getValue();
|
||||
|
||||
List<ByteBuffer> actual = new ArrayList<>();
|
||||
journalTable.readAll(e.getKey(), (key, in, userVersion) -> actual.add(journal.valueSerializer.deserialize(key, in, userVersion)));
|
||||
Assert.assertEquals(actual.size(), expected.size());
|
||||
for (int i = 0; i < actual.size(); i++)
|
||||
{
|
||||
if (!actual.get(i).equals(expected.get(i)))
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Actual:\n");
|
||||
for (ByteBuffer bb : actual)
|
||||
sb.append(ByteBufferUtil.bytesToHex(bb)).append('\n');
|
||||
sb.append("Expected:\n");
|
||||
for (ByteBuffer bb : expected)
|
||||
sb.append(ByteBufferUtil.bytesToHex(bb)).append('\n');
|
||||
throw new AssertionError(sb.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkAll.run();
|
||||
journal.runCompactorForTesting();
|
||||
checkAll.run();
|
||||
journal.shutdown();
|
||||
}
|
||||
|
||||
private static Journal<TimeUUID, ByteBuffer> journal(File directory)
|
||||
{
|
||||
return new Journal<>("TestJournal", directory,
|
||||
new TestParams() {
|
||||
@Override
|
||||
public int segmentSize()
|
||||
{
|
||||
return 1024 * 1024;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enableCompaction()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
},
|
||||
TimeUUIDKeySupport.INSTANCE,
|
||||
JournalTest.ByteBufferSerializer.INSTANCE,
|
||||
new AccordSegmentCompactor<>());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.service.accord;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import accord.local.DurableBefore;
|
||||
import accord.local.RedundantBefore;
|
||||
import accord.primitives.Deps;
|
||||
import accord.primitives.KeyDeps;
|
||||
import accord.primitives.Ranges;
|
||||
import accord.primitives.Timestamp;
|
||||
import accord.primitives.TxnId;
|
||||
import accord.utils.AccordGens;
|
||||
import accord.utils.DefaultRandom;
|
||||
import accord.utils.Gen;
|
||||
import accord.utils.RandomSource;
|
||||
import org.apache.cassandra.ServerTestUtils;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.dht.Murmur3Partitioner;
|
||||
import org.apache.cassandra.io.util.File;
|
||||
import org.apache.cassandra.journal.TestParams;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.service.accord.AccordJournalValueSerializers.HistoricalTransactionsAccumulator;
|
||||
import org.apache.cassandra.utils.AccordGenerators;
|
||||
import org.apache.cassandra.utils.concurrent.Condition;
|
||||
|
||||
import static accord.local.CommandStores.RangesForEpoch;
|
||||
import static org.apache.cassandra.service.accord.AccordJournalValueSerializers.DurableBeforeAccumulator;
|
||||
import static org.apache.cassandra.service.accord.AccordJournalValueSerializers.IdentityAccumulator;
|
||||
import static org.apache.cassandra.service.accord.AccordJournalValueSerializers.RedundantBeforeAccumulator;
|
||||
|
||||
|
||||
public class AccordJournalCompactionTest
|
||||
{
|
||||
@BeforeClass
|
||||
public static void setUp() throws Throwable
|
||||
{
|
||||
ServerTestUtils.daemonInitialization();
|
||||
StorageService.instance.registerMBeans();
|
||||
StorageService.instance.setPartitionerUnsafe(Murmur3Partitioner.instance);
|
||||
ServerTestUtils.prepareServerNoRegister();
|
||||
|
||||
StorageService.instance.initServer();
|
||||
Keyspace.setInitialized();
|
||||
}
|
||||
|
||||
private AtomicInteger counter = new AtomicInteger();
|
||||
@Before
|
||||
public void beforeTest() throws Throwable
|
||||
{
|
||||
File directory = new File(Files.createTempDirectory(Integer.toString(counter.incrementAndGet())));
|
||||
directory.deleteRecursiveOnExit();
|
||||
DatabaseDescriptor.setAccordJournalDirectory(directory.path());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void segmentMergeTest() throws InterruptedException
|
||||
{
|
||||
RedundantBeforeAccumulator redundantBeforeAccumulator = new RedundantBeforeAccumulator();
|
||||
DurableBeforeAccumulator durableBeforeAccumulator = new DurableBeforeAccumulator();
|
||||
IdentityAccumulator<NavigableMap<TxnId, Ranges>> bootstrapBeganAtAccumulator = new IdentityAccumulator<>(ImmutableSortedMap.of(TxnId.NONE, Ranges.EMPTY));
|
||||
IdentityAccumulator<NavigableMap<Timestamp, Ranges>> safeToReadAccumulator = new IdentityAccumulator<>(ImmutableSortedMap.of(Timestamp.NONE, Ranges.EMPTY));
|
||||
IdentityAccumulator<RangesForEpoch.Snapshot> rangesForEpochAccumulator = new IdentityAccumulator<>(null);
|
||||
HistoricalTransactionsAccumulator historicalTransactionsAccumulator = new HistoricalTransactionsAccumulator();
|
||||
|
||||
Gen<RedundantBefore> basicRedundantBeforeGen = AccordGenerators.redundantBefore(DatabaseDescriptor.getPartitioner());
|
||||
Gen<RedundantBefore> redundantBeforeGen = rs -> {
|
||||
// TODO: find a better way to generate consecutive redundant befores
|
||||
while (true)
|
||||
{
|
||||
RedundantBefore next = basicRedundantBeforeGen.next(rs);
|
||||
try
|
||||
{
|
||||
RedundantBefore.merge(redundantBeforeAccumulator.get(), next);
|
||||
return next;
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
// retry;
|
||||
}
|
||||
}
|
||||
};
|
||||
Gen<DurableBefore> durableBeforeGen = AccordGenerators.durableBeforeGen(DatabaseDescriptor.getPartitioner());
|
||||
Gen<NavigableMap<Timestamp, Ranges>> safeToReadGen = AccordGenerators.safeToReadGen(DatabaseDescriptor.getPartitioner());
|
||||
Gen<RangesForEpoch.Snapshot> rangesForEpochGen = AccordGenerators.rangesForEpoch(DatabaseDescriptor.getPartitioner());
|
||||
Gen<Deps> historicalTransactionsGen = depsGen();
|
||||
|
||||
AccordJournal journal = new AccordJournal(new TestParams()
|
||||
{
|
||||
@Override
|
||||
public int segmentSize()
|
||||
{
|
||||
return 1024 * 1024;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enableCompaction()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
try
|
||||
{
|
||||
journal.start(null);
|
||||
Timestamp timestamp = Timestamp.NONE;
|
||||
|
||||
RandomSource rs = new DefaultRandom();
|
||||
|
||||
int count = 1_000;
|
||||
Condition condition = Condition.newOneTimeCondition();
|
||||
for (int i = 0; i <= count; i++)
|
||||
{
|
||||
timestamp = timestamp.next();
|
||||
AccordSafeCommandStore.FieldUpdates updates = new AccordSafeCommandStore.FieldUpdates();
|
||||
updates.durableBefore = durableBeforeGen.next(rs);
|
||||
updates.redundantBefore = redundantBeforeGen.next(rs);
|
||||
updates.safeToRead = safeToReadGen.next(rs);
|
||||
updates.rangesForEpoch = rangesForEpochGen.next(rs);
|
||||
updates.historicalTransactions = historicalTransactionsGen.next(rs);
|
||||
|
||||
if (i == count)
|
||||
journal.persistStoreState(1, updates, condition::signal);
|
||||
else
|
||||
journal.persistStoreState(1, updates, null);
|
||||
|
||||
redundantBeforeAccumulator.update(updates.redundantBefore);
|
||||
durableBeforeAccumulator.update(updates.durableBefore);
|
||||
if (updates.bootstrapBeganAt != null)
|
||||
bootstrapBeganAtAccumulator.update(updates.bootstrapBeganAt);
|
||||
safeToReadAccumulator.update(updates.safeToRead);
|
||||
rangesForEpochAccumulator.update(updates.rangesForEpoch);
|
||||
historicalTransactionsAccumulator.update(updates.historicalTransactions);
|
||||
}
|
||||
|
||||
condition.await();
|
||||
|
||||
journal.closeCurrentSegmentForTesting();
|
||||
journal.runCompactorForTesting();
|
||||
|
||||
Assert.assertEquals(redundantBeforeAccumulator.get(), journal.loadRedundantBefore(1));
|
||||
Assert.assertEquals(durableBeforeAccumulator.get(), journal.loadDurableBefore(1));
|
||||
Assert.assertEquals(bootstrapBeganAtAccumulator.get(), journal.loadBootstrapBeganAt(1));
|
||||
Assert.assertEquals(safeToReadAccumulator.get(), journal.loadSafeToRead(1));
|
||||
Assert.assertEquals(rangesForEpochAccumulator.get(), journal.loadRangesForEpoch(1));
|
||||
Assert.assertEquals(historicalTransactionsAccumulator.get(), journal.loadHistoricalTransactions(1));
|
||||
}
|
||||
finally
|
||||
{
|
||||
journal.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public static Gen<Deps> depsGen()
|
||||
{
|
||||
Gen<KeyDeps> keyDepsGen = AccordGenerators.keyDepsGen(DatabaseDescriptor.getPartitioner());
|
||||
return AccordGens.deps(keyDepsGen::next,
|
||||
(rs) -> Deps.NONE.rangeDeps,
|
||||
(rs) -> Deps.NONE.directKeyDeps);
|
||||
}
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ public class AccordJournalSimulationTest extends SimulationTestBase
|
|||
ListenableFileSystem fs = new ListenableFileSystem(Jimfs.newFileSystem());
|
||||
File.unsafeSetFilesystem(fs);
|
||||
DatabaseDescriptor.daemonInitialization();
|
||||
DatabaseDescriptor.setCommitLogCompression(new ParameterizedClass("LZ4Compressor", ImmutableMap.of())); //
|
||||
DatabaseDescriptor.setCommitLogCompression(new ParameterizedClass("LZ4Compressor", ImmutableMap.of()));
|
||||
DatabaseDescriptor.setCommitLogWriteDiskAccessMode(Config.DiskAccessMode.standard);
|
||||
DatabaseDescriptor.initializeCommitLogDiskAccessMode();
|
||||
DatabaseDescriptor.setPartitionerUnsafe(Murmur3Partitioner.instance);
|
||||
|
|
@ -130,12 +130,6 @@ public class AccordJournalSimulationTest extends SimulationTestBase
|
|||
@Isolated
|
||||
public static class IdentityValueSerializer implements ValueSerializer<String, String>
|
||||
{
|
||||
@Override
|
||||
public int serializedSize(String key, String value, int userVersion)
|
||||
{
|
||||
return TypeSizes.INT_SIZE + key.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(String key, String value, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,11 +28,6 @@ import org.junit.Assert;
|
|||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.cql3.terms.Constants;
|
||||
import org.apache.cassandra.cql3.terms.MultiElements;
|
||||
import org.apache.cassandra.cql3.terms.Sets;
|
||||
import org.apache.cassandra.cql3.terms.Term;
|
||||
import org.apache.cassandra.cql3.terms.Terms;
|
||||
import org.apache.cassandra.db.Clustering;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.Int32Type;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableList;
|
|||
import com.google.common.collect.Iterators;
|
||||
|
||||
import accord.local.CommandStores;
|
||||
import accord.local.StoreParticipants;
|
||||
import accord.primitives.Route;
|
||||
import org.apache.cassandra.config.CassandraRelevantProperties;
|
||||
import org.apache.cassandra.distributed.shared.WithProperties;
|
||||
|
|
@ -51,9 +52,9 @@ import accord.local.Command;
|
|||
import accord.local.CommandStore;
|
||||
import accord.local.DurableBefore;
|
||||
import accord.local.RedundantBefore;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.local.Status;
|
||||
import accord.local.Status.Durability;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.Status.Durability;
|
||||
import accord.primitives.Ballot;
|
||||
import accord.primitives.Deps;
|
||||
import accord.primitives.FullRoute;
|
||||
|
|
@ -85,7 +86,7 @@ import org.apache.cassandra.schema.KeyspaceParams;
|
|||
import org.apache.cassandra.schema.SchemaConstants;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
||||
import org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
|
@ -187,10 +188,10 @@ public class CompactionAccordIteratorsTest
|
|||
this.singleCompaction = singleCompaction;
|
||||
// Null redudnant before should make no change since we have no information on this CommandStore
|
||||
testAccordCommandsPurger(null, DurableBefore.EMPTY, expectAccordCommandsNoChange());
|
||||
// Universally durable (and global to boot) should be erased since literally everyone knows about it
|
||||
// The way Commands.shouldCleanup was implemented (when this was written) it doesn't check redundantBefore
|
||||
// at all for this
|
||||
testAccordCommandsPurger(redundantBefore(LT_TXN_ID), durableBefore(UNIVERSAL), expectAccordCommandsErase());
|
||||
// Universally and locally durable (and global to boot) should be erased since literally everyone knows about it
|
||||
testAccordCommandsPurger(redundantBefore(GT_TXN_ID), durableBefore(UNIVERSAL), expectAccordCommandsErase());
|
||||
// Universally durable but not locally; we're stale, but shouldn't erase
|
||||
testAccordCommandsPurger(redundantBefore(LT_TXN_ID), durableBefore(UNIVERSAL), expectAccordCommandsNoChange());
|
||||
// With redundantBefore at the txnId there should be no change because it is < not <=
|
||||
testAccordCommandsPurger(redundantBefore(TXN_ID), durableBefore(MAJORITY), expectAccordCommandsNoChange());
|
||||
testAccordCommandsPurger(redundantBefore(LT_TXN_ID), durableBefore(MAJORITY), expectAccordCommandsNoChange());
|
||||
|
|
@ -262,7 +263,7 @@ public class CompactionAccordIteratorsTest
|
|||
return partitions -> {
|
||||
assertEquals(1, partitions.size());
|
||||
Partition partition = partitions.get(0);
|
||||
PartitionKey partitionKey = new PartitionKey(partition.metadata().id, partition.partitionKey());
|
||||
TokenKey partitionKey = new TokenKey(partition.metadata().id, partition.partitionKey().getToken());
|
||||
CommandsForKey cfk = CommandsForKeysAccessor.getCommandsForKey(partitionKey, ((Row) partition.unfilteredIterator().next()));
|
||||
assertEquals(TXN_IDS.length, cfk.size());
|
||||
for (int i = 0; i < TXN_IDS.length; ++i)
|
||||
|
|
@ -384,7 +385,7 @@ public class CompactionAccordIteratorsTest
|
|||
{
|
||||
Ranges ranges = AccordTestUtils.fullRange(AccordTestUtils.keys(table, 42));
|
||||
txnId = txnId.as(Kind.Read, Range);
|
||||
return RedundantBefore.create(ranges, Long.MIN_VALUE, Long.MAX_VALUE, txnId, txnId, LT_TXN_ID.as(Range));
|
||||
return RedundantBefore.create(ranges, Long.MIN_VALUE, Long.MAX_VALUE, txnId, txnId, txnId, LT_TXN_ID.as(Range));
|
||||
}
|
||||
|
||||
enum DurableBeforeType
|
||||
|
|
@ -469,19 +470,19 @@ public class CompactionAccordIteratorsTest
|
|||
PartialDeps partialDeps = Deps.NONE.intersecting(AccordTestUtils.fullRange(txn));
|
||||
PartialTxn partialTxn = txn.slice(commandStore.unsafeRangesForEpoch().currentRanges(), true);
|
||||
Route<?> partialRoute = route.slice(commandStore.unsafeRangesForEpoch().currentRanges());
|
||||
getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys(), COMMANDS), safe -> {
|
||||
getUninterruptibly(commandStore.execute(contextFor(txnId, route, COMMANDS), safe -> {
|
||||
CheckedCommands.preaccept(safe, txnId, partialTxn, route, appendDiffToKeyspace(commandStore));
|
||||
}).beginAsResult());
|
||||
flush(commandStore);
|
||||
getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys(), COMMANDS), safe -> {
|
||||
CheckedCommands.accept(safe, txnId, Ballot.ZERO, partialRoute, partialTxn.keys(), txnId, partialDeps, appendDiffToKeyspace(commandStore));
|
||||
getUninterruptibly(commandStore.execute(contextFor(txnId, route, COMMANDS), safe -> {
|
||||
CheckedCommands.accept(safe, txnId, Ballot.ZERO, partialRoute, txnId, partialDeps, appendDiffToKeyspace(commandStore));
|
||||
}).beginAsResult());
|
||||
flush(commandStore);
|
||||
getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys(), COMMANDS), safe -> {
|
||||
getUninterruptibly(commandStore.execute(contextFor(txnId, route, COMMANDS), safe -> {
|
||||
CheckedCommands.commit(safe, SaveStatus.Stable, Ballot.ZERO, txnId, route, partialTxn, txnId, partialDeps, appendDiffToKeyspace(commandStore));
|
||||
}).beginAsResult());
|
||||
flush(commandStore);
|
||||
getUninterruptibly(commandStore.execute(contextFor(txnId, txn.keys(), COMMANDS), safe -> {
|
||||
getUninterruptibly(commandStore.execute(contextFor(txnId, route, COMMANDS), safe -> {
|
||||
Pair<Writes, Result> result = AccordTestUtils.processTxnResultDirect(safe, txnId, partialTxn, txnId);
|
||||
CheckedCommands.apply(safe, txnId, route, txnId, partialDeps, partialTxn, result.left, result.right, appendDiffToKeyspace(commandStore));
|
||||
}).beginAsResult());
|
||||
|
|
@ -489,8 +490,9 @@ public class CompactionAccordIteratorsTest
|
|||
// The apply chain is asychronous, so it is easiest to just spin until it is applied
|
||||
// in order to have the updated state in the system table
|
||||
spinAssertEquals(true, 5, () -> {
|
||||
return getUninterruptibly(commandStore.submit(contextFor(txnId, txn.keys(), COMMANDS), safe -> {
|
||||
Command command = safe.get(txnId, route.homeKey()).current();
|
||||
return getUninterruptibly(commandStore.submit(contextFor(txnId, route, COMMANDS), safe -> {
|
||||
StoreParticipants participants = StoreParticipants.all(route);
|
||||
Command command = safe.get(txnId, participants).current();
|
||||
appendDiffToKeyspace(commandStore).accept(null, command);
|
||||
return command.hasBeen(Status.Applied);
|
||||
}).beginAsResult());
|
||||
|
|
@ -506,7 +508,7 @@ public class CompactionAccordIteratorsTest
|
|||
UntypedResultSet commandsForKeyTable = QueryProcessor.executeInternal("SELECT * FROM " + ACCORD_KEYSPACE_NAME + "." + COMMANDS_FOR_KEY + ";");
|
||||
logger.info(commandsForKeyTable.toStringUnsafe());
|
||||
assertEquals(1, commandsForKeyTable.size());
|
||||
CommandsForKey cfk = CommandsForKeySerializer.fromBytes((Key) key, commandsForKeyTable.iterator().next().getBytes("data"));
|
||||
CommandsForKey cfk = CommandsForKeySerializer.fromBytes(((Key) key).toUnseekable(), commandsForKeyTable.iterator().next().getBytes("data"));
|
||||
assertEquals(txnIds.length, cfk.size());
|
||||
for (int i = 0; i < txnIds.length; ++i)
|
||||
assertEquals(txnIds[i], cfk.txnId(i));
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import org.junit.Test;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import accord.local.SaveStatus;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.messages.TxnRequest;
|
||||
import accord.primitives.Routable;
|
||||
import accord.primitives.Txn;
|
||||
|
|
|
|||
|
|
@ -41,8 +41,9 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.Node;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.local.Status;
|
||||
import accord.local.StoreParticipants;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Status;
|
||||
import accord.primitives.FullKeyRoute;
|
||||
import accord.primitives.Range;
|
||||
import accord.primitives.Ranges;
|
||||
|
|
@ -399,7 +400,7 @@ public class AccordIndexStressTest extends CQLTester
|
|||
int minToken, int maxToken,
|
||||
int numRecords)
|
||||
{
|
||||
var cql = "INSERT INTO system_accord.commands (store_id, domain, txn_id, status, route, durability) VALUES (?, ?, ?, ?, ?, ?)";
|
||||
var cql = "INSERT INTO system_accord.commands (store_id, domain, txn_id, status, participants, durability) VALUES (?, ?, ?, ?, ?, ?)";
|
||||
for (int i = 0; i < numRecords; i++)
|
||||
{
|
||||
int store = rs.nextInt(0, numStores);
|
||||
|
|
@ -409,8 +410,8 @@ public class AccordIndexStressTest extends CQLTester
|
|||
ByteBuffer routeBB;
|
||||
try
|
||||
{
|
||||
Route<?> route = createRoute(rs, numRecords, i, rs.nextInt(1, 20), tables, minToken, maxToken);
|
||||
for (var u : route)
|
||||
StoreParticipants participants = StoreParticipants.all(createRoute(rs, numRecords, i, rs.nextInt(1, 20), tables, minToken, maxToken));
|
||||
for (var u : participants.route())
|
||||
{
|
||||
switch (u.domain())
|
||||
{
|
||||
|
|
@ -439,7 +440,7 @@ public class AccordIndexStressTest extends CQLTester
|
|||
throw new AssertionError("Unexpected domain: " + u.domain());
|
||||
}
|
||||
}
|
||||
routeBB = AccordKeyspace.serializeRoute(route);
|
||||
routeBB = AccordKeyspace.serializeParticipants(participants);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,8 +36,9 @@ import org.junit.Test;
|
|||
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.Node;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.local.Status.Durability;
|
||||
import accord.local.StoreParticipants;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Status.Durability;
|
||||
import accord.primitives.FullKeyRoute;
|
||||
import accord.primitives.Range;
|
||||
import accord.primitives.Ranges;
|
||||
|
|
@ -250,12 +251,12 @@ public class RouteIndexTest extends CQLTester.InMemory
|
|||
|
||||
private class InsertTxn implements UnitCommand<State, ColumnFamilyStore>
|
||||
{
|
||||
private static final String cql = "INSERT INTO system_accord.commands (store_id, domain, txn_id, status, route, durability) VALUES (?, ?, ?, ?, ?, ?)";
|
||||
private static final String cql = "INSERT INTO system_accord.commands (store_id, domain, txn_id, status, participants, durability) VALUES (?, ?, ?, ?, ?, ?)";
|
||||
private final int storeId;
|
||||
private final TxnId txnId;
|
||||
private final SaveStatus saveStatus;
|
||||
private final Durability durability;
|
||||
private final Route<?> route;
|
||||
private final StoreParticipants participants;
|
||||
|
||||
private InsertTxn(int storeId, TxnId txnId, SaveStatus saveStatus, Durability durability, Route<?> route)
|
||||
{
|
||||
|
|
@ -263,13 +264,13 @@ public class RouteIndexTest extends CQLTester.InMemory
|
|||
this.txnId = txnId;
|
||||
this.saveStatus = saveStatus;
|
||||
this.durability = durability;
|
||||
this.route = route;
|
||||
this.participants = StoreParticipants.all(route);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyUnit(State state)
|
||||
{
|
||||
for (var u : route)
|
||||
for (var u : participants.route())
|
||||
{
|
||||
switch (u.domain())
|
||||
{
|
||||
|
|
@ -302,7 +303,7 @@ public class RouteIndexTest extends CQLTester.InMemory
|
|||
@Override
|
||||
public void runUnit(ColumnFamilyStore sut) throws Throwable
|
||||
{
|
||||
execute(cql, storeId, txnId.domain().ordinal(), AccordKeyspace.serializeTimestamp(txnId), saveStatus.ordinal(), AccordKeyspace.serializeRoute(route), durability.ordinal());
|
||||
execute(cql, storeId, txnId.domain().ordinal(), AccordKeyspace.serializeTimestamp(txnId), saveStatus.ordinal(), AccordKeyspace.serializeParticipants(participants), durability.ordinal());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -313,7 +314,7 @@ public class RouteIndexTest extends CQLTester.InMemory
|
|||
", txnId=" + txnId +
|
||||
", saveStatus=" + saveStatus +
|
||||
", durability=" + durability +
|
||||
", route=" + route +
|
||||
", participants=" + participants +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,8 @@
|
|||
package org.apache.cassandra.journal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
|
@ -38,8 +36,6 @@ import static org.junit.Assert.assertEquals;
|
|||
|
||||
public class JournalTest
|
||||
{
|
||||
private static final Set<Integer> SENTINEL_HOSTS = Collections.singleton(0);
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp()
|
||||
{
|
||||
|
|
@ -87,29 +83,6 @@ public class JournalTest
|
|||
journal.shutdown();
|
||||
}
|
||||
|
||||
static class ByteBufferSerializer implements ValueSerializer<TimeUUID, ByteBuffer>
|
||||
{
|
||||
static final ByteBufferSerializer INSTANCE = new ByteBufferSerializer();
|
||||
|
||||
public int serializedSize(TimeUUID key, ByteBuffer value, int userVersion)
|
||||
{
|
||||
return Integer.BYTES + value.capacity();
|
||||
}
|
||||
|
||||
public void serialize(TimeUUID key, ByteBuffer value, DataOutputPlus out, int userVersion) throws IOException
|
||||
{
|
||||
out.writeInt(value.capacity());
|
||||
out.write(value);
|
||||
}
|
||||
|
||||
public ByteBuffer deserialize(TimeUUID key, DataInputPlus in, int userVersion) throws IOException
|
||||
{
|
||||
byte[] bytes = new byte[in.readInt()];
|
||||
in.readFully(bytes);
|
||||
return ByteBuffer.wrap(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
static class LongSerializer implements ValueSerializer<TimeUUID, Long>
|
||||
{
|
||||
static final LongSerializer INSTANCE = new LongSerializer();
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ public class StorageServiceTest extends TestBaseImpl
|
|||
ServerTestUtils.prepareServerNoRegister();
|
||||
DatabaseDescriptor.daemonInitialization();
|
||||
DatabaseDescriptor.setTransientReplicationEnabledUnsafe(true);
|
||||
DatabaseDescriptor.setAccordTransactionsEnabled(false);
|
||||
|
||||
ClusterMetadataService.instance().commit(new Register(NodeAddresses.current(),
|
||||
SimpleLocationProvider.LOCATION,
|
||||
|
|
|
|||
|
|
@ -34,9 +34,10 @@ import accord.api.Result;
|
|||
import accord.impl.TimestampsForKey;
|
||||
import accord.impl.TimestampsForKeys;
|
||||
import accord.local.Command;
|
||||
import accord.local.StoreParticipants;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.local.CommonAttributes;
|
||||
import accord.local.SaveStatus;
|
||||
import accord.primitives.SaveStatus;
|
||||
import accord.primitives.Ballot;
|
||||
import accord.primitives.PartialDeps;
|
||||
import accord.primitives.PartialTxn;
|
||||
|
|
@ -61,12 +62,13 @@ import org.apache.cassandra.schema.SchemaConstants;
|
|||
import org.apache.cassandra.schema.TableId;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
|
||||
import org.apache.cassandra.service.accord.api.PartitionKey;
|
||||
import org.apache.cassandra.service.accord.serializers.CommandSerializers;
|
||||
import org.apache.cassandra.service.consensus.TransactionalMode;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
import static accord.local.Status.Durability.Majority;
|
||||
import static accord.primitives.Status.Durability.Majority;
|
||||
import static com.google.common.collect.Iterables.getOnlyElement;
|
||||
import static org.apache.cassandra.cql3.statements.schema.CreateTableStatement.parse;
|
||||
import static org.apache.cassandra.service.accord.AccordTestUtils.Commands.preaccepted;
|
||||
|
|
@ -125,7 +127,7 @@ public class AccordCommandStoreTest
|
|||
PartialTxn txn = createPartialTxn(0);
|
||||
Route<?> route = RoutingKeys.of(key.toUnseekable()).toRoute(key.toUnseekable());
|
||||
attrs.partialTxn(txn);
|
||||
attrs.route(route);
|
||||
attrs.setParticipants(StoreParticipants.all(route));
|
||||
attrs.durability(Majority);
|
||||
attrs.partialTxn(txn);
|
||||
Ballot promised = ballot(1, clock.incrementAndGet(), 1);
|
||||
|
|
@ -160,7 +162,7 @@ public class AccordCommandStoreTest
|
|||
Timestamp maxTimestamp = timestamp(1, clock.incrementAndGet(), 1);
|
||||
|
||||
PartialTxn txn = createPartialTxn(1);
|
||||
PartitionKey key = (PartitionKey) getOnlyElement(txn.keys());
|
||||
TokenKey key = ((PartitionKey) getOnlyElement(txn.keys())).toUnseekable();
|
||||
TxnId txnId1 = txnId(1, clock.incrementAndGet(), 1);
|
||||
TxnId txnId2 = txnId(1, clock.incrementAndGet(), 1);
|
||||
|
||||
|
|
@ -170,10 +172,10 @@ public class AccordCommandStoreTest
|
|||
AccordSafeTimestampsForKey tfk = new AccordSafeTimestampsForKey(loaded(key, null));
|
||||
tfk.initialize();
|
||||
|
||||
TimestampsForKeys.updateLastExecutionTimestamps(commandStore, tfk, txnId1, true);
|
||||
TimestampsForKeys.updateLastExecutionTimestamps(commandStore, tfk, txnId1, txnId1, true);
|
||||
Assert.assertEquals(txnId1.hlc(), AccordSafeTimestampsForKey.timestampMicrosFor(tfk.current(), txnId1, true));
|
||||
|
||||
TimestampsForKeys.updateLastExecutionTimestamps(commandStore, tfk, txnId2, true);
|
||||
TimestampsForKeys.updateLastExecutionTimestamps(commandStore, tfk, txnId2, txnId2, true);
|
||||
Assert.assertEquals(txnId2.hlc(), AccordSafeTimestampsForKey.timestampMicrosFor(tfk.current(), txnId2, true));
|
||||
|
||||
Assert.assertEquals(txnId2, tfk.current().lastExecutedTimestamp());
|
||||
|
|
@ -200,7 +202,7 @@ public class AccordCommandStoreTest
|
|||
AccordCommandStore commandStore = createAccordCommandStore(clock::incrementAndGet, "ks", "tbl");
|
||||
|
||||
PartialTxn txn = createPartialTxn(1);
|
||||
PartitionKey key = (PartitionKey) getOnlyElement(txn.keys());
|
||||
TokenKey key = ((PartitionKey) getOnlyElement(txn.keys())).toUnseekable();
|
||||
TxnId txnId1 = txnId(1, clock.incrementAndGet(), 1);
|
||||
TxnId txnId2 = txnId(1, clock.incrementAndGet(), 1);
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import org.junit.Test;
|
|||
|
||||
import accord.api.Key;
|
||||
import accord.api.RoutingKey;
|
||||
import accord.local.StoreParticipants;
|
||||
import accord.local.cfk.CommandsForKey;
|
||||
import accord.local.Command;
|
||||
import accord.local.KeyHistory;
|
||||
|
|
@ -33,7 +34,7 @@ import accord.local.Node;
|
|||
import accord.local.PreLoadContext;
|
||||
import accord.local.SafeCommand;
|
||||
import accord.local.SafeCommandStore;
|
||||
import accord.local.Status;
|
||||
import accord.primitives.Status;
|
||||
import accord.messages.Accept;
|
||||
import accord.messages.Commit;
|
||||
import accord.messages.PreAccept;
|
||||
|
|
@ -105,7 +106,7 @@ public class AccordCommandTest
|
|||
|
||||
// Check preaccept
|
||||
getUninterruptibly(commandStore.execute(preAccept, safeStore -> {
|
||||
SafeCommand safeCommand = safeStore.get(txnId, txnId, route);
|
||||
SafeCommand safeCommand = safeStore.get(txnId, StoreParticipants.all(route));
|
||||
Command before = safeCommand.current();
|
||||
PreAccept.PreAcceptReply reply = preAccept.apply(safeStore);
|
||||
Command after = safeCommand.current();
|
||||
|
|
@ -120,12 +121,12 @@ public class AccordCommandTest
|
|||
|
||||
getUninterruptibly(commandStore.execute(preAccept, safeStore -> {
|
||||
Command before = safeStore.ifInitialised(txnId).current();
|
||||
SafeCommand safeCommand = safeStore.get(txnId, txnId, route);
|
||||
SafeCommand safeCommand = safeStore.get(txnId, StoreParticipants.all(route));
|
||||
Assert.assertEquals(txnId, before.executeAt());
|
||||
Assert.assertEquals(Status.PreAccepted, before.status());
|
||||
Assert.assertTrue(before.partialDeps() == null || before.partialDeps().isEmpty());
|
||||
|
||||
CommandsForKey cfk = safeStore.get(key(1)).current();
|
||||
CommandsForKey cfk = safeStore.get(key(1).toUnseekable()).current();
|
||||
Assert.assertTrue(cfk.indexOf(txnId) >= 0);
|
||||
Command after = safeCommand.current();
|
||||
AccordTestUtils.appendCommandsBlocking(commandStore, before, after);
|
||||
|
|
@ -137,10 +138,10 @@ public class AccordCommandTest
|
|||
PartialDeps deps;
|
||||
try (PartialDeps.Builder builder = PartialDeps.builder(route))
|
||||
{
|
||||
builder.add(key, txnId2);
|
||||
builder.add(key.toUnseekable(), txnId2);
|
||||
deps = builder.build();
|
||||
}
|
||||
Accept accept = Accept.SerializerSupport.create(txnId, route, 1, 1, Ballot.ZERO, executeAt, partialTxn.keys(), deps);
|
||||
Accept accept = Accept.SerializerSupport.create(txnId, route, 1, 1, Ballot.ZERO, executeAt, deps);
|
||||
|
||||
getUninterruptibly(commandStore.execute(accept, safeStore -> {
|
||||
Command before = safeStore.ifInitialised(txnId).current();
|
||||
|
|
@ -157,23 +158,23 @@ public class AccordCommandTest
|
|||
Assert.assertEquals(Status.Accepted, before.status());
|
||||
Assert.assertEquals(deps, before.partialDeps());
|
||||
|
||||
CommandsForKey cfk = safeStore.get(key(1)).current();
|
||||
CommandsForKey cfk = safeStore.get(key(1).toUnseekable()).current();
|
||||
Assert.assertTrue(cfk.indexOf(txnId) >= 0);
|
||||
Command after = safeStore.ifInitialised(txnId).current();
|
||||
AccordTestUtils.appendCommandsBlocking(commandStore, before, after);
|
||||
}));
|
||||
|
||||
// check commit
|
||||
Commit commit = Commit.SerializerSupport.create(txnId, route, 1, Commit.Kind.StableWithTxnAndDeps, Ballot.ZERO, executeAt, partialTxn.keys(), partialTxn, deps, fullRoute, null);
|
||||
Commit commit = Commit.SerializerSupport.create(txnId, route, 1, 1, Commit.Kind.StableWithTxnAndDeps, Ballot.ZERO, executeAt, partialTxn, deps, fullRoute, null);
|
||||
getUninterruptibly(commandStore.execute(commit, commit::apply));
|
||||
|
||||
getUninterruptibly(commandStore.execute(PreLoadContext.contextFor(txnId, Keys.of(key), KeyHistory.COMMANDS), safeStore -> {
|
||||
getUninterruptibly(commandStore.execute(PreLoadContext.contextFor(txnId, Keys.of(key).toParticipants(), KeyHistory.COMMANDS), safeStore -> {
|
||||
Command before = safeStore.ifInitialised(txnId).current();
|
||||
Assert.assertEquals(commit.executeAt, before.executeAt());
|
||||
Assert.assertTrue(before.hasBeen(Status.Committed));
|
||||
Assert.assertEquals(commit.partialDeps, before.partialDeps());
|
||||
|
||||
CommandsForKey cfk = safeStore.get(key(1)).current();
|
||||
CommandsForKey cfk = safeStore.get(key(1).toUnseekable()).current();
|
||||
Assert.assertTrue(cfk.indexOf(txnId) >= 0);
|
||||
Command after = safeStore.ifInitialised(txnId).current();
|
||||
AccordTestUtils.appendCommandsBlocking(commandStore, before, after);
|
||||
|
|
@ -216,7 +217,7 @@ public class AccordCommandTest
|
|||
|
||||
private static void persistDiff(AccordCommandStore commandStore, SafeCommandStore safeStore, TxnId txnId, Route<?> route, Runnable runnable)
|
||||
{
|
||||
SafeCommand safeCommand = safeStore.get(txnId, txnId, route);
|
||||
SafeCommand safeCommand = safeStore.get(txnId, StoreParticipants.all(route));
|
||||
Command before = safeCommand.current();
|
||||
runnable.run();
|
||||
Command after = safeCommand.current();
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
package org.apache.cassandra.service.accord;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
|
@ -64,7 +63,7 @@ public class AccordJournalOrderTest
|
|||
{
|
||||
if (new File(DatabaseDescriptor.getAccordJournalDirectory()).exists())
|
||||
ServerTestUtils.cleanupDirectory(DatabaseDescriptor.getAccordJournalDirectory());
|
||||
AccordJournal accordJournal = new AccordJournal(SimpleAccordEndpointMapper.INSTANCE, TestParams.INSTANCE);
|
||||
AccordJournal accordJournal = new AccordJournal(TestParams.INSTANCE);
|
||||
accordJournal.start(null);
|
||||
RandomSource randomSource = RandomSource.wrap(new Random());
|
||||
TxnId id1 = AccordGens.txnIds().next(randomSource);
|
||||
|
|
@ -74,11 +73,10 @@ public class AccordJournalOrderTest
|
|||
for (int i = 0; i < 10_000; i++)
|
||||
{
|
||||
TxnId txnId = randomSource.nextBoolean() ? id1 : id2;
|
||||
JournalKey key = new JournalKey(txnId, randomSource.nextInt(5));
|
||||
JournalKey key = new JournalKey(txnId, JournalKey.Type.COMMAND_DIFF, randomSource.nextInt(5));
|
||||
res.compute(key, (k, prev) -> prev == null ? 1 : prev + 1);
|
||||
accordJournal.appendCommand(key.commandStoreId,
|
||||
Collections.singletonList(new SavedCommand.DiffWriter(txnId, null, null)),
|
||||
null,
|
||||
new SavedCommand.DiffWriter(txnId, null, null),
|
||||
() -> {});
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue