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:
Alex Petrov 2024-09-12 13:45:10 +02:00 committed by David Capwell
parent 1c7c311a2d
commit 1c269348a4
119 changed files with 3534 additions and 2051 deletions

@ -1 +1 @@
Subproject commit 593e042535d60e773cfa5f7c4b6a63e2fb6e5b30
Subproject commit 2a7aceb96cb1e03bcfe150403b9d245b1d2562f9

View File

@ -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.

View File

@ -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
}

View File

@ -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)
{

View File

@ -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");
}
}

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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);

View File

@ -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");

View File

@ -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())
{

View File

@ -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(),

View File

@ -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;

View File

@ -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);

View File

@ -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();
}
}
}

View File

@ -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;
}

View File

@ -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;
/**

View File

@ -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)));
}

View File

@ -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 */

View File

@ -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);
});
}
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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(); }

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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));
}
}
}

View File

@ -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)
{

View File

@ -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;

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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());
}
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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();
}

View File

@ -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,

View File

@ -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)
{

View File

@ -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
{

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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 +
'}';

View File

@ -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

View File

@ -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)
{

View File

@ -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(),

View File

@ -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()
{

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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
{

View File

@ -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;

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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);
}
};

View File

@ -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);
}

View File

@ -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);
}
};
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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 };
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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);
}
};

View File

@ -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);
}
};

View File

@ -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;

View File

@ -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;

View File

@ -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>
{

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
}
};
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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);

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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.
*

View File

@ -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))

View File

@ -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

View File

@ -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());
});
}
}

View File

@ -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]));
}
}
}
}

View File

@ -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());

View File

@ -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<>());
}
}

View File

@ -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);
}
}

View File

@ -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
{

View File

@ -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;

View File

@ -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));

View File

@ -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;

View File

@ -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)
{

View File

@ -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 +
'}';
}
}

View File

@ -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();

View File

@ -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,

View File

@ -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);

View File

@ -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();

View File

@ -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