InMemoryRangeIndex

Also Add:
 - txn_graph and txn_graph_all virtual tables for debugging transaction dependency graphs
 - txn_cache virtual table to debug accord cache contents
Also Fix:
 - NPE in AccordDebugKeyspace.TxnOpsTable
 - AccordCoordinatorMetrics latency metrics should use Timer not Histogram
 - OnDiskIndex.readLast
Also Improve:
 - Catchup should run after progress log is started
 - Cancelling AccordTask should early-terminate range scanning
 - Improve RangeTxnScanner description
 - Add ballot to txn tables
 - Add NodeId to logging output

patch by Benedict; reviewed by Alex Petrov for CASSANDRA-21306
This commit is contained in:
Benedict Elliott Smith 2025-12-03 12:38:20 +00:00
parent 5fd8290157
commit 6a665f3ee4
62 changed files with 2699 additions and 1094 deletions

@ -1 +1 @@
Subproject commit 93d78be37ef904118da2426716b5d77c0fb227db
Subproject commit 8229cbf269f14553c338cf47efc9466bbc7ee81b

View File

@ -30,6 +30,7 @@ import org.apache.cassandra.service.consensus.TransactionalMode;
import static org.apache.cassandra.config.AccordSpec.QueueShardModel.THREAD_POOL_PER_SHARD;
import static org.apache.cassandra.config.AccordSpec.QueueSubmissionModel.SYNC;
import static org.apache.cassandra.config.AccordSpec.RangeIndexMode.in_memory;
public class AccordSpec
{
@ -184,9 +185,13 @@ public class AccordSpec
public DurationSpec.IntSecondsBound catchup_on_start_success_latency = new DurationSpec.IntSecondsBound(60);
public DurationSpec.IntSecondsBound catchup_on_start_fail_latency = new DurationSpec.IntSecondsBound(900);
public int catchup_on_start_max_attempts = 5;
public boolean catchup_on_start_exit_on_failure = true;
// TODO (required): roll this back to catchup_on_start_exit_on_failure: true
public boolean catchup_on_start_exit_on_failure = false;
public boolean catchup_on_start = true;
public enum RangeIndexMode { in_memory, journal_sai }
public RangeIndexMode range_index_mode = in_memory;
public final JournalSpec journal = new JournalSpec();
public enum MixedTimeSourceHandling

View File

@ -735,30 +735,31 @@ public final class StatementRestrictions
VariableSpecifications boundNames,
IndexRegistry indexRegistry)
{
if (expressions.size() > 1)
if (expressions.size() > 1 && !indexRegistry.supportsMultipleIndexExpressions())
throw new InvalidRequestException(IndexRestrictions.MULTIPLE_EXPRESSIONS);
CustomIndexExpression expression = expressions.get(0);
for (CustomIndexExpression expression : expressions)
{
QualifiedName name = expression.targetIndex;
QualifiedName name = expression.targetIndex;
if (name.hasKeyspace() && !name.getKeyspace().equals(table.keyspace))
throw IndexRestrictions.invalidIndex(expression.targetIndex, table);
if (name.hasKeyspace() && !name.getKeyspace().equals(table.keyspace))
throw IndexRestrictions.invalidIndex(expression.targetIndex, table);
if (!table.indexes.has(expression.targetIndex.getName()))
throw IndexRestrictions.indexNotFound(expression.targetIndex, table);
if (!table.indexes.has(expression.targetIndex.getName()))
throw IndexRestrictions.indexNotFound(expression.targetIndex, table);
Index index = indexRegistry.getIndex(table.indexes.get(expression.targetIndex.getName()).get());
if (!index.getIndexMetadata().isCustom())
throw IndexRestrictions.nonCustomIndexInExpression(expression.targetIndex);
Index index = indexRegistry.getIndex(table.indexes.get(expression.targetIndex.getName()).get());
if (!index.getIndexMetadata().isCustom())
throw IndexRestrictions.nonCustomIndexInExpression(expression.targetIndex);
AbstractType<?> expressionType = index.customExpressionValueType();
if (expressionType == null)
throw IndexRestrictions.customExpressionNotSupported(expression.targetIndex);
AbstractType<?> expressionType = index.customExpressionValueType();
if (expressionType == null)
throw IndexRestrictions.customExpressionNotSupported(expression.targetIndex);
expression.prepareValue(table, expressionType, boundNames);
expression.prepareValue(table, expressionType, boundNames);
filterRestrictions.add(expression);
filterRestrictions.add(expression);
}
}
public RowFilter getRowFilter(IndexRegistry indexRegistry, QueryOptions options)

View File

@ -464,7 +464,7 @@ public abstract class ReadCommand extends AbstractReadQuery
static Index.QueryPlan findIndexQueryPlan(TableMetadata table, RowFilter rowFilter)
{
if (table.indexes.isEmpty() || rowFilter.isEmpty())
if (table.indexes.isEmpty() || rowFilter.isEmpty() || table.isVirtual())
return null;
ColumnFamilyStore cfs = Keyspace.openAndGetStore(table);

View File

@ -0,0 +1,85 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.db.marshal;
import java.nio.ByteBuffer;
import accord.primitives.Timestamp;
import org.apache.cassandra.cql3.functions.ArgumentDeserializer;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.serializers.UTF8Serializer;
import org.apache.cassandra.utils.ByteBufferUtil;
public class TimestampUtf8Type extends PseudoUtf8Type
{
public static final TimestampUtf8Type instance = new TimestampUtf8Type();
static final TypeSerializer<String> timestampSerializer = new UTF8Serializer()
{
@Override
public <V> void validate(V value, ValueAccessor<V> accessor) throws MarshalException
{
super.validate(value, accessor);
String str = deserialize(value, accessor);
if (!str.isEmpty() && null == Timestamp.tryParse(str))
throw new MarshalException("Invalid Timestamp: " + str);
}
};
private static final ArgumentDeserializer ARGUMENT_DESERIALIZER = new DefaultArgumentDeserializer(instance);
private static final ByteBuffer MASKED_VALUE = ByteBufferUtil.EMPTY_BYTE_BUFFER;
TimestampUtf8Type() {} // singleton
String describe() { return "TxnId"; }
@Override
public TypeSerializer<String> getSerializer()
{
return timestampSerializer;
}
@Override
public <VL, VR> int compareCustom(VL left, ValueAccessor<VL> accessorL, VR right, ValueAccessor<VR> accessorR)
{
String leftStr = UTF8Serializer.instance.deserialize(left, accessorL);
String rightStr = UTF8Serializer.instance.deserialize(right, accessorR);
if (leftStr.isEmpty() || rightStr.isEmpty())
{
if (leftStr.isEmpty() && rightStr.isEmpty())
return 0;
return leftStr.isEmpty() ? -1 : 1;
}
Timestamp leftId = Timestamp.parse(leftStr);
Timestamp rightId = Timestamp.parse(rightStr);
return leftId.compareTo(rightId);
}
@Override
public ArgumentDeserializer getArgumentDeserializer()
{
return ARGUMENT_DESERIALIZER;
}
@Override
public ByteBuffer getMaskedValue()
{
return MASKED_VALUE;
}
}

View File

@ -31,6 +31,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import accord.primitives.Ranges;
import accord.primitives.TxnId;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
@ -230,7 +231,7 @@ public class CassandraStreamReceiver implements StreamReceiver
long deadlineNanos = startedAtNanos + timeoutNanos;
// TODO (expected): use the source bounds for the streams to avoid waiting unnecessarily long
AccordService.getBlocking(accordService.maxConflict(accordRanges)
.flatMap(min -> accordService.sync("[Stream #" + session.planId() + ']', min, accordRanges, null, Self, NoRemote, timeoutNanos, NANOSECONDS).chain())
.flatMap(min -> accordService.sync("[Stream #" + session.planId() + ']', TxnId.atLeast(min), accordRanges, null, Self, NoRemote, timeoutNanos, NANOSECONDS).chain())
, accordRanges, new LatencyRequestBookkeeping(cfs.metric.accordPostStreamRepair), startedAtNanos, deadlineNanos);
}

View File

@ -19,20 +19,27 @@ package org.apache.cassandra.db.virtual;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import accord.utils.Invariants;
import org.apache.cassandra.config.DatabaseDescriptor;
@ -47,6 +54,7 @@ import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.filter.DataLimits;
import org.apache.cassandra.db.filter.IndexHints;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CompositeType;
@ -63,9 +71,13 @@ import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.Bounds;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.ReadTimeoutException;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.index.IndexRegistry;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.ClientWarn;
import org.apache.cassandra.utils.BulkIterator;
import org.apache.cassandra.utils.Clock;
@ -820,4 +832,76 @@ public abstract class AbstractLazyVirtualTable implements VirtualTable
}
return Clustering.make(clusteringByteBuffers);
}
/**
* An empty {@code IndexRegistry}
*/
static IndexRegistry indexes(Index... indexes)
{
final List<Index> list = ImmutableList.copyOf(indexes);
return new IndexRegistry()
{
@Override
public void registerIndex(Index index, Index.Group.Key groupKey, Supplier<Index.Group> groupSupplier)
{
}
@Override
public void unregisterIndex(Index index, Index.Group.Key groupKey)
{
}
@Override
public Collection<Index> listIndexes()
{
return list;
}
@Override
public Collection<Index.Group> listIndexGroups()
{
return Collections.emptySet();
}
@Override
public Index getIndex(IndexMetadata indexMetadata)
{
for (Index index : indexes)
{
if (index.getIndexMetadata() == indexMetadata)
return index;
}
return null;
}
@Override
public Index getIndexByName(String indexName)
{
for (Index index : indexes)
{
if (index.getIndexMetadata().name.equals(indexName))
return index;
}
return null;
}
@Override
public Optional<Index> getBestIndexFor(RowFilter.Expression expression, IndexHints indexHints)
{
return Optional.empty();
}
@Override
public void validate(PartitionUpdate update, ClientState state)
{
}
@Override
public boolean supportsMultipleIndexExpressions()
{
return true;
}
};
}
}

View File

@ -41,6 +41,7 @@ import java.util.stream.Collectors;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.slf4j.Logger;
@ -79,6 +80,7 @@ import accord.local.cfk.CommandsForKey;
import accord.local.cfk.CommandsForKey.TxnInfo;
import accord.local.cfk.SafeCommandsForKey;
import accord.local.durability.ShardDurability;
import accord.primitives.Ballot;
import accord.primitives.FullRoute;
import accord.primitives.Known;
import accord.primitives.Participants;
@ -110,6 +112,7 @@ import org.apache.cassandra.db.DataRange;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.PartitionPosition;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CompositeType;
import org.apache.cassandra.db.marshal.Int32Type;
@ -119,11 +122,16 @@ import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.dht.NormalizedRanges;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.index.IndexRegistry;
import org.apache.cassandra.index.accord.NoOpIndex;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.schema.Indexes;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.accord.AccordCache;
import org.apache.cassandra.service.accord.AccordCacheEntry;
import org.apache.cassandra.service.accord.AccordCommandStore;
import org.apache.cassandra.service.accord.AccordCommandStores;
import org.apache.cassandra.service.accord.AccordExecutor;
@ -131,17 +139,20 @@ import org.apache.cassandra.service.accord.AccordJournal;
import org.apache.cassandra.service.accord.AccordKeyspace;
import org.apache.cassandra.service.accord.AccordOperations;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.AccordTracing;
import org.apache.cassandra.service.accord.AccordTracing.BucketMode;
import org.apache.cassandra.service.accord.AccordTracing.CoordinationKinds;
import org.apache.cassandra.service.accord.AccordTracing.TracePattern;
import org.apache.cassandra.service.accord.AccordTracing.TxnKindsAndDomains;
import org.apache.cassandra.service.accord.DebugBlockedTxns;
import org.apache.cassandra.service.accord.IAccordService;
import org.apache.cassandra.service.accord.JournalKey;
import org.apache.cassandra.service.accord.TokenRange;
import org.apache.cassandra.service.accord.api.AccordAgent;
import org.apache.cassandra.service.accord.api.TokenKey;
import org.apache.cassandra.service.accord.debug.AccordTracing;
import org.apache.cassandra.service.accord.debug.AccordTracing.BucketMode;
import org.apache.cassandra.service.accord.debug.AccordTracing.TracePattern;
import org.apache.cassandra.service.accord.debug.CoordinationKinds;
import org.apache.cassandra.service.accord.debug.DebugBlockedTxns;
import org.apache.cassandra.service.accord.debug.DebugTxnDepsAll;
import org.apache.cassandra.service.accord.debug.DebugTxnDepsOrdered;
import org.apache.cassandra.service.accord.debug.DebugTxnGraph;
import org.apache.cassandra.service.accord.debug.TxnKindsAndDomains;
import org.apache.cassandra.service.consensus.migration.ConsensusMigrationState;
import org.apache.cassandra.service.consensus.migration.TableMigrationState;
import org.apache.cassandra.tcm.ClusterMetadata;
@ -150,6 +161,8 @@ import org.apache.cassandra.utils.LocalizeString;
import org.apache.cassandra.utils.concurrent.Future;
import static accord.coordinate.Infer.InvalidIf.NotKnownToBeInvalid;
import static accord.impl.CommandChange.Field.ACCEPTED;
import static accord.impl.CommandChange.Field.PROMISED;
import static accord.local.RedundantStatus.Property.GC_BEFORE;
import static accord.local.RedundantStatus.Property.LOCALLY_APPLIED;
import static accord.local.RedundantStatus.Property.LOCALLY_DURABLE_TO_COMMAND_STORE;
@ -170,6 +183,7 @@ import static org.apache.cassandra.db.virtual.AbstractLazyVirtualTable.OnTimeout
import static org.apache.cassandra.db.virtual.VirtualTable.Sorted.ASC;
import static org.apache.cassandra.db.virtual.VirtualTable.Sorted.SORTED;
import static org.apache.cassandra.db.virtual.VirtualTable.Sorted.UNSORTED;
import static org.apache.cassandra.schema.IndexMetadata.Kind.CUSTOM;
import static org.apache.cassandra.schema.SchemaConstants.VIRTUAL_ACCORD_DEBUG;
import static org.apache.cassandra.service.accord.AccordService.toFuture;
import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime;
@ -196,7 +210,10 @@ public class AccordDebugKeyspace extends VirtualKeyspace
public static final String REDUNDANT_BEFORE = "redundant_before";
public static final String REJECT_BEFORE = "reject_before";
public static final String TXN = "txn";
public static final String TXN_CACHE = "txn_cache";
public static final String TXN_BLOCKED_BY = "txn_blocked_by";
public static final String TXN_GRAPH = "txn_graph";
public static final String TXN_GRAPH_ALL = "txn_graph_all";
public static final String TXN_PATTERN_TRACE = "txn_pattern_trace";
public static final String TXN_PATTERN_TRACES = "txn_pattern_traces";
public static final String TXN_TRACE = "txn_trace";
@ -204,7 +221,11 @@ public class AccordDebugKeyspace extends VirtualKeyspace
public static final String TXN_OPS = "txn_ops";
public static final String SHARD_EPOCHS = "shard_epochs";
public static final String INTERSECTS_INDEX_NAME = "intersects";
public static final String KIND_INDEX_NAME = "kind";
private static final Function<Object, String> TO_STRING = AccordDebugKeyspace::toStringOrNull;
private static final Function<Object, String> TO_BALLOT_STRING = b -> b == null ? null : b.equals(Ballot.MAX) ? "+Inf" : b.toString();
public static final AccordDebugKeyspace instance = new AccordDebugKeyspace();
@ -231,6 +252,9 @@ public class AccordDebugKeyspace extends VirtualKeyspace
new RejectBeforeTable(),
new TxnBlockedByTable(),
new TxnTable(),
new TxnCacheTable(),
new TxnGraphTable(),
new TxnGraphAllTable(),
new TxnTraceTable(),
new TxnTracesTable(),
new TxnPatternTraceTable(),
@ -1327,7 +1351,7 @@ public class AccordDebugKeyspace extends VirtualKeyspace
" trace_bucket_sub_size int,\n" +
" trace_events text,\n" +
" PRIMARY KEY (id)" +
')', Int32Type.instance), FAIL, SORTED);
')', Int32Type.instance), FAIL, UNSORTED);
}
@Override
@ -1342,7 +1366,7 @@ public class AccordDebugKeyspace extends VirtualKeyspace
.add("bucket_seen", state.bucketSeen())
.add("chance", state.pattern().chance)
.add("current_size", state.currentSize())
.add("if_intersects", state.pattern().intersects, TxnPatternTraceTable::toString)
.add("if_intersects", state.pattern().intersects, AccordDebugKeyspace::toString)
.add("if_kind", state.pattern().kinds, TO_STRING)
.add("on_failure", state.pattern().traceFailures, TO_STRING)
.add("on_new", state.pattern().traceNew, TO_STRING)
@ -1424,49 +1448,6 @@ public class AccordDebugKeyspace extends VirtualKeyspace
tracing().setPattern(id, pattern, newBucketMode, newBucketSeen, newBucketSize, newTraceBucketMode, newTraceBucketSize, newTraceBucketSubSize, newTraceEvents);
}
private static String toString(Participants<?> participants)
{
StringBuilder out = new StringBuilder();
for (Routable r : participants)
{
if (out.length() != 0)
out.append('|');
out.append(r);
}
return out.toString();
}
private static Participants<?> parseParticipants(Object input)
{
if (input == null)
return null;
String[] vs = ((String)input).split("\\|");
if (vs.length == 0)
return RoutingKeys.EMPTY;
if (!vs[0].endsWith("]"))
{
RoutingKey[] keys = new RoutingKey[vs.length];
for (int i = 0 ; i < keys.length ; ++i)
{
try { keys[i] = TokenKey.parse(vs[i], DatabaseDescriptor.getPartitioner()); }
catch (Throwable t) { throw new InvalidRequestException("Could not parse TokenKey " + vs[0]); }
}
return RoutingKeys.of(keys);
}
else
{
TokenRange[] ranges = new TokenRange[vs.length];
for (int i = 0 ; i < ranges.length ; ++i)
{
try { ranges[i] = TokenRange.parse(vs[0], DatabaseDescriptor.getPartitioner()); }
catch (Throwable t) { throw new InvalidRequestException("Could not parse TokenKey " + vs[0]); }
}
return Ranges.of(ranges);
}
}
@Override
public void truncate()
{
@ -1484,7 +1465,7 @@ public class AccordDebugKeyspace extends VirtualKeyspace
" id int,\n" +
" txn_id 'TxnIdUtf8Type',\n" +
" PRIMARY KEY (id, txn_id)" +
')', Int32Type.instance), FAIL, SORTED, SORTED);
')', Int32Type.instance), FAIL, UNSORTED, UNSORTED);
}
@Override
@ -1603,30 +1584,32 @@ public class AccordDebugKeyspace extends VirtualKeyspace
}
// TODO (desired): don't report null as "null"
public static final class TxnTable extends AbstractJournalTable
public static abstract class AbstractTxnTable extends AbstractJournalTable
{
private TxnTable()
private AbstractTxnTable(String tableName)
{
super(parse(VIRTUAL_ACCORD_DEBUG, TXN,
super(parse(VIRTUAL_ACCORD_DEBUG, tableName,
"Accord per-CommandStore Transaction State",
"CREATE TABLE %s (\n" +
" command_store_id int,\n" +
" txn_id 'TxnIdUtf8Type',\n" +
" save_status text,\n" +
" route text,\n" +
" ballot_accepted text,\n" +
" ballot_promised text,\n" +
" deps text,\n" +
" durability text,\n" +
" execute_at text,\n" +
" executes_at_least text,\n" +
" txn text,\n" +
" deps text,\n" +
" waiting_on text,\n" +
" writes text,\n" +
" result text,\n" +
" participants_owns text,\n" +
" participants_touches text,\n" +
" participants_has_touched text,\n" +
" participants_executes text,\n" +
" participants_waits_on text,\n" +
" result text,\n" +
" save_status text,\n" +
" route text,\n" +
" txn text,\n" +
" waiting_on text,\n" +
" writes text,\n" +
" PRIMARY KEY ((command_store_id, txn_id))" +
')', PK));
}
@ -1641,7 +1624,7 @@ public class AccordDebugKeyspace extends VirtualKeyspace
if (commandStore == null)
return;
Command command = commandStore.loadCommand(key.id);
Command command = get(commandStore, key.id);
if (command == null)
return;
@ -1649,24 +1632,61 @@ public class AccordDebugKeyspace extends VirtualKeyspace
.lazyCollect(columns -> addColumns(command, columns));
}
abstract Command get(AccordCommandStore commandStore, TxnId txnId);
private static void addColumns(Command command, ColumnsCollector columns)
{
StoreParticipants participants = command.participants();
columns.add("save_status", command.saveStatus(), TO_STRING)
.add("route", participants, StoreParticipants::route, TO_STRING)
columns.add("ballot_promised", command, Command::promised, TO_BALLOT_STRING)
.add("ballot_accepted", command, Command::acceptedOrCommitted, TO_BALLOT_STRING)
.add("deps", command, Command::partialDeps, TO_STRING)
.add("durability", command, Command::durability, TO_STRING)
.add("execute_at", command, Command::executeAt, TO_STRING)
.add("executes_at_least", command, Command::executesAtLeast, TO_STRING)
.add("participants_owns", participants, p -> toStr(p, StoreParticipants::owns, StoreParticipants::stillOwns))
.add("participants_touches", participants, p -> toStr(p, StoreParticipants::touches, StoreParticipants::stillTouches))
.add("participants_has_touched", participants, StoreParticipants::hasTouched, TO_STRING)
.add("participants_executes", participants, p -> toStr(p, StoreParticipants::executes, StoreParticipants::stillExecutes))
.add("participants_waits_on", participants, p -> toStr(p, StoreParticipants::waitsOn, StoreParticipants::stillWaitsOn))
.add("durability", command, Command::durability, TO_STRING)
.add("execute_at", command, Command::executeAt, TO_STRING)
.add("executes_at_least", command, Command::executesAtLeast, TO_STRING)
.add("save_status", command.saveStatus(), TO_STRING)
.add("result", command, Command::result, TO_STRING)
.add("route", participants, StoreParticipants::route, TO_STRING)
.add("txn", command, Command::partialTxn, TO_STRING)
.add("deps", command, Command::partialDeps, TO_STRING)
.add("waiting_on", command, Command::waitingOn, TO_STRING)
.add("writes", command, Command::writes, TO_STRING)
.add("result", command, Command::result, TO_STRING);
;
}
}
public static final class TxnTable extends AbstractTxnTable
{
private TxnTable()
{
super(TXN);
}
@Override
Command get(AccordCommandStore commandStore, TxnId txnId)
{
return commandStore.loadCommand(txnId);
}
}
public static final class TxnCacheTable extends AbstractTxnTable
{
private TxnCacheTable()
{
super(TXN_CACHE);
}
@Override
Command get(AccordCommandStore commandStore, TxnId txnId)
{
try (AccordCommandStore.ExclusiveCaches caches = commandStore.lockCaches())
{
AccordCacheEntry<TxnId, Command> entry = caches.commands().getUnsafe(txnId);
return entry == null ? null : entry.getExclusive();
}
}
}
@ -1681,20 +1701,22 @@ public class AccordDebugKeyspace extends VirtualKeyspace
" command_store_id int,\n" +
" segment bigint,\n" +
" segment_position int,\n" +
" save_status text,\n" +
" route text,\n" +
" ballot_accepted text,\n" +
" ballot_promised text,\n" +
" deps text,\n" +
" durability text,\n" +
" execute_at text,\n" +
" executes_at_least text,\n" +
" txn text,\n" +
" deps text,\n" +
" writes text,\n" +
" result text,\n" +
" participants_owns text,\n" +
" participants_touches text,\n" +
" participants_has_touched text,\n" +
" participants_executes text,\n" +
" participants_waits_on text,\n" +
" result text,\n" +
" route text,\n" +
" save_status text,\n" +
" txn text,\n" +
" writes text,\n" +
" PRIMARY KEY ((command_store_id, txn_id), segment, segment_position)" +
')', PK));
}
@ -1710,20 +1732,22 @@ public class AccordDebugKeyspace extends VirtualKeyspace
StoreParticipants participants = b.participants() != null ? b.participants() : StoreParticipants.empty(key.id);
rows.add(e.segment, e.position)
.lazyCollect(columns -> {
columns.add("save_status", b.saveStatus(), TO_STRING)
.add("route", participants, StoreParticipants::route, TO_STRING)
columns.add("ballot_accepted", b.get(ACCEPTED), TO_BALLOT_STRING)
.add("ballot_promised", b.get(PROMISED), TO_BALLOT_STRING)
.add("deps", b.partialDeps(), TO_STRING)
.add("durability", b.durability(), TO_STRING)
.add("execute_at", b.executeAt(), TO_STRING)
.add("executes_at_least", b.executesAtLeast(), TO_STRING)
.add("participants_owns", participants, p -> toStr(p, StoreParticipants::owns, StoreParticipants::stillOwns))
.add("participants_touches", participants, p -> toStr(p, StoreParticipants::touches, StoreParticipants::stillTouches))
.add("participants_has_touched", participants, StoreParticipants::hasTouched, TO_STRING)
.add("participants_executes", participants, p -> toStr(p, StoreParticipants::executes, StoreParticipants::stillExecutes))
.add("participants_waits_on", participants, p -> toStr(p, StoreParticipants::waitsOn, StoreParticipants::stillWaitsOn))
.add("durability", b.durability(), TO_STRING)
.add("execute_at", b.executeAt(), TO_STRING)
.add("executes_at_least", b.executesAtLeast(), TO_STRING)
.add("result", b.result(), TO_STRING)
.add("route", participants, StoreParticipants::route, TO_STRING)
.add("save_status", b.saveStatus(), TO_STRING)
.add("txn", b.partialTxn(), TO_STRING)
.add("deps", b.partialDeps(), TO_STRING)
.add("writes", b.writes(), TO_STRING)
.add("result", b.result(), TO_STRING);
.add("writes", b.writes(), TO_STRING);
});
}
});
@ -1804,7 +1828,7 @@ public class AccordDebugKeyspace extends VirtualKeyspace
SafeCommand safeCommand = safeStore.unsafeGet(txnId);
Command command = safeCommand.current();
if (command.saveStatus() == SaveStatus.Applying)
return Commands.applyChain(safeStore, (Command.Executed) command);
return Commands.applyChain(safeStore, command);
Commands.maybeExecute(safeStore, safeCommand, command, true, true, NotifyWaitingOnPlus.adapter(ignore -> {}, true, true));
return AsyncChains.success(null);
});
@ -2089,22 +2113,17 @@ public class AccordDebugKeyspace extends VirtualKeyspace
}
}
public static class TxnBlockedByTable extends AbstractLazyVirtualTable
static abstract class AbstractTxnGraphTable extends AbstractLazyVirtualTable
{
protected TxnBlockedByTable()
protected AbstractTxnGraphTable(TableMetadata metadata, OnTimeout onTimeout, Sorted sorted)
{
super(parse(VIRTUAL_ACCORD_DEBUG, TXN_BLOCKED_BY,
"Accord Transactions Blocked By Table",
"CREATE TABLE %s (\n" +
" txn_id 'TxnIdUtf8Type',\n" +
" command_store_id int,\n" +
" depth int,\n" +
" blocked_by_key text,\n" +
" blocked_by_txn_id 'TxnIdUtf8Type',\n" +
" save_status text,\n" +
" execute_at text,\n" +
" PRIMARY KEY (txn_id, depth, command_store_id, blocked_by_txn_id, blocked_by_key)" +
')', TxnIdUtf8Type.instance), BEST_EFFORT, ASC);
super(metadata, onTimeout, sorted);
}
protected AbstractTxnGraphTable(TableMetadata metadata, OnTimeout onTimeout, Sorted sorted, Sorted sortedByPartitionKey)
{
super(metadata, onTimeout, sorted, sortedByPartitionKey);
}
@Override
@ -2117,24 +2136,165 @@ public class AccordDebugKeyspace extends VirtualKeyspace
FilterRange<Integer> depthRange = collector.filters("depth", Function.identity(), i -> i + 1, i -> i - 1);
int maxDepth = depthRange.max == null ? Integer.MAX_VALUE : depthRange.max;
// TODO (expected): cleanly handle Timestamp.NONE / Timestamp.MAX
FilterRange<String> executeAtRange = collector.filters("execute_at", Function.identity(), i -> Timestamp.parse(i).next().toString(), i -> Timestamp.parse(i).prev().toString());
FilterRange<String> parentRange = collector.filters("parent", Function.identity(), i -> Timestamp.parse(i).next().toString(), i -> Timestamp.parse(i).prev().toString());
Timestamp min = Timestamp.nonNullOrMax(Timestamp.nonNullOrMax(executeAtRange == null ? null : executeAtRange.min == null ? null : Timestamp.tryParse(executeAtRange.min), parentRange == null ? null : parentRange.min == null ? null : Timestamp.tryParse(parentRange.min)), Timestamp.NONE);
TxnKindsAndDomains kinds;
Participants<?> intersects;
{
TxnKindsAndDomains tmpKinds = TxnKindsAndDomains.ALL;
Participants<?> tmpIntersects = null;
for (RowFilter.Expression expr : collector.rowFilter().getExpressions())
{
if (expr.isCustom())
{
switch (((RowFilter.CustomExpression)expr).getTargetIndex().name)
{
default: continue;
case KIND_INDEX_NAME:
tmpKinds = TxnKindsAndDomains.parse(UTF8Type.instance.compose(expr.getIndexValue()));
break;
case INTERSECTS_INDEX_NAME:
tmpIntersects = parseParticipants(UTF8Type.instance.compose(expr.getIndexValue()));
break;
}
}
}
kinds = tmpKinds;
intersects = tmpIntersects;
}
TxnId txnId = TxnId.parse((String) pks[0]);
PartitionCollector partition = collector.partition(pks[0]);
partition.collect(rows -> {
try
{
DebugBlockedTxns.visit(AccordService.unsafeInstance(), txnId, maxDepth, collector.deadlineNanos(), txn -> {
String keyStr = txn.blockedViaKey == null ? "" : txn.blockedViaKey.toString();
String txnIdStr = txn.txnId == null || txn.txnId.equals(txnId) ? "" : txn.txnId.toString();
rows.add(txn.depth, txn.commandStoreId, txnIdStr, keyStr)
.eagerCollect(columns -> {
columns.add("save_status", txn.saveStatus, TO_STRING)
.add("execute_at", txn.executeAt, TO_STRING);
});
try { collect(txnId, maxDepth, min, kinds, intersects, collector, rows); }
catch (TimeoutException e) { throw new InternalTimeoutException(); }
});
}
abstract void collect(TxnId txnId, int maxDepth, Timestamp min, TxnKindsAndDomains kinds, Participants<?> intersecting, PartitionsCollector collector, RowsCollector rows) throws TimeoutException;
}
public static class TxnBlockedByTable extends AbstractTxnGraphTable
{
protected TxnBlockedByTable()
{
super(parse(VIRTUAL_ACCORD_DEBUG, TXN_BLOCKED_BY,
"Accord Transactions Blocked By Table",
"CREATE TABLE %s (\n" +
" txn_id 'TxnIdUtf8Type',\n" +
" depth int,\n" +
" command_store_id int,\n" +
" blocked_by_txn_id 'TxnIdUtf8Type',\n" +
" blocked_by_key text,\n" +
" execute_at 'TimestampUtf8Type',\n" +
" save_status text,\n" +
" PRIMARY KEY (txn_id, depth, command_store_id, blocked_by_txn_id, blocked_by_key)" +
')', TxnIdUtf8Type.instance), BEST_EFFORT, ASC);
}
@Override
void collect(TxnId txnId, int maxDepth, Timestamp min, TxnKindsAndDomains kinds, Participants<?> intersecting, PartitionsCollector collector, RowsCollector rows) throws TimeoutException
{
DebugBlockedTxns.visit(AccordService.unsafeInstance(), txnId, maxDepth, collector.deadlineNanos(), txn -> {
String keyStr = txn.blockedViaKey == null ? "" : txn.blockedViaKey.toString();
String txnIdStr = txn.txnId == null || txn.txnId.equals(txnId) ? "" : txn.txnId.toString();
rows.add(txn.depth, txn.commandStoreId, txnIdStr, keyStr)
.eagerCollect(columns -> {
columns.add("save_status", txn.saveStatus, TO_STRING)
.add("execute_at", txn.executeAt, TO_STRING);
});
}
catch (TimeoutException e)
});
}
}
static class TxnGraphTable extends AbstractTxnGraphTable
{
final IndexRegistry indexes;
protected TxnGraphTable()
{
super(parse(VIRTUAL_ACCORD_DEBUG, TXN_GRAPH,
"Accord Transaction Graph Table",
"CREATE TABLE %s (\n" +
" txn_id 'TxnIdUtf8Type',\n" +
" depth int,\n" +
" command_store_id int,\n" +
" parent_txn_id 'TxnIdUtf8Type',\n" +
" execute_at 'TimestampUtf8Type',\n" +
" child_txn_id 'TxnIdUtf8Type',\n" +
" save_status text,\n" +
" via text,\n" +
" PRIMARY KEY (txn_id, depth, command_store_id, parent_txn_id, execute_at, child_txn_id)" +
") WITH CLUSTERING ORDER BY (depth ASC, command_store_id ASC, parent_txn_id DESC, execute_at DESC, child_txn_id DESC)",
TxnIdUtf8Type.instance)
.unbuild()
.indexes(Indexes.of(IndexMetadata.fromSchemaMetadata(INTERSECTS_INDEX_NAME, CUSTOM, ImmutableMap.of("class_name", NoOpIndex.class.getCanonicalName(), "target", "via")),
IndexMetadata.fromSchemaMetadata(KIND_INDEX_NAME, CUSTOM, ImmutableMap.of("class_name", NoOpIndex.class.getCanonicalName(), "target", "child_txn_id"))
))
.build(), BEST_EFFORT, ASC);
indexes = indexes(new NoOpIndex(metadata().indexes.get(INTERSECTS_INDEX_NAME).get()),
new NoOpIndex(metadata().indexes.get(KIND_INDEX_NAME).get()));
}
@Override
public IndexRegistry indexes()
{
return indexes;
}
@Override
void collect(TxnId txnId, int maxDepth, Timestamp min, TxnKindsAndDomains kinds, Participants<?> intersecting, PartitionsCollector collector, RowsCollector rows) throws TimeoutException
{
DebugTxnDepsOrdered.visit(AccordService.unsafeInstance(), txnId, kinds, intersecting, min, maxDepth, collector.deadlineNanos(), parent -> {
for (DebugTxnGraph.TxnInfo info : parent.infos)
{
throw new InternalTimeoutException();
rows.add(parent.depth, parent.commandStoreId, parent.parent.toString(), (info.executeAt == null ? "" : info.executeAt.toString()), info.txnId.toString())
.eagerCollect(columns -> {
columns.add("save_status", info.saveStatus, TO_STRING)
.add("via", info.via, TO_STRING);
});
}
});
}
}
static class TxnGraphAllTable extends AbstractTxnGraphTable
{
protected TxnGraphAllTable()
{
super(parse(VIRTUAL_ACCORD_DEBUG, TXN_GRAPH_ALL,
"Accord Transaction Graph All Table",
"CREATE TABLE %s (\n" +
" txn_id 'TxnIdUtf8Type',\n" +
" depth int,\n" +
" command_store_id int,\n" +
" parent_txn_id 'TxnIdUtf8Type',\n" +
" execute_at 'TimestampUtf8Type',\n" +
" child_txn_id 'TxnIdUtf8Type',\n" +
" first_parent 'TxnIdUtf8Type',\n" +
" save_status text,\n" +
" via text,\n" +
" PRIMARY KEY (txn_id, depth, command_store_id, parent_txn_id, execute_at, child_txn_id)" +
") WITH CLUSTERING ORDER BY (depth ASC, command_store_id ASC, parent_txn_id DESC, execute_at DESC, child_txn_id DESC)",
TxnIdUtf8Type.instance), BEST_EFFORT, ASC);
}
@Override
void collect(TxnId txnId, int maxDepth, Timestamp min, TxnKindsAndDomains kinds, Participants<?> intersecting, PartitionsCollector collector, RowsCollector rows) throws TimeoutException
{
DebugTxnDepsAll.visit(AccordService.unsafeInstance(), txnId, null, TxnKindsAndDomains.ALL, min, maxDepth, collector.deadlineNanos(), parent -> {
for (DebugTxnDepsAll.TxnInfo info : parent.infos)
{
rows.add(parent.depth, parent.commandStoreId, parent.parent.toString(), (info.executeAt == null ? "" : info.executeAt.toString()), info.txnId.toString())
.eagerCollect(columns -> {
columns.add("save_status", info.saveStatus, TO_STRING)
.add("first_parent", info.firstParent, TO_STRING)
.add("via", info.via, TO_STRING);
});
}
});
}
@ -2291,11 +2451,6 @@ public class AccordDebugKeyspace extends VirtualKeyspace
return av + " (" + bv + ')';
}
private static CoordinationKind parseEventType(String input)
{
return tryParse(input, false, CoordinationKind.class, CoordinationKind::valueOf);
}
private static String toStringOrNull(Object o)
{
return toStringOrNull(o, Object::toString);
@ -2395,4 +2550,48 @@ public class AccordDebugKeyspace extends VirtualKeyspace
return TxnKindsAndDomains.parse((String) input);
}
public static Participants<?> parseParticipants(Object input)
{
if (input == null)
return null;
String str = (String) input;
if (str.isEmpty())
return RoutingKeys.EMPTY;
String[] vs = str.split("\\|");
if (!vs[0].endsWith("]"))
{
RoutingKey[] keys = new RoutingKey[vs.length];
for (int i = 0 ; i < keys.length ; ++i)
{
try { keys[i] = TokenKey.parse(vs[i], DatabaseDescriptor.getPartitioner()); }
catch (Throwable t) { throw new InvalidRequestException("Could not parse TokenKey " + vs[i]); }
}
return RoutingKeys.of(keys);
}
else
{
TokenRange[] ranges = new TokenRange[vs.length];
for (int i = 0 ; i < ranges.length ; ++i)
{
try { ranges[i] = TokenRange.parse(vs[i], DatabaseDescriptor.getPartitioner()); }
catch (Throwable t) { throw new InvalidRequestException("Could not parse TokenKey " + vs[i]); }
}
return Ranges.of(ranges);
}
}
public static String toString(Participants<?> participants)
{
StringBuilder out = new StringBuilder();
for (Routable r : participants)
{
if (out.length() != 0)
out.append('|');
out.append(r);
}
return out.toString();
}
}

View File

@ -25,6 +25,7 @@ import org.apache.cassandra.db.filter.DataLimits;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
import org.apache.cassandra.index.IndexRegistry;
import org.apache.cassandra.schema.TableMetadata;
/**
@ -102,4 +103,9 @@ public interface VirtualTable
}
default Sorted sorted() { return Sorted.UNSORTED; }
default IndexRegistry indexes()
{
return IndexRegistry.EMPTY;
}
}

View File

@ -46,6 +46,8 @@ import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.BytesType;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry;
import org.apache.cassandra.db.virtual.VirtualTable;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.index.transactions.IndexTransaction;
import org.apache.cassandra.io.sstable.Component;
@ -346,6 +348,11 @@ public interface IndexRegistry extends Iterable<Index>
Optional<Index> getBestIndexFor(RowFilter.Expression expression, IndexHints hints);
default boolean supportsMultipleIndexExpressions()
{
return false;
}
/**
* Called at write time to ensure that values present in the update
* are valid according to the rules of all registered indexes which
@ -369,6 +376,13 @@ public interface IndexRegistry extends Iterable<Index>
if (!DatabaseDescriptor.isDaemonInitialized())
return NON_DAEMON;
return table.isVirtual() ? EMPTY : Keyspace.openAndGetStore(table).indexManager;
if (!table.isVirtual())
return Keyspace.openAndGetStore(table).indexManager;
VirtualTable vtable = VirtualKeyspaceRegistry.instance.getTableNullable(table.id);
if (vtable == null)
return EMPTY;
return vtable.indexes();
}
}

View File

@ -0,0 +1,156 @@
/*
* 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.index.accord;
import java.util.Optional;
import java.util.concurrent.Callable;
import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.ReadCommand;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.WriteContext;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.index.IndexRegistry;
import org.apache.cassandra.index.transactions.IndexTransaction;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.service.ClientState;
public class NoOpIndex implements Index
{
final IndexMetadata indexMetadata;
public NoOpIndex(IndexMetadata indexMetadata)
{
this.indexMetadata = indexMetadata;
}
@Override
public IndexMetadata getIndexMetadata()
{
return indexMetadata;
}
@Override
public boolean shouldBuildBlocking()
{
return false;
}
@Override
public Optional<ColumnFamilyStore> getBackingTable()
{
return Optional.empty();
}
@Override
public Callable<?> getInitializationTask()
{
return null;
}
@Override
public Callable<?> getTruncateTask(long truncatedAt)
{
return null;
}
@Override
public Callable<?> getBlockingFlushTask()
{
return null;
}
@Override
public Callable<?> getMetadataReloadTask(IndexMetadata indexMetadata)
{
return null;
}
@Override
public void register(IndexRegistry registry)
{
registry.registerIndex(this);
}
@Override
public Callable<?> getInvalidateTask()
{
return null;
}
@Override
public void validate(PartitionUpdate update, ClientState state) throws InvalidRequestException
{
}
@Override
public Indexer indexerFor(DecoratedKey key,
RegularAndStaticColumns columns,
long nowInSec,
WriteContext ctx,
IndexTransaction.Type transactionType,
Memtable memtable)
{
return null;
}
@Override
public boolean supportsExpression(ColumnMetadata column, Operator operator)
{
return false;
}
@Override
public RowFilter getPostIndexQueryFilter(RowFilter filter)
{
return RowFilter.none();
}
@Override
public Searcher searcherFor(ReadCommand command)
{
return null;
}
@Override
public boolean dependsOn(ColumnMetadata column)
{
throw new UnsupportedOperationException();
}
@Override
public AbstractType<?> customExpressionValueType()
{
return UTF8Type.instance;
}
@Override
public long getEstimatedResultRows()
{
throw new UnsupportedOperationException();
}
}

View File

@ -218,8 +218,16 @@ final class OnDiskIndex<K> extends Index<K>
if (!mayContainId(id))
return -1L;
int keyIndex = binarySearch(id);
return keyIndex < 0 ? -1 : recordAtIndex(keyIndex);
int someIndex = binarySearch(id);
if (someIndex < 0)
return -1L;
// sorted descending, so first key index is last value index
int firstKeyIndex = someIndex;
while (firstKeyIndex > 0 && id.equals(keyAtIndex(firstKeyIndex - 1)))
--firstKeyIndex;
return recordAtIndex(firstKeyIndex);
}
@Override

View File

@ -75,17 +75,17 @@ public class AccordCoordinatorMetrics
/**
* A histogram of the time to preaccept on this coordinator
*/
public final Histogram preacceptLatency;
public final Timer preacceptLatency;
/**
* A histogram of the time to begin execution on this coordinator
*/
public final Histogram executeLatency;
public final Timer executeLatency;
/**
* A histogram of the time to complete execution on this coordinator
*/
public final Histogram applyLatency;
public final Timer applyLatency;
/**
* The number of epochs used to coordinate the transaction
@ -161,9 +161,9 @@ public class AccordCoordinatorMetrics
{
DefaultNameFactory coordinator = new DefaultNameFactory(ACCORD_COORDINATOR, scope);
dependencies = Metrics.histogram(coordinator.createMetricName(COORDINATOR_DEPENDENCIES), true);
preacceptLatency = Metrics.histogram(coordinator.createMetricName(COORDINATOR_PREACCEPT_LATENCY), true);
executeLatency = Metrics.histogram(coordinator.createMetricName(COORDINATOR_EXECUTE_LATENCY), true);
applyLatency = Metrics.histogram(coordinator.createMetricName(COORDINATOR_APPLY_LATENCY), true);
preacceptLatency = Metrics.timer(coordinator.createMetricName(COORDINATOR_PREACCEPT_LATENCY));
executeLatency = Metrics.timer(coordinator.createMetricName(COORDINATOR_EXECUTE_LATENCY));
applyLatency = Metrics.timer(coordinator.createMetricName(COORDINATOR_APPLY_LATENCY));
epochs = Metrics.histogram(coordinator.createMetricName(COORDINATOR_EPOCHS), true);
keys = Metrics.histogram(coordinator.createMetricName(COORDINATOR_KEYS), true);
tables = Metrics.histogram(coordinator.createMetricName(COORDINATOR_TABLES), true);
@ -230,7 +230,7 @@ public class AccordCoordinatorMetrics
if (metrics != null)
{
long now = AccordTimeService.nowMicros();
metrics.preacceptLatency.update(Math.max(0, now - txnId.hlc()));
metrics.preacceptLatency.update(Math.max(0, now - txnId.hlc()), MICROSECONDS);
}
}
@ -243,7 +243,7 @@ public class AccordCoordinatorMetrics
{
metrics.dependencies.update(deps.txnIdCount());
long now = AccordTimeService.nowMicros();
metrics.executeLatency.update(Math.max(0, now - txnId.hlc()));
metrics.executeLatency.update(Math.max(0, now - txnId.hlc()), MICROSECONDS);
if (path != null)
{
switch (path)
@ -264,7 +264,7 @@ public class AccordCoordinatorMetrics
if (metrics != null)
{
long now = AccordTimeService.nowMicros();
metrics.applyLatency.update(Math.max(0, now - txnId.hlc()));
metrics.applyLatency.update(Math.max(0, now - txnId.hlc()), MICROSECONDS);
}
}

View File

@ -59,6 +59,7 @@ public class AccordSystemMetrics implements SystemEventListener
public static final String MAX_PENDING_EPOCH = "MaxPendingEpoch";
public static final String ERRORS = "Errors";
public static final String EPOCH_WAITS = "EpochWaits";
public static final String PAUSED_EXECUTOR_LOADING = "PausedExecutorLoading";
public static final String EPOCH_TIMEOUTS = "EpochTimeouts";
public static final String PROGRESS_LOG_ACTIVE = "ProgressLogActive";
public static final String PROGRESS_LOG_SIZE = "ProgressLogSize";
@ -78,6 +79,7 @@ public class AccordSystemMetrics implements SystemEventListener
public final Counter errors;
public final Counter epochWaits;
public final Counter epochTimeouts;
public final Counter pausedExecutorLoading;
public final Gauge<Long> progressLogActive;
public final Gauge<Long> durabilityQueueActive;
public final Gauge<Long> durabilityQueuePending;
@ -158,6 +160,7 @@ public class AccordSystemMetrics implements SystemEventListener
errors = Metrics.counter(factory.createMetricName(ERRORS));
epochTimeouts = Metrics.counter(factory.createMetricName(EPOCH_TIMEOUTS));
epochWaits = Metrics.counter(factory.createMetricName(EPOCH_WAITS));
pausedExecutorLoading = Metrics.counter(factory.createMetricName(PAUSED_EXECUTOR_LOADING));
durabilityQueueActive = Metrics.gauge(factory.createMetricName(DURABILITY_QUEUE_ACTIVE), fromDurabilityService(durability -> (long)durability.queue().activeCount()));
durabilityQueuePending = Metrics.gauge(factory.createMetricName(DURABILITY_QUEUE_PENDING), fromDurabilityService(durability -> (long)durability.queue().pendingCount()));
progressLogActive = Metrics.gauge(factory.createMetricName(PROGRESS_LOG_ACTIVE), fromDurabilityService(durability -> (long)durability.queue().activeCount()));

View File

@ -579,18 +579,18 @@ public class DecayingEstimatedHistogramReservoir implements CassandraReservoir
throw new IllegalStateException("Unable to compute when histogram overflowed");
long elements = 0;
long sum = 0;
double sum = 0;
for (int i = 0; i < lastBucket; i++)
{
long bCount = decayingBuckets[i];
elements += bCount;
sum += bCount * bucketOffsets[i];
sum += bCount * (double)bucketOffsets[i];
}
if (elements == 0)
return 0d;
return (double) sum / elements;
return sum / elements;
}
/**

View File

@ -1139,6 +1139,8 @@ public class AccordCache implements CacheSize
public static class CommandsForKeyAdapter implements Adapter<RoutingKey, CommandsForKey, AccordSafeCommandsForKey>
{
public static final CommandsForKeyAdapter CFK_ADAPTER = new CommandsForKeyAdapter();
private static int SHRINK_WITHOUT_LOCK = -1;
private CommandsForKeyAdapter() {}
@Override
@ -1176,7 +1178,7 @@ public class AccordCache implements CacheSize
if (value.isEmpty() || value.isLoadingPruned())
return Shrink.EVICT;
if (value.size() < 64)
if (SHRINK_WITHOUT_LOCK <= 0 || value.size() < SHRINK_WITHOUT_LOCK)
return Shrink.DONE;
return Shrink.PERFORM_WITHOUT_LOCK;
@ -1249,7 +1251,10 @@ public class AccordCache implements CacheSize
public static class CommandAdapter implements Adapter<TxnId, Command, AccordSafeCommand>
{
private static int SHRINK_WITHOUT_LOCK = -1;
public static final CommandAdapter COMMAND_ADAPTER = new CommandAdapter();
private CommandAdapter() {}
@Override
@ -1309,7 +1314,7 @@ public class AccordCache implements CacheSize
Invariants.expect(value.saveStatus().compareTo(SaveStatus.ReadyToExecute) < 0);
// TODO (expected): improve heuristics and consider transaction size
if (value.partialDeps() == null || value.partialDeps().txnIds().size() < 64)
if (SHRINK_WITHOUT_LOCK < 0 || value.partialDeps() == null || value.partialDeps().txnIds().size() < SHRINK_WITHOUT_LOCK)
return Shrink.DONE;
return Shrink.PERFORM_WITHOUT_LOCK;

View File

@ -50,6 +50,7 @@ import accord.impl.progresslog.DefaultProgressLog;
import accord.local.Command;
import accord.local.CommandStore;
import accord.local.CommandStores;
import accord.local.CommandSummaries;
import accord.local.NodeCommandStoreService;
import accord.local.PreLoadContext;
import accord.local.RedundantBefore;
@ -63,10 +64,12 @@ import accord.primitives.Route;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import accord.utils.Invariants;
import accord.utils.UnhandledEnum;
import accord.utils.async.AsyncChain;
import accord.utils.async.AsyncChains;
import accord.utils.async.AsyncResults.CountingResult;
import org.apache.cassandra.config.AccordSpec;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.metrics.LogLinearDecayingHistograms;
@ -155,12 +158,11 @@ public class AccordCommandStore extends CommandStore
public final String loggingId;
public final Journal journal;
private final RangeSearcher rangeSearcher;
private final AccordExecutor sharedExecutor;
final AccordExecutor.SequentialExecutor exclusiveExecutor;
private final ExclusiveCaches caches;
private long lastSystemTimestampMicros = Long.MIN_VALUE;
private final CommandsForRanges.Manager commandsForRanges;
private final RangeIndex rangeIndex;
private final TableId tableId;
private TableMetadataRef metadata;
volatile SafeRedundantBefore safeRedundantBefore;
@ -181,7 +183,6 @@ public class AccordCommandStore extends CommandStore
super(id, node, agent, dataStore, progressLogFactory, listenerFactory, epochUpdateHolder);
this.loggingId = String.format("[%s]", id);
this.journal = journal;
this.rangeSearcher = RangeSearcher.extractRangeSearcher(journal);
this.sharedExecutor = sharedExecutor;
if (this.progressLog instanceof DefaultProgressLog)
((DefaultProgressLog)this.progressLog).unsafeSetConfig(DatabaseDescriptor.getAccordProgressLogConfig());
@ -196,7 +197,15 @@ public class AccordCommandStore extends CommandStore
}
this.exclusiveExecutor = sharedExecutor.executor(id);
this.commandsForRanges = new CommandsForRanges.Manager(this);
{
AccordSpec.RangeIndexMode mode = DatabaseDescriptor.getAccord().range_index_mode;
switch (mode)
{
default: throw new UnhandledEnum(mode);
case journal_sai: rangeIndex = new JournalRangeIndex(this); break;
case in_memory: rangeIndex = new InMemoryRangeIndex(this); break;
}
}
maybeLoadRedundantBefore(journal.loadRedundantBefore(id()));
maybeLoadBootstrapBeganAt(journal.loadBootstrapBeganAt(id()));
@ -227,9 +236,9 @@ public class AccordCommandStore extends CommandStore
new AccordCommandStore(id, node, agent, dataStore, progressLogFactory, listenerFactory, rangesForEpoch, journal, executorFactory.apply(id));
}
public CommandsForRanges.Manager commandsForRanges()
public RangeIndex rangeIndex()
{
return commandsForRanges;
return rangeIndex;
}
@Override
@ -369,7 +378,7 @@ public class AccordCommandStore extends CommandStore
taskExecutor().execute(run);
}
public AccordSafeCommandStore begin(AccordTask<?> operation, @Nullable CommandsForRanges commandsForRanges)
public AccordSafeCommandStore begin(AccordTask<?> operation, @Nullable CommandSummaries commandsForRanges)
{
require(current == null);
current = AccordSafeCommandStore.create(operation, commandsForRanges, this);
@ -472,11 +481,6 @@ public class AccordCommandStore extends CommandStore
return safeRedundantBefore.redundantBefore;
}
public RangeSearcher rangeSearcher()
{
return rangeSearcher;
}
public AccordCommandStoreReplayer replayer()
{
boolean replayOnlyNonDurable = true;

View File

@ -71,6 +71,7 @@ import org.apache.cassandra.concurrent.Shutdownable;
import org.apache.cassandra.metrics.AccordCacheMetrics;
import org.apache.cassandra.metrics.AccordExecutorMetrics;
import org.apache.cassandra.metrics.AccordReplicaMetrics;
import org.apache.cassandra.metrics.AccordSystemMetrics;
import org.apache.cassandra.metrics.LogLinearDecayingHistograms;
import org.apache.cassandra.metrics.LogLinearDecayingHistograms.LogLinearDecayingHistogram;
import org.apache.cassandra.metrics.ShardedDecayingHistograms;
@ -376,6 +377,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
// we have too much in memory already, and we have work waiting to run, so let that complete before queueing more
if (!loading.isEmpty() || !waitingToRun.isEmpty())
{
AccordSystemMetrics.metrics.pausedExecutorLoading.inc();
hasPausedLoading = true;
return;
}
@ -1722,6 +1724,8 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
{
if (task instanceof AccordTask)
return ((AccordTask<?>) task).preLoadContext();
if (task instanceof WrappedIOTask && ((WrappedIOTask) task).wrapped instanceof AccordTask.RangeTxnScanner)
return ((AccordTask<?>.RangeTxnScanner) ((WrappedIOTask) task).wrapped).preLoadContext();
return null;
}
@ -1755,14 +1759,6 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
return result;
}
private static List<Task> toSimpleSnapshotList(TaskQueue<?> queue)
{
List<Task> list = new ArrayList<>();
for (int i = 0 ; i < queue.size() ; i++)
list.add(queue.get(i));
return list;
}
private static void addToSnapshot(List<TaskInfo> snapshot, TaskQueue<?> queue, TaskInfo.Status ifCurrent, TaskInfo.Status ifQueued)
{
for (int i = 0 ; i < queue.size() ; ++i)

View File

@ -123,7 +123,7 @@ import static org.apache.cassandra.service.accord.journal.AccordTopologyUpdate.T
import static org.apache.cassandra.service.accord.journal.AccordTopologyUpdate.newTopology;
import static org.apache.cassandra.utils.FBUtilities.getAvailableProcessors;
public class AccordJournal implements accord.api.Journal, RangeSearcher.Supplier, Shutdownable
public class AccordJournal implements accord.api.Journal, JournalRangeSearcher.Supplier, Shutdownable
{
private static final Logger logger = LoggerFactory.getLogger(AccordJournal.class);
static final ThreadLocal<byte[]> keyCRCBytes = ThreadLocal.withInitial(() -> new byte[JournalKeySupport.TOTAL_SIZE]);
@ -572,7 +572,7 @@ public class AccordJournal implements accord.api.Journal, RangeSearcher.Supplier
public void truncateForTesting()
{
journal.truncateForTesting();
journalTable.safeNotify(RouteInMemoryIndex::truncateForTesting);
journalTable.safeNotify(JournalSegmentRangeSearcher::truncateForTesting);
}
@VisibleForTesting
@ -844,7 +844,7 @@ public class AccordJournal implements accord.api.Journal, RangeSearcher.Supplier
}
@Override
public RangeSearcher rangeSearcher()
public JournalRangeSearcher rangeSearcher()
{
return journalTable.rangeSearcher();
}

View File

@ -96,7 +96,7 @@ import org.apache.cassandra.utils.concurrent.OpOrder;
import static org.apache.cassandra.io.sstable.SSTableReadsListener.NOOP_LISTENER;
import static org.apache.cassandra.service.accord.AccordKeyspace.JournalColumns.getJournalKey;
public class AccordJournalTable<K extends JournalKey, V> implements RangeSearcher.Supplier
public class AccordJournalTable<K extends JournalKey, V> implements JournalRangeSearcher.Supplier
{
private static final Logger logger = LoggerFactory.getLogger(AccordJournalTable.class);
@ -113,7 +113,7 @@ public class AccordJournalTable<K extends JournalKey, V> implements RangeSearche
* this property holds true.
*/
@Nullable
private final RouteInMemoryIndex<Object> index;
private final JournalSegmentRangeSearcher<Object> index;
private final Version accordJournalVersion;
public AccordJournalTable(Journal<K, V> journal, KeySupport<K> keySupport, ColumnFamilyStore cfs, Version accordJournalVersion)
@ -126,7 +126,7 @@ public class AccordJournalTable<K extends JournalKey, V> implements RangeSearche
this.accordJournalVersion = accordJournalVersion;
this.index = cfs.indexManager.getIndexByName(AccordKeyspace.JOURNAL_INDEX_NAME) != null
? new RouteInMemoryIndex<>()
? new JournalSegmentRangeSearcher<>()
: null;
}
@ -136,7 +136,7 @@ public class AccordJournalTable<K extends JournalKey, V> implements RangeSearche
return RouteJournalIndex.allowed(key);
}
void safeNotify(Consumer<RouteInMemoryIndex<Object>> fn)
void safeNotify(Consumer<JournalSegmentRangeSearcher<Object>> fn)
{
if (index == null)
return;
@ -157,11 +157,11 @@ public class AccordJournalTable<K extends JournalKey, V> implements RangeSearche
}
@Override
public RangeSearcher rangeSearcher()
public JournalRangeSearcher rangeSearcher()
{
if (index == null)
return RangeSearcher.NoopRangeSearcher.instance;
return new TableRangeSearcher();
return JournalRangeSearcher.NoopJournalRangeSearcher.instance;
return new JournalTableRangeSearcher();
}
public void start()
@ -246,11 +246,11 @@ public class AccordJournalTable<K extends JournalKey, V> implements RangeSearche
}
}
private class TableRangeSearcher implements RangeSearcher
private class JournalTableRangeSearcher implements JournalRangeSearcher
{
private final Index tableIndex;
private TableRangeSearcher()
private JournalTableRangeSearcher()
{
this.tableIndex = cfs.indexManager.getIndexByName("record");
if (!cfs.indexManager.isIndexQueryable(tableIndex))

View File

@ -42,6 +42,7 @@ import accord.local.cfk.Serialize;
import accord.primitives.TxnId;
import accord.utils.Invariants;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.statements.schema.CreateTableStatement;
import org.apache.cassandra.db.Clustering;
@ -115,6 +116,7 @@ import org.apache.cassandra.utils.vint.VIntCoding;
import static java.lang.String.format;
import static java.util.Collections.emptyMap;
import static org.apache.cassandra.config.AccordSpec.RangeIndexMode.journal_sai;
import static org.apache.cassandra.db.partitions.PartitionUpdate.singleRowUpdate;
import static org.apache.cassandra.db.rows.BTreeRow.singleCellRow;
import static org.apache.cassandra.schema.SchemaConstants.ACCORD_KEYSPACE_NAME;
@ -154,7 +156,7 @@ public class AccordKeyspace
return builder.build();
}
public static final TableMetadata Journal = journalMetadata(JOURNAL, true);
public static final TableMetadata Journal = journalMetadata(JOURNAL, DatabaseDescriptor.getAccord().range_index_mode == journal_sai);
private static ColumnMetadata getColumn(TableMetadata metadata, String name)
{

View File

@ -33,12 +33,14 @@ import accord.api.Journal.FieldUpdates;
import accord.api.ProgressLog;
import accord.api.RoutingKey;
import accord.impl.AbstractSafeCommandStore;
import accord.local.Command;
import accord.local.CommandStores;
import accord.local.CommandSummaries;
import accord.local.NodeCommandStoreService;
import accord.local.cfk.CommandsForKey;
import accord.local.cfk.SafeCommandsForKey;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.Txn.Kind.Kinds;
import accord.primitives.TxnId;
import accord.primitives.Unseekables;
import accord.utils.Invariants;
@ -53,11 +55,11 @@ import static accord.utils.Invariants.illegalState;
public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeCommand, AccordSafeCommandsForKey, AccordCommandStore.ExclusiveCaches>
{
final AccordTask<?> task;
private final @Nullable CommandsForRanges commandsForRanges;
private final @Nullable CommandSummaries commandsForRanges;
private final AccordCommandStore commandStore;
private AccordSafeCommandStore(AccordTask<?> task,
@Nullable CommandsForRanges commandsForRanges,
@Nullable CommandSummaries commandsForRanges,
AccordCommandStore commandStore)
{
super(task.preLoadContext(), commandStore);
@ -78,7 +80,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
}
public static AccordSafeCommandStore create(AccordTask<?> operation,
@Nullable CommandsForRanges commandsForRanges,
@Nullable CommandSummaries commandsForRanges,
AccordCommandStore commandStore)
{
return new AccordSafeCommandStore(operation, commandsForRanges, commandStore);
@ -181,6 +183,12 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
commandStore.updateMinHlc(PaxosState.ballotTracker().getLowBound().unixMicros() + 1);
}
@Override
public void updateCommandsForRanges(Command prev, Command updated, boolean force)
{
commandStore().rangeIndex().update(prev, updated, force);
}
@Override
public AccordCommandStore commandStore()
{
@ -242,7 +250,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
}
@Override
public <P1, P2> void visit(Unseekables<?> keysOrRanges, Timestamp startedBefore, Txn.Kind.Kinds testKind, ActiveCommandVisitor<P1, P2> visitor, P1 p1, P2 p2)
public <P1, P2> void visit(Unseekables<?> keysOrRanges, Timestamp startedBefore, Kinds testKind, ActiveCommandVisitor<P1, P2> visitor, P1 p1, P2 p2)
{
visitForKey(keysOrRanges, cfk -> { cfk.visit(startedBefore, testKind, visitor, p1, p2); return true; });
if (commandsForRanges != null)
@ -250,15 +258,15 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
}
@Override
public boolean visit(Unseekables<?> keysOrRanges, TxnId testTxnId, Txn.Kind.Kinds testKind, TestStartedAt testStartedAt, Timestamp testStartedAtTimestamp, ComputeIsDep computeIsDep, AllCommandVisitor visit)
public boolean visit(Unseekables<?> keysOrRanges, TxnId testTxnId, Kinds testKind, SupersedingCommandVisitor visit)
{
return visitForKey(keysOrRanges, cfk -> cfk.visit(testTxnId, testKind, testStartedAt, testStartedAtTimestamp, computeIsDep, null, visit))
&& (commandsForRanges == null || commandsForRanges.visit(keysOrRanges, testTxnId, testKind, testStartedAt, testStartedAtTimestamp, computeIsDep, visit));
return visitForKey(keysOrRanges, cfk -> cfk.visit(testTxnId, testKind, visit))
&& (commandsForRanges == null || commandsForRanges.visit(keysOrRanges, testTxnId, testKind, visit));
}
@Override
public String toString()
{
return "AccordSafeCommandStore(id=" + commandStore().id() + ")";
return "AccordSafeCommandStore(id=" + commandStore().id() + ')';
}
}

View File

@ -371,9 +371,10 @@ public class AccordService implements IAccordService, Shutdownable
AccordKeyspace.truncateCommandsForKey();
as.node.commandStores().forAllUnsafe(cs -> cs.unsafeProgressLog().stop());
as.journal().replay(as.node().commandStores());
as.journal().replay(as.node.commandStores());
logger.info("Waiting for command stores to quiesce.");
((AccordCommandStores)as.node.commandStores()).waitForQuiescence();
getBlocking(as.node.commandStores().forAll("Post Replay", safeStore -> ((AccordCommandStore)safeStore.commandStore()).rangeIndex().postReplay()));
as.journal.unsafeSetStarted();
as.node.commandStores().forAllUnsafe(cs -> cs.unsafeProgressLog().start());
@ -488,21 +489,23 @@ public class AccordService implements IAccordService, Shutdownable
finishTopologyInitialization();
WatermarkCollector.fetchAndReportWatermarksAsync(topology());
catchup();
fastPathCoordinator.start();
ClusterMetadataService.instance().log().addListener(fastPathCoordinator);
// we set ourselves to STARTED before starting progress logs as this is the condition we use to decide if we
// start the progress log on command store initialisation (so creates a synchronisation point)
state = State.STARTED;
node.commandStores().forAll("", safeStore -> safeStore.progressLog().start());
// trigger catchup only after our progress mechanisms are initialised
catchup();
node.durability().shards().reconfigure(Ints.checkedCast(getAccordShardDurabilityTargetSplits()),
Ints.checkedCast(getAccordShardDurabilityMaxSplits()),
Ints.checkedCast(getAccordShardDurabilityCycle(SECONDS)), SECONDS);
node.durability().global().setGlobalCycleTime(Ints.checkedCast(getAccordGlobalDurabilityCycle(SECONDS)), SECONDS);
// Only enable durability scheduling and progress logs _after_ we have fully replayed journal
node.durability().start();
// we set ourselves to STARTED before starting progress logs as this is the condition we use to decide if we
// start the progress log on command store initialisation (so creates a synchronisation point)
state = State.STARTED;
node.commandStores().forAll("", safeStore -> safeStore.progressLog().start());
}
void catchup()
@ -741,13 +744,13 @@ public class AccordService implements IAccordService, Shutdownable
}
@Override
public AsyncResult<Void> sync(Object requestedBy, Timestamp minBound, Ranges ranges, @Nullable Collection<Id> include, DurabilityService.SyncLocal syncLocal, DurabilityService.SyncRemote syncRemote, long timeout, TimeUnit timeoutUnits)
public AsyncResult<Void> sync(Object requestedBy, TxnId minBound, Ranges ranges, @Nullable Collection<Id> include, DurabilityService.SyncLocal syncLocal, DurabilityService.SyncRemote syncRemote, long timeout, TimeUnit timeoutUnits)
{
return node.durability().sync(requestedBy, ExclusiveSyncPoint, minBound, ranges, include, syncLocal, syncRemote, timeout, timeoutUnits);
}
@Override
public AsyncChain<Void> sync(Timestamp minBound, Keys keys, DurabilityService.SyncLocal syncLocal, DurabilityService.SyncRemote syncRemote)
public AsyncChain<Void> sync(TxnId minBound, Keys keys, DurabilityService.SyncLocal syncLocal, DurabilityService.SyncRemote syncRemote)
{
if (keys.size() != 1)
return syncInternal(minBound, keys, syncLocal, syncRemote);

View File

@ -21,11 +21,13 @@ import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
@ -45,6 +47,8 @@ import accord.api.Journal;
import accord.api.RoutingKey;
import accord.local.Command;
import accord.local.CommandStore;
import accord.local.CommandSummaries;
import accord.local.CommandSummaries.Summary;
import accord.local.LoadKeys;
import accord.local.PreLoadContext;
import accord.local.SafeCommandStore;
@ -53,6 +57,7 @@ import accord.primitives.AbstractRanges;
import accord.primitives.AbstractUnseekableKeys;
import accord.primitives.Range;
import accord.primitives.Ranges;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import accord.primitives.Unseekables;
import accord.utils.Invariants;
@ -211,7 +216,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
@Nullable ArrayDeque<AccordCacheEntry<?, ?>> waitingToLoad;
@Nullable RangeTxnScanner rangeScanner;
boolean hasRanges;
@Nullable CommandsForRanges commandsForRanges;
@Nullable CommandSummaries commandsForRanges;
@Nullable private TaskQueue queued;
private BiConsumer<? super R, Throwable> callback;
@ -233,8 +238,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
String id = loggingId;
if (id == null)
{
TxnId primaryTxnId = preLoadContext.primaryTxnId();
id = "0x" + Long.toHexString(nextLoggingId.incrementAndGet()) + (primaryTxnId != null ? '/' + primaryTxnId.toString() : "");
id = "0x" + Long.toHexString(nextLoggingId.incrementAndGet());
if (!loggingIdUpdater.compareAndSet(this, null, id))
id = loggingId;
}
@ -244,12 +248,17 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
@Override
public String toString()
{
return "AccordTask{" + state + "}-" + loggingId();
return preLoadContext.describe() + ' ' + toBriefString();
}
public String toBriefString()
{
return '{' + loggingId() + ',' + state + '}';
}
public String toDescription()
{
return "AccordTask{" + state + "}-" + loggingId() + ": "
return toBriefString() + ": "
+ (queued == null ? "unqueued" : queued.kind)
+ ", primaryTxnId: " + preLoadContext.primaryTxnId()
+ ", waitingToLoad: " + summarise(waitingToLoad)
@ -803,6 +812,8 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
public void cancelExclusive()
{
state(CANCELLED);
if (rangeScanner != null)
rangeScanner.cancelled = true;
if (callback != null)
callback.accept(null, new CancellationException());
}
@ -969,6 +980,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
}
}
// TODO (expected): produce key summaries to avoid locking all in memory
final Set<TokenKey> intersectingKeys = new ObjectHashSet<>();
final KeyWatcher keyWatcher = new KeyWatcher();
final Ranges ranges = ((AbstractRanges) preLoadContext.keys()).toRanges();
@ -979,8 +991,6 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
this.commandsForKeyCache = commandsForKeyCache;
}
boolean scanned;
protected void runInternal()
{
for (Range range : ranges)
@ -988,7 +998,11 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
CommandsForKeyAccessor.findAllKeysBetween(commandStore.id(), commandStore.tableId(), getPartitioner(),
(TokenKey) range.start(), range.startInclusive(),
(TokenKey) range.end(), range.endInclusive(),
intersectingKeys::add);
key -> {
if (cancelled)
throw new CancellationException();
intersectingKeys.add(key);
});
}
super.runInternal();
}
@ -1017,7 +1031,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
if (v == null) return;
else if (v instanceof CommandsForKey)
{
if (!summaryLoader.isRelevant((CommandsForKey) v))
if (!loader.isRelevant((CommandsForKey) v))
return;
}
else
@ -1025,7 +1039,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
TxnId last = CommandSerializers.txnId.deserialize((ByteBuffer) v);
int position = (int)CommandSerializers.txnId.serializedSize(last);
TxnId minUndecided = CommandSerializers.txnId.deserialize((ByteBuffer) v, position);
if (!summaryLoader.isRelevant(entry.key(), last, minUndecided))
if (!loader.isRelevant(entry.key(), last, minUndecided))
return;
}
@ -1060,7 +1074,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
super.cleanup(caches);
}
CommandsForRanges finish(Caches caches)
CommandSummaries finish(Caches caches)
{
caches.commandsForKeys().unregister(keyWatcher);
return super.finish(caches);
@ -1069,39 +1083,23 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
public class RangeTxnScanner extends AccordExecutor.AbstractIOTask
{
class CommandWatcher implements AccordCache.Listener<TxnId, Command>
{
@Override
public void onUpdate(AccordCacheEntry<TxnId, Command> state)
{
CommandsForRanges.Summary summary = summaryLoader.ifRelevant(state);
if (summary != null)
summaries.put(summary.plainTxnId(), summary);
}
}
final Map<Timestamp, Summary> summaries = new HashMap<>();
final Map<Timestamp, Summary> mutexSummaries = Collections.synchronizedMap(summaries);
final ConcurrentHashMap<TxnId, CommandsForRanges.Summary> summaries = new ConcurrentHashMap<>();
// TODO (expected): produce key summaries to avoid locking all in memory
final CommandWatcher commandWatcher = new CommandWatcher();
final Unseekables<?> keysOrRanges = preLoadContext.keys();
CommandsForRanges.Loader summaryLoader;
RangeIndex.Loader loader;
boolean scanned;
Throwable failure;
volatile boolean cancelled;
protected void runInternal()
{
summaryLoader.intersects(txnId -> {
if (summaries.containsKey(txnId))
return;
loader.load(mutexSummaries, () -> cancelled);
}
CommandsForRanges.Summary summary = summaryLoader.load(txnId);
if (summary != null)
{
summaries.putIfAbsent(txnId, summary);
summaryLoader.maybeRecordFutureRx(summary);
}
});
PreLoadContext preLoadContext()
{
return preLoadContext;
}
@Override
@ -1126,9 +1124,8 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
void startInternal(Caches caches)
{
summaryLoader = commandStore.commandsForRanges().loader(preLoadContext.primaryTxnId(), preLoadContext.loadKeysFor(), keysOrRanges);
summaryLoader.forEachInCache(keysOrRanges, summary -> summaries.put(summary.plainTxnId(), summary), caches);
caches.commands().register(commandWatcher);
loader = commandStore.rangeIndex().loader(preLoadContext.primaryTxnId(), preLoadContext.executeAt(), preLoadContext.loadKeysFor(), preLoadContext.keys());
loader.loadExclusive(mutexSummaries, caches);
}
public void scannedExclusive()
@ -1147,19 +1144,20 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
void cleanup(Caches caches)
{
caches.commands().tryUnregister(commandWatcher);
loader.cleanupExclusive(caches);
}
CommandsForRanges finish(Caches caches)
CommandSummaries finish(Caches caches)
{
caches.commands().unregister(commandWatcher);
return new CommandsForRanges(summaries);
loader.finish(summaries);
TreeMap<Timestamp, Summary> byId = new TreeMap<>(summaries);
return (CommandSummaries.ByTxnIdSnapshot) () -> byId;
}
@Override
public String description()
{
return "Scanning range intersections for " + AccordTask.this;
return "Scanning range intersections for " + preLoadContext.reason() + ' ' + toBriefString();
}
@Override

View File

@ -1,524 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.service.accord;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import org.agrona.collections.Object2ObjectHashMap;
import accord.api.RoutingKey;
import accord.local.Command;
import accord.local.CommandSummaries;
import accord.local.CommandSummaries.Summary;
import accord.local.LoadKeysFor;
import accord.local.MaxDecidedRX;
import accord.local.RedundantBefore;
import accord.primitives.AbstractRanges;
import accord.primitives.AbstractUnseekableKeys;
import accord.primitives.Range;
import accord.primitives.RangeRoute;
import accord.primitives.Ranges;
import accord.primitives.Routable;
import accord.primitives.Timestamp;
import accord.primitives.Txn.Kind.Kinds;
import accord.primitives.TxnId;
import accord.primitives.Unseekable;
import accord.primitives.Unseekables;
import accord.utils.AsymmetricComparator;
import accord.utils.Invariants;
import accord.utils.SymmetricComparator;
import accord.utils.UnhandledEnum;
import org.apache.cassandra.exceptions.UnknownTableException;
import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.service.accord.api.TokenKey;
import org.apache.cassandra.service.accord.serializers.Version;
import org.apache.cassandra.utils.btree.BTree;
import org.apache.cassandra.utils.btree.BTreeSet;
import org.apache.cassandra.utils.btree.IntervalBTree;
import org.apache.cassandra.utils.concurrent.IntrusiveStack;
import static accord.api.Journal.Load.MINIMAL;
import static accord.api.Journal.Load.MINIMAL_WITH_DEPS;
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.endWithStart;
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.keyEndWithStart;
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.keyStartWithEnd;
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.keyStartWithStart;
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.startWithEnd;
import static org.apache.cassandra.utils.btree.IntervalBTree.InclusiveEndHelper.startWithStart;
// TODO (expected): move to accord-core, merge with existing logic there
public class CommandsForRanges extends TreeMap<Timestamp, Summary> implements CommandSummaries.ByTxnIdSnapshot
{
static final IntervalComparators COMPARATORS = new IntervalComparators();
static final IntervalKeyComparators KEY_COMPARATORS = new IntervalKeyComparators();
static class TxnIdInterval extends TokenRange
{
final TxnId txnId;
TxnIdInterval(RoutingKey start, RoutingKey end, TxnId txnId)
{
super((TokenKey) start, (TokenKey) end);
this.txnId = txnId;
}
TxnIdInterval(Range range, TxnId txnId)
{
this(range.start(), range.end(), txnId);
}
@Override
public String toString()
{
return super.toString() + ':' + txnId;
}
}
static class IntervalComparators implements IntervalBTree.IntervalComparators<TxnIdInterval>
{
@Override
public Comparator<TxnIdInterval> totalOrder()
{
return (a, b) -> {
int c = a.start().compareTo(b.start());
if (c == 0) c = a.end().compareTo(b.end());
if (c == 0) c = a.txnId.compareTo(b.txnId);
return c;
};
}
@Override public Comparator<TxnIdInterval> endWithEndSorter() { return (a, b) -> a.end().compareTo(b.end()); }
@Override public SymmetricComparator<TxnIdInterval> startWithStartSeeker() { return (a, b) -> startWithStart(a.start().compareTo(b.start())); }
@Override public SymmetricComparator<TxnIdInterval> startWithEndSeeker() { return (a, b) -> startWithEnd(a.start().compareTo(b.end())); }
@Override public SymmetricComparator<TxnIdInterval> endWithStartSeeker() { return (a, b) -> endWithStart(a.end().compareTo(b.start())); }
}
static class IntervalKeyComparators implements IntervalBTree.WithIntervalComparators<RoutingKey, TxnIdInterval>
{
@Override public AsymmetricComparator<RoutingKey, TxnIdInterval> startWithStartSeeker() { return (a, b) -> keyStartWithStart(a.compareTo(b.start()));}
@Override public AsymmetricComparator<RoutingKey, TxnIdInterval> startWithEndSeeker() { return (a, b) -> keyStartWithEnd(a.compareTo(b.end())); }
@Override public AsymmetricComparator<RoutingKey, TxnIdInterval> endWithStartSeeker() { return (a, b) -> keyEndWithStart(a.compareTo(b.start())); }
}
public CommandsForRanges(Map<? extends Timestamp, ? extends Summary> m)
{
super(m);
}
@Override
public NavigableMap<Timestamp, CommandSummaries.Summary> byTxnId()
{
return this;
}
public static class Manager implements AccordCache.Listener<TxnId, Command>, Runnable
{
static class IntervalTreeEdit extends IntrusiveStack<IntervalTreeEdit>
{
final TxnId txnId;
final @Nullable Object[] update, remove;
IntervalTreeEdit(TxnId txnId, Object[] update, Object[] remove)
{
this.txnId = txnId;
this.update = update;
this.remove = remove;
}
public static boolean push(IntervalTreeEdit edit, Manager manager)
{
return null == IntrusiveStack.getAndPush(pendingEditsUpdater, manager, edit);
}
public IntervalTreeEdit reverse()
{
return reverse(this);
}
boolean isSize(int size)
{
return IntrusiveStack.isSize(size, this);
}
IntervalTreeEdit merge(IntervalTreeEdit next)
{
Invariants.require(this.txnId.equals(next.txnId));
Object[] remove = this.remove == null ? next.remove : next.remove == null ? this.remove : IntervalBTree.update(this.remove, next.remove, COMPARATORS);
return new IntervalTreeEdit(txnId, next.update, remove);
}
}
private final AccordCommandStore commandStore;
private final RangeSearcher searcher;
// TODO (desired): manage memory consumed by this auxillary information
private final Object2ObjectHashMap<TxnId, RangeRoute> cachedRangeTxnsById = new Object2ObjectHashMap<>();
private Object[] cachedRangeTxnsByRange = IntervalBTree.empty();
private volatile IntervalTreeEdit pendingEdits;
private final Lock drainPendingEditsLock = new ReentrantLock();
private static final AtomicReferenceFieldUpdater<Manager, IntervalTreeEdit> pendingEditsUpdater = AtomicReferenceFieldUpdater.newUpdater(Manager.class, IntervalTreeEdit.class, "pendingEdits");
public Manager(AccordCommandStore commandStore)
{
this.commandStore = commandStore;
try (AccordCommandStore.ExclusiveCaches caches = commandStore.lockCaches())
{
caches.commands().register(this);
}
this.searcher = commandStore.rangeSearcher();
}
@Override
public void onUpdate(AccordCacheEntry<TxnId, Command> state)
{
TxnId txnId = state.key();
if (txnId.is(Routable.Domain.Range))
{
Command cmd = state.tryGetExclusive();
if (cmd != null)
{
RangeRoute upd = (RangeRoute) cmd.route();
if (upd != null)
{
RangeRoute cur = cachedRangeTxnsById.put(cmd.txnId(), upd);
if (!upd.equals(cur))
pushEdit(new IntervalTreeEdit(txnId, toMap(txnId, upd), cur == null ? null : toMap(txnId, cur)));
}
else
{
RangeRoute cur = cachedRangeTxnsById.remove(cmd.txnId());
if (cur != null)
pushEdit(new IntervalTreeEdit(txnId, null, toMap(txnId, cur)));
}
}
}
}
private void pushEdit(IntervalTreeEdit edit)
{
if (IntervalTreeEdit.push(edit, this))
commandStore.executor().submitExclusive(this);
}
@Override
public void run()
{
if (drainPendingEditsLock.tryLock())
{
try
{
drainPendingEditsInternal();
}
finally
{
drainPendingEditsLock.unlock();
postUnlock();
}
}
}
Object[] cachedRangeTxnsByRange()
{
drainPendingEditsLock.lock();
try
{
drainPendingEditsInternal();
return cachedRangeTxnsByRange;
}
finally
{
drainPendingEditsLock.unlock();
postUnlock();
}
}
void drainPendingEditsInternal()
{
IntervalTreeEdit edits = pendingEditsUpdater.getAndSet(this, null);
if (edits == null)
return;
if (edits.isSize(1))
{
if (edits.remove != null) cachedRangeTxnsByRange = IntervalBTree.subtract(cachedRangeTxnsByRange, edits.remove, COMPARATORS);
if (edits.update != null) cachedRangeTxnsByRange = IntervalBTree.update(cachedRangeTxnsByRange, edits.update, COMPARATORS);
return;
}
edits = edits.reverse();
Map<TxnId, IntervalTreeEdit> editMap = new HashMap<>();
for (IntervalTreeEdit edit : edits)
editMap.merge(edit.txnId, edit, IntervalTreeEdit::merge);
List<TxnIdInterval> update = new ArrayList<>(), remove = new ArrayList<>();
for (IntervalTreeEdit edit : editMap.values())
{
if (edit.remove != null) remove.addAll(BTreeSet.wrap(edit.remove, COMPARATORS.totalOrder()));
if (edit.update != null) update.addAll(BTreeSet.wrap(edit.update, COMPARATORS.totalOrder()));
}
if (!remove.isEmpty())
{
remove.sort(COMPARATORS.totalOrder());
cachedRangeTxnsByRange = IntervalBTree.subtract(cachedRangeTxnsByRange, IntervalBTree.build(remove, COMPARATORS), COMPARATORS);
}
if (!update.isEmpty())
{
update.sort(COMPARATORS.totalOrder());
cachedRangeTxnsByRange = IntervalBTree.update(cachedRangeTxnsByRange, IntervalBTree.build(update, COMPARATORS), COMPARATORS);
}
if (Invariants.isParanoid())
{
try (AccordCommandStore.ExclusiveCaches caches = commandStore.tryLockCaches())
{
if (caches != null)
{
for (TxnIdInterval i : BTree.<TxnIdInterval>iterable(cachedRangeTxnsByRange))
{
if (caches.commands().getUnsafe(i.txnId) == null)
{
boolean removed = pendingEdits != null && pendingEdits.foldl((edit, interval, r) -> {
return r || (edit.txnId.equals(i.txnId) && BTree.find(edit.remove, COMPARATORS.totalOrder(), i) != null);
}, i, false);
Invariants.require(removed);
}
}
}
}
}
}
private void postUnlock()
{
if (pendingEdits != null)
commandStore.executor().submit(this);
}
@Override
public void onEvict(AccordCacheEntry<TxnId, Command> state)
{
TxnId txnId = state.key();
if (txnId.is(Routable.Domain.Range))
{
RangeRoute cur = cachedRangeTxnsById.remove(txnId);
if (cur != null)
pushEdit(new IntervalTreeEdit(txnId, null, toMap(txnId, cur)));
}
}
static Object[] toMap(TxnId txnId, RangeRoute route)
{
int size = route.size();
switch (size)
{
case 0: return IntervalBTree.empty();
case 1: return IntervalBTree.singleton(new TxnIdInterval(route.get(0), txnId));
default:
{
try (IntervalBTree.FastIntervalTreeBuilder<TxnIdInterval> builder = IntervalBTree.fastBuilder(COMPARATORS))
{
for (int i = 0 ; i < size ; ++i)
builder.add(new TxnIdInterval(route.get(i), txnId));
return builder.build();
}
}
}
}
public CommandsForRanges.Loader loader(TxnId primaryTxnId, LoadKeysFor loadKeysFor, Unseekables<?> keysOrRanges)
{
RedundantBefore redundantBefore = commandStore.unsafeGetRedundantBefore();
MaxDecidedRX maxDecidedRX = commandStore.unsafeGetMaxDecidedRX();
return SummaryLoader.loader(redundantBefore, maxDecidedRX, primaryTxnId, loadKeysFor, keysOrRanges, this::newLoader);
}
private Loader newLoader(RedundantBefore redundantBefore, MaxDecidedRX maxDecidedRX, @Nullable TxnId primaryTxnId, Unseekables<?> searchKeysOrRanges, Kinds testKind, TxnId minTxnId, Timestamp maxTxnId, @Nullable TxnId findAsDep)
{
return new Loader(this, redundantBefore, maxDecidedRX, primaryTxnId, searchKeysOrRanges, testKind, minTxnId, maxTxnId, findAsDep);
}
}
public static class Loader extends SummaryLoader
{
private final Manager manager;
public Loader(Manager manager, RedundantBefore redundantBefore, MaxDecidedRX maxDecidedRX, TxnId primaryTxnId, Unseekables<?> searchKeysOrRanges, Kinds testKinds, TxnId minTxnId, Timestamp maxTxnId, @Nullable TxnId findAsDep)
{
super(redundantBefore, maxDecidedRX, primaryTxnId, searchKeysOrRanges, testKinds, minTxnId, maxTxnId, findAsDep);
this.manager = manager;
}
public void intersects(Consumer<TxnId> forEach)
{
switch (searchKeysOrRanges.domain())
{
case Range:
for (Unseekable range : searchKeysOrRanges)
manager.searcher.search(manager.commandStore.id(), (TokenRange) range, minTxnId, maxTxnId, decidedRx).consume(forEach);
break;
case Key:
for (Unseekable key : searchKeysOrRanges)
manager.searcher.search(manager.commandStore.id(), (TokenKey) key, minTxnId, maxTxnId, decidedRx).consume(forEach);
}
}
boolean isMaybeRelevant(TxnIdInterval txnIdInterval)
{
return isMaybeRelevant(txnIdInterval.txnId, null, Ranges.of(txnIdInterval));
}
public void forEachInCache(Unseekables<?> keysOrRanges, Consumer<Summary> forEach, AccordCommandStore.Caches caches)
{
switch (keysOrRanges.domain())
{
default: throw new UnhandledEnum(keysOrRanges.domain());
case Key:
{
for (RoutingKey key : (AbstractUnseekableKeys)keysOrRanges)
{
IntervalBTree.accumulate(manager.cachedRangeTxnsByRange(), KEY_COMPARATORS, key, (f, s, i, c) -> {
if (isMaybeRelevant(i))
{
TxnId txnId = i.txnId;
Summary summary = ifRelevant(c.getUnsafe(txnId));
if (summary != null)
f.accept(summary);
}
return c;
}, forEach, this, caches.commands());
}
break;
}
case Range:
{
for (Range range : (AbstractRanges)keysOrRanges)
{
IntervalBTree.accumulate(manager.cachedRangeTxnsByRange(), COMPARATORS, new TxnIdInterval(range.start(), range.end(), TxnId.NONE), (f, s, i, c) -> {
if (isMaybeRelevant(i))
{
TxnId txnId = i.txnId;
AccordCacheEntry<TxnId, Command> entry = c.getUnsafe(txnId);
Invariants.expect(entry != null, "%s found interval %s but no matching transaction in cache", manager.commandStore, i);
if (entry != null)
{
Summary summary = ifRelevant(entry);
if (summary != null)
f.accept(summary);
}
}
return c;
}, forEach, this, caches.commands());
}
break;
}
}
}
public Summary load(TxnId txnId)
{
if (!isMaybeRelevant(txnId))
return null;
if (findAsDep == null)
{
Command.Minimal cmd = manager.commandStore.loadMinimal(txnId);
if (cmd != null)
return ifRelevant(cmd);
}
else
{
Command.MinimalWithDeps cmd = manager.commandStore.loadMinimalWithDeps(txnId);
if (cmd != null)
return ifRelevant(cmd);
}
return null;
}
public Summary ifRelevant(AccordCacheEntry<TxnId, Command> state)
{
if (state.key().domain() != Routable.Domain.Range)
return null;
switch (state.status())
{
default: throw new AssertionError("Unhandled status: " + state.status());
case LOADING:
case WAITING_TO_LOAD:
case UNINITIALIZED:
return null;
case LOADED:
case MODIFIED:
case SAVING:
case WAITING_TO_SAVE:
case FAILED_TO_SAVE:
}
TxnId txnId = state.key();
if (!isMaybeRelevant(txnId))
return null;
Object command = state.getOrShrunkExclusive();
if (command == null)
return null;
if (command instanceof Command)
return ifRelevant((Command) command);
Invariants.require(command instanceof ByteBuffer);
AccordJournal.Builder builder = new AccordJournal.Builder(txnId, findAsDep == null ? MINIMAL : MINIMAL_WITH_DEPS);
ByteBuffer buffer = (ByteBuffer) command;
buffer.mark();
try (DataInputBuffer buf = new DataInputBuffer(buffer, false))
{
builder.deserializeNext(buf, Version.LATEST);
if (findAsDep == null) return ifRelevant(builder.asMinimal());
else return ifRelevant(builder.asMinimalWithDeps());
}
catch (UnknownTableException e)
{
return null;
}
catch (IOException e)
{
throw new RuntimeException(e);
}
finally
{
buffer.reset();
}
}
}
}

View File

@ -47,6 +47,7 @@ import accord.primitives.Keys;
import accord.primitives.Ranges;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.topology.EpochReady;
import accord.topology.TopologyManager;
import accord.utils.Invariants;
@ -83,8 +84,8 @@ public interface IAccordService
IVerbHandler<? extends Request> requestHandler();
IVerbHandler<? extends Reply> responseHandler();
AsyncResult<Void> sync(Object requestedBy, @Nullable Timestamp minBound, Ranges ranges, @Nullable Collection<Id> include, SyncLocal syncLocal, SyncRemote syncRemote, long timeout, TimeUnit timeoutUnits);
AsyncChain<Void> sync(@Nullable Timestamp minBound, Keys keys, SyncLocal syncLocal, SyncRemote syncRemote);
AsyncResult<Void> sync(Object requestedBy, @Nullable TxnId minBound, Ranges ranges, @Nullable Collection<Id> include, SyncLocal syncLocal, SyncRemote syncRemote, long timeout, TimeUnit timeoutUnits);
AsyncChain<Void> sync(@Nullable TxnId minBound, Keys keys, SyncLocal syncLocal, SyncRemote syncRemote);
AsyncChain<Timestamp> maxConflict(Ranges ranges);
@Nonnull
@ -215,13 +216,13 @@ public interface IAccordService
}
@Override
public AsyncResult<Void> sync(Object requestedBy, @Nullable Timestamp onOrAfter, Ranges ranges, @Nullable Collection<Id> include, SyncLocal syncLocal, SyncRemote syncRemote, long timeout, TimeUnit timeoutUnits)
public AsyncResult<Void> sync(Object requestedBy, @Nullable TxnId onOrAfter, Ranges ranges, @Nullable Collection<Id> include, SyncLocal syncLocal, SyncRemote syncRemote, long timeout, TimeUnit timeoutUnits)
{
throw new UnsupportedOperationException("No accord transaction should be executed when accord.enabled = false in cassandra.yaml");
}
@Override
public AsyncChain<Void> sync(@Nullable Timestamp onOrAfter, Keys keys, SyncLocal syncLocal, SyncRemote syncRemote)
public AsyncChain<Void> sync(@Nullable TxnId onOrAfter, Keys keys, SyncLocal syncLocal, SyncRemote syncRemote)
{
throw new UnsupportedOperationException("No accord transaction should be executed when accord.enabled = false in cassandra.yaml");
}
@ -405,13 +406,13 @@ public interface IAccordService
}
@Override
public AsyncResult<Void> sync(Object requestedBy, @Nullable Timestamp onOrAfter, Ranges ranges, @Nullable Collection<Id> include, SyncLocal syncLocal, SyncRemote syncRemote, long timeout, TimeUnit timeoutUnits)
public AsyncResult<Void> sync(Object requestedBy, @Nullable TxnId onOrAfter, Ranges ranges, @Nullable Collection<Id> include, SyncLocal syncLocal, SyncRemote syncRemote, long timeout, TimeUnit timeoutUnits)
{
return delegate.sync(requestedBy, onOrAfter, ranges, include, syncLocal, syncRemote, timeout, timeoutUnits);
}
@Override
public AsyncChain<Void> sync(@Nullable Timestamp onOrAfter, Keys keys, SyncLocal syncLocal, SyncRemote syncRemote)
public AsyncChain<Void> sync(@Nullable TxnId onOrAfter, Keys keys, SyncLocal syncLocal, SyncRemote syncRemote)
{
return delegate.sync(onOrAfter, keys, syncLocal, syncRemote);
}

View File

@ -1,78 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.service.accord;
import javax.annotation.Nullable;
public abstract class ImmutableAccordSafeState<K, V> implements AccordSafeState<K, V>
{
protected final K key;
@Nullable
protected V original;
protected boolean invalidated;
protected ImmutableAccordSafeState(K key)
{
this.key = key;
}
@Override
public K key()
{
return key;
}
@Override
public V original()
{
checkNotInvalidated();
return original;
}
@Override
public V current()
{
checkNotInvalidated();
return original;
}
@Override
public void invalidate()
{
invalidated = true;
}
@Override
public boolean invalidated()
{
return invalidated;
}
@Override
public void set(V update)
{
throw new UnsupportedOperationException();
}
@Override
public void revert()
{
checkNotInvalidated();
}
}

View File

@ -0,0 +1,155 @@
/*
* 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.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.function.BooleanSupplier;
import javax.annotation.Nullable;
import accord.impl.cfr.InMemoryRangeSummaryIndex;
import accord.impl.cfr.LoadListener;
import accord.local.Command;
import accord.local.CommandSummaries.Summary;
import accord.local.CommandSummaries.SummaryLoader;
import accord.local.LoadKeysFor;
import accord.local.MaxDecidedRX;
import accord.local.RedundantBefore;
import accord.primitives.Timestamp;
import accord.primitives.Txn.Kind.Kinds;
import accord.primitives.TxnId;
import accord.primitives.Unseekables;
import accord.utils.async.Cancellable;
public class InMemoryRangeIndex extends InMemoryRangeSummaryIndex implements RangeIndex
{
public static class Loader extends RangeIndex.Loader
{
private final InMemoryRangeIndex owner;
private Cancellable unregister;
public Loader(InMemoryRangeIndex owner, RedundantBefore redundantBefore, MaxDecidedRX maxDecidedRX, TxnId primaryTxnId, Unseekables<?> searchKeysOrRanges, Kinds testKinds, TxnId minTxnId, Timestamp maxTxnId, LoadKeysFor loadKeysFor)
{
super(redundantBefore, maxDecidedRX, primaryTxnId, searchKeysOrRanges, testKinds, minTxnId, maxTxnId, loadKeysFor);
this.owner = owner;
}
@Override
public void loadExclusive(Map<Timestamp, Summary> into, AccordCommandStore.Caches caches)
{
if (loadKeysFor == LoadKeysFor.RECOVERY)
unregister = owner.registerListener(new LoadListener(this, into));
}
public void load(Map<Timestamp, Summary> into, BooleanSupplier abort)
{
if (loadKeysFor != LoadKeysFor.RECOVERY)
return;
if (abort.getAsBoolean())
throw new CancellationException();
List<TxnId> load = new ArrayList<>();
owner.search(this, null, load::add);
if (abort.getAsBoolean())
throw new CancellationException();
ArrayDeque<TxnId> loadFromDisk = new ArrayDeque<>();
try (AccordCommandStore.ExclusiveCaches caches = commandStore().lockCaches())
{
for (TxnId txnId : load)
{
AccordCacheEntry<TxnId, Command> entry = caches.commands().getUnsafe(txnId);
if (entry == null)
{
loadFromDisk.add(txnId);
}
else
{
Summary summary = ifRelevant(entry);
if (summary != null)
into.putIfAbsent(txnId, summary);
}
}
}
for (TxnId txnId = loadFromDisk.poll(); txnId != null; txnId = loadFromDisk.poll())
{
if (abort.getAsBoolean())
throw new CancellationException();
Summary summary = loadFromDisk(txnId);
if (summary != null)
into.putIfAbsent(txnId, summary);
}
}
public void finish(Map<Timestamp, Summary> into)
{
cleanupExclusive(null);
owner.search(this, into::put, null);
}
@Override
public void cleanupExclusive(AccordCommandStore.Caches caches)
{
if (unregister != null)
{
unregister.cancel();
unregister = null;
}
}
@Override
AccordCommandStore commandStore()
{
return owner.commandStore;
}
}
private final AccordCommandStore commandStore;
public InMemoryRangeIndex(AccordCommandStore commandStore)
{
this.commandStore = commandStore;
}
public RangeIndex.Loader loader(TxnId primaryTxnId, Timestamp primaryExecuteAt, LoadKeysFor loadKeysFor, Unseekables<?> keysOrRanges)
{
RedundantBefore redundantBefore = commandStore.unsafeGetRedundantBefore();
MaxDecidedRX maxDecidedRX = commandStore.unsafeGetMaxDecidedRX();
return SummaryLoader.loader(redundantBefore, maxDecidedRX, primaryTxnId, primaryExecuteAt, loadKeysFor, keysOrRanges, this::newLoader);
}
@Override
public void postReplay()
{
prune(commandStore);
}
private RangeIndex.Loader newLoader(RedundantBefore redundantBefore, MaxDecidedRX maxDecidedRX, @Nullable TxnId primaryTxnId, Unseekables<?> searchKeysOrRanges, Kinds testKind, TxnId minTxnId, Timestamp maxTxnId, LoadKeysFor loadKeysFor)
{
return new Loader(this, redundantBefore, maxDecidedRX, primaryTxnId, searchKeysOrRanges, testKind, minTxnId, maxTxnId, loadKeysFor);
}
}

View File

@ -0,0 +1,389 @@
/*
* 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.util.Map;
import java.util.concurrent.CancellationException;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import org.agrona.collections.Object2ObjectHashMap;
import accord.api.RoutingKey;
import accord.impl.RangeIntervalComparators;
import accord.impl.RangeIntervalComparators.InclusiveEndWithKeyComparators;
import accord.impl.RangeIntervalComparators.InclusiveEndWithRangeComparators;
import accord.local.Command;
import accord.local.CommandSummaries.Summary;
import accord.local.CommandSummaries.SummaryLoader;
import accord.local.LoadKeysFor;
import accord.local.MaxDecidedRX;
import accord.local.RedundantBefore;
import accord.primitives.AbstractRanges;
import accord.primitives.AbstractUnseekableKeys;
import accord.primitives.Range;
import accord.primitives.RangeRoute;
import accord.primitives.Ranges;
import accord.primitives.Routable;
import accord.primitives.Timestamp;
import accord.primitives.Txn.Kind.Kinds;
import accord.primitives.TxnId;
import accord.primitives.Unseekable;
import accord.primitives.Unseekables;
import accord.utils.Invariants;
import accord.utils.SemiSyncIntervalTree;
import accord.utils.UnhandledEnum;
import accord.utils.btree.BTree;
import accord.utils.btree.IntervalBTree;
import org.apache.cassandra.service.accord.AccordCommandStore.Caches;
import org.apache.cassandra.service.accord.api.TokenKey;
import static accord.local.CommandSummaries.Relevance.IRRELEVANT;
import static accord.local.LoadKeysFor.RECOVERY;
public class JournalRangeIndex extends SemiSyncIntervalTree<Object[]> implements AccordCache.Listener<TxnId, Command>, Runnable, RangeIndex
{
static final IntervalBTree.IntervalComparators<TxnIdInterval> ENTRIES = new RangeIntervalComparators.InclusiveEndEntryComparators<>(a -> a, (a, b) -> a.txnId.compareTo(b.txnId));
static final IntervalBTree.WithIntervalComparators<RoutingKey, TxnIdInterval> WITH_KEY = new InclusiveEndWithKeyComparators<>(a -> a);
static final IntervalBTree.WithIntervalComparators<Range, TxnIdInterval> WITH_RANGE = new InclusiveEndWithRangeComparators<>(a -> a);
static class TxnIdInterval extends TokenRange
{
final TxnId txnId;
TxnIdInterval(RoutingKey start, RoutingKey end, TxnId txnId)
{
super((TokenKey) start, (TokenKey) end);
this.txnId = txnId;
}
TxnIdInterval(Range range, TxnId txnId)
{
this(range.start(), range.end(), txnId);
}
@Override
public String toString()
{
return super.toString() + ':' + txnId;
}
}
public static class Loader extends RangeIndex.Loader
{
static class CommandWatcher implements AccordCache.Listener<TxnId, Command>
{
final Loader loader;
final Map<Timestamp, Summary> summaries;
CommandWatcher(Loader loader, Map<Timestamp, Summary> summaries)
{
this.loader = loader;
this.summaries = summaries;
}
@Override
public void onUpdate(AccordCacheEntry<TxnId, Command> state)
{
Summary summary = loader.ifRelevant(state);
if (summary != null)
summaries.put(summary.plainTxnId(), summary);
}
}
private final JournalRangeIndex owner;
private CommandWatcher commandWatcher;
public Loader(JournalRangeIndex owner, RedundantBefore redundantBefore, MaxDecidedRX maxDecidedRX, TxnId primaryTxnId, Unseekables<?> searchKeysOrRanges, Kinds testKinds, TxnId minTxnId, Timestamp maxTxnId, LoadKeysFor loadKeysFor)
{
super(redundantBefore, maxDecidedRX, primaryTxnId, searchKeysOrRanges, testKinds, minTxnId, maxTxnId, loadKeysFor);
this.owner = owner;
}
@Override
public void loadExclusive(Map<Timestamp, Summary> into, Caches caches)
{
forEachInCache(searchFor, summary -> into.put(summary.plainTxnId(), summary), caches);
commandWatcher = new CommandWatcher(this, into);
caches.commands().register(commandWatcher);
}
public void forEachInCache(Unseekables<?> keysOrRanges, Consumer<Summary> forEach, Caches caches)
{
switch (keysOrRanges.domain())
{
default: throw new UnhandledEnum(keysOrRanges.domain());
case Key:
{
for (RoutingKey key : (AbstractUnseekableKeys)keysOrRanges)
{
IntervalBTree.accumulate(owner.cachedRangeTxnsByRange(), WITH_KEY, key, (f, s, i, c) -> {
if (isMaybeRelevant(i))
{
TxnId txnId = i.txnId;
Summary summary = ifRelevant(c.getUnsafe(txnId));
if (summary != null)
f.accept(summary);
}
return c;
}, forEach, this, caches.commands());
}
break;
}
case Range:
{
for (Range range : (AbstractRanges)keysOrRanges)
{
IntervalBTree.accumulate(owner.cachedRangeTxnsByRange(), WITH_RANGE, new TxnIdInterval(range.start(), range.end(), TxnId.NONE), (f, s, i, c) -> {
if (isMaybeRelevant(i))
{
TxnId txnId = i.txnId;
AccordCacheEntry<TxnId, Command> entry = c.getUnsafe(txnId);
Invariants.expect(entry != null, "%s found interval %s but no matching transaction in cache", owner.commandStore, i);
if (entry != null)
{
Summary summary = ifRelevant(entry);
if (summary != null)
f.accept(summary);
}
}
return c;
}, forEach, this, caches.commands());
}
break;
}
}
}
public void load(Map<Timestamp, Summary> into, BooleanSupplier abort)
{
forEachIntersectingOnDisk(txnId -> {
if (abort.getAsBoolean())
throw new CancellationException();
if (into.containsKey(txnId))
return;
Summary summary = loadFromDisk(txnId);
if (summary != null)
{
into.putIfAbsent(txnId, summary);
if (shouldRecordFutureRx(txnId, summary.status()))
recordFutureRx(txnId, summary.participants());
}
});
}
@Override
void finish(Map<Timestamp, Summary> into)
{
}
private void forEachIntersectingOnDisk(Consumer<TxnId> forEach)
{
Timestamp maxTxnId = loadKeysFor == RECOVERY || !primaryTxnId.isSyncPoint() ? Timestamp.MAX : primaryTxnId;
switch (searchFor.domain())
{
case Range:
for (Unseekable range : searchFor)
owner.searcher.search(owner.commandStore.id(), (TokenRange) range, minTxnId, maxTxnId, decidedRx).consume(forEach);
break;
case Key:
for (Unseekable key : searchFor)
owner.searcher.search(owner.commandStore.id(), (TokenKey) key, minTxnId, maxTxnId, decidedRx).consume(forEach);
}
}
@Override
protected Summary loadFromDisk(TxnId txnId)
{
if (!isMaybeRelevant(txnId))
return null;
return super.loadFromDisk(txnId);
}
boolean isMaybeRelevant(TxnIdInterval txnIdInterval)
{
return relevance(txnIdInterval.txnId, null, null, null, Ranges.of(txnIdInterval)) != IRRELEVANT;
}
@Override
void cleanupExclusive(Caches caches)
{
if (commandWatcher != null)
{
CommandWatcher unregister = commandWatcher;
commandWatcher = null;
caches.commands().unregister(unregister);
}
}
@Override
AccordCommandStore commandStore()
{
return owner.commandStore;
}
}
private final AccordCommandStore commandStore;
// TODO (expected): do we need one of these per command store?
private final JournalRangeSearcher searcher;
private final Object2ObjectHashMap<TxnId, RangeRoute> cachedRangeTxnsById = new Object2ObjectHashMap<>();
public JournalRangeIndex(AccordCommandStore commandStore)
{
super(ENTRIES);
this.commandStore = commandStore;
try (AccordCommandStore.ExclusiveCaches caches = commandStore.lockCaches())
{
caches.commands().register(this);
}
this.searcher = JournalRangeSearcher.extractRangeSearcher(commandStore.journal);
}
@Override
public void onUpdate(AccordCacheEntry<TxnId, Command> state)
{
TxnId txnId = state.key();
if (txnId.is(Routable.Domain.Range))
{
Command cmd = state.tryGetExclusive();
if (cmd != null)
{
RangeRoute upd = (RangeRoute) cmd.route();
if (upd != null)
{
RangeRoute cur = cachedRangeTxnsById.put(cmd.txnId(), upd);
if (!upd.equals(cur))
pushEdit(txnId, toMap(txnId, upd), cur == null ? null : toMap(txnId, cur));
}
else
{
RangeRoute cur = cachedRangeTxnsById.remove(cmd.txnId());
if (cur != null)
pushEdit(txnId, null, toMap(txnId, cur));
}
}
}
}
Object[] cachedRangeTxnsByRange()
{
return get();
}
protected void drainPendingEditsExclusive()
{
super.drainPendingEditsExclusive();
if (Invariants.isParanoid())
{
try (AccordCommandStore.ExclusiveCaches caches = commandStore.tryLockCaches())
{
if (caches != null)
{
for (TxnIdInterval i : BTree.<TxnIdInterval>iterable(value))
{
if (caches.commands().getUnsafe(i.txnId) == null)
{
boolean removed = pendingEdits != null && pendingEdits.foldl((edit, interval, r) -> {
return r || (edit.group.equals(i.txnId) && BTree.find(edit.replace, ENTRIES.totalOrder(), i) != null);
}, i, false);
Invariants.require(removed);
}
}
}
}
}
}
@Override
protected void onNewEdits()
{
commandStore.executor().submitExclusive(this);
}
@Override
protected void onRemainingEdits()
{
commandStore.executor().submit(this);
}
@Override
public void onEvict(AccordCacheEntry<TxnId, Command> state)
{
TxnId txnId = state.key();
if (txnId.is(Routable.Domain.Range))
{
RangeRoute cur = cachedRangeTxnsById.remove(txnId);
if (cur != null)
pushEdit(txnId, null, toMap(txnId, cur));
}
}
static Object[] toMap(TxnId txnId, RangeRoute route)
{
int size = route.size();
switch (size)
{
case 0: return IntervalBTree.empty();
case 1: return IntervalBTree.singleton(new TxnIdInterval(route.get(0), txnId));
default:
{
try (IntervalBTree.FastIntervalTreeBuilder<TxnIdInterval> builder = IntervalBTree.fastBuilder(ENTRIES))
{
for (int i = 0 ; i < size ; ++i)
builder.add(new TxnIdInterval(route.get(i), txnId));
return builder.build();
}
}
}
}
public JournalRangeIndex.Loader loader(TxnId primaryTxnId, Timestamp primaryExecuteAt, LoadKeysFor loadKeysFor, Unseekables<?> keysOrRanges)
{
RedundantBefore redundantBefore = commandStore.unsafeGetRedundantBefore();
MaxDecidedRX maxDecidedRX = commandStore.unsafeGetMaxDecidedRX();
return SummaryLoader.loader(redundantBefore, maxDecidedRX, primaryTxnId, primaryExecuteAt, loadKeysFor, keysOrRanges, this::newLoader);
}
@Override
public void update(Command prev, Command updated, boolean force)
{
}
@Override
public void postReplay()
{
}
private Loader newLoader(RedundantBefore redundantBefore, MaxDecidedRX maxDecidedRX, @Nullable TxnId primaryTxnId, Unseekables<?> searchKeysOrRanges, Kinds testKind, TxnId minTxnId, Timestamp maxTxnId, LoadKeysFor loadKeysFor)
{
return new Loader(this, redundantBefore, maxDecidedRX, primaryTxnId, searchKeysOrRanges, testKind, minTxnId, maxTxnId, loadKeysFor);
}
@Override
protected Object[] tree(Object[] edit)
{
return edit;
}
}

View File

@ -30,21 +30,21 @@ import org.apache.cassandra.index.accord.RouteIndexFormat;
import org.apache.cassandra.service.accord.api.TokenKey;
import org.apache.cassandra.utils.CloseableIterator;
public interface RangeSearcher
public interface JournalRangeSearcher
{
Result search(int commandStoreId, TokenRange range, TxnId minTxnId, Timestamp maxTxnId, @Nullable MaxDecidedRX.DecidedRX decidedRX);
Result search(int commandStoreId, TokenKey key, TxnId minTxnId, Timestamp maxTxnId, @Nullable MaxDecidedRX.DecidedRX decidedRX);
static RangeSearcher extractRangeSearcher(Object o)
static JournalRangeSearcher extractRangeSearcher(Object o)
{
if (o instanceof RangeSearcher.Supplier)
return ((RangeSearcher.Supplier) o).rangeSearcher();
return NoopRangeSearcher.instance;
if (o instanceof JournalRangeSearcher.Supplier)
return ((JournalRangeSearcher.Supplier) o).rangeSearcher();
return NoopJournalRangeSearcher.instance;
}
interface Supplier
{
RangeSearcher rangeSearcher();
JournalRangeSearcher rangeSearcher();
}
interface Result
@ -117,7 +117,7 @@ public interface RangeSearcher
}
}
enum NoopRangeSearcher implements RangeSearcher
enum NoopJournalRangeSearcher implements JournalRangeSearcher
{
instance;

View File

@ -58,7 +58,7 @@ import org.apache.cassandra.utils.RangeTree;
import static accord.primitives.Routable.Domain.Range;
public class RouteInMemoryIndex<V> implements RangeSearcher
public class JournalSegmentRangeSearcher<V> implements JournalRangeSearcher
{
private final Long2ObjectHashMap<SegmentIndex> segmentIndexes = new Long2ObjectHashMap<>();
@ -85,7 +85,7 @@ public class RouteInMemoryIndex<V> implements RangeSearcher
}
@Override
public RangeSearcher.Result search(int commandStoreId, TokenRange range, TxnId minTxnId, Timestamp maxTxnId, @Nullable DecidedRX decidedRX)
public JournalRangeSearcher.Result search(int commandStoreId, TokenRange range, TxnId minTxnId, Timestamp maxTxnId, @Nullable DecidedRX decidedRX)
{
NavigableSet<TxnId> result = search(commandStoreId, range.table(),
OrderedRouteSerializer.serializeTokenOnly(range.start()),
@ -103,7 +103,7 @@ public class RouteInMemoryIndex<V> implements RangeSearcher
}
@Override
public RangeSearcher.Result search(int commandStoreId, TokenKey key, TxnId minTxnId, Timestamp maxTxnId, @Nullable MaxDecidedRX.DecidedRX decidedRX)
public JournalRangeSearcher.Result search(int commandStoreId, TokenKey key, TxnId minTxnId, Timestamp maxTxnId, @Nullable MaxDecidedRX.DecidedRX decidedRX)
{
NavigableSet<TxnId> result = search(commandStoreId, key.table(), OrderedRouteSerializer.serializeTokenOnly(key), minTxnId, maxTxnId, decidedRX);
return new DefaultResult(minTxnId, maxTxnId, decidedRX, CloseableIterator.wrap(result.iterator()));

View File

@ -0,0 +1,140 @@
/*
* 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.nio.ByteBuffer;
import java.util.Map;
import java.util.function.BooleanSupplier;
import accord.local.Command;
import accord.local.CommandSummaries;
import accord.local.LoadKeysFor;
import accord.local.MaxDecidedRX;
import accord.local.RedundantBefore;
import accord.primitives.Routable;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.primitives.Unseekables;
import accord.utils.Invariants;
import org.apache.cassandra.exceptions.UnknownTableException;
import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.service.accord.serializers.Version;
import static accord.api.Journal.Load.MINIMAL;
import static accord.api.Journal.Load.MINIMAL_WITH_DEPS;
import static accord.local.LoadKeysFor.RECOVERY;
public interface RangeIndex
{
abstract class Loader extends CommandSummaries.SummaryLoader
{
public Loader(RedundantBefore redundantBefore, MaxDecidedRX maxDecidedRX, TxnId primaryTxnId, Unseekables<?> searchKeysOrRanges, Txn.Kind.Kinds testKinds, TxnId minTxnId, Timestamp maxTxnId, LoadKeysFor loadKeysFor)
{
super(redundantBefore, maxDecidedRX, primaryTxnId, searchKeysOrRanges, testKinds, minTxnId, maxTxnId, loadKeysFor);
}
abstract AccordCommandStore commandStore();
abstract void loadExclusive(Map<Timestamp, CommandSummaries.Summary> into, AccordCommandStore.Caches caches);
abstract void load(Map<Timestamp, CommandSummaries.Summary> into, BooleanSupplier abort);
abstract void finish(Map<Timestamp, CommandSummaries.Summary> into);
abstract void cleanupExclusive(AccordCommandStore.Caches caches);
protected CommandSummaries.Summary loadFromDisk(TxnId txnId)
{
if (loadKeysFor != RECOVERY)
{
Command.Minimal cmd = commandStore().loadMinimal(txnId);
if (cmd != null)
return ifRelevant(cmd);
}
else
{
Command.MinimalWithDeps cmd = commandStore().loadMinimalWithDeps(txnId);
if (cmd != null)
return ifRelevant(cmd);
}
return null;
}
public CommandSummaries.Summary ifRelevant(AccordCacheEntry<TxnId, Command> state)
{
if (state.key().domain() != Routable.Domain.Range)
return null;
switch (state.status())
{
default:
throw new AssertionError("Unhandled status: " + state.status());
case LOADING:
case WAITING_TO_LOAD:
case UNINITIALIZED:
return null;
case LOADED:
case MODIFIED:
case SAVING:
case WAITING_TO_SAVE:
case FAILED_TO_SAVE:
}
TxnId txnId = state.key();
if (!isMaybeRelevant(txnId))
return null;
Object command = state.getOrShrunkExclusive();
if (command == null)
return null;
if (command instanceof Command)
return ifRelevant((Command) command);
Invariants.require(command instanceof ByteBuffer);
AccordJournal.Builder builder = new AccordJournal.Builder(txnId, loadKeysFor != RECOVERY ? MINIMAL : MINIMAL_WITH_DEPS);
ByteBuffer buffer = (ByteBuffer) command;
buffer.mark();
try (DataInputBuffer buf = new DataInputBuffer(buffer, false))
{
builder.deserializeNext(buf, Version.LATEST);
if (loadKeysFor != RECOVERY) return ifRelevant(builder.asMinimal());
else return ifRelevant(builder.asMinimalWithDeps());
}
catch (UnknownTableException e)
{
return null;
}
catch (IOException e)
{
throw new RuntimeException(e);
}
finally
{
buffer.reset();
}
}
}
Loader loader(TxnId primaryTxnId, Timestamp primaryExecuteAt, LoadKeysFor loadKeysFor, Unseekables<?> keysOrRanges);
void update(Command prev, Command updated, boolean force);
void postReplay();
}

View File

@ -23,6 +23,7 @@ import accord.primitives.Range;
import org.apache.cassandra.utils.RangeTree;
// TODO (expected): move to test package?
public enum RangeTreeRangeAccessor implements RangeTree.Accessor<RoutingKey, Range>
{
instance;

View File

@ -38,6 +38,7 @@ import accord.api.ReplicaEventListener;
import accord.api.RoutingKey;
import accord.api.Tracing;
import accord.coordinate.Coordination;
import accord.coordinate.Preempted;
import accord.coordinate.Timeout;
import accord.local.Command;
import accord.local.Node;
@ -73,7 +74,7 @@ import org.apache.cassandra.metrics.AccordSystemMetrics;
import org.apache.cassandra.net.ResponseContext;
import org.apache.cassandra.service.RetryStrategy;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.AccordTracing;
import org.apache.cassandra.service.accord.debug.AccordTracing;
import org.apache.cassandra.service.accord.serializers.TableMetadatasAndKeys;
import org.apache.cassandra.service.accord.txn.TxnQuery;
import org.apache.cassandra.service.accord.txn.TxnRead;
@ -204,9 +205,11 @@ public class AccordAgent implements Agent, OwnershipEventListener
return;
AccordSystemMetrics.metrics.errors.inc();
if (t instanceof CancellationException || t instanceof TimeoutException || t instanceof Timeout)
return;
JVMStabilityInspector.uncaughtException(Thread.currentThread(), t);
if (t instanceof CancellationException || t instanceof TimeoutException || t instanceof Timeout || t instanceof Preempted)
// TODO (required): leaky logger, permitting multiple messages per time period and reporting how many were dropped
noSpamLogger.warn("", t);
else
JVMStabilityInspector.uncaughtException(Thread.currentThread(), t);
}
@Override

View File

@ -16,7 +16,7 @@
* limitations under the License.
*/
package org.apache.cassandra.service.accord;
package org.apache.cassandra.service.accord.debug;
import java.util.ArrayList;
import java.util.Arrays;
@ -46,8 +46,6 @@ import accord.coordinate.Coordination.CoordinationKind;
import accord.local.CommandStore;
import accord.local.Node;
import accord.primitives.Participants;
import accord.primitives.Routable;
import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.utils.Invariants;
import accord.utils.SortedListMap;
@ -59,8 +57,8 @@ import org.apache.cassandra.service.ClientWarn;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.NoSpamLogger;
import static org.apache.cassandra.service.accord.AccordTracing.BucketMode.LEAKY;
import static org.apache.cassandra.service.accord.AccordTracing.BucketMode.SAMPLE;
import static org.apache.cassandra.service.accord.debug.AccordTracing.BucketMode.LEAKY;
import static org.apache.cassandra.service.accord.debug.AccordTracing.BucketMode.SAMPLE;
public class AccordTracing extends AccordCoordinatorMetrics.Listener
{
@ -381,161 +379,6 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
NEW, FAILURE
}
public static class CoordinationKinds extends TinyEnumSet<CoordinationKind>
{
private static final int ALL_BITS = CoordinationKind.ALL.bitset();
public static final CoordinationKinds ALL = new CoordinationKinds(false, ALL_BITS);
public static final CoordinationKinds NONE = new CoordinationKinds(false, 0);
final boolean printAsSubtraction;
public CoordinationKinds(boolean printAsSubtraction, int bitset)
{
super(bitset);
this.printAsSubtraction = printAsSubtraction;
}
@Override
public String toString()
{
if (bitset == ALL_BITS)
return "*";
if (printAsSubtraction)
return '-' + toString(ALL_BITS & ~bitset);
return toString(bitset, CoordinationKind::forOrdinal);
}
public static CoordinationKinds parse(String input)
{
input = input.trim();
if (input.equals("*"))
return ALL;
if (input.equals("{}"))
return NONE;
boolean subtraction = false;
if (input.length() >= 1 && input.charAt(0) == '-')
{
subtraction = true;
input = input.substring(1);
}
if (input.length() < 2 || input.charAt(0) != '{' || input.charAt(input.length() - 1) != '}')
throw new IllegalArgumentException("Invalid CoordinationKinds specification: " + input);
int bits = 0;
for (String name : input.substring(1, input.length() - 1).split("\\s*,\\s*"))
bits |= TinyEnumSet.encode(CoordinationKind.valueOf(name));
if (subtraction)
bits = ALL_BITS & ~bits;
return new CoordinationKinds(subtraction, bits);
}
private static String toString(int bitset)
{
return TinyEnumSet.toString(bitset, CoordinationKind::forOrdinal);
}
}
public static class TxnKindsAndDomains
{
static final int ALL_KINDS = Txn.Kind.All.bitset();
static final TxnKindsAndDomains ALL = new TxnKindsAndDomains(false, ALL_KINDS, ALL_KINDS);
static final TxnKindsAndDomains NONE = new TxnKindsAndDomains(false, 0, 0);
final boolean printAsSubtraction;
final int keys, ranges;
public TxnKindsAndDomains(boolean printAsSubtraction, int keys, int ranges)
{
this.printAsSubtraction = printAsSubtraction;
this.keys = keys;
this.ranges = ranges;
}
boolean matches(TxnId txnId)
{
int bits = txnId.is(Routable.Domain.Key) ? keys : ranges;
return TinyEnumSet.contains(bits, txnId.kind());
}
@Override
public String toString()
{
if (keys == ALL_KINDS && ranges == ALL_KINDS)
return "*";
if (printAsSubtraction)
return '-' + toString(ALL_KINDS & ~keys, ALL_KINDS & ~ranges);
return '+' + toString(keys, ranges);
}
public static TxnKindsAndDomains parse(String input)
{
input = input.trim();
if (input.equals("*"))
return ALL;
if (input.equals("{}"))
return NONE;
boolean subtraction = false;
if (input.length() >= 1 && input.charAt(0) == '-')
{
subtraction = true;
input = input.substring(1);
}
if (input.length() < 2 || input.charAt(0) != '{' || input.charAt(input.length() - 1) != '}')
throw new IllegalArgumentException("Invalid TxnKindsAndDomain specification: " + input);
int keys = 0, ranges = 0;
for (String element : input.substring(1, input.length() - 1).split("\\s*,\\s*"))
{
if (element.length() != 2)
throw new IllegalArgumentException("Invalid TxnKindsAndDomain element: " + element);
int kinds;
if (element.charAt(1) == '*') kinds = ALL_KINDS;
else
{
Txn.Kind kind = Txn.Kind.forShortName(element.charAt(1));
if (kind == null) throw new IllegalArgumentException("Unknown Txn.Kind: " + element.charAt(1));
kinds = TinyEnumSet.encode(kind);
}
switch (element.charAt(0))
{
default: throw new IllegalArgumentException("Invalid TxnKindsAndDomain element: " + element);
case '*': keys |= kinds; ranges |= kinds; break;
case 'K': keys |= kinds; break;
case 'R': ranges |= kinds; break;
}
}
if (subtraction)
{
keys = ALL_KINDS & ~keys;
ranges = ALL_KINDS & ~ranges;
}
return new TxnKindsAndDomains(subtraction, keys, ranges);
}
private static String toString(int keys, int ranges)
{
StringBuilder out = new StringBuilder("{");
if (keys != 0)
{
if (keys == ALL_KINDS) out.append("K*");
else TinyEnumSet.append(keys, Txn.Kind::forOrdinal, k -> "K" + k.shortName(), out);
}
if (ranges != 0)
{
if (keys != 0) out.append(',');
if (ranges == ALL_KINDS) out.append("R*");
else TinyEnumSet.append(ranges, Txn.Kind::forOrdinal, k -> "R" + k.shortName(), out);
}
out.append('}');
return out.toString();
}
}
public static class TracePattern
{
private static final TracePattern EMPTY = new TracePattern(null, null, null, null, 1.0f);

View File

@ -0,0 +1,78 @@
/*
* 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.debug;
import accord.coordinate.Coordination;
import accord.utils.TinyEnumSet;
public class CoordinationKinds extends TinyEnumSet<Coordination.CoordinationKind>
{
private static final int ALL_BITS = Coordination.CoordinationKind.ALL.bitset();
public static final CoordinationKinds ALL = new CoordinationKinds(false, ALL_BITS);
public static final CoordinationKinds NONE = new CoordinationKinds(false, 0);
final boolean printAsSubtraction;
public CoordinationKinds(boolean printAsSubtraction, int bitset)
{
super(bitset);
this.printAsSubtraction = printAsSubtraction;
}
@Override
public String toString()
{
if (bitset == ALL_BITS)
return "*";
if (printAsSubtraction)
return '-' + toString(ALL_BITS & ~bitset);
return toString(bitset, Coordination.CoordinationKind::forOrdinal);
}
public static CoordinationKinds parse(String input)
{
input = input.trim();
if (input.equals("*"))
return ALL;
if (input.equals("{}"))
return NONE;
boolean subtraction = false;
if (input.length() >= 1 && input.charAt(0) == '-')
{
subtraction = true;
input = input.substring(1);
}
if (input.length() < 2 || input.charAt(0) != '{' || input.charAt(input.length() - 1) != '}')
throw new IllegalArgumentException("Invalid CoordinationKinds specification: " + input);
int bits = 0;
for (String name : input.substring(1, input.length() - 1).split("\\s*,\\s*"))
bits |= TinyEnumSet.encode(Coordination.CoordinationKind.valueOf(name));
if (subtraction)
bits = ALL_BITS & ~bits;
return new CoordinationKinds(subtraction, bits);
}
private static String toString(int bitset)
{
return TinyEnumSet.toString(bitset, Coordination.CoordinationKind::forOrdinal);
}
}

View File

@ -16,7 +16,7 @@
* limitations under the License.
*/
package org.apache.cassandra.service.accord;
package org.apache.cassandra.service.accord.debug;
import java.util.ArrayList;
import java.util.Collections;
@ -39,7 +39,6 @@ import accord.local.CommandStore;
import accord.local.CommandStores;
import accord.local.PreLoadContext;
import accord.local.SafeCommandStore;
import accord.local.cfk.CommandsForKey;
import accord.local.cfk.SafeCommandsForKey;
import accord.primitives.RoutingKeys;
import accord.primitives.SaveStatus;
@ -49,6 +48,8 @@ import accord.primitives.TxnId;
import accord.utils.async.AsyncChain;
import accord.utils.async.AsyncChains;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.IAccordService;
import org.apache.cassandra.service.accord.api.TokenKey;
import org.apache.cassandra.utils.concurrent.Future;
@ -94,8 +95,8 @@ public class DebugBlockedTxns
@Override
public int compareTo(Txn that)
{
int c = Integer.compare(this.commandStoreId, that.commandStoreId);
if (c == 0) c = Integer.compare(this.depth, that.depth);
int c = Integer.compare(this.depth, that.depth);
if (c == 0) c = Integer.compare(this.commandStoreId, that.commandStoreId);
if (c == 0) c = this.txnId.compareTo(that.txnId);
if (c == 0) c = this.blockedViaKeyString().compareTo(that.blockedViaKeyString());
return c;
@ -246,9 +247,6 @@ public class DebugBlockedTxns
}
else
{
// TODO (required): this type check should not be needed; release accord version that fixes it at origin
if (blocking instanceof CommandsForKey.TxnInfo)
blocking = ((CommandsForKey.TxnInfo) blocking).plainTxnId();
boolean recurse = visited.add(blocking);
queuedTxn.add(visitTxnAsync(commandStore, blocking, rootExecuteAt, key, depth, recurse));
}

View File

@ -0,0 +1,86 @@
/*
* 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.debug;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import accord.local.Command;
import accord.local.CommandStore;
import accord.local.SafeCommandStore;
import accord.primitives.Participants;
import accord.primitives.Routables;
import accord.primitives.SaveStatus;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import accord.utils.async.AsyncChain;
import org.apache.cassandra.service.accord.IAccordService;
import static accord.primitives.Routables.Slice.Minimal;
public class DebugTxnDepsAll extends DebugTxnGraph<DebugTxnDepsAll.TxnInfo, Map<TxnId, TxnId>>
{
public static class TxnInfo extends DebugTxnGraph.TxnInfo
{
public final TxnId firstParent;
public TxnInfo(TxnId txnId, SaveStatus saveStatus, @Nullable Timestamp executeAt, TxnId firstParent, Routables<?> via)
{
super(txnId, saveStatus, executeAt, via);
this.firstParent = firstParent;
}
}
public DebugTxnDepsAll(IAccordService service, TxnId root, @Nullable Participants<?> intersecting, TxnKindsAndDomains kinds, Timestamp min, int maxDepth, Consumer<TxnInfos<TxnInfo>> visit)
{
super(service, root, kinds, intersecting, min, maxDepth, visit);
}
public static void visit(IAccordService accord, TxnId root, @Nullable Participants<?> intersecting, TxnKindsAndDomains kinds, Timestamp min, int maxDepth, long deadlineNanos, Consumer<TxnInfos<TxnInfo>> visit) throws TimeoutException
{
new DebugTxnDepsAll(accord, root, intersecting, kinds, min, maxDepth, visit).visit(deadlineNanos);
}
@Override
protected AsyncChain<TxnInfos<TxnInfo>> visitRoot(SafeCommandStore safeStore, Command command)
{
return visitRoot(safeStore, command, new HashMap<>());
}
protected TxnInfos<TxnInfo> build(CommandStore commandStore, int depth, Command parent, List<SortInfo> sortedInfos, @Nullable Participants<?> intersecting, Map<TxnId, TxnId> visited)
{
ImmutableList.Builder<TxnInfo> children = ImmutableList.builder();
for (int i = 0; i < sortedInfos.size() ; ++i)
{
SortInfo info = sortedInfos.get(i);
Participants<?> p = parent.partialDeps().participants(info.txnId);
if (intersecting != null) p = p.intersecting(intersecting, Minimal);
children.add(new TxnInfo(info.txnId, info.saveStatus, info.executeAt, visited.putIfAbsent(info.txnId, parent.txnId()), p));
}
return new TxnInfos<>(commandStore.id(), depth, parent.txnId(), children.build());
}
}

View File

@ -0,0 +1,84 @@
/*
* 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.debug;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import accord.local.Command;
import accord.local.CommandStore;
import accord.local.SafeCommandStore;
import accord.primitives.Participants;
import accord.primitives.Status;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import accord.utils.async.AsyncChain;
import org.apache.cassandra.service.accord.IAccordService;
import static accord.primitives.Routables.Slice.Minimal;
public class DebugTxnDepsOrdered extends DebugTxnGraph<DebugTxnGraph.TxnInfo, Set<TxnId>>
{
public DebugTxnDepsOrdered(IAccordService service, TxnId root, TxnKindsAndDomains kinds, @Nullable Participants<?> intersecting, Timestamp min, int maxDepth, Consumer<TxnInfos<TxnInfo>> visit)
{
super(service, root, kinds, intersecting, min, maxDepth, visit);
}
public static void visit(IAccordService accord, TxnId root, TxnKindsAndDomains kinds, @Nullable Participants<?> intersecting, Timestamp min, int maxDepth, long deadlineNanos, Consumer<TxnInfos<TxnInfo>> visit) throws TimeoutException
{
new DebugTxnDepsOrdered(accord, root, kinds, intersecting, min, maxDepth, visit).visit(deadlineNanos);
}
@Override
protected AsyncChain<TxnInfos<TxnInfo>> visitRoot(SafeCommandStore safeStore, Command command)
{
return visitRoot(safeStore, command, new HashSet<>());
}
protected TxnInfos<TxnInfo> build(CommandStore commandStore, int depth, Command parent, List<SortInfo> sortedInfos, @Nullable Participants<?> intersecting, Set<TxnId> visited)
{
ArrayList<TxnInfo> children = new ArrayList<>();
visitLatestCommitted(sortedInfos, parent, (next, via) -> {
children.add(new TxnInfo(next.txnId, next.saveStatus, next.executeAt, via));
});
for (int i = 0; i < sortedInfos.size() ; ++i)
{
SortInfo next = sortedInfos.get(i);
if (next.saveStatus.hasBeen(Status.Committed) || !visited.add(next.txnId))
continue;
Participants<?> p = parent.partialDeps().participants(next.txnId);
if (intersecting != null)
p = p.intersecting(intersecting, Minimal);
if (p.isEmpty()) continue;
children.add(new TxnInfo(next.txnId, next.saveStatus, next.executeAt, p));
}
children.sort(Comparator.naturalOrder());
children.trimToSize();
return new TxnInfos<>(commandStore.id(), depth, parent.txnId(), children);
}
}

View File

@ -0,0 +1,355 @@
/*
* 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.debug;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import accord.local.Command;
import accord.local.CommandStore;
import accord.local.CommandStores;
import accord.local.PreLoadContext;
import accord.local.SafeCommandStore;
import accord.primitives.PartialDeps;
import accord.primitives.Participants;
import accord.primitives.Routable.Domain;
import accord.primitives.Routables;
import accord.primitives.SaveStatus;
import accord.primitives.Status;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.utils.Invariants;
import accord.utils.LargeBitSet;
import accord.utils.Reduce;
import accord.utils.async.AsyncChain;
import accord.utils.async.AsyncChains;
import accord.utils.async.Cancellable;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.IAccordService;
import org.apache.cassandra.utils.concurrent.Future;
import static accord.primitives.Routables.Slice.Minimal;
public abstract class DebugTxnGraph<T, P>
{
public static class TxnInfos<T> implements Comparable<TxnInfos<T>>
{
public final int commandStoreId;
public final int depth;
public final TxnId parent;
public final List<T> infos;
public TxnInfos(int commandStoreId, int depth, TxnId parent, List<T> infos)
{
this.commandStoreId = commandStoreId;
this.depth = depth;
this.infos = infos;
this.parent = parent;
}
@Override
public int compareTo(TxnInfos that)
{
int c = Integer.compare(this.depth, that.depth);
if (c == 0) c = Integer.compare(this.commandStoreId, that.commandStoreId);
if (c == 0) c = -this.parent.compareTo(that.parent);
return c;
}
}
public static class TxnInfo implements Comparable<TxnInfo>
{
public final TxnId txnId;
public final SaveStatus saveStatus;
public final @Nullable Timestamp executeAt;
public final Routables<?> via;
public TxnInfo(TxnId txnId, SaveStatus saveStatus, @Nullable Timestamp executeAt, Routables<?> via)
{
this.txnId = txnId;
this.saveStatus = saveStatus;
this.executeAt = executeAt;
this.via = via;
}
@Override
public int compareTo(@Nonnull TxnInfo that)
{
int c = -compareExecuteAt(this.executeAt, that.executeAt);
if (c == 0) c = -this.txnId.compareTo(that.txnId);
return c;
}
}
protected static class SaveInfo
{
private static final SaveInfo NONE = new SaveInfo(SaveStatus.NotDefined, null);
public final SaveStatus saveStatus;
public final @Nullable Timestamp executeAt;
private SaveInfo(SaveStatus saveStatus, Timestamp executeAt)
{
this.saveStatus = saveStatus;
this.executeAt = executeAt;
}
}
protected static class SortInfo extends SaveInfo implements Comparable<SortInfo>
{
public final TxnId txnId;
private SortInfo(TxnId txnId, SaveStatus saveStatus, Timestamp executeAt)
{
super(saveStatus, executeAt);
this.txnId = txnId;
}
@Override
public int compareTo(@Nonnull SortInfo that)
{
int c = compareExecuteAt(this.executeAt, that.executeAt);
if (c == 0) c = this.txnId.compareTo(that.txnId);
return c;
}
}
final IAccordService service;
final Consumer<? super TxnInfos<T>> visit;
final TxnId root;
final @Nullable Participants<?> intersecting;
final TxnKindsAndDomains kinds;
final Timestamp min;
final int maxDepth;
final ConcurrentLinkedQueue<AsyncChain<TxnInfos<T>>> queued = new ConcurrentLinkedQueue<>();
public DebugTxnGraph(IAccordService service, TxnId root, TxnKindsAndDomains kinds, @Nullable Participants<?> intersecting, Timestamp min, int maxDepth, Consumer<? super TxnInfos<T>> visit)
{
this.service = service;
this.visit = visit;
this.root = root;
this.intersecting = intersecting;
this.kinds = kinds;
this.min = min;
this.maxDepth = maxDepth;
}
protected abstract TxnInfos<T> build(CommandStore commandStore, int depth, Command parent, List<SortInfo> sortedInfos, @Nullable Participants<?> intersecting, P param);
protected abstract AsyncChain<TxnInfos<T>> visitRoot(SafeCommandStore safeStore, Command command);
protected AsyncChain<TxnInfos<T>> visitRoot(SafeCommandStore safeStore, Command command, P param)
{
return visitParent(safeStore, command, param, new HashMap<>(), 0);
}
void visit(long deadlineNanos) throws TimeoutException
{
CommandStores commandStores = service.node().commandStores();
if (commandStores.count() == 0)
return;
int[] ids = commandStores.ids();
List<AsyncChain<TxnInfos<T>>> chains = new ArrayList<>(ids.length);
for (int id : ids)
chains.add(submitRoot(commandStores.forId(id), root));
List<AsyncChain<TxnInfos<T>>> tmp = new ArrayList<>();
Future<List<TxnInfos<T>>> next = AccordService.toFuture(AsyncChains.allOf(chains));
while (next != null)
{
if (!next.awaitUntilThrowUncheckedOnInterrupt(deadlineNanos))
throw new TimeoutException();
next.rethrowIfFailed();
List<TxnInfos<T>> process = next.getNow().stream()
.filter(Objects::nonNull)
.sorted(Comparator.naturalOrder())
.collect(Collectors.toList());
for (TxnInfos<T> txn : process)
visit.accept(txn);
next = drainToFuture(queued, tmp);
}
}
static <V> Future<List<V>> drainToFuture(Queue<AsyncChain<V>> drain, List<AsyncChain<V>> tmp)
{
AsyncChain<V> next;
while (null != (next = drain.poll()))
tmp.add(next);
if (tmp.isEmpty())
return null;
Future<List<V>> result = AccordService.toFuture(AsyncChains.allOf(List.copyOf(tmp)));
tmp.clear();
return result;
}
private AsyncChain<TxnInfos<T>> submitRoot(CommandStore commandStore, TxnId txnId)
{
return commandStore.chain(PreLoadContext.contextFor(txnId, "Populate txn_graph"), safeStore -> {
Command command = safeStore.unsafeGetNoCleanup(txnId).current();
if (command == null || command.saveStatus() == SaveStatus.Uninitialised)
return AsyncChains.<TxnInfos<T>>success(null);
return visitRoot(safeStore, command);
}).flatMap(i -> i);
}
private AsyncChain<TxnInfos<T>> submitParent(CommandStore commandStore, TxnId txnId, P param, Map<TxnId, SaveInfo> infos, int depth)
{
return commandStore.chain(PreLoadContext.contextFor(txnId, "Populate txn_graph"), safeStore -> {
Command command = safeStore.unsafeGetNoCleanup(txnId).current();
if (command == null || command.saveStatus() == SaveStatus.Uninitialised)
return AsyncChains.<TxnInfos<T>>success(null);
return visitParent(safeStore, command, param, infos, depth);
}).flatMap(i -> i);
}
private AsyncChain<TxnInfos<T>> visitParent(SafeCommandStore safeStore, Command command, P param, Map<TxnId, SaveInfo> infos, int depth)
{
CommandStore commandStore = safeStore.commandStore();
if (depth < maxDepth)
{
PartialDeps deps = command.partialDeps();
if (deps != null)
{
LargeBitSet recurse = new LargeBitSet(deps.txnIdCount());
if (intersecting != null)
{
if (kinds.matchesAny(Domain.Key))
deps.keyDeps.forEach(intersecting, recurse, null, (r, n, i) -> r.set(i));
if (kinds.matchesAny(Domain.Range))
deps.rangeDeps.forEach(intersecting, recurse, deps.keyDeps, (r, d, i) -> r.set(d.txnIdCount() + i));
}
else
{
if (kinds.matchesAny(Domain.Key))
recurse.setRange(0, deps.keyDeps.txnIdCount());
if (kinds.matchesAny(Domain.Range))
recurse.setRange(deps.keyDeps.txnIdCount(), deps.txnIdCount());
}
List<AsyncChain<Void>> populate = new ArrayList<>();
for (int i = recurse.nextSetBit(0, -1); i >= 0; i = recurse.nextSetBit(i + 1, -1))
{
TxnId txnId = deps.txnId(i);
if (!kinds.matches(txnId) || txnId.compareTo(min) < 0)
recurse.unset(i);
else if (!infos.containsKey(txnId))
populate.add(populateTxnAsync(commandStore, txnId, infos));
}
if (recurse.getSetBitCount() > 0)
{
AsyncChain<Void> first = populate.isEmpty() ? AsyncChains.success(null) : AsyncChains.reduce(populate, Reduce.toNull());
return first.flatMap(ignore -> new AsyncChains.Head<>()
{
@Override
protected @Nullable Cancellable start(BiConsumer<? super TxnInfos<T>, Throwable> callback)
{
List<SortInfo> list = new ArrayList<>(recurse.getSetBitCount());
for (int i = recurse.nextSetBit(0, -1); i >= 0; i = recurse.nextSetBit(i + 1, -1))
{
TxnId txnId = deps.txnId(i);
SaveInfo info = infos.get(txnId);
if (Invariants.expect(info != null, "populate txn_graph ordering failure; {} has no info", txnId))
list.add(new SortInfo(txnId, info.saveStatus, info.executeAt));
}
list.sort(Comparator.reverseOrder());
visitLatestCommitted(list, command, (next, p) -> {
if (next.txnId.is(Txn.Kind.Read))
return;
if (!next.saveStatus.hasBeen(Status.Committed) || next.saveStatus.hasBeen(Status.Truncated))
return;
queued.add(submitParent(commandStore, next.txnId, param, infos, depth + 1));
});
callback.accept(build(commandStore, depth, command, list, intersecting, param), null);
return null;
}
});
}
}
}
return AsyncChains.success(new TxnInfos<>(commandStore.id(), depth, command.txnId(), Collections.emptyList()));
}
protected void visitLatestCommitted(List<SortInfo> sortedInfos, Command parent, BiConsumer<SortInfo, Participants<?>> forEach)
{
Participants<?> writes = parent.participants().owns();
if (intersecting != null) writes = writes.intersecting(intersecting, Minimal);
Participants<?> syncpoints = writes;
boolean awaitsOnlyDeps = parent.txnId().awaitsOnlyDeps();
for (int i = 0; i < sortedInfos.size() ; ++i)
{
SortInfo info = sortedInfos.get(i);
boolean isCommitted = info.saveStatus.hasBeen(Status.Committed) && !info.saveStatus.hasBeen(Status.Invalidated);
if (!isCommitted) continue;
Participants<?> visit = info.txnId.isSyncPoint() ? syncpoints : writes;
if (visit.isEmpty() || (!awaitsOnlyDeps && info.executeAt.compareTo(parent.executeAt()) > 0))
continue;
Participants<?> p = parent.partialDeps().participants(info.txnId);
if (intersecting != null) p = p.intersecting(intersecting, Minimal);
if (!p.intersects(visit))
continue;
forEach.accept(info, p);
if (info.txnId.isSyncPoint()) syncpoints = syncpoints.without(p);
if (info.txnId.isSyncPoint() || info.txnId.isWrite()) writes = writes.without(p);
if ((parent.txnId().isSyncPoint() ? syncpoints : writes).isEmpty())
break;
}
}
private AsyncChain<Void> populateTxnAsync(CommandStore commandStore, TxnId txnId, Map<TxnId, SaveInfo> visited)
{
return commandStore.chain(PreLoadContext.contextFor(txnId, "Populate txn_graph"), safeStore -> {
Command command = safeStore.unsafeGetNoCleanup(txnId).current();
visited.putIfAbsent(txnId, command == null || command.saveStatus() == SaveStatus.Uninitialised ? SaveInfo.NONE : new SaveInfo(command.saveStatus(), command.executeAtIfKnown()));
});
}
static int compareExecuteAt(Timestamp a, Timestamp b)
{
if (a == null || b == null)
return a == b ? 0 : a == null ? -1 : 1;
return a.compareTo(b);
}
}

View File

@ -0,0 +1,144 @@
/*
* 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.debug;
import accord.primitives.Routable.Domain;
import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.utils.TinyEnumSet;
import accord.utils.UnhandledEnum;
public class TxnKindsAndDomains
{
static final int ALL_KINDS = Txn.Kind.All.bitset();
public static final TxnKindsAndDomains ALL = new TxnKindsAndDomains(false, ALL_KINDS, ALL_KINDS);
public static final TxnKindsAndDomains NONE = new TxnKindsAndDomains(false, 0, 0);
final boolean printAsSubtraction;
final int keys, ranges;
public TxnKindsAndDomains(boolean printAsSubtraction, int keys, int ranges)
{
this.printAsSubtraction = printAsSubtraction;
this.keys = keys;
this.ranges = ranges;
}
public boolean matches(TxnId txnId)
{
int bits = txnId.is(Domain.Key) ? keys : ranges;
return TinyEnumSet.contains(bits, txnId.kind());
}
public boolean matchesAny(Domain domain)
{
switch (domain)
{
default: throw new UnhandledEnum(domain);
case Key: return keys != 0;
case Range: return ranges != 0;
}
}
@Override
public String toString()
{
if (keys == ALL_KINDS && ranges == ALL_KINDS)
return "*";
if (printAsSubtraction)
return '-' + toString(ALL_KINDS & ~keys, ALL_KINDS & ~ranges);
return '+' + toString(keys, ranges);
}
public static TxnKindsAndDomains parse(String input)
{
input = input.trim();
if (input.equals("*"))
return ALL;
if (input.equals("{}"))
return NONE;
boolean subtraction = false;
if (input.length() >= 1 && input.charAt(0) == '-')
{
subtraction = true;
input = input.substring(1);
}
if (input.length() < 2 || input.charAt(0) != '{' || input.charAt(input.length() - 1) != '}')
throw new IllegalArgumentException("Invalid TxnKindsAndDomain specification: " + input);
int keys = 0, ranges = 0;
for (String element : input.substring(1, input.length() - 1).split("\\s*,\\s*"))
{
if (element.length() != 2)
throw new IllegalArgumentException("Invalid TxnKindsAndDomain element: " + element);
int kinds;
if (element.charAt(1) == '*') kinds = ALL_KINDS;
else
{
Txn.Kind kind = Txn.Kind.forShortName(element.charAt(1));
if (kind == null) throw new IllegalArgumentException("Unknown Txn.Kind: " + element.charAt(1));
kinds = TinyEnumSet.encode(kind);
}
switch (element.charAt(0))
{
default:
throw new IllegalArgumentException("Invalid TxnKindsAndDomain element: " + element);
case '*':
keys |= kinds;
ranges |= kinds;
break;
case 'K':
keys |= kinds;
break;
case 'R':
ranges |= kinds;
break;
}
}
if (subtraction)
{
keys = ALL_KINDS & ~keys;
ranges = ALL_KINDS & ~ranges;
}
return new TxnKindsAndDomains(subtraction, keys, ranges);
}
private static String toString(int keys, int ranges)
{
StringBuilder out = new StringBuilder("{");
if (keys != 0)
{
if (keys == ALL_KINDS) out.append("K*");
else TinyEnumSet.append(keys, Txn.Kind::forOrdinal, k -> "K" + k.shortName(), out);
}
if (ranges != 0)
{
if (keys != 0) out.append(',');
if (ranges == ALL_KINDS) out.append("R*");
else TinyEnumSet.append(ranges, Txn.Kind::forOrdinal, k -> "R" + k.shortName(), out);
}
out.append('}');
return out.toString();
}
}

View File

@ -28,7 +28,7 @@ import javax.annotation.Nullable;
import accord.local.Node;
import accord.local.durability.DurabilityService.SyncRemote;
import accord.primitives.Ranges;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Keyspace;
@ -56,7 +56,6 @@ import org.apache.cassandra.utils.concurrent.Future;
import static accord.local.durability.DurabilityService.SyncLocal.NoLocal;
import static accord.local.durability.DurabilityService.SyncRemote.All;
import static accord.primitives.Timestamp.mergeMax;
import static accord.primitives.Timestamp.minForEpoch;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.config.DatabaseDescriptor.getAccordRepairTimeoutNanos;
@ -184,7 +183,7 @@ public class AccordRepair
RequestBookkeeping bookkeeping = new LatencyRequestBookkeeping(latency);
long timeoutNanos = getAccordRepairTimeoutNanos();
long maxHlc = AccordService.getBlocking(service.maxConflict(ranges).flatMap(conflict -> {
Timestamp conflictMax = mergeMax(conflict, minForEpoch(this.minEpoch.getEpoch()));
TxnId conflictMax = mergeMax(TxnId.atLeast(conflict), TxnId.minForEpoch(this.minEpoch.getEpoch()), TxnId::fromValues);
return service.sync("[repairId #" + repairId + ']', conflictMax, Ranges.of(range), including, NoLocal, syncRemote, timeoutNanos, NANOSECONDS).map(ignored -> conflictMax.hlc()).chain();
}), ranges, bookkeeping, start, start + timeoutNanos);
waiting = null;

View File

@ -47,12 +47,11 @@ public class AcceptSerializers
public static class RequestSerializer extends TxnRequestSerializer.WithUnsyncedSerializer<Accept>
{
private static final Accept.Kind[] kinds = Accept.Kind.values();
private static final int IS_PARTIAL = 1;
@Override
public void serializeBody(Accept accept, DataOutputPlus out, Version version) throws IOException
{
out.writeByte((accept.kind.ordinal() << 1) | (accept.isPartialAccept ? IS_PARTIAL : 0));
out.writeByte((accept.kind.ordinal() << 1) | (accept.acceptFlags & 1) | ((accept.acceptFlags & ~1) << 1));
CommandSerializers.ballot.serialize(accept.ballot, out);
ExecuteAtSerializer.serialize(accept.txnId, accept.executeAt, out);
DepsSerializers.partialDeps.serialize(accept.partialDeps(), out);
@ -68,7 +67,7 @@ public class AcceptSerializers
CommandSerializers.ballot.deserialize(in),
ExecuteAtSerializer.deserialize(txnId, in),
DepsSerializers.partialDeps.deserialize(in),
(flags & IS_PARTIAL) != 0);
(flags & 1) | ((flags >>> 1) & ~1));
}
@Override

View File

@ -96,7 +96,7 @@ public class CheckStatusSerializers
Known max = kind == 1 ? min : known.deserialize(in);
values[i] = new MinAndMaxKnown(min, max);
}
return KnownMap.SerializerSupport.create(true, starts, values);
return KnownMap.SerializerSupport.create(starts, values);
}
@Override

View File

@ -21,6 +21,7 @@ package org.apache.cassandra.service.accord.serializers;
import java.io.IOException;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.function.BiFunction;
import java.util.function.IntFunction;
import accord.api.RoutingKey;
@ -32,7 +33,6 @@ import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import accord.utils.Invariants;
import accord.utils.ReducingRangeMap;
import accord.utils.TriFunction;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.io.UnversionedSerializer;
@ -53,9 +53,9 @@ public class CommandStoreSerializers
{
final UnversionedSerializer<T> valueSerializer;
final IntFunction<T[]> newValueArray;
final TriFunction<Boolean, RoutingKey[], T[], R> constructor;
final BiFunction<RoutingKey[], T[], R> constructor;
public ReducingRangeMapSerializer(UnversionedSerializer<T> valueSerializer, IntFunction<T[]> newValueArray, TriFunction<Boolean, RoutingKey[], T[], R> constructor)
public ReducingRangeMapSerializer(UnversionedSerializer<T> valueSerializer, IntFunction<T[]> newValueArray, BiFunction<RoutingKey[], T[], R> constructor)
{
this.valueSerializer = valueSerializer;
this.newValueArray = newValueArray;
@ -65,7 +65,7 @@ public class CommandStoreSerializers
@Override
public void serialize(R map, DataOutputPlus out) throws IOException
{
out.writeBoolean(map.inclusiveEnds());
out.writeBoolean(true);
int mapSize = map.size();
out.writeUnsignedVInt32(mapSize);
@ -81,7 +81,7 @@ public class CommandStoreSerializers
@Override
public R deserialize(DataInputPlus in) throws IOException
{
boolean inclusiveEnds = in.readBoolean();
in.readBoolean();
int mapSize = in.readUnsignedVInt32();
RoutingKey[] keys = new RoutingKey[mapSize + 1];
T[] values = newValueArray.apply(mapSize);
@ -92,7 +92,7 @@ public class CommandStoreSerializers
}
if (mapSize > 0)
keys[mapSize] = KeySerializers.routingKey.deserialize(in);
return constructor.apply(inclusiveEnds, keys, values);
return constructor.apply(keys, values);
}
@Override
@ -113,7 +113,7 @@ public class CommandStoreSerializers
}
}
public static UnversionedSerializer<DurableBefore> durableBefore = new ReducingRangeMapSerializer<>(NullableSerializer.wrap(new UnversionedSerializer<>()
public static UnversionedSerializer<DurableBefore> durableBefore = new ReducingRangeMapSerializer<>(NullableSerializer.wrap(new UnversionedSerializer<DurableBefore.Entry>()
{
@Override
public void serialize(DurableBefore.Entry t, DataOutputPlus out) throws IOException

View File

@ -91,7 +91,7 @@ public class LatestDepsSerializers
}
starts[size] = KeySerializers.routingKey.deserialize(in);
return LatestDeps.SerializerSupport.create(true, starts, values);
return LatestDeps.SerializerSupport.create(starts, values);
}
@Override

View File

@ -117,9 +117,9 @@ public class RecoverySerializers
CommandSerializers.ballot.serialize(recoverOk.accepted, out);
ExecuteAtSerializer.serializeNullable(recoverOk.executeAt, out);
latestDeps.serialize(recoverOk.deps, out);
DepsSerializers.deps.serialize(recoverOk.earlierWait, out);
DepsSerializers.deps.serialize(recoverOk.earlierNoWait, out);
DepsSerializers.deps.serialize(recoverOk.laterCoordRejects, out);
DepsSerializers.deps.serialize(recoverOk.simpleWait, out);
DepsSerializers.deps.serialize(recoverOk.simpleNoWait, out);
DepsSerializers.deps.serialize(recoverOk.supersedingCoordRejects, out);
out.writeBoolean(recoverOk.selfAcceptsFastPath);
KeySerializers.nullableParticipants.serialize(recoverOk.coordinatorAcceptsFastPath, out);
out.writeBoolean(recoverOk.supersedingRejects);
@ -187,9 +187,9 @@ public class RecoverySerializers
size += CommandSerializers.ballot.serializedSize(recoverOk.accepted);
size += ExecuteAtSerializer.serializedNullableSize(recoverOk.executeAt);
size += latestDeps.serializedSize(recoverOk.deps);
size += DepsSerializers.deps.serializedSize(recoverOk.earlierWait);
size += DepsSerializers.deps.serializedSize(recoverOk.earlierNoWait);
size += DepsSerializers.deps.serializedSize(recoverOk.laterCoordRejects);
size += DepsSerializers.deps.serializedSize(recoverOk.simpleWait);
size += DepsSerializers.deps.serializedSize(recoverOk.simpleNoWait);
size += DepsSerializers.deps.serializedSize(recoverOk.supersedingCoordRejects);
size += TypeSizes.sizeof(recoverOk.selfAcceptsFastPath);
size += KeySerializers.nullableParticipants.serializedSize(recoverOk.coordinatorAcceptsFastPath);
size += TypeSizes.sizeof(recoverOk.supersedingRejects);
@ -251,7 +251,7 @@ public class RecoverySerializers
}
starts[size] = KeySerializers.routingKey.deserialize(in);
return LatestDeps.SerializerSupport.create(true, starts, values);
return LatestDeps.SerializerSupport.create(starts, values);
}
@Override

View File

@ -41,7 +41,7 @@ import org.slf4j.LoggerFactory;
import accord.primitives.Keys;
import accord.primitives.Range;
import accord.primitives.Ranges;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import org.apache.cassandra.concurrent.ImmediateExecutor;
import org.apache.cassandra.concurrent.Stage;
@ -322,7 +322,7 @@ public abstract class ConsensusKeyMigrationState
long start = nanoTime();
long deadline = requestTime.computeDeadline(isForWrite ? getWriteRpcTimeout(TimeUnit.NANOSECONDS) : getReadRpcTimeout(TimeUnit.NANOSECONDS));
RequestBookkeeping bookkeeping = new LatencyRequestBookkeeping(cfs == null ? null : cfs.metric.keyMigration);
AccordService.getBlocking(accord.sync(Timestamp.minForEpoch(minEpoch), partitionKeys, Self, global ? Quorum : NoRemote),
AccordService.getBlocking(accord.sync(TxnId.minForEpoch(minEpoch), partitionKeys, Self, global ? Quorum : NoRemote),
partitionKeys, bookkeeping, start, deadline);
Range[] asRanges = new Range[partitionKeys.size()];
for (int i = 0; i < partitionKeys.size(); i++)

View File

@ -165,7 +165,7 @@ public class StandaloneJournalUtil implements Runnable
if (kind != null && key.type != JournalKey.Type.valueOf(kind))
return;
if (txnId != null && !TxnId.fromString(txnId).equals(key.id))
if (txnId != null && !TxnId.parse(txnId).equals(key.id))
return;
try (DataInputBuffer in = new DataInputBuffer(buffer, false))
@ -222,9 +222,9 @@ public class StandaloneJournalUtil implements Runnable
if (sstables == null && journalSegments == null)
throw new IllegalArgumentException("Either --sstables or --journal-segments must be provided");
Timestamp txnId = this.txnId != null ? TxnId.fromString(this.txnId) : null;
Timestamp sinceTimestamp = this.since != null ? TxnId.fromString(this.since) : null;
Timestamp untilTimestamp = this.until != null ? TxnId.fromString(this.until) : null;
Timestamp txnId = this.txnId != null ? TxnId.parse(this.txnId) : null;
Timestamp sinceTimestamp = this.since != null ? TxnId.parse(this.since) : null;
Timestamp untilTimestamp = this.until != null ? TxnId.parse(this.until) : null;
boolean skipAllErrors;
Set<String> skipExceptionTypes = new HashSet<>();

View File

@ -44,7 +44,6 @@ import accord.primitives.Keys;
import accord.primitives.Ranges;
import accord.primitives.RoutingKeys;
import accord.primitives.Status;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import accord.utils.async.AsyncChain;
import accord.utils.async.AsyncResult;
@ -90,7 +89,7 @@ public class AccordIncrementalRepairTest extends TestBaseImpl
}
@Override
public AsyncResult<Void> sync(Object requestedBy, @Nullable Timestamp onOrAfter, Ranges ranges, @Nullable Collection<Node.Id> include, DurabilityService.SyncLocal syncLocal, DurabilityService.SyncRemote syncRemote, long timeout, TimeUnit timeoutUnits)
public AsyncResult<Void> sync(Object requestedBy, @Nullable TxnId onOrAfter, Ranges ranges, @Nullable Collection<Node.Id> include, DurabilityService.SyncLocal syncLocal, DurabilityService.SyncRemote syncRemote, long timeout, TimeUnit timeoutUnits)
{
return delegate.sync(requestedBy, onOrAfter, ranges, include, syncLocal, syncRemote, 10L, TimeUnit.MINUTES).map(v -> {
executedBarriers = true;
@ -99,7 +98,7 @@ public class AccordIncrementalRepairTest extends TestBaseImpl
}
@Override
public AsyncChain<Void> sync(@Nullable Timestamp onOrAfter, Keys keys, DurabilityService.SyncLocal syncLocal, DurabilityService.SyncRemote syncRemote)
public AsyncChain<Void> sync(@Nullable TxnId onOrAfter, Keys keys, DurabilityService.SyncLocal syncLocal, DurabilityService.SyncRemote syncRemote)
{
return delegate.sync(onOrAfter, keys, syncLocal, syncRemote).map(v -> {
executedBarriers = true;

View File

@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory;
import accord.local.durability.DurabilityService;
import accord.primitives.Ranges;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.distributed.Cluster;
@ -93,7 +93,7 @@ public class JournalAccessRouteIndexOnStartupRaceTest extends TestBaseImpl
Ranges ranges = Ranges.single(TokenRange.fullRange(metadata.id, metadata.partitioner));
for (int i = 0; i < 10; i++)
{
getBlocking(accord.sync(null, Timestamp.NONE, ranges, null, DurabilityService.SyncLocal.Self, DurabilityService.SyncRemote.Quorum, 10L, TimeUnit.MINUTES));
getBlocking(accord.sync(null, TxnId.NONE, ranges, null, DurabilityService.SyncLocal.Self, DurabilityService.SyncRemote.Quorum, 1L, TimeUnit.MINUTES));
accord.journal().closeCurrentSegmentForTestingIfNonEmpty();
accord.journal().runCompactorForTesting();

View File

@ -29,7 +29,7 @@ import org.slf4j.LoggerFactory;
import accord.local.durability.DurabilityService;
import accord.primitives.Ranges;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import accord.utils.Property;
import accord.utils.Property.SimpleCommand;
@ -120,8 +120,7 @@ public class StatefulJournalRestartTest extends TestBaseImpl
Ranges ranges = Ranges.single(TokenRange.fullRange(metadata.id, metadata.partitioner));
for (int i = 0; i < 10; i++)
{
getBlocking(accord.sync(null, Timestamp.NONE, ranges, null, DurabilityService.SyncLocal.Self, DurabilityService.SyncRemote.Quorum, 10L, TimeUnit.MINUTES));
getBlocking(accord.sync(null, TxnId.NONE, ranges, null, DurabilityService.SyncLocal.Self, DurabilityService.SyncRemote.Quorum, 1L, TimeUnit.MINUTES));
accord.journal().closeCurrentSegmentForTestingIfNonEmpty();
accord.journal().runCompactorForTesting();
}

View File

@ -78,13 +78,14 @@ public class DatabaseDescriptorRefTest
"org.apache.cassandra.auth.INetworkAuthorizer",
"org.apache.cassandra.auth.IRoleManager",
"org.apache.cassandra.config.AccordSpec",
"org.apache.cassandra.config.AccordSpec$FetchRetrySpec",
"org.apache.cassandra.config.AccordSpec$JournalSpec",
"org.apache.cassandra.config.AccordSpec$MinEpochRetrySpec",
"org.apache.cassandra.config.AccordSpec$MixedTimeSourceHandling",
"org.apache.cassandra.config.AccordSpec$FetchRetrySpec",
"org.apache.cassandra.config.AccordSpec$TransactionalRangeMigration",
"org.apache.cassandra.config.AccordSpec$QueueShardModel",
"org.apache.cassandra.config.AccordSpec$QueueSubmissionModel",
"org.apache.cassandra.config.AccordSpec$RangeIndexMode",
"org.apache.cassandra.config.AccordSpec$TransactionalRangeMigration",
"org.apache.cassandra.config.CassandraRelevantProperties",
"org.apache.cassandra.config.CassandraRelevantProperties$PropertyConverter",
"org.apache.cassandra.config.Config",

View File

@ -19,6 +19,7 @@
package org.apache.cassandra.db.virtual;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
@ -30,23 +31,41 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.BiPredicate;
import javax.annotation.Nullable;
import org.assertj.core.api.Assertions;
import org.awaitility.Awaitility;
import org.junit.After;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import accord.api.Journal;
import accord.api.ProtocolModifiers;
import accord.api.RoutingKey;
import accord.local.Command;
import accord.local.CommandStore;
import accord.local.Node;
import accord.local.StoreParticipants;
import accord.messages.NoWaitRequest;
import accord.primitives.Ballot;
import accord.primitives.FullRoute;
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.Ranges;
import accord.primitives.Routable;
import accord.primitives.SaveStatus;
import accord.primitives.Status;
import accord.primitives.Status.Durability.HasOutcome;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.topology.TopologyException;
import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.DatabaseDescriptor;
@ -71,12 +90,15 @@ import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.IAccordService;
import org.apache.cassandra.service.accord.TokenRange;
import org.apache.cassandra.service.accord.api.TokenKey;
import org.apache.cassandra.service.accord.debug.TxnKindsAndDomains;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.concurrent.Condition;
import static accord.api.ProtocolModifiers.Toggles.SendStableMessages.TO_ALL;
import static accord.primitives.Routables.Slice.Minimal;
import static accord.primitives.Status.Durability.NotDurable;
import static accord.primitives.TxnId.FastPath.Unoptimised;
import static org.apache.cassandra.Util.spinUntilSuccess;
import static org.apache.cassandra.net.Verb.ACCORD_APPLY_AND_WAIT_REQ;
@ -84,6 +106,7 @@ import static org.apache.cassandra.net.Verb.ACCORD_APPLY_REQ;
import static org.apache.cassandra.net.Verb.ACCORD_BEGIN_RECOVER_REQ;
import static org.apache.cassandra.service.accord.AccordService.getBlocking;
import static org.apache.cassandra.service.accord.AccordTestUtils.createTxn;
import static org.apache.cassandra.service.accord.debug.TxnKindsAndDomains.ALL;
public class AccordDebugKeyspaceTest extends CQLTester
{
@ -93,6 +116,27 @@ public class AccordDebugKeyspaceTest extends CQLTester
private static final String QUERY_TXN_BLOCKED_BY_REMOTE =
String.format("SELECT * FROM %s.%s WHERE node_id = ? AND txn_id=?", SchemaConstants.VIRTUAL_ACCORD_DEBUG_REMOTE, AccordDebugKeyspace.TXN_BLOCKED_BY);
private static final String QUERY_TXN_GRAPH =
String.format("SELECT * FROM %s.%s WHERE txn_id=?", SchemaConstants.VIRTUAL_ACCORD_DEBUG, AccordDebugKeyspace.TXN_GRAPH);
private static final String QUERY_TXN_GRAPH_DESC =
String.format("SELECT * FROM %s.%s WHERE txn_id=? ORDER BY depth DESC", SchemaConstants.VIRTUAL_ACCORD_DEBUG, AccordDebugKeyspace.TXN_GRAPH);
private static final String QUERY_TXN_GRAPH_INTERSECTS =
String.format("SELECT * FROM %s.%s WHERE txn_id=? AND expr(intersects, ?)", SchemaConstants.VIRTUAL_ACCORD_DEBUG, AccordDebugKeyspace.TXN_GRAPH);
private static final String QUERY_TXN_GRAPH_KIND =
String.format("SELECT * FROM %s.%s WHERE txn_id=? AND expr(kind, ?)", SchemaConstants.VIRTUAL_ACCORD_DEBUG, AccordDebugKeyspace.TXN_GRAPH);
private static final String QUERY_TXN_GRAPH_INTERSECTS_AND_KIND =
String.format("SELECT * FROM %s.%s WHERE txn_id=? AND expr(intersects, ?) AND expr(kind, ?)", SchemaConstants.VIRTUAL_ACCORD_DEBUG, AccordDebugKeyspace.TXN_GRAPH);
private static final String QUERY_TXN_GRAPH_MIN =
String.format("SELECT * FROM %s.%s WHERE txn_id=? AND child_txn_id >= ?", SchemaConstants.VIRTUAL_ACCORD_DEBUG, AccordDebugKeyspace.TXN_GRAPH);
private static final String QUERY_TXN_GRAPH_REMOTE =
String.format("SELECT * FROM %s.%s WHERE node_id = ? AND txn_id=?", SchemaConstants.VIRTUAL_ACCORD_DEBUG_REMOTE, AccordDebugKeyspace.TXN_GRAPH);
private static final String QUERY_COMMANDS_FOR_KEY =
String.format("SELECT txn_id, status FROM %s.%s WHERE key=?", SchemaConstants.VIRTUAL_ACCORD_DEBUG, AccordDebugKeyspace.COMMANDS_FOR_KEY);
@ -673,6 +717,306 @@ public class AccordDebugKeyspaceTest extends CQLTester
}
}
// TODO (expected): test graph_all (though mostly shared logic)
// TODO (required): we have some bug with visiting same txn twice via multiple intersecting dependency relations; test and fix
@Test
public void graph() throws TopologyException
{
AccordService accord = accord();
String tableName = createTable("CREATE TABLE %s (k int, c int, v int, PRIMARY KEY (k, c)) WITH transactional_mode = 'full'");
String insertTxn = String.format("BEGIN TRANSACTION\n" +
" LET r = (SELECT * FROM %s.%s WHERE k = ? AND c = ?);\n" +
" IF r IS NULL THEN\n " +
" INSERT INTO %s.%s (k, c, v) VALUES (?, ?, ?);\n" +
" END IF\n" +
"COMMIT TRANSACTION", KEYSPACE, tableName, KEYSPACE, tableName);
Txn txa = createTxn(insertTxn, 0, 0, 0, 0, 0);
AccordCommandStore commandStore = (AccordCommandStore) accord.node().commandStores().unsafeForKey((RoutingKey) txa.keys().get(0).toUnseekable());
// TODO (expected): test multi-key transactions (though functionally the same as range txns)
Txn txb, txc;
{
int i = 0;
Txn tmp;
do { ++i; tmp = createTxn(insertTxn, i, 0, i, 0, 0); }
while (!commandStore.unsafeGetRangesForEpoch().all().contains(tmp.keys().get(0).asKey()));
txb = tmp;
txc = createTxn(insertTxn, i, 0, 0, 0, 0);
}
long epoch = accord.currentEpoch();
TxnId[] ida = ids(epoch, Txn.Kind.Write, Routable.Domain.Key, new Node.Id(1), 1, 2, 3, 4, 6);
SaveStatus[] ssa = new SaveStatus[] { SaveStatus.PreAccepted, SaveStatus.Committed, SaveStatus.Committed, SaveStatus.Committed, SaveStatus.Committed };
Timestamp[] tsa = toTimestamps(ida);
tsa[2] = Timestamp.fromValues(epoch, 5, new Node.Id(1));
FullRoute<?> rta = accord.node().computeRoute(ida[0], txa.keys().toParticipants());
TxnId[] idb = ids(epoch, Txn.Kind.Write, Routable.Domain.Key, new Node.Id(2), 1, 2, 3, 4, 6);
SaveStatus[] ssb = new SaveStatus[] { SaveStatus.PreAccepted, SaveStatus.PreAccepted, SaveStatus.Committed, SaveStatus.Committed, SaveStatus.Committed };
Timestamp[] tsb = toTimestamps(idb);
tsb[2] = Timestamp.fromValues(epoch, 5, new Node.Id(2));
FullRoute<?> rtb = accord.node().computeRoute(idb[0], txb.keys().toParticipants());
TxnId[] ide = ids(epoch, Txn.Kind.ExclusiveSyncPoint, Routable.Domain.Range, new Node.Id(3), 1, 2, 3, 4, 6);
SaveStatus[] sse = new SaveStatus[] { SaveStatus.Committed, SaveStatus.Committed, SaveStatus.Committed, SaveStatus.Committed, SaveStatus.Committed };
Timestamp[] tse = toTimestamps(ide);
Txn txe = accord.node().agent().emptySystemTxn(Txn.Kind.ExclusiveSyncPoint, Routable.Domain.Range);
FullRoute<?> rte = accord.node().computeRoute(ide[0], txa.keys().toParticipants().toRanges().with(txb.keys().toParticipants().toRanges()));
write(commandStore, preaccepted(rta, ida[0], tsa[0], txa));
write(commandStore, committed(rta, ida[1], tsa[1], txa, ida, 0));
write(commandStore, committed(rta, ida[2], tsa[2], txa, ida, 0, 1, 3));
write(commandStore, committed(rta, ida[3], tsa[3], txa, ida, 0, 1, 2));
write(commandStore, committed(rta, ida[4], tsa[4], txa, ida, 0, 1, 2, 3));
write(commandStore, preaccepted(rtb, idb[0], tsb[0], txb));
write(commandStore, preaccepted(rtb, idb[1], tsb[1], txb));
write(commandStore, committed(rtb, idb[2], tsb[2], txb, idb, 0, 1, 3));
write(commandStore, committed(rtb, idb[3], tsb[3], txb, idb, 0, 1, 2));
write(commandStore, committed(rtb, idb[4], tsb[4], txb, idb, 0, 1, 2, 3));
write(commandStore, committed1(rte, ide[0], ide[0], txe, rta, ida, new int[] { 0 }, rtb, idb, new int[] { 0 }, rte, ide, new int[] { }));
write(commandStore, committed1(rte, ide[1], ide[1], txe, rta, ida, new int[] { 0, 1 }, rtb, idb, new int[] { 0, 1 }, rte, ide, new int[] { 0 }));
write(commandStore, committed1(rte, ide[2], ide[2], txe, rta, ida, new int[] { 0, 1, 2 }, rtb, idb, new int[] { 0, 1, 2 }, rte, ide, new int[] { 0, 1 }));
// txn_id, depth, command_store_id, parent_txn_id, execute_at, child_txn_id
assertRows(execute(QUERY_TXN_GRAPH, ida[0].toString()));
assertRows(execute(QUERY_TXN_GRAPH, ida[1].toString()), graphRows(commandStore, txa, ida, ssa, tsa, 1, 0));
assertRows(execute(QUERY_TXN_GRAPH, ida[2].toString()), graphRows(commandStore, txa, ida, ssa, tsa, 2, 3, 0, 1));
assertRows(execute(QUERY_TXN_GRAPH, ida[3].toString()), graphRows(commandStore, txa, ida, ssa, tsa, 3, 1, 0));
assertRows(execute(QUERY_TXN_GRAPH, ida[4].toString()), graphRows(commandStore, txa, ida, ssa, tsa, 4, 2, 0, 3, 1));
assertRows(execute(QUERY_TXN_GRAPH_INTERSECTS, ida[4].toString(), rta.homeKey().toString()), graphRows(commandStore, txa, ida, ssa, tsa, 4, 2, 0, 3, 1));
assertRows(execute(QUERY_TXN_GRAPH_INTERSECTS, ida[4].toString(), rtb.homeKey().toString()));
assertRows(execute(QUERY_TXN_GRAPH_INTERSECTS, ida[4].toString(), ""));
assertRows(execute(QUERY_TXN_GRAPH_KIND, ida[4].toString(), "{R*}"));
assertRows(execute(QUERY_TXN_GRAPH_KIND, ida[4].toString(), "{*R}"));
assertRows(execute(QUERY_TXN_GRAPH_KIND, ida[4].toString(), "{KW}"), graphRows(commandStore, txa, ida, ssa, tsa, 4, 2, 0, 3, 1));
assertRows(execute(QUERY_TXN_GRAPH, idb[0].toString()));
assertRows(execute(QUERY_TXN_GRAPH, idb[1].toString()));
assertRows(execute(QUERY_TXN_GRAPH, idb[2].toString()), graphRows(commandStore, txb, idb, ssb, tsb, 2, 3, 1, 0));
assertRows(execute(QUERY_TXN_GRAPH, idb[3].toString()), graphRows(commandStore, txb, idb, ssb, tsb, 3, 1, 0));
assertRows(execute(QUERY_TXN_GRAPH, idb[4].toString()), graphRows(commandStore, txb, idb, ssb, tsb, 4, 2, 1, 0, 3));
assertRows(execute(QUERY_TXN_GRAPH_INTERSECTS, idb[4].toString(), rtb.homeKey().toString()), graphRows(commandStore, txb, idb, ssb, tsb, 4, 2, 1, 0, 3));
assertRows(execute(QUERY_TXN_GRAPH_INTERSECTS, idb[4].toString(), rta.homeKey().toString()));
assertRows(execute(QUERY_TXN_GRAPH_INTERSECTS, idb[4].toString(), ""));
FullRoute[] rts = new FullRoute[] { rta, rtb, rte };
TxnId[][] ids = new TxnId[][] { ida, idb, ide };
Timestamp[][] tss = new Timestamp[][] { tsa, tsb, tse };
SaveStatus[][] sss = new SaveStatus[][] { ssa, ssb, sse };
int[] ide0_rows = new int[]
{
0, 2, 0, 1, 0,
0, 2, 0, 0, 0
};
assertRows(execute(QUERY_TXN_GRAPH, ide[0].toString()), graphRows(commandStore, rts, ids, sss, tss, ALL, null, null, ide[0], ide0_rows));
assertRows(execute(QUERY_TXN_GRAPH_REMOTE, 1, ide[0].toString()), prepend(new Object[] { 1 }, graphRows(commandStore, rts, ids, sss, tss, ALL, null, null, ide[0], ide0_rows)));
int[] ide1_rows = new int[]
{
0, 2, 1, 0, 1,
0, 2, 1, 2, 0,
0, 2, 1, 1, 1,
0, 2, 1, 1, 0,
0, 2, 1, 0, 0
};
assertRows(execute(QUERY_TXN_GRAPH, ide[1].toString()), graphRows(commandStore, rts, ids, sss, tss, ALL, null, null, ide[1], ide1_rows));
assertRows(execute(QUERY_TXN_GRAPH_REMOTE, 1, ide[1].toString()), prepend(new Object[] { 1 }, graphRows(commandStore, rts, ids, sss, tss, ALL, null, null, ide[1], ide1_rows)));
int[] ide2_rows = new int[]
{
0, 2, 2, 1, 2,
0, 2, 2, 0, 2,
0, 2, 2, 2, 1,
0, 2, 2, 1, 1,
0, 2, 2, 1, 0,
0, 2, 2, 0, 0,
1, 1, 2, 1, 3,
1, 0, 2, 0, 3,
1, 2, 1, 0, 1,
1, 2, 1, 2, 0,
2, 0, 3, 0, 1
};
assertRows(execute(QUERY_TXN_GRAPH, ide[2].toString()), graphRows(commandStore, rts, ids, sss, tss, ALL, null, null, ide[2], ide2_rows));
assertRows(execute(QUERY_TXN_GRAPH_DESC, ide[2].toString()), graphRows(commandStore, rts, ids, sss, tss, ALL, null, null, ide[2], reverse(ide2_rows, 5)));
assertRows(execute(QUERY_TXN_GRAPH_REMOTE, 1, ide[2].toString()), prepend(new Object[] { 1 }, graphRows(commandStore, rts, ids, sss, tss, ALL, null, null, ide[2], ide2_rows)));
assertRows(execute(QUERY_TXN_GRAPH_INTERSECTS, ide[2].toString(), AccordDebugKeyspace.toString(rte)), graphRows(commandStore, rts, ids, sss, tss, ALL, null, null, ide[2], ide2_rows));
assertRows(execute(QUERY_TXN_GRAPH_INTERSECTS, ide[2].toString(), ""));
assertRows(execute(QUERY_TXN_GRAPH_INTERSECTS, ide[2].toString(), AccordDebugKeyspace.toString(rta)), graphRows(commandStore, rts, ids, sss, tss, ALL, rta, null, ide[2], ide2_rows));
assertRows(execute(QUERY_TXN_GRAPH_INTERSECTS, ide[2].toString(), AccordDebugKeyspace.toString(rtb)), graphRows(commandStore, rts, ids, sss, tss, ALL, rtb, null, ide[2], ide2_rows));
assertRows(execute(QUERY_TXN_GRAPH_KIND, ide[2].toString(), "{KR}"));
assertRows(execute(QUERY_TXN_GRAPH_KIND, ide[2].toString(), "{KW,RX}"), graphRows(commandStore, rts, ids, sss, tss, ALL, null, null, ide[2], ide2_rows));
assertRows(execute(QUERY_TXN_GRAPH_KIND, ide[2].toString(), "{RX}"), graphRows(commandStore, rts, ids, sss, tss, TxnKindsAndDomains.parse("{RX}"), null, null, ide[2], ide2_rows));
assertRows(execute(QUERY_TXN_GRAPH_KIND, ide[2].toString(), "{KW}"), graphRows(commandStore, rts, ids, sss, tss, TxnKindsAndDomains.parse("{KW}"), null, null, ide[2], ide2_rows));
assertRows(execute(QUERY_TXN_GRAPH_INTERSECTS_AND_KIND, ide[2].toString(), AccordDebugKeyspace.toString(rta), "{KW}"), graphRows(commandStore, rts, ids, sss, tss, TxnKindsAndDomains.parse("{KW}"), rta, null, ide[2], ide2_rows));
assertRows(execute(QUERY_TXN_GRAPH_INTERSECTS_AND_KIND, ide[2].toString(), AccordDebugKeyspace.toString(rtb), "{KW}"), graphRows(commandStore, rts, ids, sss, tss, TxnKindsAndDomains.parse("{KW}"), rtb, null, ide[2], ide2_rows));
assertRows(execute(QUERY_TXN_GRAPH_MIN, ide[2].toString(), ida[0].toString()), graphRows(commandStore, rts, ids, sss, tss, ALL, null, null, ide[2], ide2_rows));
assertRows(execute(QUERY_TXN_GRAPH_MIN, ide[2].toString(), ida[1].toString()), graphRows(commandStore, rts, ids, sss, tss, ALL, null, ida[1], ide[2], ide2_rows));
}
private static int[] reverse(int[] copy, int stride)
{
int[] tmp = new int[stride];
copy = copy.clone();
for (int i = 0 ; i < copy.length/2 ; i += 5)
{
int j = copy.length - (stride + i);
System.arraycopy(copy, i, tmp, 0, stride);
System.arraycopy(copy, j, copy, i, stride);
System.arraycopy(tmp, 0, copy, j, stride);
}
return copy;
}
private static Object[][] graphRows(CommandStore commandStore, Txn txn, TxnId[] ids, SaveStatus[] saveStatuses, Timestamp[] timestamps, int pk, int ... children)
{
int depth = 0;
Object[][] result = new Object[children.length][];
int nextParent = Integer.MAX_VALUE;
int parent = pk;
for (int i = 0 ; i < children.length ; i++)
{
int child = children[i];
if (saveStatuses[child].hasBeen(Status.Committed))
{
if (i > 0)
{
++depth;
parent = nextParent;
}
nextParent = child;
}
result[i] = row(ids[pk].toString(), depth, commandStore.id(), ids[parent].toString(),
saveStatuses[child].hasBeen(Status.Committed) ? timestamps[child].toString() : "",
ids[child].toString(), saveStatuses[child].toString(), txn.keys().toParticipants().toString());
}
return result;
}
private static Object[][] prepend(Object[] prefix, Object[][] rows)
{
Object[][] result = new Object[rows.length][];
for (int i = 0 ; i < result.length ; ++i)
{
result[i] = new Object[rows[i].length + prefix.length];
System.arraycopy(prefix, 0, result[i], 0, prefix.length);
System.arraycopy(rows[i], 0, result[i], prefix.length, rows[i].length);
}
return result;
}
private static Object[][] graphRows(CommandStore commandStore, FullRoute[] rts,
TxnId[][] ids, SaveStatus[][] saveStatuses, Timestamp[][] timestamps,
TxnKindsAndDomains kinds, Participants<?> intersecting, @Nullable TxnId min,
TxnId pk, int ... children)
{
int count = 0;
Object[][] result = new Object[children.length/5][];
for (int i = 0 ; i < children.length ; i+=5)
{
int depth = children[i];
int parentGroup = children[i + 1];
int parent = children[i + 2];
int childGroup = children[i + 3];
int child = children[i + 4];
TxnId childId = ids[childGroup][child];
TxnId parentId = ids[parentGroup][parent];
if (!kinds.matches(childId) || (!kinds.matches(parentId) && !parentId.equals(pk)))
continue;
if (intersecting != null && !intersecting.intersects(rts[childGroup]))
continue;
if (min != null && min.compareTo(childId) > 0)
continue;
Participants<?> via = rts[childGroup].participantsOnly().intersecting(rts[parentGroup], Minimal);
if (intersecting != null)
via = via.intersecting(intersecting, Minimal);
result[count++] = row(pk.toString(), depth, commandStore.id(), parentId.toString(),
saveStatuses[childGroup][child].hasBeen(Status.Committed) ? timestamps[childGroup][child].toString() : "",
childId.toString(), saveStatuses[childGroup][child].toString(), via.toString());
}
if (count != result.length)
result = Arrays.copyOf(result, count);
return result;
}
private static Timestamp[] toTimestamps(TxnId[] ids)
{
Timestamp[] ts = new Timestamp[ids.length];
System.arraycopy(ids, 0, ts, 0, ids.length);
return ts;
}
private static TxnId[] ids(long epoch, Txn.Kind kind, Routable.Domain domain, Node.Id id, long ... hlcs)
{
TxnId[] ids = new TxnId[hlcs.length];
for (int i = 0 ; i < hlcs.length ; ++i)
ids[i] = new TxnId(epoch, hlcs[i], kind, domain, id);
return ids;
}
private static void write(AccordCommandStore commandStore, Command command)
{
commandStore.journal.saveCommand(commandStore.id(), new Journal.CommandUpdate(null, command), ()->{});
}
private static Command committed(FullRoute<?> route, TxnId txnId, Timestamp executeAt, Txn txn, TxnId[] ids, int ... deps)
{
Arrays.sort(deps);
try (KeyDeps.Builder keys = KeyDeps.builder(); RangeDeps.BuilderByTxnId ranges = RangeDeps.byTxnIdBuilder())
{
for (int depIndex : deps)
{
TxnId dep = ids[depIndex];
if (dep.is(Routable.Domain.Key)) keys.add(route.homeKey(), dep);
else ranges.add(route.homeKey().asRange(), dep);
}
PartialDeps partialDeps = new PartialDeps(route, keys.build(), ranges.build());
return Command.Committed.committed(txnId, SaveStatus.Committed, NotDurable, StoreParticipants.all(route), Ballot.ZERO, executeAt, txn.intersecting(route, true), partialDeps, Ballot.ZERO, null);
}
}
private static Command committed1(FullRoute<?> route, TxnId txnId, Timestamp executeAt, Txn txn, Object ... triples)
{
try (KeyDeps.Builder keys = KeyDeps.builder(); RangeDeps.BuilderByTxnId ranges = RangeDeps.byTxnIdBuilder())
{
for (int i = 0 ; i < triples.length ; i+= 3)
{
FullRoute<?> rt = (FullRoute<?>) triples[i];
TxnId[] ids = (TxnId[]) triples[i + 1];
int[] deps = (int[]) triples[i + 2];
for (int depIndex : deps)
{
TxnId dep = ids[depIndex];
if (dep.is(Routable.Domain.Key)) keys.add(rt.homeKey(), dep);
else
{
for (Range range : rt.toRanges())
ranges.add(range, dep);
}
}
}
PartialDeps partialDeps = new PartialDeps(route, keys.build(), ranges.build());
return Command.Committed.committed(txnId, SaveStatus.Committed, NotDurable, StoreParticipants.all(route), Ballot.ZERO, executeAt, txn.intersecting(route, true), partialDeps, Ballot.ZERO, null);
}
}
private static Command preaccepted(FullRoute<?> route, TxnId txnId, Timestamp executeAt, Txn txn) throws TopologyException
{
return Command.PreAccepted.preaccepted(txnId, SaveStatus.PreAccepted, NotDurable, StoreParticipants.all(route), Ballot.ZERO, executeAt, txn.intersecting(route, true), null);
}
@Ignore
@Test
public void patchJournalVestigialTest()
{

View File

@ -106,6 +106,7 @@ import org.apache.cassandra.utils.concurrent.CountDownLatch;
import static accord.local.RedundantStatus.SomeStatus.NONE;
import static accord.utils.Property.commands;
import static accord.utils.Property.stateful;
import static org.apache.cassandra.config.AccordSpec.RangeIndexMode.journal_sai;
import static org.apache.cassandra.config.DatabaseDescriptor.getPartitioner;
import static org.apache.cassandra.schema.SchemaConstants.ACCORD_KEYSPACE_NAME;
@ -131,6 +132,7 @@ public class RouteIndexTest extends CQLTester
DatabaseDescriptor.setAccordTransactionsEnabled(true);
// disable journal compaction so the test can control when it happens
DatabaseDescriptor.getAccord().enable_journal_compaction = false;
DatabaseDescriptor.getAccord().range_index_mode = journal_sai;
DatabaseDescriptor.setIncrementalBackupsEnabled(false);
DatabaseDescriptor.setAutoSnapshot(false);

View File

@ -60,6 +60,8 @@ import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import static org.apache.cassandra.Util.throwAssert;
import static org.apache.cassandra.config.AccordSpec.RangeIndexMode.journal_sai;
import static org.apache.cassandra.config.DatabaseDescriptor.getAccord;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@ -585,28 +587,33 @@ public class CassandraIndexTest extends CQLTester
// Wait for any background index clearing tasks to complete. Warn: When we used to run tests in parallel there
// could also be cross test class talk and have other indices pop up here.
Object[][] indexRows = getAccord().range_index_mode == journal_sai ? new Object[][] { row("system", "PaxosUncommittedIndex", null), row("system_accord", AccordKeyspace.JOURNAL_INDEX_NAME, null) }
: new Object[][] { row("system", "PaxosUncommittedIndex", null) };
Awaitility.await()
.atMost(1, TimeUnit.MINUTES)
.pollDelay(1, TimeUnit.SECONDS)
.untilAsserted(() -> assertRows(execute(selectBuiltIndexesQuery), row("system", "PaxosUncommittedIndex", null), row("system_accord", AccordKeyspace.JOURNAL_INDEX_NAME, null)));
.untilAsserted(() -> assertRows(execute(selectBuiltIndexesQuery), indexRows));
String indexName = "build_remove_test_idx";
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
createIndex(String.format("CREATE INDEX %s ON %%s(c)", indexName));
// check that there are no other rows in the built indexes table
assertRows(execute(selectBuiltIndexesQuery), row(KEYSPACE, indexName, null), row("system", "PaxosUncommittedIndex", null), row("system_accord", AccordKeyspace.JOURNAL_INDEX_NAME, null));
Object[][] indexRows2 = new Object[indexRows.length + 1][];
System.arraycopy(indexRows, 0, indexRows2, 1, indexRows.length);
indexRows2[0] = row(KEYSPACE, indexName, null);
assertRows(execute(selectBuiltIndexesQuery), indexRows2);
// rebuild the index and verify the built status table
getCurrentColumnFamilyStore().rebuildSecondaryIndex(indexName);
waitForIndexQueryable(indexName);
// check that there are no other rows in the built indexes table
assertRows(execute(selectBuiltIndexesQuery), row(KEYSPACE, indexName, null), row("system", "PaxosUncommittedIndex", null), row("system_accord", AccordKeyspace.JOURNAL_INDEX_NAME, null));
assertRows(execute(selectBuiltIndexesQuery), indexRows2);
// check that dropping the index removes it from the built indexes table
dropIndex("DROP INDEX %s." + indexName);
assertRows(execute(selectBuiltIndexesQuery), row("system", "PaxosUncommittedIndex", null), row("system_accord", AccordKeyspace.JOURNAL_INDEX_NAME, null));
assertRows(execute(selectBuiltIndexesQuery), indexRows);
}

View File

@ -145,7 +145,7 @@ public class AccordCommandTest
builder.add(key.toUnseekable(), txnId2);
deps = builder.build();
}
Accept accept = Accept.SerializerSupport.create(txnId, route, 1, 1, SLOW, Ballot.ZERO, executeAt, deps, false);
Accept accept = Accept.SerializerSupport.create(txnId, route, 1, 1, SLOW, Ballot.ZERO, executeAt, deps, 0);
getBlocking(commandStore.execute(accept, safeStore -> {
Command before = safeStore.ifInitialised(txnId).current();

View File

@ -133,7 +133,7 @@ public class RouteInMemoryIndexTest
private static class State
{
private final RouteInMemoryIndex<?> index = new RouteInMemoryIndex<>();
private final JournalSegmentRangeSearcher<?> index = new JournalSegmentRangeSearcher<>();
private final Model model = new Model();
private final float unfiltered;
private final float minDecidedIdNull;
@ -279,17 +279,17 @@ public class RouteInMemoryIndexTest
segments.computeIfAbsent(segment, i -> new Segment()).add(range, txnId);
}
public RangeSearcher.Result search(TokenRange range, TxnId minTxnId, TxnId maxTxnId, @Nullable DecidedRX decidedRX)
public JournalRangeSearcher.Result search(TokenRange range, TxnId minTxnId, TxnId maxTxnId, @Nullable DecidedRX decidedRX)
{
return search(vrange -> range.compareIntersecting(vrange) == 0, minTxnId, maxTxnId, decidedRX);
}
public RangeSearcher.Result search(TokenKey key, TxnId minTxnId, TxnId maxTxnId, @Nullable DecidedRX decidedRX)
public JournalRangeSearcher.Result search(TokenKey key, TxnId minTxnId, TxnId maxTxnId, @Nullable DecidedRX decidedRX)
{
return search(range -> range.contains(key), minTxnId, maxTxnId, decidedRX);
}
public RangeSearcher.Result search(Predicate<TokenRange> test, TxnId minTxnId, TxnId maxTxnId, @Nullable DecidedRX decidedRX)
public JournalRangeSearcher.Result search(Predicate<TokenRange> test, TxnId minTxnId, TxnId maxTxnId, @Nullable DecidedRX decidedRX)
{
TreeSet<TxnId> result = new TreeSet<>();
for (var segment: segments.values())
@ -302,7 +302,7 @@ public class RouteInMemoryIndexTest
result.add(value.txnId);
}
}
return new RangeSearcher.DefaultResult(minTxnId, maxTxnId, decidedRX, CloseableIterator.wrap(result.iterator()));
return new JournalRangeSearcher.DefaultResult(minTxnId, maxTxnId, decidedRX, CloseableIterator.wrap(result.iterator()));
}
void remove(long segment)

View File

@ -496,9 +496,9 @@ public class SimulatedAccordCommandStore implements AutoCloseable
commandStore.shutdown();
}
private static class DefaultJournal extends InMemoryJournal implements RangeSearcher.Supplier
private static class DefaultJournal extends InMemoryJournal implements JournalRangeSearcher.Supplier
{
private final RouteInMemoryIndex<?> index = new RouteInMemoryIndex<>();
private final JournalSegmentRangeSearcher<?> index = new JournalSegmentRangeSearcher<>();
private DefaultJournal(Node.Id id, RandomSource rs)
{
super(id, rs);
@ -524,7 +524,7 @@ public class SimulatedAccordCommandStore implements AutoCloseable
}
@Override
public RangeSearcher rangeSearcher()
public JournalRangeSearcher rangeSearcher()
{
return index;
}

View File

@ -188,7 +188,7 @@ public class CommandsForKeySerializerTest
builder.durability(isDurable ? AllQuorums : NotDurable);
if (saveStatus.known.deps().hasPreAcceptedOrProposedOrDecidedDeps())
{
try (KeyDeps.Builder keyBuilder = KeyDeps.builder();)
try (KeyDeps.Builder keyBuilder = KeyDeps.builder())
{
for (TxnId id : deps)
keyBuilder.add(((Key)txn.keys().get(0)).toUnseekable(), id);
@ -718,7 +718,7 @@ public class CommandsForKeySerializerTest
@Override public long now() { return 0; }
@Override public long elapsed(TimeUnit unit) { return 0; }
}; }
@Override public boolean visit(Unseekables<?> keysOrRanges, TxnId testTxnId, Kind.Kinds testKind, TestStartedAt testStartedAt, Timestamp testStartAtTimestamp, ComputeIsDep computeIsDep, AllCommandVisitor visit) { return false; }
@Override public boolean visit(Unseekables<?> keysOrRanges, TxnId testTxnId, Kind.Kinds testKind, SupersedingCommandVisitor visit) { return false; }
@Override public <P1, P2> void visit(Unseekables<?> keysOrRanges, Timestamp startedBefore, Kind.Kinds testKind, ActiveCommandVisitor<P1, P2> visit, P1 p1, P2 p2) { }
}

View File

@ -105,7 +105,7 @@ public class LatestDepsSerializerTest
rs.nextBoolean() ? null : deps.next(rs),
rs.nextBoolean() ? null : deps.next(rs));
}
LatestDeps latestDeps = LatestDeps.SerializerSupport.create(true, starts, entries);
LatestDeps latestDeps = LatestDeps.SerializerSupport.create(starts, entries);
DataOutputBuffer buf = new DataOutputBuffer();
Serializers.testSerde(buf, LatestDepsSerializers.latestDeps, latestDeps);
}

View File

@ -94,6 +94,7 @@ import org.apache.cassandra.db.marshal.StringType;
import org.apache.cassandra.db.marshal.TimeType;
import org.apache.cassandra.db.marshal.TimeUUIDType;
import org.apache.cassandra.db.marshal.TimestampType;
import org.apache.cassandra.db.marshal.TimestampUtf8Type;
import org.apache.cassandra.db.marshal.TokenUtf8Type;
import org.apache.cassandra.db.marshal.TupleType;
import org.apache.cassandra.db.marshal.TxnIdUtf8Type;
@ -126,6 +127,7 @@ public final class AbstractTypeGenerators
.put(FrozenType.class, "Fake class only used during parsing... the parsing creates this and the real type under it, then this gets swapped for the real type")
.put(TxnIdUtf8Type.class, "Used only internally by accord debug virtual tables - could be tested, but class initialisation order prevents easy reuse of the relevant type generators")
.put(TokenUtf8Type.class, "Used only internally by accord debug virtual tables - could be tested, but class initialisation order prevents easy reuse of the relevant type generators")
.put(TimestampUtf8Type.class, "Used only internally by accord debug virtual tables - could be tested, but class initialisation order prevents easy reuse of the relevant type generators")
.build();
/**