mirror of https://github.com/apache/cassandra
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:
parent
5fd8290157
commit
6a665f3ee4
|
|
@ -1 +1 @@
|
|||
Subproject commit 93d78be37ef904118da2426716b5d77c0fb227db
|
||||
Subproject commit 8229cbf269f14553c338cf47efc9466bbc7ee81b
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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() + ')';
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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()));
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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++)
|
||||
|
|
|
|||
|
|
@ -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<>();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) { }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue