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.QueueShardModel.THREAD_POOL_PER_SHARD;
|
||||||
import static org.apache.cassandra.config.AccordSpec.QueueSubmissionModel.SYNC;
|
import static org.apache.cassandra.config.AccordSpec.QueueSubmissionModel.SYNC;
|
||||||
|
import static org.apache.cassandra.config.AccordSpec.RangeIndexMode.in_memory;
|
||||||
|
|
||||||
public class AccordSpec
|
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_success_latency = new DurationSpec.IntSecondsBound(60);
|
||||||
public DurationSpec.IntSecondsBound catchup_on_start_fail_latency = new DurationSpec.IntSecondsBound(900);
|
public DurationSpec.IntSecondsBound catchup_on_start_fail_latency = new DurationSpec.IntSecondsBound(900);
|
||||||
public int catchup_on_start_max_attempts = 5;
|
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 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 final JournalSpec journal = new JournalSpec();
|
||||||
|
|
||||||
public enum MixedTimeSourceHandling
|
public enum MixedTimeSourceHandling
|
||||||
|
|
|
||||||
|
|
@ -735,30 +735,31 @@ public final class StatementRestrictions
|
||||||
VariableSpecifications boundNames,
|
VariableSpecifications boundNames,
|
||||||
IndexRegistry indexRegistry)
|
IndexRegistry indexRegistry)
|
||||||
{
|
{
|
||||||
if (expressions.size() > 1)
|
if (expressions.size() > 1 && !indexRegistry.supportsMultipleIndexExpressions())
|
||||||
throw new InvalidRequestException(IndexRestrictions.MULTIPLE_EXPRESSIONS);
|
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))
|
if (!table.indexes.has(expression.targetIndex.getName()))
|
||||||
throw IndexRestrictions.invalidIndex(expression.targetIndex, table);
|
throw IndexRestrictions.indexNotFound(expression.targetIndex, table);
|
||||||
|
|
||||||
if (!table.indexes.has(expression.targetIndex.getName()))
|
Index index = indexRegistry.getIndex(table.indexes.get(expression.targetIndex.getName()).get());
|
||||||
throw IndexRestrictions.indexNotFound(expression.targetIndex, table);
|
if (!index.getIndexMetadata().isCustom())
|
||||||
|
throw IndexRestrictions.nonCustomIndexInExpression(expression.targetIndex);
|
||||||
|
|
||||||
Index index = indexRegistry.getIndex(table.indexes.get(expression.targetIndex.getName()).get());
|
AbstractType<?> expressionType = index.customExpressionValueType();
|
||||||
if (!index.getIndexMetadata().isCustom())
|
if (expressionType == null)
|
||||||
throw IndexRestrictions.nonCustomIndexInExpression(expression.targetIndex);
|
throw IndexRestrictions.customExpressionNotSupported(expression.targetIndex);
|
||||||
|
|
||||||
AbstractType<?> expressionType = index.customExpressionValueType();
|
expression.prepareValue(table, expressionType, boundNames);
|
||||||
if (expressionType == null)
|
|
||||||
throw IndexRestrictions.customExpressionNotSupported(expression.targetIndex);
|
|
||||||
|
|
||||||
expression.prepareValue(table, expressionType, boundNames);
|
filterRestrictions.add(expression);
|
||||||
|
}
|
||||||
filterRestrictions.add(expression);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RowFilter getRowFilter(IndexRegistry indexRegistry, QueryOptions options)
|
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)
|
static Index.QueryPlan findIndexQueryPlan(TableMetadata table, RowFilter rowFilter)
|
||||||
{
|
{
|
||||||
if (table.indexes.isEmpty() || rowFilter.isEmpty())
|
if (table.indexes.isEmpty() || rowFilter.isEmpty() || table.isVirtual())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
ColumnFamilyStore cfs = Keyspace.openAndGetStore(table);
|
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 org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import accord.primitives.Ranges;
|
import accord.primitives.Ranges;
|
||||||
|
import accord.primitives.TxnId;
|
||||||
|
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
|
|
@ -230,7 +231,7 @@ public class CassandraStreamReceiver implements StreamReceiver
|
||||||
long deadlineNanos = startedAtNanos + timeoutNanos;
|
long deadlineNanos = startedAtNanos + timeoutNanos;
|
||||||
// TODO (expected): use the source bounds for the streams to avoid waiting unnecessarily long
|
// TODO (expected): use the source bounds for the streams to avoid waiting unnecessarily long
|
||||||
AccordService.getBlocking(accordService.maxConflict(accordRanges)
|
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);
|
, accordRanges, new LatencyRequestBookkeeping(cfs.metric.accordPostStreamRepair), startedAtNanos, deadlineNanos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,20 +19,27 @@ package org.apache.cassandra.db.virtual;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NavigableMap;
|
import java.util.NavigableMap;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
import accord.utils.Invariants;
|
import accord.utils.Invariants;
|
||||||
|
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
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.ClusteringIndexFilter;
|
||||||
import org.apache.cassandra.db.filter.ColumnFilter;
|
import org.apache.cassandra.db.filter.ColumnFilter;
|
||||||
import org.apache.cassandra.db.filter.DataLimits;
|
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.filter.RowFilter;
|
||||||
import org.apache.cassandra.db.marshal.AbstractType;
|
import org.apache.cassandra.db.marshal.AbstractType;
|
||||||
import org.apache.cassandra.db.marshal.CompositeType;
|
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.dht.Bounds;
|
||||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||||
import org.apache.cassandra.exceptions.ReadTimeoutException;
|
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.ColumnMetadata;
|
||||||
|
import org.apache.cassandra.schema.IndexMetadata;
|
||||||
import org.apache.cassandra.schema.SchemaConstants;
|
import org.apache.cassandra.schema.SchemaConstants;
|
||||||
import org.apache.cassandra.schema.TableMetadata;
|
import org.apache.cassandra.schema.TableMetadata;
|
||||||
|
import org.apache.cassandra.service.ClientState;
|
||||||
import org.apache.cassandra.service.ClientWarn;
|
import org.apache.cassandra.service.ClientWarn;
|
||||||
import org.apache.cassandra.utils.BulkIterator;
|
import org.apache.cassandra.utils.BulkIterator;
|
||||||
import org.apache.cassandra.utils.Clock;
|
import org.apache.cassandra.utils.Clock;
|
||||||
|
|
@ -820,4 +832,76 @@ public abstract class AbstractLazyVirtualTable implements VirtualTable
|
||||||
}
|
}
|
||||||
return Clustering.make(clusteringByteBuffers);
|
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 javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
@ -79,6 +80,7 @@ import accord.local.cfk.CommandsForKey;
|
||||||
import accord.local.cfk.CommandsForKey.TxnInfo;
|
import accord.local.cfk.CommandsForKey.TxnInfo;
|
||||||
import accord.local.cfk.SafeCommandsForKey;
|
import accord.local.cfk.SafeCommandsForKey;
|
||||||
import accord.local.durability.ShardDurability;
|
import accord.local.durability.ShardDurability;
|
||||||
|
import accord.primitives.Ballot;
|
||||||
import accord.primitives.FullRoute;
|
import accord.primitives.FullRoute;
|
||||||
import accord.primitives.Known;
|
import accord.primitives.Known;
|
||||||
import accord.primitives.Participants;
|
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.DecoratedKey;
|
||||||
import org.apache.cassandra.db.Keyspace;
|
import org.apache.cassandra.db.Keyspace;
|
||||||
import org.apache.cassandra.db.PartitionPosition;
|
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.AbstractType;
|
||||||
import org.apache.cassandra.db.marshal.CompositeType;
|
import org.apache.cassandra.db.marshal.CompositeType;
|
||||||
import org.apache.cassandra.db.marshal.Int32Type;
|
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.NormalizedRanges;
|
||||||
import org.apache.cassandra.dht.Token;
|
import org.apache.cassandra.dht.Token;
|
||||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
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.ColumnMetadata;
|
||||||
|
import org.apache.cassandra.schema.IndexMetadata;
|
||||||
|
import org.apache.cassandra.schema.Indexes;
|
||||||
import org.apache.cassandra.schema.Schema;
|
import org.apache.cassandra.schema.Schema;
|
||||||
import org.apache.cassandra.schema.TableId;
|
import org.apache.cassandra.schema.TableId;
|
||||||
import org.apache.cassandra.schema.TableMetadata;
|
import org.apache.cassandra.schema.TableMetadata;
|
||||||
import org.apache.cassandra.service.accord.AccordCache;
|
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.AccordCommandStore;
|
||||||
import org.apache.cassandra.service.accord.AccordCommandStores;
|
import org.apache.cassandra.service.accord.AccordCommandStores;
|
||||||
import org.apache.cassandra.service.accord.AccordExecutor;
|
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.AccordKeyspace;
|
||||||
import org.apache.cassandra.service.accord.AccordOperations;
|
import org.apache.cassandra.service.accord.AccordOperations;
|
||||||
import org.apache.cassandra.service.accord.AccordService;
|
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.IAccordService;
|
||||||
import org.apache.cassandra.service.accord.JournalKey;
|
import org.apache.cassandra.service.accord.JournalKey;
|
||||||
import org.apache.cassandra.service.accord.TokenRange;
|
import org.apache.cassandra.service.accord.TokenRange;
|
||||||
import org.apache.cassandra.service.accord.api.AccordAgent;
|
import org.apache.cassandra.service.accord.api.AccordAgent;
|
||||||
import org.apache.cassandra.service.accord.api.TokenKey;
|
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.ConsensusMigrationState;
|
||||||
import org.apache.cassandra.service.consensus.migration.TableMigrationState;
|
import org.apache.cassandra.service.consensus.migration.TableMigrationState;
|
||||||
import org.apache.cassandra.tcm.ClusterMetadata;
|
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||||
|
|
@ -150,6 +161,8 @@ import org.apache.cassandra.utils.LocalizeString;
|
||||||
import org.apache.cassandra.utils.concurrent.Future;
|
import org.apache.cassandra.utils.concurrent.Future;
|
||||||
|
|
||||||
import static accord.coordinate.Infer.InvalidIf.NotKnownToBeInvalid;
|
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.GC_BEFORE;
|
||||||
import static accord.local.RedundantStatus.Property.LOCALLY_APPLIED;
|
import static accord.local.RedundantStatus.Property.LOCALLY_APPLIED;
|
||||||
import static accord.local.RedundantStatus.Property.LOCALLY_DURABLE_TO_COMMAND_STORE;
|
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.ASC;
|
||||||
import static org.apache.cassandra.db.virtual.VirtualTable.Sorted.SORTED;
|
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.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.schema.SchemaConstants.VIRTUAL_ACCORD_DEBUG;
|
||||||
import static org.apache.cassandra.service.accord.AccordService.toFuture;
|
import static org.apache.cassandra.service.accord.AccordService.toFuture;
|
||||||
import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime;
|
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 REDUNDANT_BEFORE = "redundant_before";
|
||||||
public static final String REJECT_BEFORE = "reject_before";
|
public static final String REJECT_BEFORE = "reject_before";
|
||||||
public static final String TXN = "txn";
|
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_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_TRACE = "txn_pattern_trace";
|
||||||
public static final String TXN_PATTERN_TRACES = "txn_pattern_traces";
|
public static final String TXN_PATTERN_TRACES = "txn_pattern_traces";
|
||||||
public static final String TXN_TRACE = "txn_trace";
|
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 TXN_OPS = "txn_ops";
|
||||||
public static final String SHARD_EPOCHS = "shard_epochs";
|
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_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();
|
public static final AccordDebugKeyspace instance = new AccordDebugKeyspace();
|
||||||
|
|
||||||
|
|
@ -231,6 +252,9 @@ public class AccordDebugKeyspace extends VirtualKeyspace
|
||||||
new RejectBeforeTable(),
|
new RejectBeforeTable(),
|
||||||
new TxnBlockedByTable(),
|
new TxnBlockedByTable(),
|
||||||
new TxnTable(),
|
new TxnTable(),
|
||||||
|
new TxnCacheTable(),
|
||||||
|
new TxnGraphTable(),
|
||||||
|
new TxnGraphAllTable(),
|
||||||
new TxnTraceTable(),
|
new TxnTraceTable(),
|
||||||
new TxnTracesTable(),
|
new TxnTracesTable(),
|
||||||
new TxnPatternTraceTable(),
|
new TxnPatternTraceTable(),
|
||||||
|
|
@ -1327,7 +1351,7 @@ public class AccordDebugKeyspace extends VirtualKeyspace
|
||||||
" trace_bucket_sub_size int,\n" +
|
" trace_bucket_sub_size int,\n" +
|
||||||
" trace_events text,\n" +
|
" trace_events text,\n" +
|
||||||
" PRIMARY KEY (id)" +
|
" PRIMARY KEY (id)" +
|
||||||
')', Int32Type.instance), FAIL, SORTED);
|
')', Int32Type.instance), FAIL, UNSORTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1342,7 +1366,7 @@ public class AccordDebugKeyspace extends VirtualKeyspace
|
||||||
.add("bucket_seen", state.bucketSeen())
|
.add("bucket_seen", state.bucketSeen())
|
||||||
.add("chance", state.pattern().chance)
|
.add("chance", state.pattern().chance)
|
||||||
.add("current_size", state.currentSize())
|
.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("if_kind", state.pattern().kinds, TO_STRING)
|
||||||
.add("on_failure", state.pattern().traceFailures, TO_STRING)
|
.add("on_failure", state.pattern().traceFailures, TO_STRING)
|
||||||
.add("on_new", state.pattern().traceNew, 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);
|
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
|
@Override
|
||||||
public void truncate()
|
public void truncate()
|
||||||
{
|
{
|
||||||
|
|
@ -1484,7 +1465,7 @@ public class AccordDebugKeyspace extends VirtualKeyspace
|
||||||
" id int,\n" +
|
" id int,\n" +
|
||||||
" txn_id 'TxnIdUtf8Type',\n" +
|
" txn_id 'TxnIdUtf8Type',\n" +
|
||||||
" PRIMARY KEY (id, txn_id)" +
|
" PRIMARY KEY (id, txn_id)" +
|
||||||
')', Int32Type.instance), FAIL, SORTED, SORTED);
|
')', Int32Type.instance), FAIL, UNSORTED, UNSORTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1603,30 +1584,32 @@ public class AccordDebugKeyspace extends VirtualKeyspace
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (desired): don't report null as "null"
|
// 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",
|
"Accord per-CommandStore Transaction State",
|
||||||
"CREATE TABLE %s (\n" +
|
"CREATE TABLE %s (\n" +
|
||||||
" command_store_id int,\n" +
|
" command_store_id int,\n" +
|
||||||
" txn_id 'TxnIdUtf8Type',\n" +
|
" txn_id 'TxnIdUtf8Type',\n" +
|
||||||
" save_status text,\n" +
|
" ballot_accepted text,\n" +
|
||||||
" route text,\n" +
|
" ballot_promised text,\n" +
|
||||||
|
" deps text,\n" +
|
||||||
" durability text,\n" +
|
" durability text,\n" +
|
||||||
" execute_at text,\n" +
|
" execute_at text,\n" +
|
||||||
" executes_at_least 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_owns text,\n" +
|
||||||
" participants_touches text,\n" +
|
" participants_touches text,\n" +
|
||||||
" participants_has_touched text,\n" +
|
" participants_has_touched text,\n" +
|
||||||
" participants_executes text,\n" +
|
" participants_executes text,\n" +
|
||||||
" participants_waits_on 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))" +
|
" PRIMARY KEY ((command_store_id, txn_id))" +
|
||||||
')', PK));
|
')', PK));
|
||||||
}
|
}
|
||||||
|
|
@ -1641,7 +1624,7 @@ public class AccordDebugKeyspace extends VirtualKeyspace
|
||||||
if (commandStore == null)
|
if (commandStore == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Command command = commandStore.loadCommand(key.id);
|
Command command = get(commandStore, key.id);
|
||||||
if (command == null)
|
if (command == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -1649,24 +1632,61 @@ public class AccordDebugKeyspace extends VirtualKeyspace
|
||||||
.lazyCollect(columns -> addColumns(command, columns));
|
.lazyCollect(columns -> addColumns(command, columns));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract Command get(AccordCommandStore commandStore, TxnId txnId);
|
||||||
|
|
||||||
private static void addColumns(Command command, ColumnsCollector columns)
|
private static void addColumns(Command command, ColumnsCollector columns)
|
||||||
{
|
{
|
||||||
StoreParticipants participants = command.participants();
|
StoreParticipants participants = command.participants();
|
||||||
columns.add("save_status", command.saveStatus(), TO_STRING)
|
columns.add("ballot_promised", command, Command::promised, TO_BALLOT_STRING)
|
||||||
.add("route", participants, StoreParticipants::route, TO_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_owns", participants, p -> toStr(p, StoreParticipants::owns, StoreParticipants::stillOwns))
|
||||||
.add("participants_touches", participants, p -> toStr(p, StoreParticipants::touches, StoreParticipants::stillTouches))
|
.add("participants_touches", participants, p -> toStr(p, StoreParticipants::touches, StoreParticipants::stillTouches))
|
||||||
.add("participants_has_touched", participants, StoreParticipants::hasTouched, TO_STRING)
|
.add("participants_has_touched", participants, StoreParticipants::hasTouched, TO_STRING)
|
||||||
.add("participants_executes", participants, p -> toStr(p, StoreParticipants::executes, StoreParticipants::stillExecutes))
|
.add("participants_executes", participants, p -> toStr(p, StoreParticipants::executes, StoreParticipants::stillExecutes))
|
||||||
.add("participants_waits_on", participants, p -> toStr(p, StoreParticipants::waitsOn, StoreParticipants::stillWaitsOn))
|
.add("participants_waits_on", participants, p -> toStr(p, StoreParticipants::waitsOn, StoreParticipants::stillWaitsOn))
|
||||||
.add("durability", command, Command::durability, TO_STRING)
|
.add("save_status", command.saveStatus(), TO_STRING)
|
||||||
.add("execute_at", command, Command::executeAt, TO_STRING)
|
.add("result", command, Command::result, TO_STRING)
|
||||||
.add("executes_at_least", command, Command::executesAtLeast, TO_STRING)
|
.add("route", participants, StoreParticipants::route, TO_STRING)
|
||||||
.add("txn", command, Command::partialTxn, 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("waiting_on", command, Command::waitingOn, TO_STRING)
|
||||||
.add("writes", command, Command::writes, 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" +
|
" command_store_id int,\n" +
|
||||||
" segment bigint,\n" +
|
" segment bigint,\n" +
|
||||||
" segment_position int,\n" +
|
" segment_position int,\n" +
|
||||||
" save_status text,\n" +
|
" ballot_accepted text,\n" +
|
||||||
" route text,\n" +
|
" ballot_promised text,\n" +
|
||||||
|
" deps text,\n" +
|
||||||
" durability text,\n" +
|
" durability text,\n" +
|
||||||
" execute_at text,\n" +
|
" execute_at text,\n" +
|
||||||
" executes_at_least text,\n" +
|
" executes_at_least text,\n" +
|
||||||
" txn text,\n" +
|
|
||||||
" deps text,\n" +
|
|
||||||
" writes text,\n" +
|
|
||||||
" result text,\n" +
|
|
||||||
" participants_owns text,\n" +
|
" participants_owns text,\n" +
|
||||||
" participants_touches text,\n" +
|
" participants_touches text,\n" +
|
||||||
" participants_has_touched text,\n" +
|
" participants_has_touched text,\n" +
|
||||||
" participants_executes text,\n" +
|
" participants_executes text,\n" +
|
||||||
" participants_waits_on 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)" +
|
" PRIMARY KEY ((command_store_id, txn_id), segment, segment_position)" +
|
||||||
')', PK));
|
')', PK));
|
||||||
}
|
}
|
||||||
|
|
@ -1710,20 +1732,22 @@ public class AccordDebugKeyspace extends VirtualKeyspace
|
||||||
StoreParticipants participants = b.participants() != null ? b.participants() : StoreParticipants.empty(key.id);
|
StoreParticipants participants = b.participants() != null ? b.participants() : StoreParticipants.empty(key.id);
|
||||||
rows.add(e.segment, e.position)
|
rows.add(e.segment, e.position)
|
||||||
.lazyCollect(columns -> {
|
.lazyCollect(columns -> {
|
||||||
columns.add("save_status", b.saveStatus(), TO_STRING)
|
columns.add("ballot_accepted", b.get(ACCEPTED), TO_BALLOT_STRING)
|
||||||
.add("route", participants, StoreParticipants::route, TO_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_owns", participants, p -> toStr(p, StoreParticipants::owns, StoreParticipants::stillOwns))
|
||||||
.add("participants_touches", participants, p -> toStr(p, StoreParticipants::touches, StoreParticipants::stillTouches))
|
.add("participants_touches", participants, p -> toStr(p, StoreParticipants::touches, StoreParticipants::stillTouches))
|
||||||
.add("participants_has_touched", participants, StoreParticipants::hasTouched, TO_STRING)
|
.add("participants_has_touched", participants, StoreParticipants::hasTouched, TO_STRING)
|
||||||
.add("participants_executes", participants, p -> toStr(p, StoreParticipants::executes, StoreParticipants::stillExecutes))
|
.add("participants_executes", participants, p -> toStr(p, StoreParticipants::executes, StoreParticipants::stillExecutes))
|
||||||
.add("participants_waits_on", participants, p -> toStr(p, StoreParticipants::waitsOn, StoreParticipants::stillWaitsOn))
|
.add("participants_waits_on", participants, p -> toStr(p, StoreParticipants::waitsOn, StoreParticipants::stillWaitsOn))
|
||||||
.add("durability", b.durability(), TO_STRING)
|
.add("result", b.result(), TO_STRING)
|
||||||
.add("execute_at", b.executeAt(), TO_STRING)
|
.add("route", participants, StoreParticipants::route, TO_STRING)
|
||||||
.add("executes_at_least", b.executesAtLeast(), TO_STRING)
|
.add("save_status", b.saveStatus(), TO_STRING)
|
||||||
.add("txn", b.partialTxn(), TO_STRING)
|
.add("txn", b.partialTxn(), TO_STRING)
|
||||||
.add("deps", b.partialDeps(), TO_STRING)
|
.add("writes", b.writes(), TO_STRING);
|
||||||
.add("writes", b.writes(), TO_STRING)
|
|
||||||
.add("result", b.result(), TO_STRING);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1804,7 +1828,7 @@ public class AccordDebugKeyspace extends VirtualKeyspace
|
||||||
SafeCommand safeCommand = safeStore.unsafeGet(txnId);
|
SafeCommand safeCommand = safeStore.unsafeGet(txnId);
|
||||||
Command command = safeCommand.current();
|
Command command = safeCommand.current();
|
||||||
if (command.saveStatus() == SaveStatus.Applying)
|
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));
|
Commands.maybeExecute(safeStore, safeCommand, command, true, true, NotifyWaitingOnPlus.adapter(ignore -> {}, true, true));
|
||||||
return AsyncChains.success(null);
|
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,
|
super(metadata, onTimeout, sorted);
|
||||||
"Accord Transactions Blocked By Table",
|
}
|
||||||
"CREATE TABLE %s (\n" +
|
|
||||||
" txn_id 'TxnIdUtf8Type',\n" +
|
protected AbstractTxnGraphTable(TableMetadata metadata, OnTimeout onTimeout, Sorted sorted, Sorted sortedByPartitionKey)
|
||||||
" command_store_id int,\n" +
|
{
|
||||||
" depth int,\n" +
|
super(metadata, onTimeout, sorted, sortedByPartitionKey);
|
||||||
" 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -2117,24 +2136,165 @@ public class AccordDebugKeyspace extends VirtualKeyspace
|
||||||
FilterRange<Integer> depthRange = collector.filters("depth", Function.identity(), i -> i + 1, i -> i - 1);
|
FilterRange<Integer> depthRange = collector.filters("depth", Function.identity(), i -> i + 1, i -> i - 1);
|
||||||
int maxDepth = depthRange.max == null ? Integer.MAX_VALUE : depthRange.max;
|
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]);
|
TxnId txnId = TxnId.parse((String) pks[0]);
|
||||||
PartitionCollector partition = collector.partition(pks[0]);
|
PartitionCollector partition = collector.partition(pks[0]);
|
||||||
partition.collect(rows -> {
|
partition.collect(rows -> {
|
||||||
try
|
try { collect(txnId, maxDepth, min, kinds, intersects, collector, rows); }
|
||||||
{
|
catch (TimeoutException e) { throw new InternalTimeoutException(); }
|
||||||
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)
|
abstract void collect(TxnId txnId, int maxDepth, Timestamp min, TxnKindsAndDomains kinds, Participants<?> intersecting, PartitionsCollector collector, RowsCollector rows) throws TimeoutException;
|
||||||
.eagerCollect(columns -> {
|
}
|
||||||
columns.add("save_status", txn.saveStatus, TO_STRING)
|
|
||||||
.add("execute_at", txn.executeAt, TO_STRING);
|
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 + ')';
|
return av + " (" + bv + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CoordinationKind parseEventType(String input)
|
|
||||||
{
|
|
||||||
return tryParse(input, false, CoordinationKind.class, CoordinationKind::valueOf);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String toStringOrNull(Object o)
|
private static String toStringOrNull(Object o)
|
||||||
{
|
{
|
||||||
return toStringOrNull(o, Object::toString);
|
return toStringOrNull(o, Object::toString);
|
||||||
|
|
@ -2395,4 +2550,48 @@ public class AccordDebugKeyspace extends VirtualKeyspace
|
||||||
|
|
||||||
return TxnKindsAndDomains.parse((String) input);
|
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.filter.RowFilter;
|
||||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||||
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
|
||||||
|
import org.apache.cassandra.index.IndexRegistry;
|
||||||
import org.apache.cassandra.schema.TableMetadata;
|
import org.apache.cassandra.schema.TableMetadata;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -102,4 +103,9 @@ public interface VirtualTable
|
||||||
}
|
}
|
||||||
|
|
||||||
default Sorted sorted() { return Sorted.UNSORTED; }
|
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.marshal.BytesType;
|
||||||
import org.apache.cassandra.db.memtable.Memtable;
|
import org.apache.cassandra.db.memtable.Memtable;
|
||||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
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.exceptions.InvalidRequestException;
|
||||||
import org.apache.cassandra.index.transactions.IndexTransaction;
|
import org.apache.cassandra.index.transactions.IndexTransaction;
|
||||||
import org.apache.cassandra.io.sstable.Component;
|
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);
|
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
|
* Called at write time to ensure that values present in the update
|
||||||
* are valid according to the rules of all registered indexes which
|
* are valid according to the rules of all registered indexes which
|
||||||
|
|
@ -369,6 +376,13 @@ public interface IndexRegistry extends Iterable<Index>
|
||||||
if (!DatabaseDescriptor.isDaemonInitialized())
|
if (!DatabaseDescriptor.isDaemonInitialized())
|
||||||
return NON_DAEMON;
|
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))
|
if (!mayContainId(id))
|
||||||
return -1L;
|
return -1L;
|
||||||
|
|
||||||
int keyIndex = binarySearch(id);
|
int someIndex = binarySearch(id);
|
||||||
return keyIndex < 0 ? -1 : recordAtIndex(keyIndex);
|
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
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -75,17 +75,17 @@ public class AccordCoordinatorMetrics
|
||||||
/**
|
/**
|
||||||
* A histogram of the time to preaccept on this coordinator
|
* 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
|
* 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
|
* 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
|
* The number of epochs used to coordinate the transaction
|
||||||
|
|
@ -161,9 +161,9 @@ public class AccordCoordinatorMetrics
|
||||||
{
|
{
|
||||||
DefaultNameFactory coordinator = new DefaultNameFactory(ACCORD_COORDINATOR, scope);
|
DefaultNameFactory coordinator = new DefaultNameFactory(ACCORD_COORDINATOR, scope);
|
||||||
dependencies = Metrics.histogram(coordinator.createMetricName(COORDINATOR_DEPENDENCIES), true);
|
dependencies = Metrics.histogram(coordinator.createMetricName(COORDINATOR_DEPENDENCIES), true);
|
||||||
preacceptLatency = Metrics.histogram(coordinator.createMetricName(COORDINATOR_PREACCEPT_LATENCY), true);
|
preacceptLatency = Metrics.timer(coordinator.createMetricName(COORDINATOR_PREACCEPT_LATENCY));
|
||||||
executeLatency = Metrics.histogram(coordinator.createMetricName(COORDINATOR_EXECUTE_LATENCY), true);
|
executeLatency = Metrics.timer(coordinator.createMetricName(COORDINATOR_EXECUTE_LATENCY));
|
||||||
applyLatency = Metrics.histogram(coordinator.createMetricName(COORDINATOR_APPLY_LATENCY), true);
|
applyLatency = Metrics.timer(coordinator.createMetricName(COORDINATOR_APPLY_LATENCY));
|
||||||
epochs = Metrics.histogram(coordinator.createMetricName(COORDINATOR_EPOCHS), true);
|
epochs = Metrics.histogram(coordinator.createMetricName(COORDINATOR_EPOCHS), true);
|
||||||
keys = Metrics.histogram(coordinator.createMetricName(COORDINATOR_KEYS), true);
|
keys = Metrics.histogram(coordinator.createMetricName(COORDINATOR_KEYS), true);
|
||||||
tables = Metrics.histogram(coordinator.createMetricName(COORDINATOR_TABLES), true);
|
tables = Metrics.histogram(coordinator.createMetricName(COORDINATOR_TABLES), true);
|
||||||
|
|
@ -230,7 +230,7 @@ public class AccordCoordinatorMetrics
|
||||||
if (metrics != null)
|
if (metrics != null)
|
||||||
{
|
{
|
||||||
long now = AccordTimeService.nowMicros();
|
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());
|
metrics.dependencies.update(deps.txnIdCount());
|
||||||
long now = AccordTimeService.nowMicros();
|
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)
|
if (path != null)
|
||||||
{
|
{
|
||||||
switch (path)
|
switch (path)
|
||||||
|
|
@ -264,7 +264,7 @@ public class AccordCoordinatorMetrics
|
||||||
if (metrics != null)
|
if (metrics != null)
|
||||||
{
|
{
|
||||||
long now = AccordTimeService.nowMicros();
|
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 MAX_PENDING_EPOCH = "MaxPendingEpoch";
|
||||||
public static final String ERRORS = "Errors";
|
public static final String ERRORS = "Errors";
|
||||||
public static final String EPOCH_WAITS = "EpochWaits";
|
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 EPOCH_TIMEOUTS = "EpochTimeouts";
|
||||||
public static final String PROGRESS_LOG_ACTIVE = "ProgressLogActive";
|
public static final String PROGRESS_LOG_ACTIVE = "ProgressLogActive";
|
||||||
public static final String PROGRESS_LOG_SIZE = "ProgressLogSize";
|
public static final String PROGRESS_LOG_SIZE = "ProgressLogSize";
|
||||||
|
|
@ -78,6 +79,7 @@ public class AccordSystemMetrics implements SystemEventListener
|
||||||
public final Counter errors;
|
public final Counter errors;
|
||||||
public final Counter epochWaits;
|
public final Counter epochWaits;
|
||||||
public final Counter epochTimeouts;
|
public final Counter epochTimeouts;
|
||||||
|
public final Counter pausedExecutorLoading;
|
||||||
public final Gauge<Long> progressLogActive;
|
public final Gauge<Long> progressLogActive;
|
||||||
public final Gauge<Long> durabilityQueueActive;
|
public final Gauge<Long> durabilityQueueActive;
|
||||||
public final Gauge<Long> durabilityQueuePending;
|
public final Gauge<Long> durabilityQueuePending;
|
||||||
|
|
@ -158,6 +160,7 @@ public class AccordSystemMetrics implements SystemEventListener
|
||||||
errors = Metrics.counter(factory.createMetricName(ERRORS));
|
errors = Metrics.counter(factory.createMetricName(ERRORS));
|
||||||
epochTimeouts = Metrics.counter(factory.createMetricName(EPOCH_TIMEOUTS));
|
epochTimeouts = Metrics.counter(factory.createMetricName(EPOCH_TIMEOUTS));
|
||||||
epochWaits = Metrics.counter(factory.createMetricName(EPOCH_WAITS));
|
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()));
|
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()));
|
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()));
|
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");
|
throw new IllegalStateException("Unable to compute when histogram overflowed");
|
||||||
|
|
||||||
long elements = 0;
|
long elements = 0;
|
||||||
long sum = 0;
|
double sum = 0;
|
||||||
for (int i = 0; i < lastBucket; i++)
|
for (int i = 0; i < lastBucket; i++)
|
||||||
{
|
{
|
||||||
long bCount = decayingBuckets[i];
|
long bCount = decayingBuckets[i];
|
||||||
elements += bCount;
|
elements += bCount;
|
||||||
sum += bCount * bucketOffsets[i];
|
sum += bCount * (double)bucketOffsets[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elements == 0)
|
if (elements == 0)
|
||||||
return 0d;
|
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 class CommandsForKeyAdapter implements Adapter<RoutingKey, CommandsForKey, AccordSafeCommandsForKey>
|
||||||
{
|
{
|
||||||
public static final CommandsForKeyAdapter CFK_ADAPTER = new CommandsForKeyAdapter();
|
public static final CommandsForKeyAdapter CFK_ADAPTER = new CommandsForKeyAdapter();
|
||||||
|
private static int SHRINK_WITHOUT_LOCK = -1;
|
||||||
|
|
||||||
private CommandsForKeyAdapter() {}
|
private CommandsForKeyAdapter() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1176,7 +1178,7 @@ public class AccordCache implements CacheSize
|
||||||
if (value.isEmpty() || value.isLoadingPruned())
|
if (value.isEmpty() || value.isLoadingPruned())
|
||||||
return Shrink.EVICT;
|
return Shrink.EVICT;
|
||||||
|
|
||||||
if (value.size() < 64)
|
if (SHRINK_WITHOUT_LOCK <= 0 || value.size() < SHRINK_WITHOUT_LOCK)
|
||||||
return Shrink.DONE;
|
return Shrink.DONE;
|
||||||
|
|
||||||
return Shrink.PERFORM_WITHOUT_LOCK;
|
return Shrink.PERFORM_WITHOUT_LOCK;
|
||||||
|
|
@ -1249,7 +1251,10 @@ public class AccordCache implements CacheSize
|
||||||
|
|
||||||
public static class CommandAdapter implements Adapter<TxnId, Command, AccordSafeCommand>
|
public static class CommandAdapter implements Adapter<TxnId, Command, AccordSafeCommand>
|
||||||
{
|
{
|
||||||
|
private static int SHRINK_WITHOUT_LOCK = -1;
|
||||||
|
|
||||||
public static final CommandAdapter COMMAND_ADAPTER = new CommandAdapter();
|
public static final CommandAdapter COMMAND_ADAPTER = new CommandAdapter();
|
||||||
|
|
||||||
private CommandAdapter() {}
|
private CommandAdapter() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1309,7 +1314,7 @@ public class AccordCache implements CacheSize
|
||||||
Invariants.expect(value.saveStatus().compareTo(SaveStatus.ReadyToExecute) < 0);
|
Invariants.expect(value.saveStatus().compareTo(SaveStatus.ReadyToExecute) < 0);
|
||||||
|
|
||||||
// TODO (expected): improve heuristics and consider transaction size
|
// 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.DONE;
|
||||||
|
|
||||||
return Shrink.PERFORM_WITHOUT_LOCK;
|
return Shrink.PERFORM_WITHOUT_LOCK;
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ import accord.impl.progresslog.DefaultProgressLog;
|
||||||
import accord.local.Command;
|
import accord.local.Command;
|
||||||
import accord.local.CommandStore;
|
import accord.local.CommandStore;
|
||||||
import accord.local.CommandStores;
|
import accord.local.CommandStores;
|
||||||
|
import accord.local.CommandSummaries;
|
||||||
import accord.local.NodeCommandStoreService;
|
import accord.local.NodeCommandStoreService;
|
||||||
import accord.local.PreLoadContext;
|
import accord.local.PreLoadContext;
|
||||||
import accord.local.RedundantBefore;
|
import accord.local.RedundantBefore;
|
||||||
|
|
@ -63,10 +64,12 @@ import accord.primitives.Route;
|
||||||
import accord.primitives.Timestamp;
|
import accord.primitives.Timestamp;
|
||||||
import accord.primitives.TxnId;
|
import accord.primitives.TxnId;
|
||||||
import accord.utils.Invariants;
|
import accord.utils.Invariants;
|
||||||
|
import accord.utils.UnhandledEnum;
|
||||||
import accord.utils.async.AsyncChain;
|
import accord.utils.async.AsyncChain;
|
||||||
import accord.utils.async.AsyncChains;
|
import accord.utils.async.AsyncChains;
|
||||||
import accord.utils.async.AsyncResults.CountingResult;
|
import accord.utils.async.AsyncResults.CountingResult;
|
||||||
|
|
||||||
|
import org.apache.cassandra.config.AccordSpec;
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
import org.apache.cassandra.metrics.LogLinearDecayingHistograms;
|
import org.apache.cassandra.metrics.LogLinearDecayingHistograms;
|
||||||
|
|
@ -155,12 +158,11 @@ public class AccordCommandStore extends CommandStore
|
||||||
|
|
||||||
public final String loggingId;
|
public final String loggingId;
|
||||||
public final Journal journal;
|
public final Journal journal;
|
||||||
private final RangeSearcher rangeSearcher;
|
|
||||||
private final AccordExecutor sharedExecutor;
|
private final AccordExecutor sharedExecutor;
|
||||||
final AccordExecutor.SequentialExecutor exclusiveExecutor;
|
final AccordExecutor.SequentialExecutor exclusiveExecutor;
|
||||||
private final ExclusiveCaches caches;
|
private final ExclusiveCaches caches;
|
||||||
private long lastSystemTimestampMicros = Long.MIN_VALUE;
|
private long lastSystemTimestampMicros = Long.MIN_VALUE;
|
||||||
private final CommandsForRanges.Manager commandsForRanges;
|
private final RangeIndex rangeIndex;
|
||||||
private final TableId tableId;
|
private final TableId tableId;
|
||||||
private TableMetadataRef metadata;
|
private TableMetadataRef metadata;
|
||||||
volatile SafeRedundantBefore safeRedundantBefore;
|
volatile SafeRedundantBefore safeRedundantBefore;
|
||||||
|
|
@ -181,7 +183,6 @@ public class AccordCommandStore extends CommandStore
|
||||||
super(id, node, agent, dataStore, progressLogFactory, listenerFactory, epochUpdateHolder);
|
super(id, node, agent, dataStore, progressLogFactory, listenerFactory, epochUpdateHolder);
|
||||||
this.loggingId = String.format("[%s]", id);
|
this.loggingId = String.format("[%s]", id);
|
||||||
this.journal = journal;
|
this.journal = journal;
|
||||||
this.rangeSearcher = RangeSearcher.extractRangeSearcher(journal);
|
|
||||||
this.sharedExecutor = sharedExecutor;
|
this.sharedExecutor = sharedExecutor;
|
||||||
if (this.progressLog instanceof DefaultProgressLog)
|
if (this.progressLog instanceof DefaultProgressLog)
|
||||||
((DefaultProgressLog)this.progressLog).unsafeSetConfig(DatabaseDescriptor.getAccordProgressLogConfig());
|
((DefaultProgressLog)this.progressLog).unsafeSetConfig(DatabaseDescriptor.getAccordProgressLogConfig());
|
||||||
|
|
@ -196,7 +197,15 @@ public class AccordCommandStore extends CommandStore
|
||||||
}
|
}
|
||||||
|
|
||||||
this.exclusiveExecutor = sharedExecutor.executor(id);
|
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()));
|
maybeLoadRedundantBefore(journal.loadRedundantBefore(id()));
|
||||||
maybeLoadBootstrapBeganAt(journal.loadBootstrapBeganAt(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));
|
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
|
@Override
|
||||||
|
|
@ -369,7 +378,7 @@ public class AccordCommandStore extends CommandStore
|
||||||
taskExecutor().execute(run);
|
taskExecutor().execute(run);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccordSafeCommandStore begin(AccordTask<?> operation, @Nullable CommandsForRanges commandsForRanges)
|
public AccordSafeCommandStore begin(AccordTask<?> operation, @Nullable CommandSummaries commandsForRanges)
|
||||||
{
|
{
|
||||||
require(current == null);
|
require(current == null);
|
||||||
current = AccordSafeCommandStore.create(operation, commandsForRanges, this);
|
current = AccordSafeCommandStore.create(operation, commandsForRanges, this);
|
||||||
|
|
@ -472,11 +481,6 @@ public class AccordCommandStore extends CommandStore
|
||||||
return safeRedundantBefore.redundantBefore;
|
return safeRedundantBefore.redundantBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RangeSearcher rangeSearcher()
|
|
||||||
{
|
|
||||||
return rangeSearcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AccordCommandStoreReplayer replayer()
|
public AccordCommandStoreReplayer replayer()
|
||||||
{
|
{
|
||||||
boolean replayOnlyNonDurable = true;
|
boolean replayOnlyNonDurable = true;
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ import org.apache.cassandra.concurrent.Shutdownable;
|
||||||
import org.apache.cassandra.metrics.AccordCacheMetrics;
|
import org.apache.cassandra.metrics.AccordCacheMetrics;
|
||||||
import org.apache.cassandra.metrics.AccordExecutorMetrics;
|
import org.apache.cassandra.metrics.AccordExecutorMetrics;
|
||||||
import org.apache.cassandra.metrics.AccordReplicaMetrics;
|
import org.apache.cassandra.metrics.AccordReplicaMetrics;
|
||||||
|
import org.apache.cassandra.metrics.AccordSystemMetrics;
|
||||||
import org.apache.cassandra.metrics.LogLinearDecayingHistograms;
|
import org.apache.cassandra.metrics.LogLinearDecayingHistograms;
|
||||||
import org.apache.cassandra.metrics.LogLinearDecayingHistograms.LogLinearDecayingHistogram;
|
import org.apache.cassandra.metrics.LogLinearDecayingHistograms.LogLinearDecayingHistogram;
|
||||||
import org.apache.cassandra.metrics.ShardedDecayingHistograms;
|
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
|
// 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())
|
if (!loading.isEmpty() || !waitingToRun.isEmpty())
|
||||||
{
|
{
|
||||||
|
AccordSystemMetrics.metrics.pausedExecutorLoading.inc();
|
||||||
hasPausedLoading = true;
|
hasPausedLoading = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1722,6 +1724,8 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
|
||||||
{
|
{
|
||||||
if (task instanceof AccordTask)
|
if (task instanceof AccordTask)
|
||||||
return ((AccordTask<?>) task).preLoadContext();
|
return ((AccordTask<?>) task).preLoadContext();
|
||||||
|
if (task instanceof WrappedIOTask && ((WrappedIOTask) task).wrapped instanceof AccordTask.RangeTxnScanner)
|
||||||
|
return ((AccordTask<?>.RangeTxnScanner) ((WrappedIOTask) task).wrapped).preLoadContext();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1755,14 +1759,6 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
|
||||||
return result;
|
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)
|
private static void addToSnapshot(List<TaskInfo> snapshot, TaskQueue<?> queue, TaskInfo.Status ifCurrent, TaskInfo.Status ifQueued)
|
||||||
{
|
{
|
||||||
for (int i = 0 ; i < queue.size() ; ++i)
|
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.service.accord.journal.AccordTopologyUpdate.newTopology;
|
||||||
import static org.apache.cassandra.utils.FBUtilities.getAvailableProcessors;
|
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);
|
private static final Logger logger = LoggerFactory.getLogger(AccordJournal.class);
|
||||||
static final ThreadLocal<byte[]> keyCRCBytes = ThreadLocal.withInitial(() -> new byte[JournalKeySupport.TOTAL_SIZE]);
|
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()
|
public void truncateForTesting()
|
||||||
{
|
{
|
||||||
journal.truncateForTesting();
|
journal.truncateForTesting();
|
||||||
journalTable.safeNotify(RouteInMemoryIndex::truncateForTesting);
|
journalTable.safeNotify(JournalSegmentRangeSearcher::truncateForTesting);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|
@ -844,7 +844,7 @@ public class AccordJournal implements accord.api.Journal, RangeSearcher.Supplier
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RangeSearcher rangeSearcher()
|
public JournalRangeSearcher rangeSearcher()
|
||||||
{
|
{
|
||||||
return journalTable.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.io.sstable.SSTableReadsListener.NOOP_LISTENER;
|
||||||
import static org.apache.cassandra.service.accord.AccordKeyspace.JournalColumns.getJournalKey;
|
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);
|
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.
|
* this property holds true.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private final RouteInMemoryIndex<Object> index;
|
private final JournalSegmentRangeSearcher<Object> index;
|
||||||
private final Version accordJournalVersion;
|
private final Version accordJournalVersion;
|
||||||
|
|
||||||
public AccordJournalTable(Journal<K, V> journal, KeySupport<K> keySupport, ColumnFamilyStore cfs, 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.accordJournalVersion = accordJournalVersion;
|
||||||
|
|
||||||
this.index = cfs.indexManager.getIndexByName(AccordKeyspace.JOURNAL_INDEX_NAME) != null
|
this.index = cfs.indexManager.getIndexByName(AccordKeyspace.JOURNAL_INDEX_NAME) != null
|
||||||
? new RouteInMemoryIndex<>()
|
? new JournalSegmentRangeSearcher<>()
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,7 +136,7 @@ public class AccordJournalTable<K extends JournalKey, V> implements RangeSearche
|
||||||
return RouteJournalIndex.allowed(key);
|
return RouteJournalIndex.allowed(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
void safeNotify(Consumer<RouteInMemoryIndex<Object>> fn)
|
void safeNotify(Consumer<JournalSegmentRangeSearcher<Object>> fn)
|
||||||
{
|
{
|
||||||
if (index == null)
|
if (index == null)
|
||||||
return;
|
return;
|
||||||
|
|
@ -157,11 +157,11 @@ public class AccordJournalTable<K extends JournalKey, V> implements RangeSearche
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RangeSearcher rangeSearcher()
|
public JournalRangeSearcher rangeSearcher()
|
||||||
{
|
{
|
||||||
if (index == null)
|
if (index == null)
|
||||||
return RangeSearcher.NoopRangeSearcher.instance;
|
return JournalRangeSearcher.NoopJournalRangeSearcher.instance;
|
||||||
return new TableRangeSearcher();
|
return new JournalTableRangeSearcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start()
|
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 final Index tableIndex;
|
||||||
|
|
||||||
private TableRangeSearcher()
|
private JournalTableRangeSearcher()
|
||||||
{
|
{
|
||||||
this.tableIndex = cfs.indexManager.getIndexByName("record");
|
this.tableIndex = cfs.indexManager.getIndexByName("record");
|
||||||
if (!cfs.indexManager.isIndexQueryable(tableIndex))
|
if (!cfs.indexManager.isIndexQueryable(tableIndex))
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ import accord.local.cfk.Serialize;
|
||||||
import accord.primitives.TxnId;
|
import accord.primitives.TxnId;
|
||||||
import accord.utils.Invariants;
|
import accord.utils.Invariants;
|
||||||
|
|
||||||
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||||
import org.apache.cassandra.cql3.statements.schema.CreateTableStatement;
|
import org.apache.cassandra.cql3.statements.schema.CreateTableStatement;
|
||||||
import org.apache.cassandra.db.Clustering;
|
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.lang.String.format;
|
||||||
import static java.util.Collections.emptyMap;
|
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.partitions.PartitionUpdate.singleRowUpdate;
|
||||||
import static org.apache.cassandra.db.rows.BTreeRow.singleCellRow;
|
import static org.apache.cassandra.db.rows.BTreeRow.singleCellRow;
|
||||||
import static org.apache.cassandra.schema.SchemaConstants.ACCORD_KEYSPACE_NAME;
|
import static org.apache.cassandra.schema.SchemaConstants.ACCORD_KEYSPACE_NAME;
|
||||||
|
|
@ -154,7 +156,7 @@ public class AccordKeyspace
|
||||||
return builder.build();
|
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)
|
private static ColumnMetadata getColumn(TableMetadata metadata, String name)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,14 @@ import accord.api.Journal.FieldUpdates;
|
||||||
import accord.api.ProgressLog;
|
import accord.api.ProgressLog;
|
||||||
import accord.api.RoutingKey;
|
import accord.api.RoutingKey;
|
||||||
import accord.impl.AbstractSafeCommandStore;
|
import accord.impl.AbstractSafeCommandStore;
|
||||||
|
import accord.local.Command;
|
||||||
import accord.local.CommandStores;
|
import accord.local.CommandStores;
|
||||||
|
import accord.local.CommandSummaries;
|
||||||
import accord.local.NodeCommandStoreService;
|
import accord.local.NodeCommandStoreService;
|
||||||
import accord.local.cfk.CommandsForKey;
|
import accord.local.cfk.CommandsForKey;
|
||||||
import accord.local.cfk.SafeCommandsForKey;
|
import accord.local.cfk.SafeCommandsForKey;
|
||||||
import accord.primitives.Timestamp;
|
import accord.primitives.Timestamp;
|
||||||
import accord.primitives.Txn;
|
import accord.primitives.Txn.Kind.Kinds;
|
||||||
import accord.primitives.TxnId;
|
import accord.primitives.TxnId;
|
||||||
import accord.primitives.Unseekables;
|
import accord.primitives.Unseekables;
|
||||||
import accord.utils.Invariants;
|
import accord.utils.Invariants;
|
||||||
|
|
@ -53,11 +55,11 @@ import static accord.utils.Invariants.illegalState;
|
||||||
public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeCommand, AccordSafeCommandsForKey, AccordCommandStore.ExclusiveCaches>
|
public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeCommand, AccordSafeCommandsForKey, AccordCommandStore.ExclusiveCaches>
|
||||||
{
|
{
|
||||||
final AccordTask<?> task;
|
final AccordTask<?> task;
|
||||||
private final @Nullable CommandsForRanges commandsForRanges;
|
private final @Nullable CommandSummaries commandsForRanges;
|
||||||
private final AccordCommandStore commandStore;
|
private final AccordCommandStore commandStore;
|
||||||
|
|
||||||
private AccordSafeCommandStore(AccordTask<?> task,
|
private AccordSafeCommandStore(AccordTask<?> task,
|
||||||
@Nullable CommandsForRanges commandsForRanges,
|
@Nullable CommandSummaries commandsForRanges,
|
||||||
AccordCommandStore commandStore)
|
AccordCommandStore commandStore)
|
||||||
{
|
{
|
||||||
super(task.preLoadContext(), commandStore);
|
super(task.preLoadContext(), commandStore);
|
||||||
|
|
@ -78,7 +80,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AccordSafeCommandStore create(AccordTask<?> operation,
|
public static AccordSafeCommandStore create(AccordTask<?> operation,
|
||||||
@Nullable CommandsForRanges commandsForRanges,
|
@Nullable CommandSummaries commandsForRanges,
|
||||||
AccordCommandStore commandStore)
|
AccordCommandStore commandStore)
|
||||||
{
|
{
|
||||||
return new AccordSafeCommandStore(operation, commandsForRanges, commandStore);
|
return new AccordSafeCommandStore(operation, commandsForRanges, commandStore);
|
||||||
|
|
@ -181,6 +183,12 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
||||||
commandStore.updateMinHlc(PaxosState.ballotTracker().getLowBound().unixMicros() + 1);
|
commandStore.updateMinHlc(PaxosState.ballotTracker().getLowBound().unixMicros() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCommandsForRanges(Command prev, Command updated, boolean force)
|
||||||
|
{
|
||||||
|
commandStore().rangeIndex().update(prev, updated, force);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AccordCommandStore commandStore()
|
public AccordCommandStore commandStore()
|
||||||
{
|
{
|
||||||
|
|
@ -242,7 +250,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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; });
|
visitForKey(keysOrRanges, cfk -> { cfk.visit(startedBefore, testKind, visitor, p1, p2); return true; });
|
||||||
if (commandsForRanges != null)
|
if (commandsForRanges != null)
|
||||||
|
|
@ -250,15 +258,15 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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))
|
return visitForKey(keysOrRanges, cfk -> cfk.visit(testTxnId, testKind, visit))
|
||||||
&& (commandsForRanges == null || commandsForRanges.visit(keysOrRanges, testTxnId, testKind, testStartedAt, testStartedAtTimestamp, computeIsDep, visit));
|
&& (commandsForRanges == null || commandsForRanges.visit(keysOrRanges, testTxnId, testKind, visit));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
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();
|
AccordKeyspace.truncateCommandsForKey();
|
||||||
|
|
||||||
as.node.commandStores().forAllUnsafe(cs -> cs.unsafeProgressLog().stop());
|
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.");
|
logger.info("Waiting for command stores to quiesce.");
|
||||||
((AccordCommandStores)as.node.commandStores()).waitForQuiescence();
|
((AccordCommandStores)as.node.commandStores()).waitForQuiescence();
|
||||||
|
getBlocking(as.node.commandStores().forAll("Post Replay", safeStore -> ((AccordCommandStore)safeStore.commandStore()).rangeIndex().postReplay()));
|
||||||
as.journal.unsafeSetStarted();
|
as.journal.unsafeSetStarted();
|
||||||
as.node.commandStores().forAllUnsafe(cs -> cs.unsafeProgressLog().start());
|
as.node.commandStores().forAllUnsafe(cs -> cs.unsafeProgressLog().start());
|
||||||
|
|
||||||
|
|
@ -488,21 +489,23 @@ public class AccordService implements IAccordService, Shutdownable
|
||||||
finishTopologyInitialization();
|
finishTopologyInitialization();
|
||||||
WatermarkCollector.fetchAndReportWatermarksAsync(topology());
|
WatermarkCollector.fetchAndReportWatermarksAsync(topology());
|
||||||
|
|
||||||
catchup();
|
|
||||||
|
|
||||||
fastPathCoordinator.start();
|
fastPathCoordinator.start();
|
||||||
ClusterMetadataService.instance().log().addListener(fastPathCoordinator);
|
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()),
|
node.durability().shards().reconfigure(Ints.checkedCast(getAccordShardDurabilityTargetSplits()),
|
||||||
Ints.checkedCast(getAccordShardDurabilityMaxSplits()),
|
Ints.checkedCast(getAccordShardDurabilityMaxSplits()),
|
||||||
Ints.checkedCast(getAccordShardDurabilityCycle(SECONDS)), SECONDS);
|
Ints.checkedCast(getAccordShardDurabilityCycle(SECONDS)), SECONDS);
|
||||||
node.durability().global().setGlobalCycleTime(Ints.checkedCast(getAccordGlobalDurabilityCycle(SECONDS)), SECONDS);
|
node.durability().global().setGlobalCycleTime(Ints.checkedCast(getAccordGlobalDurabilityCycle(SECONDS)), SECONDS);
|
||||||
// Only enable durability scheduling and progress logs _after_ we have fully replayed journal
|
// Only enable durability scheduling and progress logs _after_ we have fully replayed journal
|
||||||
node.durability().start();
|
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()
|
void catchup()
|
||||||
|
|
@ -741,13 +744,13 @@ public class AccordService implements IAccordService, Shutdownable
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
return node.durability().sync(requestedBy, ExclusiveSyncPoint, minBound, ranges, include, syncLocal, syncRemote, timeout, timeoutUnits);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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)
|
if (keys.size() != 1)
|
||||||
return syncInternal(minBound, keys, syncLocal, syncRemote);
|
return syncInternal(minBound, keys, syncLocal, syncRemote);
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,13 @@ import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||||
|
|
@ -45,6 +47,8 @@ import accord.api.Journal;
|
||||||
import accord.api.RoutingKey;
|
import accord.api.RoutingKey;
|
||||||
import accord.local.Command;
|
import accord.local.Command;
|
||||||
import accord.local.CommandStore;
|
import accord.local.CommandStore;
|
||||||
|
import accord.local.CommandSummaries;
|
||||||
|
import accord.local.CommandSummaries.Summary;
|
||||||
import accord.local.LoadKeys;
|
import accord.local.LoadKeys;
|
||||||
import accord.local.PreLoadContext;
|
import accord.local.PreLoadContext;
|
||||||
import accord.local.SafeCommandStore;
|
import accord.local.SafeCommandStore;
|
||||||
|
|
@ -53,6 +57,7 @@ import accord.primitives.AbstractRanges;
|
||||||
import accord.primitives.AbstractUnseekableKeys;
|
import accord.primitives.AbstractUnseekableKeys;
|
||||||
import accord.primitives.Range;
|
import accord.primitives.Range;
|
||||||
import accord.primitives.Ranges;
|
import accord.primitives.Ranges;
|
||||||
|
import accord.primitives.Timestamp;
|
||||||
import accord.primitives.TxnId;
|
import accord.primitives.TxnId;
|
||||||
import accord.primitives.Unseekables;
|
import accord.primitives.Unseekables;
|
||||||
import accord.utils.Invariants;
|
import accord.utils.Invariants;
|
||||||
|
|
@ -211,7 +216,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
|
||||||
@Nullable ArrayDeque<AccordCacheEntry<?, ?>> waitingToLoad;
|
@Nullable ArrayDeque<AccordCacheEntry<?, ?>> waitingToLoad;
|
||||||
@Nullable RangeTxnScanner rangeScanner;
|
@Nullable RangeTxnScanner rangeScanner;
|
||||||
boolean hasRanges;
|
boolean hasRanges;
|
||||||
@Nullable CommandsForRanges commandsForRanges;
|
@Nullable CommandSummaries commandsForRanges;
|
||||||
@Nullable private TaskQueue queued;
|
@Nullable private TaskQueue queued;
|
||||||
|
|
||||||
private BiConsumer<? super R, Throwable> callback;
|
private BiConsumer<? super R, Throwable> callback;
|
||||||
|
|
@ -233,8 +238,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
|
||||||
String id = loggingId;
|
String id = loggingId;
|
||||||
if (id == null)
|
if (id == null)
|
||||||
{
|
{
|
||||||
TxnId primaryTxnId = preLoadContext.primaryTxnId();
|
id = "0x" + Long.toHexString(nextLoggingId.incrementAndGet());
|
||||||
id = "0x" + Long.toHexString(nextLoggingId.incrementAndGet()) + (primaryTxnId != null ? '/' + primaryTxnId.toString() : "");
|
|
||||||
if (!loggingIdUpdater.compareAndSet(this, null, id))
|
if (!loggingIdUpdater.compareAndSet(this, null, id))
|
||||||
id = loggingId;
|
id = loggingId;
|
||||||
}
|
}
|
||||||
|
|
@ -244,12 +248,17 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "AccordTask{" + state + "}-" + loggingId();
|
return preLoadContext.describe() + ' ' + toBriefString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toBriefString()
|
||||||
|
{
|
||||||
|
return '{' + loggingId() + ',' + state + '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toDescription()
|
public String toDescription()
|
||||||
{
|
{
|
||||||
return "AccordTask{" + state + "}-" + loggingId() + ": "
|
return toBriefString() + ": "
|
||||||
+ (queued == null ? "unqueued" : queued.kind)
|
+ (queued == null ? "unqueued" : queued.kind)
|
||||||
+ ", primaryTxnId: " + preLoadContext.primaryTxnId()
|
+ ", primaryTxnId: " + preLoadContext.primaryTxnId()
|
||||||
+ ", waitingToLoad: " + summarise(waitingToLoad)
|
+ ", waitingToLoad: " + summarise(waitingToLoad)
|
||||||
|
|
@ -803,6 +812,8 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
|
||||||
public void cancelExclusive()
|
public void cancelExclusive()
|
||||||
{
|
{
|
||||||
state(CANCELLED);
|
state(CANCELLED);
|
||||||
|
if (rangeScanner != null)
|
||||||
|
rangeScanner.cancelled = true;
|
||||||
if (callback != null)
|
if (callback != null)
|
||||||
callback.accept(null, new CancellationException());
|
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 Set<TokenKey> intersectingKeys = new ObjectHashSet<>();
|
||||||
final KeyWatcher keyWatcher = new KeyWatcher();
|
final KeyWatcher keyWatcher = new KeyWatcher();
|
||||||
final Ranges ranges = ((AbstractRanges) preLoadContext.keys()).toRanges();
|
final Ranges ranges = ((AbstractRanges) preLoadContext.keys()).toRanges();
|
||||||
|
|
@ -979,8 +991,6 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
|
||||||
this.commandsForKeyCache = commandsForKeyCache;
|
this.commandsForKeyCache = commandsForKeyCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean scanned;
|
|
||||||
|
|
||||||
protected void runInternal()
|
protected void runInternal()
|
||||||
{
|
{
|
||||||
for (Range range : ranges)
|
for (Range range : ranges)
|
||||||
|
|
@ -988,7 +998,11 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
|
||||||
CommandsForKeyAccessor.findAllKeysBetween(commandStore.id(), commandStore.tableId(), getPartitioner(),
|
CommandsForKeyAccessor.findAllKeysBetween(commandStore.id(), commandStore.tableId(), getPartitioner(),
|
||||||
(TokenKey) range.start(), range.startInclusive(),
|
(TokenKey) range.start(), range.startInclusive(),
|
||||||
(TokenKey) range.end(), range.endInclusive(),
|
(TokenKey) range.end(), range.endInclusive(),
|
||||||
intersectingKeys::add);
|
key -> {
|
||||||
|
if (cancelled)
|
||||||
|
throw new CancellationException();
|
||||||
|
intersectingKeys.add(key);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
super.runInternal();
|
super.runInternal();
|
||||||
}
|
}
|
||||||
|
|
@ -1017,7 +1031,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
|
||||||
if (v == null) return;
|
if (v == null) return;
|
||||||
else if (v instanceof CommandsForKey)
|
else if (v instanceof CommandsForKey)
|
||||||
{
|
{
|
||||||
if (!summaryLoader.isRelevant((CommandsForKey) v))
|
if (!loader.isRelevant((CommandsForKey) v))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -1025,7 +1039,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
|
||||||
TxnId last = CommandSerializers.txnId.deserialize((ByteBuffer) v);
|
TxnId last = CommandSerializers.txnId.deserialize((ByteBuffer) v);
|
||||||
int position = (int)CommandSerializers.txnId.serializedSize(last);
|
int position = (int)CommandSerializers.txnId.serializedSize(last);
|
||||||
TxnId minUndecided = CommandSerializers.txnId.deserialize((ByteBuffer) v, position);
|
TxnId minUndecided = CommandSerializers.txnId.deserialize((ByteBuffer) v, position);
|
||||||
if (!summaryLoader.isRelevant(entry.key(), last, minUndecided))
|
if (!loader.isRelevant(entry.key(), last, minUndecided))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1060,7 +1074,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
|
||||||
super.cleanup(caches);
|
super.cleanup(caches);
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandsForRanges finish(Caches caches)
|
CommandSummaries finish(Caches caches)
|
||||||
{
|
{
|
||||||
caches.commandsForKeys().unregister(keyWatcher);
|
caches.commandsForKeys().unregister(keyWatcher);
|
||||||
return super.finish(caches);
|
return super.finish(caches);
|
||||||
|
|
@ -1069,39 +1083,23 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
|
||||||
|
|
||||||
public class RangeTxnScanner extends AccordExecutor.AbstractIOTask
|
public class RangeTxnScanner extends AccordExecutor.AbstractIOTask
|
||||||
{
|
{
|
||||||
class CommandWatcher implements AccordCache.Listener<TxnId, Command>
|
final Map<Timestamp, Summary> summaries = new HashMap<>();
|
||||||
{
|
final Map<Timestamp, Summary> mutexSummaries = Collections.synchronizedMap(summaries);
|
||||||
@Override
|
|
||||||
public void onUpdate(AccordCacheEntry<TxnId, Command> state)
|
|
||||||
{
|
|
||||||
CommandsForRanges.Summary summary = summaryLoader.ifRelevant(state);
|
|
||||||
if (summary != null)
|
|
||||||
summaries.put(summary.plainTxnId(), summary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final ConcurrentHashMap<TxnId, CommandsForRanges.Summary> summaries = new ConcurrentHashMap<>();
|
RangeIndex.Loader loader;
|
||||||
// TODO (expected): produce key summaries to avoid locking all in memory
|
|
||||||
final CommandWatcher commandWatcher = new CommandWatcher();
|
|
||||||
final Unseekables<?> keysOrRanges = preLoadContext.keys();
|
|
||||||
|
|
||||||
CommandsForRanges.Loader summaryLoader;
|
|
||||||
boolean scanned;
|
boolean scanned;
|
||||||
Throwable failure;
|
Throwable failure;
|
||||||
|
|
||||||
|
volatile boolean cancelled;
|
||||||
|
|
||||||
protected void runInternal()
|
protected void runInternal()
|
||||||
{
|
{
|
||||||
summaryLoader.intersects(txnId -> {
|
loader.load(mutexSummaries, () -> cancelled);
|
||||||
if (summaries.containsKey(txnId))
|
}
|
||||||
return;
|
|
||||||
|
|
||||||
CommandsForRanges.Summary summary = summaryLoader.load(txnId);
|
PreLoadContext preLoadContext()
|
||||||
if (summary != null)
|
{
|
||||||
{
|
return preLoadContext;
|
||||||
summaries.putIfAbsent(txnId, summary);
|
|
||||||
summaryLoader.maybeRecordFutureRx(summary);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1126,9 +1124,8 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
|
||||||
|
|
||||||
void startInternal(Caches caches)
|
void startInternal(Caches caches)
|
||||||
{
|
{
|
||||||
summaryLoader = commandStore.commandsForRanges().loader(preLoadContext.primaryTxnId(), preLoadContext.loadKeysFor(), keysOrRanges);
|
loader = commandStore.rangeIndex().loader(preLoadContext.primaryTxnId(), preLoadContext.executeAt(), preLoadContext.loadKeysFor(), preLoadContext.keys());
|
||||||
summaryLoader.forEachInCache(keysOrRanges, summary -> summaries.put(summary.plainTxnId(), summary), caches);
|
loader.loadExclusive(mutexSummaries, caches);
|
||||||
caches.commands().register(commandWatcher);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void scannedExclusive()
|
public void scannedExclusive()
|
||||||
|
|
@ -1147,19 +1144,20 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
|
||||||
|
|
||||||
void cleanup(Caches caches)
|
void cleanup(Caches caches)
|
||||||
{
|
{
|
||||||
caches.commands().tryUnregister(commandWatcher);
|
loader.cleanupExclusive(caches);
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandsForRanges finish(Caches caches)
|
CommandSummaries finish(Caches caches)
|
||||||
{
|
{
|
||||||
caches.commands().unregister(commandWatcher);
|
loader.finish(summaries);
|
||||||
return new CommandsForRanges(summaries);
|
TreeMap<Timestamp, Summary> byId = new TreeMap<>(summaries);
|
||||||
|
return (CommandSummaries.ByTxnIdSnapshot) () -> byId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String description()
|
public String description()
|
||||||
{
|
{
|
||||||
return "Scanning range intersections for " + AccordTask.this;
|
return "Scanning range intersections for " + preLoadContext.reason() + ' ' + toBriefString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.Ranges;
|
||||||
import accord.primitives.Timestamp;
|
import accord.primitives.Timestamp;
|
||||||
import accord.primitives.Txn;
|
import accord.primitives.Txn;
|
||||||
|
import accord.primitives.TxnId;
|
||||||
import accord.topology.EpochReady;
|
import accord.topology.EpochReady;
|
||||||
import accord.topology.TopologyManager;
|
import accord.topology.TopologyManager;
|
||||||
import accord.utils.Invariants;
|
import accord.utils.Invariants;
|
||||||
|
|
@ -83,8 +84,8 @@ public interface IAccordService
|
||||||
IVerbHandler<? extends Request> requestHandler();
|
IVerbHandler<? extends Request> requestHandler();
|
||||||
IVerbHandler<? extends Reply> responseHandler();
|
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);
|
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 Timestamp minBound, Keys keys, SyncLocal syncLocal, SyncRemote syncRemote);
|
AsyncChain<Void> sync(@Nullable TxnId minBound, Keys keys, SyncLocal syncLocal, SyncRemote syncRemote);
|
||||||
AsyncChain<Timestamp> maxConflict(Ranges ranges);
|
AsyncChain<Timestamp> maxConflict(Ranges ranges);
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
|
@ -215,13 +216,13 @@ public interface IAccordService
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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");
|
throw new UnsupportedOperationException("No accord transaction should be executed when accord.enabled = false in cassandra.yaml");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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");
|
throw new UnsupportedOperationException("No accord transaction should be executed when accord.enabled = false in cassandra.yaml");
|
||||||
}
|
}
|
||||||
|
|
@ -405,13 +406,13 @@ public interface IAccordService
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
return delegate.sync(requestedBy, onOrAfter, ranges, include, syncLocal, syncRemote, timeout, timeoutUnits);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
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.service.accord.api.TokenKey;
|
||||||
import org.apache.cassandra.utils.CloseableIterator;
|
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, TokenRange range, TxnId minTxnId, Timestamp maxTxnId, @Nullable MaxDecidedRX.DecidedRX decidedRX);
|
||||||
Result search(int commandStoreId, TokenKey key, 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)
|
if (o instanceof JournalRangeSearcher.Supplier)
|
||||||
return ((RangeSearcher.Supplier) o).rangeSearcher();
|
return ((JournalRangeSearcher.Supplier) o).rangeSearcher();
|
||||||
return NoopRangeSearcher.instance;
|
return NoopJournalRangeSearcher.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Supplier
|
interface Supplier
|
||||||
{
|
{
|
||||||
RangeSearcher rangeSearcher();
|
JournalRangeSearcher rangeSearcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Result
|
interface Result
|
||||||
|
|
@ -117,7 +117,7 @@ public interface RangeSearcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum NoopRangeSearcher implements RangeSearcher
|
enum NoopJournalRangeSearcher implements JournalRangeSearcher
|
||||||
{
|
{
|
||||||
instance;
|
instance;
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ import org.apache.cassandra.utils.RangeTree;
|
||||||
|
|
||||||
import static accord.primitives.Routable.Domain.Range;
|
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<>();
|
private final Long2ObjectHashMap<SegmentIndex> segmentIndexes = new Long2ObjectHashMap<>();
|
||||||
|
|
||||||
|
|
@ -85,7 +85,7 @@ public class RouteInMemoryIndex<V> implements RangeSearcher
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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(),
|
NavigableSet<TxnId> result = search(commandStoreId, range.table(),
|
||||||
OrderedRouteSerializer.serializeTokenOnly(range.start()),
|
OrderedRouteSerializer.serializeTokenOnly(range.start()),
|
||||||
|
|
@ -103,7 +103,7 @@ public class RouteInMemoryIndex<V> implements RangeSearcher
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
NavigableSet<TxnId> result = search(commandStoreId, key.table(), OrderedRouteSerializer.serializeTokenOnly(key), minTxnId, maxTxnId, decidedRX);
|
||||||
return new DefaultResult(minTxnId, maxTxnId, decidedRX, CloseableIterator.wrap(result.iterator()));
|
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;
|
import org.apache.cassandra.utils.RangeTree;
|
||||||
|
|
||||||
|
// TODO (expected): move to test package?
|
||||||
public enum RangeTreeRangeAccessor implements RangeTree.Accessor<RoutingKey, Range>
|
public enum RangeTreeRangeAccessor implements RangeTree.Accessor<RoutingKey, Range>
|
||||||
{
|
{
|
||||||
instance;
|
instance;
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import accord.api.ReplicaEventListener;
|
||||||
import accord.api.RoutingKey;
|
import accord.api.RoutingKey;
|
||||||
import accord.api.Tracing;
|
import accord.api.Tracing;
|
||||||
import accord.coordinate.Coordination;
|
import accord.coordinate.Coordination;
|
||||||
|
import accord.coordinate.Preempted;
|
||||||
import accord.coordinate.Timeout;
|
import accord.coordinate.Timeout;
|
||||||
import accord.local.Command;
|
import accord.local.Command;
|
||||||
import accord.local.Node;
|
import accord.local.Node;
|
||||||
|
|
@ -73,7 +74,7 @@ import org.apache.cassandra.metrics.AccordSystemMetrics;
|
||||||
import org.apache.cassandra.net.ResponseContext;
|
import org.apache.cassandra.net.ResponseContext;
|
||||||
import org.apache.cassandra.service.RetryStrategy;
|
import org.apache.cassandra.service.RetryStrategy;
|
||||||
import org.apache.cassandra.service.accord.AccordService;
|
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.serializers.TableMetadatasAndKeys;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnQuery;
|
import org.apache.cassandra.service.accord.txn.TxnQuery;
|
||||||
import org.apache.cassandra.service.accord.txn.TxnRead;
|
import org.apache.cassandra.service.accord.txn.TxnRead;
|
||||||
|
|
@ -204,9 +205,11 @@ public class AccordAgent implements Agent, OwnershipEventListener
|
||||||
return;
|
return;
|
||||||
|
|
||||||
AccordSystemMetrics.metrics.errors.inc();
|
AccordSystemMetrics.metrics.errors.inc();
|
||||||
if (t instanceof CancellationException || t instanceof TimeoutException || t instanceof Timeout)
|
if (t instanceof CancellationException || t instanceof TimeoutException || t instanceof Timeout || t instanceof Preempted)
|
||||||
return;
|
// TODO (required): leaky logger, permitting multiple messages per time period and reporting how many were dropped
|
||||||
JVMStabilityInspector.uncaughtException(Thread.currentThread(), t);
|
noSpamLogger.warn("", t);
|
||||||
|
else
|
||||||
|
JVMStabilityInspector.uncaughtException(Thread.currentThread(), t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.apache.cassandra.service.accord;
|
package org.apache.cassandra.service.accord.debug;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -46,8 +46,6 @@ import accord.coordinate.Coordination.CoordinationKind;
|
||||||
import accord.local.CommandStore;
|
import accord.local.CommandStore;
|
||||||
import accord.local.Node;
|
import accord.local.Node;
|
||||||
import accord.primitives.Participants;
|
import accord.primitives.Participants;
|
||||||
import accord.primitives.Routable;
|
|
||||||
import accord.primitives.Txn;
|
|
||||||
import accord.primitives.TxnId;
|
import accord.primitives.TxnId;
|
||||||
import accord.utils.Invariants;
|
import accord.utils.Invariants;
|
||||||
import accord.utils.SortedListMap;
|
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.Clock;
|
||||||
import org.apache.cassandra.utils.NoSpamLogger;
|
import org.apache.cassandra.utils.NoSpamLogger;
|
||||||
|
|
||||||
import static org.apache.cassandra.service.accord.AccordTracing.BucketMode.LEAKY;
|
import static org.apache.cassandra.service.accord.debug.AccordTracing.BucketMode.LEAKY;
|
||||||
import static org.apache.cassandra.service.accord.AccordTracing.BucketMode.SAMPLE;
|
import static org.apache.cassandra.service.accord.debug.AccordTracing.BucketMode.SAMPLE;
|
||||||
|
|
||||||
public class AccordTracing extends AccordCoordinatorMetrics.Listener
|
public class AccordTracing extends AccordCoordinatorMetrics.Listener
|
||||||
{
|
{
|
||||||
|
|
@ -381,161 +379,6 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
|
||||||
NEW, FAILURE
|
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
|
public static class TracePattern
|
||||||
{
|
{
|
||||||
private static final TracePattern EMPTY = new TracePattern(null, null, null, null, 1.0f);
|
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.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.apache.cassandra.service.accord;
|
package org.apache.cassandra.service.accord.debug;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
@ -39,7 +39,6 @@ import accord.local.CommandStore;
|
||||||
import accord.local.CommandStores;
|
import accord.local.CommandStores;
|
||||||
import accord.local.PreLoadContext;
|
import accord.local.PreLoadContext;
|
||||||
import accord.local.SafeCommandStore;
|
import accord.local.SafeCommandStore;
|
||||||
import accord.local.cfk.CommandsForKey;
|
|
||||||
import accord.local.cfk.SafeCommandsForKey;
|
import accord.local.cfk.SafeCommandsForKey;
|
||||||
import accord.primitives.RoutingKeys;
|
import accord.primitives.RoutingKeys;
|
||||||
import accord.primitives.SaveStatus;
|
import accord.primitives.SaveStatus;
|
||||||
|
|
@ -49,6 +48,8 @@ import accord.primitives.TxnId;
|
||||||
import accord.utils.async.AsyncChain;
|
import accord.utils.async.AsyncChain;
|
||||||
import accord.utils.async.AsyncChains;
|
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.service.accord.api.TokenKey;
|
||||||
import org.apache.cassandra.utils.concurrent.Future;
|
import org.apache.cassandra.utils.concurrent.Future;
|
||||||
|
|
||||||
|
|
@ -94,8 +95,8 @@ public class DebugBlockedTxns
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Txn that)
|
public int compareTo(Txn that)
|
||||||
{
|
{
|
||||||
int c = Integer.compare(this.commandStoreId, that.commandStoreId);
|
int c = Integer.compare(this.depth, that.depth);
|
||||||
if (c == 0) 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.txnId.compareTo(that.txnId);
|
||||||
if (c == 0) c = this.blockedViaKeyString().compareTo(that.blockedViaKeyString());
|
if (c == 0) c = this.blockedViaKeyString().compareTo(that.blockedViaKeyString());
|
||||||
return c;
|
return c;
|
||||||
|
|
@ -246,9 +247,6 @@ public class DebugBlockedTxns
|
||||||
}
|
}
|
||||||
else
|
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);
|
boolean recurse = visited.add(blocking);
|
||||||
queuedTxn.add(visitTxnAsync(commandStore, blocking, rootExecuteAt, key, depth, recurse));
|
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.Node;
|
||||||
import accord.local.durability.DurabilityService.SyncRemote;
|
import accord.local.durability.DurabilityService.SyncRemote;
|
||||||
import accord.primitives.Ranges;
|
import accord.primitives.Ranges;
|
||||||
import accord.primitives.Timestamp;
|
import accord.primitives.TxnId;
|
||||||
|
|
||||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
import org.apache.cassandra.db.Keyspace;
|
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.SyncLocal.NoLocal;
|
||||||
import static accord.local.durability.DurabilityService.SyncRemote.All;
|
import static accord.local.durability.DurabilityService.SyncRemote.All;
|
||||||
import static accord.primitives.Timestamp.mergeMax;
|
import static accord.primitives.Timestamp.mergeMax;
|
||||||
import static accord.primitives.Timestamp.minForEpoch;
|
|
||||||
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
import static org.apache.cassandra.config.DatabaseDescriptor.getAccordRepairTimeoutNanos;
|
import static org.apache.cassandra.config.DatabaseDescriptor.getAccordRepairTimeoutNanos;
|
||||||
|
|
||||||
|
|
@ -184,7 +183,7 @@ public class AccordRepair
|
||||||
RequestBookkeeping bookkeeping = new LatencyRequestBookkeeping(latency);
|
RequestBookkeeping bookkeeping = new LatencyRequestBookkeeping(latency);
|
||||||
long timeoutNanos = getAccordRepairTimeoutNanos();
|
long timeoutNanos = getAccordRepairTimeoutNanos();
|
||||||
long maxHlc = AccordService.getBlocking(service.maxConflict(ranges).flatMap(conflict -> {
|
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();
|
return service.sync("[repairId #" + repairId + ']', conflictMax, Ranges.of(range), including, NoLocal, syncRemote, timeoutNanos, NANOSECONDS).map(ignored -> conflictMax.hlc()).chain();
|
||||||
}), ranges, bookkeeping, start, start + timeoutNanos);
|
}), ranges, bookkeeping, start, start + timeoutNanos);
|
||||||
waiting = null;
|
waiting = null;
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,11 @@ public class AcceptSerializers
|
||||||
public static class RequestSerializer extends TxnRequestSerializer.WithUnsyncedSerializer<Accept>
|
public static class RequestSerializer extends TxnRequestSerializer.WithUnsyncedSerializer<Accept>
|
||||||
{
|
{
|
||||||
private static final Accept.Kind[] kinds = Accept.Kind.values();
|
private static final Accept.Kind[] kinds = Accept.Kind.values();
|
||||||
private static final int IS_PARTIAL = 1;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serializeBody(Accept accept, DataOutputPlus out, Version version) throws IOException
|
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);
|
CommandSerializers.ballot.serialize(accept.ballot, out);
|
||||||
ExecuteAtSerializer.serialize(accept.txnId, accept.executeAt, out);
|
ExecuteAtSerializer.serialize(accept.txnId, accept.executeAt, out);
|
||||||
DepsSerializers.partialDeps.serialize(accept.partialDeps(), out);
|
DepsSerializers.partialDeps.serialize(accept.partialDeps(), out);
|
||||||
|
|
@ -68,7 +67,7 @@ public class AcceptSerializers
|
||||||
CommandSerializers.ballot.deserialize(in),
|
CommandSerializers.ballot.deserialize(in),
|
||||||
ExecuteAtSerializer.deserialize(txnId, in),
|
ExecuteAtSerializer.deserialize(txnId, in),
|
||||||
DepsSerializers.partialDeps.deserialize(in),
|
DepsSerializers.partialDeps.deserialize(in),
|
||||||
(flags & IS_PARTIAL) != 0);
|
(flags & 1) | ((flags >>> 1) & ~1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ public class CheckStatusSerializers
|
||||||
Known max = kind == 1 ? min : known.deserialize(in);
|
Known max = kind == 1 ? min : known.deserialize(in);
|
||||||
values[i] = new MinAndMaxKnown(min, max);
|
values[i] = new MinAndMaxKnown(min, max);
|
||||||
}
|
}
|
||||||
return KnownMap.SerializerSupport.create(true, starts, values);
|
return KnownMap.SerializerSupport.create(starts, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ package org.apache.cassandra.service.accord.serializers;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.NavigableMap;
|
import java.util.NavigableMap;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.IntFunction;
|
import java.util.function.IntFunction;
|
||||||
|
|
||||||
import accord.api.RoutingKey;
|
import accord.api.RoutingKey;
|
||||||
|
|
@ -32,7 +33,6 @@ import accord.primitives.Timestamp;
|
||||||
import accord.primitives.TxnId;
|
import accord.primitives.TxnId;
|
||||||
import accord.utils.Invariants;
|
import accord.utils.Invariants;
|
||||||
import accord.utils.ReducingRangeMap;
|
import accord.utils.ReducingRangeMap;
|
||||||
import accord.utils.TriFunction;
|
|
||||||
|
|
||||||
import org.apache.cassandra.db.TypeSizes;
|
import org.apache.cassandra.db.TypeSizes;
|
||||||
import org.apache.cassandra.io.UnversionedSerializer;
|
import org.apache.cassandra.io.UnversionedSerializer;
|
||||||
|
|
@ -53,9 +53,9 @@ public class CommandStoreSerializers
|
||||||
{
|
{
|
||||||
final UnversionedSerializer<T> valueSerializer;
|
final UnversionedSerializer<T> valueSerializer;
|
||||||
final IntFunction<T[]> newValueArray;
|
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.valueSerializer = valueSerializer;
|
||||||
this.newValueArray = newValueArray;
|
this.newValueArray = newValueArray;
|
||||||
|
|
@ -65,7 +65,7 @@ public class CommandStoreSerializers
|
||||||
@Override
|
@Override
|
||||||
public void serialize(R map, DataOutputPlus out) throws IOException
|
public void serialize(R map, DataOutputPlus out) throws IOException
|
||||||
{
|
{
|
||||||
out.writeBoolean(map.inclusiveEnds());
|
out.writeBoolean(true);
|
||||||
int mapSize = map.size();
|
int mapSize = map.size();
|
||||||
out.writeUnsignedVInt32(mapSize);
|
out.writeUnsignedVInt32(mapSize);
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ public class CommandStoreSerializers
|
||||||
@Override
|
@Override
|
||||||
public R deserialize(DataInputPlus in) throws IOException
|
public R deserialize(DataInputPlus in) throws IOException
|
||||||
{
|
{
|
||||||
boolean inclusiveEnds = in.readBoolean();
|
in.readBoolean();
|
||||||
int mapSize = in.readUnsignedVInt32();
|
int mapSize = in.readUnsignedVInt32();
|
||||||
RoutingKey[] keys = new RoutingKey[mapSize + 1];
|
RoutingKey[] keys = new RoutingKey[mapSize + 1];
|
||||||
T[] values = newValueArray.apply(mapSize);
|
T[] values = newValueArray.apply(mapSize);
|
||||||
|
|
@ -92,7 +92,7 @@ public class CommandStoreSerializers
|
||||||
}
|
}
|
||||||
if (mapSize > 0)
|
if (mapSize > 0)
|
||||||
keys[mapSize] = KeySerializers.routingKey.deserialize(in);
|
keys[mapSize] = KeySerializers.routingKey.deserialize(in);
|
||||||
return constructor.apply(inclusiveEnds, keys, values);
|
return constructor.apply(keys, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
@Override
|
||||||
public void serialize(DurableBefore.Entry t, DataOutputPlus out) throws IOException
|
public void serialize(DurableBefore.Entry t, DataOutputPlus out) throws IOException
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ public class LatestDepsSerializers
|
||||||
}
|
}
|
||||||
starts[size] = KeySerializers.routingKey.deserialize(in);
|
starts[size] = KeySerializers.routingKey.deserialize(in);
|
||||||
|
|
||||||
return LatestDeps.SerializerSupport.create(true, starts, values);
|
return LatestDeps.SerializerSupport.create(starts, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -117,9 +117,9 @@ public class RecoverySerializers
|
||||||
CommandSerializers.ballot.serialize(recoverOk.accepted, out);
|
CommandSerializers.ballot.serialize(recoverOk.accepted, out);
|
||||||
ExecuteAtSerializer.serializeNullable(recoverOk.executeAt, out);
|
ExecuteAtSerializer.serializeNullable(recoverOk.executeAt, out);
|
||||||
latestDeps.serialize(recoverOk.deps, out);
|
latestDeps.serialize(recoverOk.deps, out);
|
||||||
DepsSerializers.deps.serialize(recoverOk.earlierWait, out);
|
DepsSerializers.deps.serialize(recoverOk.simpleWait, out);
|
||||||
DepsSerializers.deps.serialize(recoverOk.earlierNoWait, out);
|
DepsSerializers.deps.serialize(recoverOk.simpleNoWait, out);
|
||||||
DepsSerializers.deps.serialize(recoverOk.laterCoordRejects, out);
|
DepsSerializers.deps.serialize(recoverOk.supersedingCoordRejects, out);
|
||||||
out.writeBoolean(recoverOk.selfAcceptsFastPath);
|
out.writeBoolean(recoverOk.selfAcceptsFastPath);
|
||||||
KeySerializers.nullableParticipants.serialize(recoverOk.coordinatorAcceptsFastPath, out);
|
KeySerializers.nullableParticipants.serialize(recoverOk.coordinatorAcceptsFastPath, out);
|
||||||
out.writeBoolean(recoverOk.supersedingRejects);
|
out.writeBoolean(recoverOk.supersedingRejects);
|
||||||
|
|
@ -187,9 +187,9 @@ public class RecoverySerializers
|
||||||
size += CommandSerializers.ballot.serializedSize(recoverOk.accepted);
|
size += CommandSerializers.ballot.serializedSize(recoverOk.accepted);
|
||||||
size += ExecuteAtSerializer.serializedNullableSize(recoverOk.executeAt);
|
size += ExecuteAtSerializer.serializedNullableSize(recoverOk.executeAt);
|
||||||
size += latestDeps.serializedSize(recoverOk.deps);
|
size += latestDeps.serializedSize(recoverOk.deps);
|
||||||
size += DepsSerializers.deps.serializedSize(recoverOk.earlierWait);
|
size += DepsSerializers.deps.serializedSize(recoverOk.simpleWait);
|
||||||
size += DepsSerializers.deps.serializedSize(recoverOk.earlierNoWait);
|
size += DepsSerializers.deps.serializedSize(recoverOk.simpleNoWait);
|
||||||
size += DepsSerializers.deps.serializedSize(recoverOk.laterCoordRejects);
|
size += DepsSerializers.deps.serializedSize(recoverOk.supersedingCoordRejects);
|
||||||
size += TypeSizes.sizeof(recoverOk.selfAcceptsFastPath);
|
size += TypeSizes.sizeof(recoverOk.selfAcceptsFastPath);
|
||||||
size += KeySerializers.nullableParticipants.serializedSize(recoverOk.coordinatorAcceptsFastPath);
|
size += KeySerializers.nullableParticipants.serializedSize(recoverOk.coordinatorAcceptsFastPath);
|
||||||
size += TypeSizes.sizeof(recoverOk.supersedingRejects);
|
size += TypeSizes.sizeof(recoverOk.supersedingRejects);
|
||||||
|
|
@ -251,7 +251,7 @@ public class RecoverySerializers
|
||||||
}
|
}
|
||||||
starts[size] = KeySerializers.routingKey.deserialize(in);
|
starts[size] = KeySerializers.routingKey.deserialize(in);
|
||||||
|
|
||||||
return LatestDeps.SerializerSupport.create(true, starts, values);
|
return LatestDeps.SerializerSupport.create(starts, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ import org.slf4j.LoggerFactory;
|
||||||
import accord.primitives.Keys;
|
import accord.primitives.Keys;
|
||||||
import accord.primitives.Range;
|
import accord.primitives.Range;
|
||||||
import accord.primitives.Ranges;
|
import accord.primitives.Ranges;
|
||||||
import accord.primitives.Timestamp;
|
import accord.primitives.TxnId;
|
||||||
|
|
||||||
import org.apache.cassandra.concurrent.ImmediateExecutor;
|
import org.apache.cassandra.concurrent.ImmediateExecutor;
|
||||||
import org.apache.cassandra.concurrent.Stage;
|
import org.apache.cassandra.concurrent.Stage;
|
||||||
|
|
@ -322,7 +322,7 @@ public abstract class ConsensusKeyMigrationState
|
||||||
long start = nanoTime();
|
long start = nanoTime();
|
||||||
long deadline = requestTime.computeDeadline(isForWrite ? getWriteRpcTimeout(TimeUnit.NANOSECONDS) : getReadRpcTimeout(TimeUnit.NANOSECONDS));
|
long deadline = requestTime.computeDeadline(isForWrite ? getWriteRpcTimeout(TimeUnit.NANOSECONDS) : getReadRpcTimeout(TimeUnit.NANOSECONDS));
|
||||||
RequestBookkeeping bookkeeping = new LatencyRequestBookkeeping(cfs == null ? null : cfs.metric.keyMigration);
|
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);
|
partitionKeys, bookkeeping, start, deadline);
|
||||||
Range[] asRanges = new Range[partitionKeys.size()];
|
Range[] asRanges = new Range[partitionKeys.size()];
|
||||||
for (int i = 0; i < partitionKeys.size(); i++)
|
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))
|
if (kind != null && key.type != JournalKey.Type.valueOf(kind))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (txnId != null && !TxnId.fromString(txnId).equals(key.id))
|
if (txnId != null && !TxnId.parse(txnId).equals(key.id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try (DataInputBuffer in = new DataInputBuffer(buffer, false))
|
try (DataInputBuffer in = new DataInputBuffer(buffer, false))
|
||||||
|
|
@ -222,9 +222,9 @@ public class StandaloneJournalUtil implements Runnable
|
||||||
if (sstables == null && journalSegments == null)
|
if (sstables == null && journalSegments == null)
|
||||||
throw new IllegalArgumentException("Either --sstables or --journal-segments must be provided");
|
throw new IllegalArgumentException("Either --sstables or --journal-segments must be provided");
|
||||||
|
|
||||||
Timestamp txnId = this.txnId != null ? TxnId.fromString(this.txnId) : null;
|
Timestamp txnId = this.txnId != null ? TxnId.parse(this.txnId) : null;
|
||||||
Timestamp sinceTimestamp = this.since != null ? TxnId.fromString(this.since) : null;
|
Timestamp sinceTimestamp = this.since != null ? TxnId.parse(this.since) : null;
|
||||||
Timestamp untilTimestamp = this.until != null ? TxnId.fromString(this.until) : null;
|
Timestamp untilTimestamp = this.until != null ? TxnId.parse(this.until) : null;
|
||||||
|
|
||||||
boolean skipAllErrors;
|
boolean skipAllErrors;
|
||||||
Set<String> skipExceptionTypes = new HashSet<>();
|
Set<String> skipExceptionTypes = new HashSet<>();
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ import accord.primitives.Keys;
|
||||||
import accord.primitives.Ranges;
|
import accord.primitives.Ranges;
|
||||||
import accord.primitives.RoutingKeys;
|
import accord.primitives.RoutingKeys;
|
||||||
import accord.primitives.Status;
|
import accord.primitives.Status;
|
||||||
import accord.primitives.Timestamp;
|
|
||||||
import accord.primitives.TxnId;
|
import accord.primitives.TxnId;
|
||||||
import accord.utils.async.AsyncChain;
|
import accord.utils.async.AsyncChain;
|
||||||
import accord.utils.async.AsyncResult;
|
import accord.utils.async.AsyncResult;
|
||||||
|
|
@ -90,7 +89,7 @@ public class AccordIncrementalRepairTest extends TestBaseImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 -> {
|
return delegate.sync(requestedBy, onOrAfter, ranges, include, syncLocal, syncRemote, 10L, TimeUnit.MINUTES).map(v -> {
|
||||||
executedBarriers = true;
|
executedBarriers = true;
|
||||||
|
|
@ -99,7 +98,7 @@ public class AccordIncrementalRepairTest extends TestBaseImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 -> {
|
return delegate.sync(onOrAfter, keys, syncLocal, syncRemote).map(v -> {
|
||||||
executedBarriers = true;
|
executedBarriers = true;
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import accord.local.durability.DurabilityService;
|
import accord.local.durability.DurabilityService;
|
||||||
import accord.primitives.Ranges;
|
import accord.primitives.Ranges;
|
||||||
import accord.primitives.Timestamp;
|
import accord.primitives.TxnId;
|
||||||
|
|
||||||
import org.apache.cassandra.db.Keyspace;
|
import org.apache.cassandra.db.Keyspace;
|
||||||
import org.apache.cassandra.distributed.Cluster;
|
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));
|
Ranges ranges = Ranges.single(TokenRange.fullRange(metadata.id, metadata.partitioner));
|
||||||
for (int i = 0; i < 10; i++)
|
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().closeCurrentSegmentForTestingIfNonEmpty();
|
||||||
accord.journal().runCompactorForTesting();
|
accord.journal().runCompactorForTesting();
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import accord.local.durability.DurabilityService;
|
import accord.local.durability.DurabilityService;
|
||||||
import accord.primitives.Ranges;
|
import accord.primitives.Ranges;
|
||||||
import accord.primitives.Timestamp;
|
import accord.primitives.TxnId;
|
||||||
import accord.utils.Property;
|
import accord.utils.Property;
|
||||||
import accord.utils.Property.SimpleCommand;
|
import accord.utils.Property.SimpleCommand;
|
||||||
|
|
||||||
|
|
@ -120,8 +120,7 @@ public class StatefulJournalRestartTest extends TestBaseImpl
|
||||||
Ranges ranges = Ranges.single(TokenRange.fullRange(metadata.id, metadata.partitioner));
|
Ranges ranges = Ranges.single(TokenRange.fullRange(metadata.id, metadata.partitioner));
|
||||||
for (int i = 0; i < 10; i++)
|
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().closeCurrentSegmentForTestingIfNonEmpty();
|
||||||
accord.journal().runCompactorForTesting();
|
accord.journal().runCompactorForTesting();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,13 +78,14 @@ public class DatabaseDescriptorRefTest
|
||||||
"org.apache.cassandra.auth.INetworkAuthorizer",
|
"org.apache.cassandra.auth.INetworkAuthorizer",
|
||||||
"org.apache.cassandra.auth.IRoleManager",
|
"org.apache.cassandra.auth.IRoleManager",
|
||||||
"org.apache.cassandra.config.AccordSpec",
|
"org.apache.cassandra.config.AccordSpec",
|
||||||
|
"org.apache.cassandra.config.AccordSpec$FetchRetrySpec",
|
||||||
"org.apache.cassandra.config.AccordSpec$JournalSpec",
|
"org.apache.cassandra.config.AccordSpec$JournalSpec",
|
||||||
"org.apache.cassandra.config.AccordSpec$MinEpochRetrySpec",
|
"org.apache.cassandra.config.AccordSpec$MinEpochRetrySpec",
|
||||||
"org.apache.cassandra.config.AccordSpec$MixedTimeSourceHandling",
|
"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$QueueShardModel",
|
||||||
"org.apache.cassandra.config.AccordSpec$QueueSubmissionModel",
|
"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",
|
||||||
"org.apache.cassandra.config.CassandraRelevantProperties$PropertyConverter",
|
"org.apache.cassandra.config.CassandraRelevantProperties$PropertyConverter",
|
||||||
"org.apache.cassandra.config.Config",
|
"org.apache.cassandra.config.Config",
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
package org.apache.cassandra.db.virtual;
|
package org.apache.cassandra.db.virtual;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -30,23 +31,41 @@ import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.BiPredicate;
|
import java.util.function.BiPredicate;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import org.assertj.core.api.Assertions;
|
import org.assertj.core.api.Assertions;
|
||||||
import org.awaitility.Awaitility;
|
import org.awaitility.Awaitility;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import accord.api.Journal;
|
||||||
import accord.api.ProtocolModifiers;
|
import accord.api.ProtocolModifiers;
|
||||||
import accord.api.RoutingKey;
|
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.messages.NoWaitRequest;
|
||||||
|
import accord.primitives.Ballot;
|
||||||
|
import accord.primitives.FullRoute;
|
||||||
|
import accord.primitives.KeyDeps;
|
||||||
import accord.primitives.Keys;
|
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.Ranges;
|
||||||
import accord.primitives.Routable;
|
import accord.primitives.Routable;
|
||||||
import accord.primitives.SaveStatus;
|
import accord.primitives.SaveStatus;
|
||||||
|
import accord.primitives.Status;
|
||||||
import accord.primitives.Status.Durability.HasOutcome;
|
import accord.primitives.Status.Durability.HasOutcome;
|
||||||
|
import accord.primitives.Timestamp;
|
||||||
import accord.primitives.Txn;
|
import accord.primitives.Txn;
|
||||||
import accord.primitives.TxnId;
|
import accord.primitives.TxnId;
|
||||||
|
import accord.topology.TopologyException;
|
||||||
|
|
||||||
import org.apache.cassandra.config.Config;
|
import org.apache.cassandra.config.Config;
|
||||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
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.IAccordService;
|
||||||
import org.apache.cassandra.service.accord.TokenRange;
|
import org.apache.cassandra.service.accord.TokenRange;
|
||||||
import org.apache.cassandra.service.accord.api.TokenKey;
|
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.tcm.ClusterMetadata;
|
||||||
import org.apache.cassandra.transport.Dispatcher;
|
import org.apache.cassandra.transport.Dispatcher;
|
||||||
import org.apache.cassandra.utils.Clock;
|
import org.apache.cassandra.utils.Clock;
|
||||||
import org.apache.cassandra.utils.concurrent.Condition;
|
import org.apache.cassandra.utils.concurrent.Condition;
|
||||||
|
|
||||||
import static accord.api.ProtocolModifiers.Toggles.SendStableMessages.TO_ALL;
|
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 accord.primitives.TxnId.FastPath.Unoptimised;
|
||||||
import static org.apache.cassandra.Util.spinUntilSuccess;
|
import static org.apache.cassandra.Util.spinUntilSuccess;
|
||||||
import static org.apache.cassandra.net.Verb.ACCORD_APPLY_AND_WAIT_REQ;
|
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.net.Verb.ACCORD_BEGIN_RECOVER_REQ;
|
||||||
import static org.apache.cassandra.service.accord.AccordService.getBlocking;
|
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.AccordTestUtils.createTxn;
|
||||||
|
import static org.apache.cassandra.service.accord.debug.TxnKindsAndDomains.ALL;
|
||||||
|
|
||||||
public class AccordDebugKeyspaceTest extends CQLTester
|
public class AccordDebugKeyspaceTest extends CQLTester
|
||||||
{
|
{
|
||||||
|
|
@ -93,6 +116,27 @@ public class AccordDebugKeyspaceTest extends CQLTester
|
||||||
private static final String QUERY_TXN_BLOCKED_BY_REMOTE =
|
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);
|
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 =
|
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);
|
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
|
@Test
|
||||||
public void patchJournalVestigialTest()
|
public void patchJournalVestigialTest()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ import org.apache.cassandra.utils.concurrent.CountDownLatch;
|
||||||
import static accord.local.RedundantStatus.SomeStatus.NONE;
|
import static accord.local.RedundantStatus.SomeStatus.NONE;
|
||||||
import static accord.utils.Property.commands;
|
import static accord.utils.Property.commands;
|
||||||
import static accord.utils.Property.stateful;
|
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.config.DatabaseDescriptor.getPartitioner;
|
||||||
import static org.apache.cassandra.schema.SchemaConstants.ACCORD_KEYSPACE_NAME;
|
import static org.apache.cassandra.schema.SchemaConstants.ACCORD_KEYSPACE_NAME;
|
||||||
|
|
||||||
|
|
@ -131,6 +132,7 @@ public class RouteIndexTest extends CQLTester
|
||||||
DatabaseDescriptor.setAccordTransactionsEnabled(true);
|
DatabaseDescriptor.setAccordTransactionsEnabled(true);
|
||||||
// disable journal compaction so the test can control when it happens
|
// disable journal compaction so the test can control when it happens
|
||||||
DatabaseDescriptor.getAccord().enable_journal_compaction = false;
|
DatabaseDescriptor.getAccord().enable_journal_compaction = false;
|
||||||
|
DatabaseDescriptor.getAccord().range_index_mode = journal_sai;
|
||||||
DatabaseDescriptor.setIncrementalBackupsEnabled(false);
|
DatabaseDescriptor.setIncrementalBackupsEnabled(false);
|
||||||
DatabaseDescriptor.setAutoSnapshot(false);
|
DatabaseDescriptor.setAutoSnapshot(false);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ import org.apache.cassandra.utils.ByteBufferUtil;
|
||||||
import org.apache.cassandra.utils.FBUtilities;
|
import org.apache.cassandra.utils.FBUtilities;
|
||||||
|
|
||||||
import static org.apache.cassandra.Util.throwAssert;
|
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.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
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
|
// 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.
|
// 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()
|
Awaitility.await()
|
||||||
.atMost(1, TimeUnit.MINUTES)
|
.atMost(1, TimeUnit.MINUTES)
|
||||||
.pollDelay(1, TimeUnit.SECONDS)
|
.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";
|
String indexName = "build_remove_test_idx";
|
||||||
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY (a, b))");
|
||||||
createIndex(String.format("CREATE INDEX %s ON %%s(c)", indexName));
|
createIndex(String.format("CREATE INDEX %s ON %%s(c)", indexName));
|
||||||
|
|
||||||
// check that there are no other rows in the built indexes table
|
// 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
|
// rebuild the index and verify the built status table
|
||||||
getCurrentColumnFamilyStore().rebuildSecondaryIndex(indexName);
|
getCurrentColumnFamilyStore().rebuildSecondaryIndex(indexName);
|
||||||
waitForIndexQueryable(indexName);
|
waitForIndexQueryable(indexName);
|
||||||
|
|
||||||
// check that there are no other rows in the built indexes table
|
// 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
|
// check that dropping the index removes it from the built indexes table
|
||||||
dropIndex("DROP INDEX %s." + indexName);
|
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);
|
builder.add(key.toUnseekable(), txnId2);
|
||||||
deps = builder.build();
|
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 -> {
|
getBlocking(commandStore.execute(accept, safeStore -> {
|
||||||
Command before = safeStore.ifInitialised(txnId).current();
|
Command before = safeStore.ifInitialised(txnId).current();
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ public class RouteInMemoryIndexTest
|
||||||
|
|
||||||
private static class State
|
private static class State
|
||||||
{
|
{
|
||||||
private final RouteInMemoryIndex<?> index = new RouteInMemoryIndex<>();
|
private final JournalSegmentRangeSearcher<?> index = new JournalSegmentRangeSearcher<>();
|
||||||
private final Model model = new Model();
|
private final Model model = new Model();
|
||||||
private final float unfiltered;
|
private final float unfiltered;
|
||||||
private final float minDecidedIdNull;
|
private final float minDecidedIdNull;
|
||||||
|
|
@ -279,17 +279,17 @@ public class RouteInMemoryIndexTest
|
||||||
segments.computeIfAbsent(segment, i -> new Segment()).add(range, txnId);
|
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);
|
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);
|
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<>();
|
TreeSet<TxnId> result = new TreeSet<>();
|
||||||
for (var segment: segments.values())
|
for (var segment: segments.values())
|
||||||
|
|
@ -302,7 +302,7 @@ public class RouteInMemoryIndexTest
|
||||||
result.add(value.txnId);
|
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)
|
void remove(long segment)
|
||||||
|
|
|
||||||
|
|
@ -496,9 +496,9 @@ public class SimulatedAccordCommandStore implements AutoCloseable
|
||||||
commandStore.shutdown();
|
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)
|
private DefaultJournal(Node.Id id, RandomSource rs)
|
||||||
{
|
{
|
||||||
super(id, rs);
|
super(id, rs);
|
||||||
|
|
@ -524,7 +524,7 @@ public class SimulatedAccordCommandStore implements AutoCloseable
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RangeSearcher rangeSearcher()
|
public JournalRangeSearcher rangeSearcher()
|
||||||
{
|
{
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ public class CommandsForKeySerializerTest
|
||||||
builder.durability(isDurable ? AllQuorums : NotDurable);
|
builder.durability(isDurable ? AllQuorums : NotDurable);
|
||||||
if (saveStatus.known.deps().hasPreAcceptedOrProposedOrDecidedDeps())
|
if (saveStatus.known.deps().hasPreAcceptedOrProposedOrDecidedDeps())
|
||||||
{
|
{
|
||||||
try (KeyDeps.Builder keyBuilder = KeyDeps.builder();)
|
try (KeyDeps.Builder keyBuilder = KeyDeps.builder())
|
||||||
{
|
{
|
||||||
for (TxnId id : deps)
|
for (TxnId id : deps)
|
||||||
keyBuilder.add(((Key)txn.keys().get(0)).toUnseekable(), id);
|
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 now() { return 0; }
|
||||||
@Override public long elapsed(TimeUnit unit) { 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) { }
|
@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),
|
||||||
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();
|
DataOutputBuffer buf = new DataOutputBuffer();
|
||||||
Serializers.testSerde(buf, LatestDepsSerializers.latestDeps, latestDeps);
|
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.TimeType;
|
||||||
import org.apache.cassandra.db.marshal.TimeUUIDType;
|
import org.apache.cassandra.db.marshal.TimeUUIDType;
|
||||||
import org.apache.cassandra.db.marshal.TimestampType;
|
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.TokenUtf8Type;
|
||||||
import org.apache.cassandra.db.marshal.TupleType;
|
import org.apache.cassandra.db.marshal.TupleType;
|
||||||
import org.apache.cassandra.db.marshal.TxnIdUtf8Type;
|
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(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(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(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();
|
.build();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue