Improve Tail Latency

- TransactionStatement token aware routing
 - Mitigate convoy effect:
   - ExecuteTxnBacklog v1 can execute single key transaction backlogs and disseminate the result
     - Direct local execution possible for single key transactions
   - Use shardAppliedBefore instead of gcBefore to cleanup CFK faster
Also Improve:
 - Introduce distributed tracing
 - Combine RejectBefore and MaxConflicts
 - Self-addressed messages are delivered directly
 - Disable AccordSyncPropagator by default, as incompatible with large clusters
 - FastPathStrategy.SIMPLE should not modify fast path contents; FastPathStrategy.UP introduced to adopt this behaviour
 - Configurable queue prioritisation
 - ProtocolModifiers mostly final
 - Remove unnecessary condition check when serializing TxnUpdate
Also Fix:
 - SequentialExecutor ownership bug
 - BTree.XUpdater.reset()

patch by Benedict; reviewed by Alex Petrov and Ariel Weisberg for CASSANDRA-21361
This commit is contained in:
Benedict Elliott Smith 2026-03-27 22:26:02 +00:00
parent 8ad8e56378
commit 41cf61402c
150 changed files with 3257 additions and 1131 deletions

View File

@ -1,4 +1,7 @@
6.0-alpha2
* Accord: Tail Latency Improvements (CASSANDRA-21361)
* Artificial Latency Injection (CASSANDRA-17024)
* Accord: Clean Shutdown/Restart, Rebootstrap, et al (CASSANDRA-21355)
* Reduce memory allocations in SelectStatement.getQuery (CASSANDRA-21351)
* Avoid allocation by getFunctions in SelectStatement.authorize (CASSANDRA-21347)
* Avoid unit conversion in DatabaseDescriptor.getMaxValueSize() for every deserializing Cell (CASSANDRA-21295)

@ -1 +1 @@
Subproject commit d3c5935589082f784b10dbee4d89691612f680eb
Subproject commit 01aff40402a55bada08f6e5753ee3a4010f407da

View File

@ -22,23 +22,28 @@ import java.util.concurrent.TimeUnit;
import com.fasterxml.jackson.annotation.JsonIgnore;
import accord.api.ProtocolModifiers.CleanCfkBefore;
import accord.api.ProtocolModifiers.CoordinatorBacklogExecution;
import accord.api.ProtocolModifiers.FastExecution;
import accord.api.ProtocolModifiers.ReplicaExecution;
import accord.api.ProtocolModifiers.SendStableMessages;
import accord.primitives.TxnId;
import accord.utils.Invariants;
import org.apache.cassandra.journal.Params;
import org.apache.cassandra.service.accord.serializers.Version;
import org.apache.cassandra.service.consensus.TransactionalMode;
import static org.apache.cassandra.config.AccordSpec.CatchupMode.NORMAL;
import static org.apache.cassandra.config.AccordSpec.QueueShardModel.THREAD_POOL_PER_SHARD;
import static org.apache.cassandra.config.AccordSpec.QueueSubmissionModel.SYNC;
import static org.apache.cassandra.config.AccordSpec.RangeIndexMode.in_memory;
import static org.apache.cassandra.config.AccordConfig.CatchupMode.NORMAL;
import static org.apache.cassandra.config.AccordConfig.QueuePriorityModel.HLC_FIFO;
import static org.apache.cassandra.config.AccordConfig.QueueShardModel.THREAD_POOL_PER_SHARD;
import static org.apache.cassandra.config.AccordConfig.QueueSubmissionModel.SYNC;
import static org.apache.cassandra.config.AccordConfig.RangeIndexMode.in_memory;
// TODO (expected): rename to AccordConf?
public class AccordSpec
public class AccordConfig
{
public volatile boolean enabled = false;
// TODO (expected): move to JournalSpec
public volatile String journal_directory;
/**
@ -107,6 +112,37 @@ public class AccordSpec
EXEC_ST
}
public enum QueuePriorityModel
{
/**
* All work is queued on a first-come first-serve basis.
* Overload can lead to more rapid degradation, as later phases of the state machine are delayed
* by the arrival of new work.
*/
FIFO,
/**
* If the work has an associated TxnId, prioritise by its HLC (and FIFO otherwise)
*/
HLC_FIFO,
/**
* Prioritise Apply, Stable, Commit, Accept, and PreAccept messages in that order.
* Within a given message type, prioritise by HLC.
* Other messages will be mixed with PreAccept messages, but using the next counter rather than the HLC of the TxnId.
* Note: this can have some performance edge cases for contended keys, as we may process Stable messages for later commands before
* we process earlier Accept/Commit, which may delay execution
*/
PHASE_HLC_FIFO,
/**
* Prioritise Apply, Stable, Commit, Accept, and PreAccept messages from the original coordinator only, in that order.
* Within a given message type, prioritise by HLC.
* Other messages will be mixed with PreAccept messages, but using the next counter rather than the HLC of the TxnId.
*/
ORIG_PHASE_HLC_FIFO
}
public QueueShardModel queue_shard_model = THREAD_POOL_PER_SHARD;
public QueueSubmissionModel queue_submission_model = SYNC;
@ -115,6 +151,15 @@ public class AccordSpec
*/
public volatile OptionaldPositiveInt queue_shard_count = OptionaldPositiveInt.UNDEFINED;
public QueuePriorityModel queue_priority_model = HLC_FIFO;
// yield to other executor threads after executing this many tasks in a row, if there are waiting threads and tasks
public int queue_yield_interval = 100;
/**
* If the HLC is older than this, queue by FIFO instead
*/
public DurationSpec.IntMillisecondsBound queue_priority_age_to_fifo = new DurationSpec.IntMillisecondsBound(500);
/**
* The target number of command stores to create per topology shard.
* This determines the amount of execution parallelism possible for a given table/shard on the host.
@ -165,6 +210,10 @@ public class AccordSpec
public volatile DurationSpec.IntSecondsBound shard_durability_cycle = new DurationSpec.IntSecondsBound(5, TimeUnit.MINUTES);
public volatile DurationSpec.IntSecondsBound global_durability_cycle = new DurationSpec.IntSecondsBound(5, TimeUnit.MINUTES);
public volatile DurationSpec.IntSecondsBound topology_watermark_interval = new DurationSpec.IntSecondsBound(60);
public volatile boolean topology_sync_propagator_enabled_pre_start = false;
public volatile boolean topology_sync_propagator_enabled_post_startup = false;
public enum TransactionalRangeMigration
{
auto, explicit
@ -178,11 +227,6 @@ public class AccordSpec
*/
public volatile TransactionalRangeMigration range_migration = TransactionalRangeMigration.auto;
public enum RebootstrapMode
{
full_repair, truncate_and_stream
}
public enum CatchupMode
{
DISABLED,
@ -196,15 +240,43 @@ public class AccordSpec
*/
public TransactionalMode default_transactional_mode = TransactionalMode.off;
public boolean ephemeralReadEnabled = true;
// ******** PROTOCOL MODIFIERS ***********
public CoordinatorBacklogExecution coordinator_backlog_execution;
public Boolean permit_local_delivery;
public Boolean permit_coordinator_local_execution; // if disabled, the privileged coordinator optimisation will be counter-productive and should also be disabled
public TxnId.FastPath permit_fast_path;
public Boolean permit_track_stable_medium_path;
public Boolean permit_fast_quorum_medium_path;
public Boolean always_inform_durable_single_key;
public ReplicaExecution replica_execution;
public Float replica_execution_distributed_persist_chance;
public FastExecution fast_write_execution;
public FastExecution fast_read_execution;
public CleanCfkBefore clean_cfk_before;
public SendStableMessages send_stable;
/**
* include the least information expected to be necessary in messages -
* this is more efficient but may lead to some additional traffic and latency when earlier messages had not arrived
*/
public Boolean send_minimal;
// note: simulator incompatible (for now)
public Boolean precise_micros;
public boolean ephemeral_reads = true;
public boolean state_cache_listener_jfr_enabled = false;
public float hard_reject_ratio = 0.5f;
public int min_soft_reject_count = 10;
public int max_soft_reject_count = 100;
public int min_soft_reject_count = 100;
public int max_soft_reject_count = 1000;
public DurationSpec.LongMicrosecondsBound soft_reject_age = new DurationSpec.LongMicrosecondsBound("10s");
public DurationSpec.LongMicrosecondsBound soft_reject_cumulative_age = new DurationSpec.LongMicrosecondsBound("60s");
public DurationSpec.IntSecondsBound commands_for_key_prune_delta = new DurationSpec.IntSecondsBound(1);
public int commands_for_key_prune_interval = 64;
public DurationSpec.IntSecondsBound max_conflicts_prune_delta = new DurationSpec.IntSecondsBound(1);
public DurationSpec.IntSecondsBound catchup_on_start_success_latency = new DurationSpec.IntSecondsBound(60);
public DurationSpec.IntSecondsBound catchup_on_start_fail_latency = new DurationSpec.IntSecondsBound(900);
public int catchup_on_start_max_attempts = 5;
@ -216,7 +288,7 @@ public class AccordSpec
public enum RangeIndexMode { in_memory, journal_sai }
public RangeIndexMode range_index_mode = in_memory;
public final JournalSpec journal = new JournalSpec();
public final JournalConfig journal = new JournalConfig();
public enum MixedTimeSourceHandling
{
@ -225,7 +297,7 @@ public class AccordSpec
public volatile MixedTimeSourceHandling mixedTimeSourceHandling = MixedTimeSourceHandling.reject;
public static class JournalSpec implements Params
public static class JournalConfig implements Params
{
public enum ReplayMode
{
@ -265,11 +337,17 @@ public class AccordSpec
*/
EXIT,
/**
* @deprecated since alpha release, replaced by ALLOW_UNSAFE_STARTUP for consistency with FailurePolicy.ALLOW_UNSAFE_STARTUP
*/
@Deprecated(since="6.0")
UNSAFE_STARTUP,
/**
* If the start marker exceeds the stop marker startup, assuming the consensus log has been determined complete externally.
* Note this is VERY UNSAFE if you care about isolation guarantees.
*/
UNSAFE_STARTUP,
ALLOW_UNSAFE_STARTUP,
REBOOTSTRAP
}
@ -290,7 +368,7 @@ public class AccordSpec
public Version version = Version.DOWNGRADE_SAFE_VERSION;
public boolean enable_compaction = true;
public JournalSpec setFlushPeriod(DurationSpec newFlushPeriod)
public JournalConfig setFlushPeriod(DurationSpec newFlushPeriod)
{
flushPeriod = newFlushPeriod;
flushCombinedBlockPeriod = Long.MIN_VALUE;

View File

@ -44,6 +44,7 @@ public enum CassandraRelevantProperties
{
ACCORD_AGENT_CLASS("cassandra.test.accord.agent"),
ACCORD_ALLOW_TEST_MODES("cassandra.test.accord.allow_test_modes", "false"),
ACCORD_DEBUG_EXECUTION("accord.debug_execution"),
ACCORD_KEY_PARANOIA_COSTFACTOR(Invariants.KEY_PARANOIA_COSTFACTOR),
ACCORD_KEY_PARANOIA_CPU(Invariants.KEY_PARANOIA_CPU),
ACCORD_KEY_PARANOIA_MEMORY(Invariants.KEY_PARANOIA_MEMORY),

View File

@ -165,8 +165,6 @@ public class Config
@Replaces(oldName = "cas_contention_timeout_in_ms", converter = Converters.MILLIS_DURATION_LONG, deprecated = true)
public volatile DurationSpec.LongMillisecondsBound cas_contention_timeout = new DurationSpec.LongMillisecondsBound("1800ms");
public volatile DurationSpec.LongMillisecondsBound accord_preaccept_timeout = new DurationSpec.LongMillisecondsBound("1s");
@Replaces(oldName = "truncate_request_timeout_in_ms", converter = Converters.MILLIS_DURATION_LONG, deprecated = true)
public volatile DurationSpec.LongMillisecondsBound truncate_request_timeout = new DurationSpec.LongMillisecondsBound("60000ms");
@ -1238,7 +1236,7 @@ public class Config
*/
public ParameterizedClass default_compaction = null;
public final AccordSpec accord = new AccordSpec();
public final AccordConfig accord = new AccordConfig();
public static Supplier<Config> getOverrideLoadConfig()
{

View File

@ -126,6 +126,7 @@ import org.apache.cassandra.service.CacheService.CacheType;
import org.apache.cassandra.service.FileSystemOwnershipCheck;
import org.apache.cassandra.service.StartupChecks;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.api.AccordWaitStrategies;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.service.paxos.Paxos;
@ -574,7 +575,7 @@ public class DatabaseDescriptor
applyGuardrails();
applyAccordProgressLog();
applyAccord();
applyStartupChecks();
}
@ -1370,7 +1371,7 @@ public class DatabaseDescriptor
}
}
private static void applyAccordProgressLog()
private static void applyAccord()
{
try
{
@ -1382,6 +1383,7 @@ public class DatabaseDescriptor
{
throw new ConfigurationException("Invalid accord progress log configuration: " + e.getMessage(), e);
}
AccordService.applyProtocolModifiers(getAccord());
}
public static StartupChecksConfiguration getStartupChecksConfiguration()
@ -1663,12 +1665,6 @@ public class DatabaseDescriptor
logInfo("truncate_request_timeout", conf.truncate_request_timeout, LOWEST_ACCEPTED_TIMEOUT);
conf.truncate_request_timeout = LOWEST_ACCEPTED_TIMEOUT;
}
if (conf.accord_preaccept_timeout.toMilliseconds() < LOWEST_ACCEPTED_TIMEOUT.toMilliseconds())
{
logInfo("accord_preaccept_timeout", conf.accord_preaccept_timeout, LOWEST_ACCEPTED_TIMEOUT);
conf.accord_preaccept_timeout = LOWEST_ACCEPTED_TIMEOUT;
}
}
private static void logInfo(String property, DurationSpec.LongMillisecondsBound actualValue, DurationSpec.LongMillisecondsBound lowestAcceptedValue)
@ -5634,18 +5630,17 @@ public class DatabaseDescriptor
}
}
public static AccordSpec getAccord()
public static AccordConfig getAccord()
{
return conf.accord;
}
public static AccordSpec.TransactionalRangeMigration getTransactionalRangeMigration()
public static AccordConfig.TransactionalRangeMigration getTransactionalRangeMigration()
{
return conf.accord.range_migration;
}
public static void setTransactionalRangeMigration(AccordSpec.TransactionalRangeMigration val)
public static void setTransactionalRangeMigration(AccordConfig.TransactionalRangeMigration val)
{
conf.accord.range_migration = Preconditions.checkNotNull(val);
}
@ -5670,12 +5665,12 @@ public class DatabaseDescriptor
conf.accord.enabled = b;
}
public static AccordSpec.QueueShardModel getAccordQueueShardModel()
public static AccordConfig.QueueShardModel getAccordQueueShardModel()
{
return conf.accord.queue_shard_model;
}
public static AccordSpec.QueueSubmissionModel getAccordQueueSubmissionModel()
public static AccordConfig.QueueSubmissionModel getAccordQueueSubmissionModel()
{
return conf.accord.queue_submission_model;
}
@ -6278,7 +6273,7 @@ public class DatabaseDescriptor
public static boolean getAccordEphemeralReadEnabledEnabled()
{
return conf.accord.ephemeralReadEnabled;
return conf.accord.ephemeral_reads;
}
public static AutoRepairConfig getAutoRepairConfig()

View File

@ -153,9 +153,9 @@ public class Attributes
public void collectMarkerSpecification(VariableSpecifications boundNames)
{
if (timestamp != null)
timestamp.collectMarkerSpecification(boundNames);
timestamp.collectMarkerSpecification(boundNames, this);
if (timeToLive != null)
timeToLive.collectMarkerSpecification(boundNames);
timeToLive.collectMarkerSpecification(boundNames, this);
}
public static class Raw

View File

@ -386,10 +386,10 @@ public final class ColumnsExpression
* @param boundNames the variables specification where to collect the
* bind variables of the map key/collection element in.
*/
public void collectMarkerSpecification(VariableSpecifications boundNames)
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
{
if (element != null)
element.collectMarkerSpecification(boundNames);
element.collectMarkerSpecification(boundNames, owner);
}
/**

View File

@ -140,9 +140,9 @@ public final class ElementExpression
* @param boundNames the variables specification where to collect the
* bind variables of the map key/collection element in.
*/
public void collectMarkerSpecification(VariableSpecifications boundNames)
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
{
keyOrIndex.collectMarkerSpecification(boundNames);
keyOrIndex.collectMarkerSpecification(boundNames, owner);
}
/**

View File

@ -86,7 +86,7 @@ public final class Json
public Prepared prepareAndCollectMarkers(TableMetadata metadata, Collection<ColumnMetadata> receivers, VariableSpecifications boundNames)
{
boundNames.add(bindIndex, makeReceiver(metadata));
boundNames.add(bindIndex, makeReceiver(metadata), null);
return new PreparedMarker(bindIndex, receivers);
}
@ -239,7 +239,7 @@ public final class Json
}
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames)
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
{
// We've already collected what we should (and in practice this method is never called).
}

View File

@ -103,12 +103,13 @@ public abstract class Operation
* Collects the column specification for the bind variables of this operation.
*
* @param boundNames the list of column specification where to collect the
* bind variables of this term in.
* bind variables of this term in.
* @param owner
*/
public void collectMarkerSpecification(VariableSpecifications boundNames)
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
{
if (t != null)
t.collectMarkerSpecification(boundNames);
t.collectMarkerSpecification(boundNames, owner);
}
/**

View File

@ -134,14 +134,14 @@ public class Ordering
* Resolves column identifiers against the table schema.
* Binds markers (?) to columns.
*/
public Ordering bind(TableMetadata table, VariableSpecifications boundNames)
public Ordering bind(TableMetadata table, VariableSpecifications boundNames, Object owner)
{
return new Ordering(expression.bind(table, boundNames), direction);
return new Ordering(expression.bind(table, boundNames, owner), direction);
}
public interface Expression
{
Ordering.Expression bind(TableMetadata table, VariableSpecifications boundNames);
Ordering.Expression bind(TableMetadata table, VariableSpecifications boundNames, Object owner);
}
public static class SingleColumn implements Expression
@ -154,7 +154,7 @@ public class Ordering
}
@Override
public Ordering.Expression bind(TableMetadata table, VariableSpecifications boundNames)
public Ordering.Expression bind(TableMetadata table, VariableSpecifications boundNames, Object owner)
{
return new Ordering.SingleColumn(table.getExistingColumn(column), table);
}
@ -172,11 +172,11 @@ public class Ordering
}
@Override
public Ordering.Expression bind(TableMetadata table, VariableSpecifications boundNames)
public Ordering.Expression bind(TableMetadata table, VariableSpecifications boundNames, Object owner)
{
ColumnMetadata column = table.getExistingColumn(columnId);
Term value = vectorValue.prepare(table.keyspace, column);
value.collectMarkerSpecification(boundNames);
value.collectMarkerSpecification(boundNames, owner);
return new Ordering.Ann(column, table, value);
}
}

View File

@ -191,7 +191,7 @@ public final class Relation
* @return the <code>Restriction</code> corresponding to this <code>Relation</code>
* @throws InvalidRequestException if this <code>Relation</code> is not valid
*/
public SingleRestriction toRestriction(TableMetadata table, VariableSpecifications boundNames, boolean allowFiltering)
public SingleRestriction toRestriction(TableMetadata table, VariableSpecifications boundNames, Object owner, boolean allowFiltering)
{
ColumnsExpression columnsExpression = rawExpressions.prepare(table);
@ -210,7 +210,7 @@ public final class Relation
if (column.isClusteringColumn() && baseType.isCollection() && !column.type.isMultiCell())
throw invalidRequest(FROZEN_MAP_ENTRY_PREDICATES_NOT_SUPPORTED, column.name);
columnsExpression.collectMarkerSpecification(boundNames);
columnsExpression.collectMarkerSpecification(boundNames, owner);
}
operator.validateFor(columnsExpression);
@ -220,7 +220,7 @@ public final class Relation
receiver = ((CollectionType<?>) receiver.type).makeCollectionReceiver(receiver, operator.appliesToMapKeys());
Terms terms = rawTerms.prepare(table.keyspace, receiver);
terms.collectMarkerSpecification(boundNames);
terms.collectMarkerSpecification(boundNames, owner);
// An IN restriction with only one element is the same as an EQ restriction
if (operator.isIN() && terms.containsSingleTerm())

View File

@ -21,6 +21,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import org.apache.cassandra.schema.ColumnMetadata;
@ -32,6 +34,8 @@ public class VariableSpecifications
private final List<ColumnSpecification> specs;
private volatile ImmutableList<ColumnSpecification> immutableSpecs;
private final ColumnMetadata[] targetColumns;
// TODO (desired): this is an ugly way to figure out which sub statement we're using as a key in transactions, but path of least resistance...
private @Nullable Object[] targetOwners;
public VariableSpecifications(List<ColumnIdentifier> variableNames)
{
@ -40,6 +44,12 @@ public class VariableSpecifications
this.targetColumns = new ColumnMetadata[variableNames.size()];
}
public void setSaveTargetOwners(boolean saveTargetOwners)
{
this.targetOwners = saveTargetOwners ? new Object[variableNames.size()] : null;
}
/**
* Returns an empty instance of <code>VariableSpecifications</code>.
* @return an empty instance of <code>VariableSpecifications</code>
@ -77,14 +87,14 @@ public class VariableSpecifications
*
* Callers of this method should ensure that all statements operate on the same table.
*/
public short[] getPartitionKeyBindVariableIndexes(TableMetadata metadata)
public short[] getPartitionKeyBindVariableIndexes(TableMetadata metadata, Object targetOwner)
{
short[] partitionKeyPositions = new short[metadata.partitionKeyColumns().size()];
boolean[] set = new boolean[partitionKeyPositions.length];
for (int i = 0; i < targetColumns.length; i++)
{
ColumnMetadata targetColumn = targetColumns[i];
if (targetColumn != null && targetColumn.isPartitionKey())
if (targetColumn != null && targetColumn.isPartitionKey() && (targetOwners == null || (targetOwner != null && targetOwners[i] == targetOwner)))
{
assert targetColumn.ksName.equals(metadata.keyspace) && targetColumn.cfName.equals(metadata.name);
partitionKeyPositions[targetColumn.position()] = (short) i;
@ -99,12 +109,15 @@ public class VariableSpecifications
return partitionKeyPositions;
}
public void add(int bindIndex, ColumnSpecification spec)
public void add(int bindIndex, ColumnSpecification spec, Object owner)
{
assert immutableSpecs == null : "bind variable specs cannot be modified once we started to use them";
if (spec instanceof ColumnMetadata)
targetColumns[bindIndex] = (ColumnMetadata) spec;
if (targetOwners != null)
targetOwners[bindIndex] = owner;
ColumnIdentifier bindMarkerName = variableNames.get(bindIndex);
// Use the user name, if there is one
if (bindMarkerName != null)

View File

@ -104,10 +104,10 @@ public final class ColumnCondition
* @param boundNames the list of column specification where to collect the
* bind variables of this term in.
*/
public void collectMarkerSpecification(VariableSpecifications boundNames)
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
{
columnsExpression.collectMarkerSpecification(boundNames);
values.collectMarkerSpecification(boundNames);
columnsExpression.collectMarkerSpecification(boundNames, owner);
values.collectMarkerSpecification(boundNames, owner);
}
public ColumnCondition.Bound bind(QueryOptions options)

View File

@ -61,10 +61,10 @@ public class FunctionCall extends Term.NonTerminal
fun.addFunctionsTo(functions);
}
public void collectMarkerSpecification(VariableSpecifications boundNames)
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
{
for (Term t : terms)
t.collectMarkerSpecification(boundNames);
t.collectMarkerSpecification(boundNames, owner);
}
@Override

View File

@ -45,11 +45,11 @@ public class CustomIndexExpression
this.valueRaw = value;
}
public void prepareValue(TableMetadata table, AbstractType<?> expressionType, VariableSpecifications boundNames)
public void prepareValue(TableMetadata table, AbstractType<?> expressionType, VariableSpecifications boundNames, Object owner)
{
ColumnSpecification spec = new ColumnSpecification(table.keyspace, table.keyspace, valueColId, expressionType);
value = valueRaw.prepare(table.keyspace, spec);
value.collectMarkerSpecification(boundNames);
value.collectMarkerSpecification(boundNames, owner);
}
public void addToRowFilter(RowFilter filter, TableMetadata table, QueryOptions options)

View File

@ -175,11 +175,12 @@ public final class StatementRestrictions
WhereClause whereClause,
VariableSpecifications boundNames,
List<Ordering> orderings,
Object owner,
boolean selectsOnlyStaticColumns,
boolean allowFiltering,
boolean forView)
{
this(state, type, table, indexHints, whereClause, boundNames, orderings, selectsOnlyStaticColumns, type.allowUseOfSecondaryIndices(), allowFiltering, forView);
this(state, type, table, indexHints, whereClause, boundNames, orderings, owner, selectsOnlyStaticColumns, type.allowUseOfSecondaryIndices(), allowFiltering, forView);
}
/*
@ -193,6 +194,7 @@ public final class StatementRestrictions
WhereClause whereClause,
VariableSpecifications boundNames,
List<Ordering> orderings,
Object owner,
boolean selectsOnlyStaticColumns,
boolean allowUseOfSecondaryIndices,
boolean allowFiltering,
@ -227,11 +229,11 @@ public final class StatementRestrictions
if (!forView)
throw new InvalidRequestException("Unsupported restriction: " + relation);
this.notNullColumns.addAll(relation.toRestriction(table, boundNames, allowFiltering).columns());
this.notNullColumns.addAll(relation.toRestriction(table, boundNames, owner, allowFiltering).columns());
}
else if (operator.requiresIndexing())
{
Restriction restriction = relation.toRestriction(table, boundNames, allowFiltering);
Restriction restriction = relation.toRestriction(table, boundNames, owner, allowFiltering);
if (!type.allowUseOfSecondaryIndices() || !restriction.hasSupportingIndex(indexRegistry, indexHints))
throw invalidRequest("%s restriction is only supported on properly " +
@ -241,7 +243,7 @@ public final class StatementRestrictions
}
else
{
addRestriction(relation.toRestriction(table, boundNames, allowFiltering), indexRegistry, indexHints);
addRestriction(relation.toRestriction(table, boundNames, owner, allowFiltering), indexRegistry, indexHints);
}
}
@ -756,7 +758,7 @@ public final class StatementRestrictions
if (expressionType == null)
throw IndexRestrictions.customExpressionNotSupported(expression.targetIndex);
expression.prepareValue(table, expressionType, boundNames);
expression.prepareValue(table, expressionType, boundNames, table);
filterRestrictions.add(expression);
}

View File

@ -222,7 +222,7 @@ public interface Selectable extends AssignmentTestable
// something a lot more helpful and in practice user can bind those markers by position or, even better,
// use bind markers.
Term term = rawTerm.prepare(table.keyspace, new ColumnSpecification(table.keyspace, table.name, bindMarkerNameInSelection, type));
term.collectMarkerSpecification(boundNames);
term.collectMarkerSpecification(boundNames, table);
return TermSelector.newFactory(rawTerm.getText(), term, type);
}
@ -1481,7 +1481,7 @@ public interface Selectable extends AssignmentTestable
ColumnSpecification boundSpec = specForElementOrSlice(selected, receiver, ((CollectionType) type).kind, "Element");
Term elt = element.prepare(cfm.keyspace, boundSpec);
elt.collectMarkerSpecification(boundNames);
elt.collectMarkerSpecification(boundNames, cfm);
return ElementsSelector.newElementFactory(toString(), factory, (CollectionType)type, elt);
}
@ -1571,8 +1571,8 @@ public interface Selectable extends AssignmentTestable
// The equivalent of doing this when preparing values would be to use UNSET.
Term f = from == null ? Constants.UNSET_VALUE : from.prepare(cfm.keyspace, boundSpec);
Term t = to == null ? Constants.UNSET_VALUE : to.prepare(cfm.keyspace, boundSpec);
f.collectMarkerSpecification(boundNames);
t.collectMarkerSpecification(boundNames);
f.collectMarkerSpecification(boundNames, cfm);
t.collectMarkerSpecification(boundNames, cfm);
return ElementsSelector.newSliceFactory(toString(), factory, (CollectionType)type, f, t);
}

View File

@ -222,7 +222,7 @@ public class BatchStatement implements CQLStatement.CompositeCQLStatement
// multiple tables, we won't send partition key bind indexes.
return (affectsMultipleTables || statements.isEmpty())
? null
: bindVariables.getPartitionKeyBindVariableIndexes(statements.get(0).metadata());
: bindVariables.getPartitionKeyBindVariableIndexes(statements.get(0).metadata(), statements.get(0).attrs);
}
@Override

View File

@ -176,11 +176,11 @@ public class DeleteStatement extends ModificationStatement
checkFalse(def.isPrimaryKeyColumn(), "Invalid identifier %s for deletion (should not be a PRIMARY KEY part)", def.name);
Operation op = deletion.prepare(metadata.keyspace, def, metadata);
op.collectMarkerSpecification(bindVariables);
op.collectMarkerSpecification(bindVariables, attrs);
operations.add(op, metadata);
}
StatementRestrictions restrictions = newRestrictions(state, metadata, bindVariables, operations, whereClause, conditions);
StatementRestrictions restrictions = newRestrictions(state, metadata, bindVariables, operations, whereClause, conditions, attrs);
DeleteStatement stmt = new DeleteStatement(bindVariables,
metadata,

View File

@ -282,7 +282,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
@Override
public short[] getPartitionKeyBindVariableIndexes()
{
return bindVariables.getPartitionKeyBindVariableIndexes(metadata);
return bindVariables.getPartitionKeyBindVariableIndexes(metadata, attrs);
}
@Override
@ -1328,7 +1328,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
for (ColumnCondition.Raw rawCondition : conditions)
{
ColumnCondition condition = rawCondition.prepare(metadata);
condition.collectMarkerSpecification(bindVariables);
condition.collectMarkerSpecification(bindVariables, attrs);
builder.add(condition);
}
@ -1356,13 +1356,14 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
VariableSpecifications boundNames,
Operations operations,
WhereClause where,
Conditions conditions)
Conditions conditions,
Object owner)
{
if (where.containsCustomExpressions())
throw new InvalidRequestException(CUSTOM_EXPRESSIONS_NOT_ALLOWED);
boolean applyOnlyToStaticColumns = appliesOnlyToStaticColumns(operations, conditions);
return new StatementRestrictions(state, type, metadata, IndexHints.NONE, where, boundNames, Collections.emptyList(), applyOnlyToStaticColumns, false, false);
return new StatementRestrictions(state, type, metadata, IndexHints.NONE, where, boundNames, Collections.emptyList(), owner, applyOnlyToStaticColumns, false, false);
}
public List<ColumnCondition.Raw> getConditions()

View File

@ -241,7 +241,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
@Override
public short[] getPartitionKeyBindVariableIndexes()
{
return bindVariables.getPartitionKeyBindVariableIndexes(table);
return bindVariables.getPartitionKeyBindVariableIndexes(table, table);
}
@Override
@ -1468,7 +1468,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
private List<Ordering> getOrderings(TableMetadata table)
{
return parameters.orderings.stream()
.map(o -> o.bind(table, bindVariables))
.map(o -> o.bind(table, bindVariables, table))
.collect(Collectors.toList());
}
@ -1498,6 +1498,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
whereClause,
boundNames,
orderings,
metadata,
selectsOnlyStaticColumns,
parameters.allowFiltering || !requiresAllowFilteringIfNotSpecified(metadata, true),
forView);
@ -1511,7 +1512,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement,
return null;
Term prepLimit = limit.prepare(keyspace, limitReceiver);
prepLimit.collectMarkerSpecification(boundNames);
prepLimit.collectMarkerSpecification(boundNames, null);
return prepLimit;
}

View File

@ -204,6 +204,25 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
return bindVariables.getImmutableBindVariables();
}
@Override
public short[] getPartitionKeyBindVariableIndexes()
{
if (returningSelect != null)
{
short[] result = returningSelect.select.getPartitionKeyBindVariableIndexes();
if (result != null)
return result;
}
for (ModificationStatement stmt : updates)
{
short[] result = stmt.getPartitionKeyBindVariableIndexes();
if (result != null)
return result;
}
return null;
}
@Override
public void authorize(ClientState state)
{
@ -733,6 +752,7 @@ public class TransactionStatement implements CQLStatement.CompositeCQLStatement,
if (select != null || returning != null)
checkTrue(select != null ^ returning != null, "Cannot specify both a full SELECT and a SELECT w/ LET references.");
bindVariables.setSaveTargetOwners(true);
List<NamedSelect> preparedAssignments = new ArrayList<>(assignments.size());
Map<Integer, RowDataReference.ReferenceSource> refSources = new HashMap<>();
Set<String> selectNames = new HashSet<>();

View File

@ -220,7 +220,7 @@ public class UpdateStatement extends ModificationStatement
else
{
Operation operation = new Operation.SetValue(value).prepare(metadata, def, !conditions.isEmpty());
operation.collectMarkerSpecification(bindVariables);
operation.collectMarkerSpecification(bindVariables, attrs);
operations.add(operation, metadata);
}
}
@ -234,6 +234,7 @@ public class UpdateStatement extends ModificationStatement
whereClause.build(),
bindVariables,
Collections.emptyList(),
attrs,
applyOnlyToStaticColumns,
false,
false);
@ -295,7 +296,7 @@ public class UpdateStatement extends ModificationStatement
else
{
Operation operation = new Operation.SetValue(raw).prepare(metadata, def, !conditions.isEmpty());
operation.collectMarkerSpecification(bindVariables);
operation.collectMarkerSpecification(bindVariables, attrs);
operations.add(operation, metadata);
}
}
@ -309,6 +310,7 @@ public class UpdateStatement extends ModificationStatement
whereClause.build(),
bindVariables,
Collections.emptyList(),
attrs,
applyOnlyToStaticColumns,
false,
false);
@ -417,7 +419,7 @@ public class UpdateStatement extends ModificationStatement
ColumnMetadata def = metadata.getExistingColumn(entry.left);
checkFalse(def.isPrimaryKeyColumn(), UPDATING_PRIMARY_KEY_MESSAGE, def.name);
Operation operation = entry.right.prepare(metadata, def, !conditions.isEmpty() || isForTxn);
operation.collectMarkerSpecification(bindVariables);
operation.collectMarkerSpecification(bindVariables, attrs);
operations.add(operation, metadata);
}
@ -435,7 +437,8 @@ public class UpdateStatement extends ModificationStatement
bindVariables,
operations,
whereClause,
conditions);
conditions,
attrs);
return new UpdateStatement(type,
bindVariables,

View File

@ -310,6 +310,7 @@ public final class CreateViewStatement extends AlterSchemaStatement
whereClause,
VariableSpecifications.empty(),
Collections.emptyList(),
null,
false,
false,
true,

View File

@ -54,9 +54,9 @@ public final class InMarker extends Terms.NonTerminals
public void addFunctionsTo(List<Function> functions) {}
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames)
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
{
boundNames.add(bindIndex, receiver);
boundNames.add(bindIndex, receiver, owner);
}
@Override

View File

@ -343,10 +343,10 @@ public abstract class Lists
}
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames)
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
{
super.collectMarkerSpecification(boundNames);
idx.collectMarkerSpecification(boundNames);
super.collectMarkerSpecification(boundNames, owner);
idx.collectMarkerSpecification(boundNames, owner);
}
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException

View File

@ -276,10 +276,10 @@ public final class Maps
}
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames)
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
{
super.collectMarkerSpecification(boundNames);
k.collectMarkerSpecification(boundNames);
super.collectMarkerSpecification(boundNames, owner);
k.collectMarkerSpecification(boundNames, owner);
}
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException

View File

@ -55,9 +55,9 @@ public final class Marker extends Term.NonTerminal
}
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames)
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
{
boundNames.add(bindIndex, receiver);
boundNames.add(bindIndex, receiver, owner);
}
@Override

View File

@ -116,12 +116,12 @@ public final class MultiElements
}
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames)
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
{
if (type.supportsElementBindMarkers())
{
for (int i = 0, m = elements.size(); i < m; i++)
elements.get(i).collectMarkerSpecification(boundNames);
elements.get(i).collectMarkerSpecification(boundNames, owner);
}
}

View File

@ -68,9 +68,10 @@ public interface Term
* This is obviously a no-op if the term is Terminal.
*
* @param boundNames the variables specification where to collect the
* bind variables of this term in.
* bind variables of this term in.
* @param owner
*/
void collectMarkerSpecification(VariableSpecifications boundNames);
void collectMarkerSpecification(VariableSpecifications boundNames, Object owner);
/**
* Bind the values in this term to the values contained in the {@code options}.
@ -246,7 +247,7 @@ public interface Term
abstract class Terminal implements Term
{
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames) {}
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner) {}
@Override
public Terminal bind(QueryOptions options) { return this; }

View File

@ -95,9 +95,10 @@ public interface Terms
* This is obviously a no-op if the terms are Terminals.
*
* @param boundNames the variables specification where to collect the
* bind variables of the terms in.
* bind variables of the terms in.
* @param owner
*/
void collectMarkerSpecification(VariableSpecifications boundNames);
void collectMarkerSpecification(VariableSpecifications boundNames, Object owner);
/**
* Bind the values in these terms to the values contained in {@code options}.
@ -453,7 +454,7 @@ public interface Terms
}
};
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames) {}
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner) {}
@Override
public final Terminals bind(QueryOptions options)
@ -621,9 +622,9 @@ public interface Terms
}
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames)
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
{
term.collectMarkerSpecification(boundNames);
term.collectMarkerSpecification(boundNames, owner);
}
@Override
@ -687,12 +688,12 @@ public interface Terms
}
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames)
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
{
for (int i = 0, m = terms.size(); i < m; i++)
{
Term term = terms.get(i);
term.collectMarkerSpecification(boundNames);
term.collectMarkerSpecification(boundNames, owner);
}
}

View File

@ -93,7 +93,7 @@ public class ConditionStatement
{
// In the IS NULL/IS NOT NULL case, the reference will always be on the LHS
RowDataReference reference = ((RowDataReference.Raw) lhs).prepareAsReceiver();
reference.collectMarkerSpecification(bindVariables);
reference.collectMarkerSpecification(bindVariables, null);
return new ConditionStatement(reference, kind, null, false);
}
@ -124,8 +124,8 @@ public class ConditionStatement
throw new IllegalStateException("Either the left-hand or right-hand side must be a reference!");
}
reference.collectMarkerSpecification(bindVariables);
value.collectMarkerSpecification(bindVariables);
reference.collectMarkerSpecification(bindVariables, null);
value.collectMarkerSpecification(bindVariables, null);
return new ConditionStatement(reference, kind, value, reversed);
}
}

View File

@ -77,10 +77,10 @@ public class RowDataReference extends Term.NonTerminal
}
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames)
public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
{
if (elementPath != null)
elementPath.collectMarkerSpecification(boundNames);
elementPath.collectMarkerSpecification(boundNames, owner);
}
@Override

View File

@ -74,7 +74,6 @@ import accord.local.DurableBefore;
import accord.local.MaxConflicts;
import accord.local.Node;
import accord.local.PreLoadContext;
import accord.local.RejectBefore;
import accord.local.SafeCommand;
import accord.local.SafeCommandStore;
import accord.local.StoreParticipants;
@ -107,7 +106,7 @@ import accord.utils.async.AsyncChains;
import accord.utils.async.AsyncResult;
import accord.utils.async.AsyncResults;
import org.apache.cassandra.config.AccordSpec.JournalSpec.ReplayMode;
import org.apache.cassandra.config.AccordConfig.JournalConfig.ReplayMode;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.statements.schema.CreateTableStatement;
import org.apache.cassandra.db.ColumnFamilyStore;
@ -211,7 +210,6 @@ public class AccordDebugKeyspace extends VirtualKeyspace
public static final String NODE_OPS = "node_ops";
public static final String PROGRESS_LOG = "progress_log";
public static final String REDUNDANT_BEFORE = "redundant_before";
public static final String REJECT_BEFORE = "reject_before";
public static final String TXN = "txn";
public static final String TXN_CACHE = "txn_cache";
public static final String TXN_BLOCKED_BY = "txn_blocked_by";
@ -252,7 +250,6 @@ public class AccordDebugKeyspace extends VirtualKeyspace
new NodeOpsTable(),
new ProgressLogTable(),
new RedundantBeforeTable(),
new RejectBeforeTable(),
new TxnBlockedByTable(),
new TxnTable(),
new TxnCacheTable(),
@ -819,7 +816,9 @@ public class AccordDebugKeyspace extends VirtualKeyspace
" token_start 'TokenUtf8Type',\n" +
" table_id text,\n" +
" token_end 'TokenUtf8Type',\n" +
" timestamp text,\n" +
" any text,\n" +
" write text,\n" +
" reject text,\n" +
" PRIMARY KEY (command_store_id, token_start)" +
')', Int32Type.instance), FAIL, ASC);
}
@ -843,7 +842,9 @@ public class AccordDebugKeyspace extends VirtualKeyspace
.lazyCollect(columns -> {
columns.add("token_end", entry.end(), AccordDebugKeyspace::printToken)
.add("table_id", tableIdStr)
.add("timestamp", entry, TO_STRING);
.add("any", entry.any, TO_STRING)
.add("write", entry.write, TO_STRING)
.add("reject", entry.reject, TO_STRING);
});
return rows;
}, rows
@ -1084,47 +1085,6 @@ public class AccordDebugKeyspace extends VirtualKeyspace
}
}
public static final class RejectBeforeTable extends AbstractLazyVirtualTable
{
private RejectBeforeTable()
{
super(parse(VIRTUAL_ACCORD_DEBUG, REJECT_BEFORE,
"Accord per-CommandStore RejectBefore State",
"CREATE TABLE %s (\n" +
" command_store_id int,\n" +
" token_start 'TokenUtf8Type',\n" +
" table_id text,\n" +
" token_end 'TokenUtf8Type',\n" +
" timestamp text,\n" +
" PRIMARY KEY (command_store_id, token_start)" +
')', UTF8Type.instance), FAIL, ASC);
}
@Override
protected void collect(PartitionsCollector collector)
{
CommandStores commandStores = AccordService.unsafeInstance().node().commandStores();
for (CommandStore commandStore : commandStores.all())
{
RejectBefore rejectBefore = commandStore.unsafeGetRejectBefore();
if (rejectBefore == null)
continue;
collector.partition(commandStore.id()).collect(rows -> {
TableId tableId = ((AccordCommandStore)commandStore).tableId();
String tableIdStr = tableId.toString();
rejectBefore.foldlWithBounds((timestamp, rs, start, end) -> {
rs.add(printToken(start))
.lazyCollect(columns -> columns.add("table_id", tableIdStr)
.add("token_end", end, AccordDebugKeyspace::printToken)
.add("timestamp", timestamp, AccordDebugKeyspace::toStringOrNull));
return rs;
}, rows, ignore -> false);
});
}
}
}
/**
* Usage:
* collect N events (may be more than N messages)
@ -1259,6 +1219,7 @@ public class AccordDebugKeyspace extends VirtualKeyspace
" event text,\n" +
" at_micros bigint,\n" +
" command_store_id int,\n" +
" remote_node_id int,\n" +
" message text,\n" +
" PRIMARY KEY (txn_id, id_micros, event, at_micros)" +
')', TxnIdUtf8Type.instance), FAIL, UNSORTED, UNSORTED);
@ -1302,6 +1263,7 @@ public class AccordDebugKeyspace extends VirtualKeyspace
@Override
public void collect(PartitionsCollector collector)
{
int nodeId = AccordService.unsafeInstance().nodeId().id;
tracing().forEach(id -> true, (txnId, events) -> {
events.forEach(e -> {
if (e.messages().isEmpty())
@ -1318,6 +1280,8 @@ public class AccordDebugKeyspace extends VirtualKeyspace
.eagerCollect(columns -> {
columns.add("command_store_id", m.commandStoreId)
.add("message", m.message);
if (m.nodeId >= 0)
columns.add("remote_node_id", m.nodeId);
});
});
}

View File

@ -18,6 +18,7 @@
package org.apache.cassandra.journal;
import java.nio.ByteBuffer;
import java.util.concurrent.RejectedExecutionException;
import accord.utils.Invariants;
@ -44,8 +45,12 @@ public abstract class Segment<K, V> implements SelfRefCounted<Segment<K, V>>, Co
public final void tidy()
{
if (executor != null) executor.execute(this);
else onUnreferenced();
if (executor != null)
{
try { executor.execute(this); return; }
catch (RejectedExecutionException rje) { /* fallthrough and call directly */ }
}
onUnreferenced();
}
}

View File

@ -34,6 +34,7 @@ import accord.local.Node;
import accord.primitives.Ballot;
import accord.primitives.Deps;
import accord.primitives.TxnId;
import accord.utils.UnhandledEnum;
import org.apache.cassandra.service.accord.api.AccordTimeService;
import org.apache.cassandra.tracing.Tracing;
@ -248,10 +249,14 @@ public class AccordCoordinatorMetrics
{
switch (path)
{
default: throw new UnhandledEnum(path);
case EPHEMERAL: metrics.ephemeral.mark(); break;
case FAST: metrics.fastPaths.mark(); break;
case MEDIUM: metrics.mediumPaths.mark(); break;
case SLOW: metrics.slowPaths.mark(); break;
case BACKLOG:
case RECOVER:
break;
}
}
}

View File

@ -39,6 +39,7 @@ public class AccordExecutorMetrics
public final ShardedDecayingHistogram elapsedPreparingToRun = HISTOGRAMS.newHistogram(TimeUnit.SECONDS.toNanos(1L));
public final ShardedDecayingHistogram elapsedWaitingToRun = HISTOGRAMS.newHistogram(TimeUnit.SECONDS.toNanos(1L));
public final ShardedDecayingHistogram elapsedRunning = HISTOGRAMS.newHistogram(TimeUnit.SECONDS.toNanos(1L));
public final ShardedDecayingHistogram elapsed = HISTOGRAMS.newHistogram(TimeUnit.SECONDS.toNanos(1L));
// number of keys involved
public final ShardedDecayingHistogram keys = HISTOGRAMS.newHistogram(1 << 12);

View File

@ -167,6 +167,13 @@ public class LogLinearDecayingHistograms
Invariants.require(buffer.histograms == LogLinearDecayingHistograms.this);
buffer.add(histogramIndex, value);
}
public void clear()
{
flush();
totalCount = 0;
Arrays.fill(buckets, 0);
}
}
private static final long ANTI_DECAY_REFRESH_RATE = TimeUnit.SECONDS.toNanos(1L);

View File

@ -317,4 +317,10 @@ public class LogLinearHistogram
snapshot.totalCount += totalCount;
snapshot.cumulative = null;
}
public void clear()
{
totalCount = 0;
Arrays.fill(buckets, 0);
}
}

View File

@ -76,6 +76,41 @@ public class ShardedDecayingHistograms
return shard.histograms.get(histogramIndex);
}
public LogLinearSnapshot refresh()
{
synchronized (ShardedDecayingHistograms.this)
{
long now = Clock.Global.currentTimeMillis();
List<LogLinearSnapshot> snapshot = new ArrayList<>(ShardedDecayingHistograms.this.snapshot);
if (snapshot.size() <= histogramIndex)
return ShardedDecayingHistograms.this.refresh(now).get(histogramIndex);
LogLinearSnapshot result = LogLinearSnapshot.emptyForMax(initialMaxValue);
snapshot.set(histogramIndex, result);
for (DecayingHistogramsShard shard : shards)
shard.updateSnapshot(histogramIndex, result, now);
ShardedDecayingHistograms.this.snapshot = snapshot;
// don't update snapshotAt, since we have only refreshed one histogram
return result;
}
}
public void clear()
{
for (DecayingHistogramsShard shard : shards)
{
shard.lock.lock();
try
{
shard.histograms.get(histogramIndex).clear();
}
finally
{
shard.lock.unlock();
}
}
}
@Override
public boolean isCumulative()
{
@ -108,6 +143,19 @@ public class ShardedDecayingHistograms
}
}
private void updateSnapshot(int histogramIndex, LogLinearSnapshot update, long at)
{
lock.lock();
try
{
histograms.get(histogramIndex).updateSnapshot(update, at);
}
finally
{
lock.unlock();
}
}
public LogLinearDecayingHistograms unsafeGetInternal()
{
return histograms;

View File

@ -139,4 +139,20 @@ public class ShardedHistogram extends OverrideHistogram
{
return isCumulative;
}
public void clear()
{
for (HistogramShard shard : shards)
{
shard.lock.lock();
try
{
shard.histogram.clear();
}
finally
{
shard.lock.unlock();
}
}
}
}

View File

@ -33,6 +33,8 @@ import java.util.stream.Collectors;
import org.agrona.collections.Object2LongHashMap;
import accord.api.Tracing;
import org.apache.cassandra.concurrent.ExecutorLocals;
import org.apache.cassandra.concurrent.Interruptible;
import org.apache.cassandra.config.CassandraRelevantProperties;
@ -258,6 +260,9 @@ public class ArtificialLatency extends ExecutorLocals.Impl
long now = nanoTime();
while (null != (delayed = out.peek()) && delayed.deadline <= now)
{
Tracing tracing = (Tracing)delayed.message.params().get(ParamType.ACCORD_TRACING);
if (tracing != null)
tracing.trace(null, (delayed.message.verb().isResponse() ? "Reply" : "Request") + " delayed to " + ClusterMetadata.current().directory.peerId(delayed.to));
delayed.sink.accept(delayed.message, delayed.to, delayed.type);
out.poll();
}

View File

@ -35,6 +35,7 @@ import org.apache.cassandra.net.FrameDecoder.CorruptFrame;
import org.apache.cassandra.net.FrameDecoder.IntactFrame;
import org.apache.cassandra.net.Message.Header;
import org.apache.cassandra.net.ResourceLimits.Limit;
import org.apache.cassandra.service.accord.debug.AccordRemoteTracing;
import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.tracing.TraceState;
import org.apache.cassandra.tracing.Tracing;
@ -422,6 +423,7 @@ public class InboundMessageHandler extends AbstractMessageHandler
TraceState state = Tracing.instance.initializeFromMessage(header);
if (state != null) state.trace("{} message received from {}", header.verb, header.from);
AccordRemoteTracing.traceOffWire(header);
callbacks.onDispatched(task.size(), header);
header.verb.stage.execute(ExecutorLocals.create(state), task);

View File

@ -61,7 +61,6 @@ import static org.apache.cassandra.net.MessageFlag.ARTIFICIAL_LATENCY;
import static org.apache.cassandra.net.MessagingService.VERSION_40;
import static org.apache.cassandra.net.MessagingService.VERSION_50;
import static org.apache.cassandra.net.MessagingService.VERSION_60;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
import static org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort;
import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime;
import static org.apache.cassandra.utils.vint.VIntCoding.computeUnsignedVIntSize;
@ -154,6 +153,12 @@ public class Message<T> implements ResponseContext
return header.hasFlag(flag);
}
@Override
public Map<ParamType, Object> params()
{
return header.params();
}
/** For how long the message has lived. */
public long elapsedSinceCreated(TimeUnit units)
{
@ -223,8 +228,6 @@ public class Message<T> implements ResponseContext
*/
public static <T> Message<T> out(Verb verb, T payload)
{
assert !verb.isResponse() : verb;
return outWithParam(nextId(), verb, payload, null, null);
}
@ -240,7 +243,6 @@ public class Message<T> implements ResponseContext
public static <T> Message<T> out(Verb verb, T payload, boolean isUrgent)
{
assert !verb.isResponse();
if (isUrgent)
return outWithFlag(verb, payload, MessageFlag.URGENT);
else
@ -249,7 +251,6 @@ public class Message<T> implements ResponseContext
public static <T> Message<T> outWithFlag(Verb verb, T payload, MessageFlag flag)
{
assert !verb.isResponse();
return outWithParam(nextId(), verb, 0, payload, flag.addTo(0), null, null);
}
@ -260,24 +261,7 @@ public class Message<T> implements ResponseContext
public static <T> Message<T> outWithFlags(Verb verb, T payload, Dispatcher.RequestTime requestTime, int encodedFlags)
{
assert !verb.isResponse();
if (ArtificialLatency.isEligibleForArtificialLatency())
encodedFlags = ARTIFICIAL_LATENCY.addTo(encodedFlags);
return newWithFlags(nextId(), verb, payload, requestTime.startedAtNanos(), requestTime.computeDeadline(verb.expiresAfterNanos()), encodedFlags);
}
private static <T> Message<T> newWithFlags(long id, Verb verb, T payload, long startedAtNanos, long expiresAtNanos, int encodedFlags)
{
return new Message<>(new Header(id,
epochSupplier.get(),
verb,
getBroadcastAddressAndPort(),
startedAtNanos,
expiresAtNanos,
encodedFlags,
buildParams(null, null)),
payload);
return outWithParam(nextId(), verb, requestTime.computeDeadline(verb.expiresAfterNanos()), payload, encodedFlags, null, null);
}
@VisibleForTesting
@ -293,10 +277,11 @@ public class Message<T> implements ResponseContext
private static <T> Message<T> outWithParam(long id, Verb verb, long expiresAtNanos, T payload, int flags, ParamType paramType, Object paramValue)
{
return withParam(getBroadcastAddressAndPort(), id, verb, expiresAtNanos, payload, flags, paramType, paramValue);
assert !verb.isManagedResponse();
return withParam(id, verb, expiresAtNanos, payload, flags, paramType, paramValue);
}
private static <T> Message<T> withParam(InetAddressAndPort from, long id, Verb verb, long expiresAtNanos, T payload, int flags, ParamType paramType, Object paramValue)
private static <T> Message<T> withParam(long id, Verb verb, long expiresAtNanos, T payload, int flags, ParamType paramType, Object paramValue)
{
if (payload == null)
throw new IllegalArgumentException();
@ -307,13 +292,14 @@ public class Message<T> implements ResponseContext
if (ArtificialLatency.isEligibleForArtificialLatency())
flags = ARTIFICIAL_LATENCY.addTo(flags);
InetAddressAndPort from = getBroadcastAddressAndPort();
return new Message<>(new Header(id, epochSupplier.get(), verb, from, createdAtNanos, expiresAtNanos, flags, buildParams(paramType, paramValue)), payload);
}
public static <T> Message<T> internalResponse(Verb verb, T payload)
{
assert verb.isResponse();
return outWithParam(0, verb, payload, null, null);
assert verb.isManagedResponse();
return withParam(0, verb, 0, payload, 0, null, null);
}
/**
@ -323,7 +309,7 @@ public class Message<T> implements ResponseContext
@VisibleForTesting
public static <T> Message<T> remoteResponse(InetAddressAndPort from, Verb verb, T payload)
{
assert verb.isResponse();
assert verb.isManagedResponse();
long createdAtNanos = approxTime.now();
long expiresAtNanos = verb.expiresAtNanos(createdAtNanos);
return new Message<>(new Header(0, epochSupplier.get(), verb, from, createdAtNanos, expiresAtNanos, 0, NO_PARAMS), payload);
@ -336,7 +322,7 @@ public class Message<T> implements ResponseContext
@VisibleForTesting
public static <T> Message<T> remoteResponseForTests(long id, InetAddressAndPort from, Verb verb, T payload)
{
assert verb.isResponse();
assert verb.isManagedResponse();
long createdAtNanos = approxTime.now();
long expiresAtNanos = verb.expiresAtNanos(createdAtNanos);
return new Message<>(new Header(id, epochSupplier.get(), verb, from, createdAtNanos, expiresAtNanos, 0, NO_PARAMS), payload);
@ -368,7 +354,7 @@ public class Message<T> implements ResponseContext
public static <T> Message<T> responseWith(T payload, ResponseContext respondTo)
{
int encodedFlags = respondTo.hasFlag(ARTIFICIAL_LATENCY) ? ARTIFICIAL_LATENCY.addTo(0) : 0;
return newWithFlags(respondTo.id(), respondTo.verb().responseVerb, payload, nanoTime(), respondTo.expiresAtNanos(), encodedFlags);
return withParam(respondTo.id(), respondTo.verb().responseVerb, respondTo.expiresAtNanos(), payload, encodedFlags, null, null);
}
/** Builds a response Message with no payload, and all the right fields inferred from request Message */
@ -395,7 +381,7 @@ public class Message<T> implements ResponseContext
static Message<RequestFailure> failureResponse(long id, long expiresAtNanos, RequestFailure reason)
{
return outWithParam(id, Verb.FAILURE_RSP, expiresAtNanos, reason, null, null);
return withParam(id, Verb.FAILURE_RSP, expiresAtNanos, reason, 0, null, null);
}
public <V> Message<V> withPayload(V newPayload)
@ -603,11 +589,6 @@ public class Message<T> implements ResponseContext
return MessageFlag.TRACK_WARNINGS.isIn(flags);
}
boolean isFinal()
{
return !MessageFlag.NOT_FINAL.isIn(flags);
}
boolean permitsArtificialLatency()
{
return ARTIFICIAL_LATENCY.isIn(flags);
@ -763,7 +744,7 @@ public class Message<T> implements ResponseContext
this.verb = verb;
if (expiresAtNanos == 0 && verb != null && createdAtNanos != 0)
expiresAtNanos = verb.expiresAtNanos(createdAtNanos);
if (!this.verb.isResponse() && from == null) // default to sending from self if we're a request verb
if (!this.verb.isManagedResponse() && from == null) // default to sending from self if we're a request verb
from = getBroadcastAddressAndPort();
return this;
}

View File

@ -32,9 +32,7 @@ public enum MessageFlag
TRACK_WARNINGS (2),
/** whether this message should be sent on an URGENT channel despite its Verb default priority */
URGENT (3),
/** Allow a single callback to receive multiple responses until a final response is received **/
NOT_FINAL (4),
ARTIFICIAL_LATENCY (5)
ARTIFICIAL_LATENCY (4)
;
private final int id;

View File

@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.io.util.DataOutputBufferFixed;
import org.apache.cassandra.net.OutboundConnectionInitiator.Result.MessagingSuccess;
import org.apache.cassandra.service.accord.debug.AccordRemoteTracing;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.FBUtilities;
@ -829,6 +830,7 @@ public class OutboundConnection
}
Tracing.instance.traceOutgoingMessage(next, messageSize, settings.connectTo);
AccordRemoteTracing.traceOnWire(next.header, settings.connectTo);
Message.serializer.serialize(next, out, messagingVersion);
if (sending.length() != sendingBytes + messageSize)

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.net;
import javax.annotation.Nullable;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.service.accord.debug.AccordRemoteTracing;
import org.apache.cassandra.service.writes.thresholds.WriteThresholdMapSerializer;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.Int32Serializer;
@ -57,7 +58,9 @@ public enum ParamType
TOO_MANY_REFERENCED_INDEXES_WARN (16, Int32Serializer.serializer),
TOO_MANY_REFERENCED_INDEXES_FAIL (17, Int32Serializer.serializer),
WRITE_SIZE_WARN (18, WriteThresholdMapSerializer.serializer),
WRITE_TOMBSTONE_WARN (19, WriteThresholdMapSerializer.serializer);
WRITE_TOMBSTONE_WARN (19, WriteThresholdMapSerializer.serializer),
ACCORD_TRACING (20, AccordRemoteTracing.tracingSerializer),
;
final int id;
final IVersionedSerializer serializer;

View File

@ -17,9 +17,18 @@
*/
package org.apache.cassandra.net;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import accord.api.MessageSink;
import accord.local.Node;
import accord.messages.Reply;
import accord.messages.ReplyContext;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.service.accord.AccordMessageSink;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
public interface ResponseContext extends ReplyContext
{
@ -28,4 +37,17 @@ public interface ResponseContext extends ReplyContext
Verb verb();
long expiresAtNanos();
boolean hasFlag(MessageFlag flag);
Map<ParamType, Object> params();
@Override
default long expiresAt(TimeUnit units)
{
return units.convert(expiresAtNanos(), NANOSECONDS);
}
@Override
default void reply(Node.Id to, MessageSink sink, Reply success, Throwable failure)
{
((AccordMessageSink)sink).reply(to, this, success, failure);
}
}

View File

@ -61,11 +61,7 @@ class ResponseVerbHandler implements IVerbHandler
@Override
public void doVerb(Message message)
{
RequestCallbacks.CallbackInfo callbackInfo;
if (message.header.isFinal())
callbackInfo = MessagingService.instance().callbacks.remove(message.id(), message.from());
else
callbackInfo = MessagingService.instance().callbacks.get(message.id(), message.from());
RequestCallbacks.CallbackInfo callbackInfo = MessagingService.instance().callbacks.remove(message.id(), message.from());
if (callbackInfo == null)
{
String msg = "Callback already removed for {} (from {})";

View File

@ -84,6 +84,7 @@ import org.apache.cassandra.schema.SchemaVersionVerbHandler;
import org.apache.cassandra.service.EchoVerbHandler;
import org.apache.cassandra.service.SnapshotVerbHandler;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.debug.AccordRemoteTracing;
import org.apache.cassandra.service.accord.interop.AccordInteropApply;
import org.apache.cassandra.service.accord.interop.AccordInteropRead;
import org.apache.cassandra.service.accord.interop.AccordInteropReadRepair;
@ -104,6 +105,7 @@ import org.apache.cassandra.service.accord.serializers.LatestDepsSerializers;
import org.apache.cassandra.service.accord.serializers.PreacceptSerializers;
import org.apache.cassandra.service.accord.serializers.ReadDataSerializer;
import org.apache.cassandra.service.accord.serializers.RecoverySerializers;
import org.apache.cassandra.service.accord.serializers.RemoteSuccessSerializers;
import org.apache.cassandra.service.accord.serializers.SetDurableSerializers;
import org.apache.cassandra.service.accord.serializers.SimpleReplySerializer;
import org.apache.cassandra.service.accord.serializers.Version;
@ -373,9 +375,11 @@ public enum Verb
ACCORD_INTEROP_APPLY_REQ (166, P2, writeTimeout, IMMEDIATE, () -> accordEmbedded(AccordInteropApply.serializer), AccordService::requestHandlerOrNoop, ACCORD_APPLY_RSP),
ACCORD_FETCH_WATERMARKS_RSP (167, P0, shortTimeout, FETCH_METADATA, () -> accordEmbedded(WatermarkCollector.serializer), RESPONSE_HANDLER),
// NoPayload can not be prefixed with accord version as it is special cased in C* messaging
ACCORD_FETCH_WATERMARKS_REQ (168, P0, shortTimeout, FETCH_METADATA, () -> NoPayload.serializer, AccordService::watermarkHandlerOrNoop, ACCORD_FETCH_WATERMARKS_RSP),
ACCORD_FETCH_WATERMARKS_REQ (168, P0, shortTimeout, FETCH_METADATA, () -> NoPayload.serializer, AccordService::watermarkHandlerOrNoop, ACCORD_FETCH_WATERMARKS_RSP),
ACCORD_FETCH_TOPOLOGY_RSP (169, P0, shortTimeout, FETCH_METADATA, () -> accordEmbedded(FetchTopologies.responseSerializer), RESPONSE_HANDLER),
ACCORD_FETCH_TOPOLOGY_REQ (170, P0, shortTimeout, FETCH_METADATA, () -> accordEmbedded(FetchTopologies.serializer), () -> FetchTopologies.handler, ACCORD_FETCH_TOPOLOGY_RSP),
ACCORD_REMOTE_SUCCESS_REQ (173, P0, shortTimeout, IMMEDIATE, () -> accordEmbedded(RemoteSuccessSerializers.remoteSuccess), AccordService::requestHandlerOrNoop),
ACCORD_REMOTE_TRACE(174, P0, shortTimeout, IMMEDIATE, () -> AccordRemoteTracing.traceSerializer, () -> AccordRemoteTracing.traceMessageHandler),
DICTIONARY_UPDATE_RSP (171, P1, rpcTimeout, REQUEST_RESPONSE, () -> NoPayload.serializer, RESPONSE_HANDLER ),
DICTIONARY_UPDATE_REQ (172, P1, rpcTimeout, MISC, () -> CompressionDictionaryUpdateMessage.serializer, () -> CompressionDictionaryUpdateVerbHandler.instance, DICTIONARY_UPDATE_RSP ),
@ -440,6 +444,7 @@ public enum Verb
private final Supplier<? extends IVerbHandler<?>> handler;
public final Verb responseVerb;
private final boolean isResponse;
private final ToLongFunction<TimeUnit> expiration;
@ -488,6 +493,8 @@ public enum Verb
this.responseVerb = responseVerb;
this.expiration = expiration;
this.kind = kind;
// this is a little hacky, but reduces the number of parameters up top
this.isResponse = name().endsWith("_RSP") || handler == RESPONSE_HANDLER;
}
public <In, Out> IVersionedAsymmetricSerializer<In, Out> serializer()
@ -516,9 +523,14 @@ public enum Verb
}
// this is a little hacky, but reduces the number of parameters up top
public boolean isManagedResponse()
{
return handler == RESPONSE_HANDLER;
}
public boolean isResponse()
{
return handler.get() == ResponseVerbHandler.instance;
return isResponse;
}
@VisibleForTesting

View File

@ -61,7 +61,7 @@ import org.apache.cassandra.batchlog.Batch;
import org.apache.cassandra.batchlog.BatchlogManager;
import org.apache.cassandra.concurrent.DebuggableTask.RunnableDebuggableTask;
import org.apache.cassandra.concurrent.Stage;
import org.apache.cassandra.config.AccordSpec;
import org.apache.cassandra.config.AccordConfig;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.DatabaseDescriptor;
@ -1355,7 +1355,7 @@ public class StorageProxy implements StorageProxyMBean
private static void checkMixedTimeSourceHandling()
{
AccordSpec.MixedTimeSourceHandling handling = DatabaseDescriptor.getAccord().mixedTimeSourceHandling;
AccordConfig.MixedTimeSourceHandling handling = DatabaseDescriptor.getAccord().mixedTimeSourceHandling;
switch (handling)
{
case log:
@ -1363,7 +1363,7 @@ public class StorageProxy implements StorageProxyMBean
{
ClientWarn.instance.warn(UNSAFE_MIXED_MUTATIONS_MSG);
logger.warn(UNSAFE_MIXED_MUTATIONS_MSG);
if (handling == AccordSpec.MixedTimeSourceHandling.reject)
if (handling == AccordConfig.MixedTimeSourceHandling.reject)
throw new InvalidRequestException(UNSAFE_MIXED_MUTATIONS_MSG);
}
break;

View File

@ -67,6 +67,7 @@ import org.apache.cassandra.metrics.ShardedHitRate;
import org.apache.cassandra.service.accord.AccordCache.Adapter.Shrink;
import org.apache.cassandra.service.accord.AccordCacheEntry.LoadExecutor;
import org.apache.cassandra.service.accord.AccordCacheEntry.Status;
import org.apache.cassandra.service.accord.AccordSafeCommandsForKey.CommandsForKeyCacheEntry;
import org.apache.cassandra.service.accord.events.CacheEvents;
import org.apache.cassandra.service.accord.journal.CommandChangeWriter;
import org.apache.cassandra.service.accord.journal.CommandChanges;
@ -1193,7 +1194,7 @@ public class AccordCache implements CacheSize
return null;
TxnId last = value.size() == 0 ? null : value.get(value.size() - 1);
TxnId minUndecided = value.minUndecided();
TxnId minUndecided = value.minUndecidedManaged();
int lastSize = (int) CommandSerializers.txnId.serializedSize(last);
int minUndecidedSize = (int) CommandSerializers.txnId.serializedSize(minUndecided);
ByteBuffer result = Serialize.toBytesWithoutKey(lastSize + minUndecidedSize, value.maximalPrune());
@ -1249,11 +1250,19 @@ public class AccordCache implements CacheSize
{
return RoutingKey::compareAsRoutingKey;
}
@Override
public AccordCacheEntry<RoutingKey, CommandsForKey> newEntry(RoutingKey key, Type<RoutingKey, CommandsForKey, ?>.Instance owner)
{
CommandsForKeyCacheEntry entry = new CommandsForKeyCacheEntry(key, owner);
entry.readyToLoad();
return entry;
}
}
public static class CommandAdapter implements Adapter<TxnId, Command, AccordSafeCommand>
{
private static int SHRINK_WITHOUT_LOCK = -1;
private static final int SHRINK_WITHOUT_LOCK = -1;
public static final CommandAdapter COMMAND_ADAPTER = new CommandAdapter();

View File

@ -60,6 +60,8 @@ import accord.local.CommandStores;
import accord.local.CommandSummaries;
import accord.local.MaxConflicts;
import accord.local.MaxDecidedRX;
import accord.local.MinimalCommand;
import accord.local.MinimalCommand.MinimalWithDeps;
import accord.local.NodeCommandStoreService;
import accord.local.PreLoadContext;
import accord.local.PreLoadContext.Empty;
@ -67,7 +69,6 @@ import accord.local.RedundantBefore;
import accord.local.RedundantBefore.Bounds;
import accord.local.RedundantStatus.Property;
import accord.local.RedundantStatus.SomeStatus;
import accord.local.RejectBefore;
import accord.local.SafeCommandStore;
import accord.local.cfk.CommandsForKey;
import accord.primitives.PartialTxn;
@ -87,8 +88,8 @@ import accord.utils.async.AsyncChains;
import accord.utils.async.AsyncResult;
import accord.utils.async.AsyncResults.CountingResult;
import org.apache.cassandra.config.AccordSpec;
import org.apache.cassandra.config.AccordSpec.JournalSpec.ReplayMode;
import org.apache.cassandra.config.AccordConfig;
import org.apache.cassandra.config.AccordConfig.JournalConfig.ReplayMode;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.io.util.File;
@ -255,7 +256,7 @@ public class AccordCommandStore extends CommandStore
this.exclusiveExecutor = sharedExecutor.executor(id);
{
AccordSpec.RangeIndexMode mode = getAccord().range_index_mode;
AccordConfig.RangeIndexMode mode = getAccord().range_index_mode;
switch (mode)
{
default: throw new UnhandledEnum(mode);
@ -388,7 +389,7 @@ public class AccordCommandStore extends CommandStore
RedundantBefore.QuickBounds bounds = safeGetRedundantBefore().get(key);
if (!Invariants.expect(bounds != null, "No RedundantBefore information found when loading key %s", key))
return cfk;
return cfk.withGcBeforeAtLeast(bounds.gcBefore, false);
return cfk.withCleanCfkBeforeAtLeast(bounds.cleanCfkBefore(), false);
}
boolean validateCommandsForKey(RoutableKey key, CommandsForKey evicting)
@ -422,6 +423,18 @@ public class AccordCommandStore extends CommandStore
return AccordTask.create(this, preLoadContext, consumer).chain();
}
@Override
public AsyncChain<Void> priorityChain(PreLoadContext preLoadContext, Consumer<? super SafeCommandStore> consumer)
{
return AccordTask.create(this, preLoadContext, consumer).priorityChain();
}
@Override
public <T> AsyncChain<T> priorityChain(PreLoadContext preLoadContext, Function<? super SafeCommandStore, T> function)
{
return AccordTask.create(this, preLoadContext, function).priorityChain();
}
@Override
public <T> AsyncChain<T> chain(Callable<T> call)
{
@ -434,6 +447,12 @@ public class AccordCommandStore extends CommandStore
taskExecutor().execute(run);
}
@Override
public boolean tryExecuteImmediately(Runnable run)
{
return taskExecutor().tryExecuteImmediately(run);
}
public AccordSafeCommandStore begin(AccordTask<?> operation, @Nullable CommandSummaries commandsForRanges)
{
require(current == null);
@ -575,12 +594,12 @@ public class AccordCommandStore extends CommandStore
return command;
}
public Command.Minimal loadMinimal(TxnId txnId)
public MinimalCommand loadMinimal(TxnId txnId)
{
return journal.loadMinimal(id, txnId, safeGetRedundantBefore(), durableBefore());
}
public Command.MinimalWithDeps loadMinimalWithDeps(TxnId txnId)
public MinimalWithDeps loadMinimalWithDeps(TxnId txnId)
{
return journal.loadMinimalWithDeps(id, txnId, safeGetRedundantBefore(), durableBefore());
}
@ -805,7 +824,6 @@ public class AccordCommandStore extends CommandStore
tmpSaveDir.createDirectoriesIfNotExists();
writeOne(new File(tmpSaveDir, "max_decidedrx"), unsafeGetMaxDecidedRX(), maxDecidedRX);
writeOne(new File(tmpSaveDir, "max_conflicts"), unsafeGetMaxConflicts(), maxConflicts);
writeOne(new File(tmpSaveDir, "reject_before"), unsafeGetRejectBefore(), rejectBefore);
writeList(new File(tmpSaveDir, "listeners"), ((DefaultLocalListeners)listeners).snapshot(), txnListener);
writeList(new File(tmpSaveDir, "progress_log"), ((DefaultProgressLog)progressLog).snapshot(), progressLogState);
rangeIndex.save(new File(tmpSaveDir, "range_index"));
@ -872,7 +890,7 @@ public class AccordCommandStore extends CommandStore
File savePoint = savePoints[savePoints.length - 1];
long segment = Long.parseLong(savePoint.name());
MaxDecidedRX mxd; MaxConflicts mxc; RejectBefore rjb;
MaxDecidedRX mxd; MaxConflicts mxc;
List<TxnListener> dll; List<TxnState> dpl; Object rgi;
RedundantBefore rdb;
try
@ -880,7 +898,11 @@ public class AccordCommandStore extends CommandStore
logger.info("{} loading state from {}", this, savePoint);
mxd = readOne(new File(savePoint, "max_decidedrx"), maxDecidedRX);
mxc = readOne(new File(savePoint, "max_conflicts"), maxConflicts);
rjb = readOne(new File(savePoint, "reject_before"), rejectBefore);
{
File rjbf = new File(savePoint, "reject_before");
if (rjbf.exists())
mxc = mxc.with(readOne(rjbf, rejectBefore));
}
dll = readList(new File(savePoint, "listeners"), txnListener);
dpl = readList(new File(savePoint, "progress_log"), progressLogState);
rgi = rangeIndex.load(new File(savePoint, "range_index"));
@ -898,7 +920,6 @@ public class AccordCommandStore extends CommandStore
rangeIndex.restore(rgi);
unsafeSetMaxDecidedRX(mxd);
unsafeSetMaxConflicts(mxc);
unsafeSetRejectBefore(rjb);
((DefaultLocalListeners) listeners).restore(dll);
boolean unsetCatchup = ((DefaultProgressLog) progressLog).setModeExclusive(safeStore, CATCH_UP);
((DefaultProgressLog) progressLog).restore(safeStore, dpl);

View File

@ -48,14 +48,14 @@ import accord.utils.async.AsyncResults;
import org.apache.cassandra.cache.CacheSize;
import org.apache.cassandra.concurrent.ScheduledExecutors;
import org.apache.cassandra.concurrent.Shutdownable;
import org.apache.cassandra.config.AccordSpec.QueueShardModel;
import org.apache.cassandra.config.AccordConfig.QueueShardModel;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.journal.Descriptor;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.accord.AccordCommandStore.DurablyAppliedTo;
import org.apache.cassandra.service.accord.AccordExecutor.AccordExecutorFactory;
import static org.apache.cassandra.config.AccordSpec.QueueShardModel.THREAD_PER_SHARD;
import static org.apache.cassandra.config.AccordConfig.QueueShardModel.THREAD_PER_SHARD;
import static org.apache.cassandra.config.DatabaseDescriptor.getAccordQueueShardCount;
import static org.apache.cassandra.config.DatabaseDescriptor.getAccordQueueSubmissionModel;
import static org.apache.cassandra.service.accord.AccordExecutor.Mode.RUN_WITHOUT_LOCK;

View File

@ -25,6 +25,7 @@ import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
@ -46,6 +47,13 @@ import accord.local.Command;
import accord.local.PreLoadContext;
import accord.local.SequentialAsyncExecutor;
import accord.local.cfk.CommandsForKey;
import accord.messages.Accept;
import accord.messages.Commit;
import accord.messages.MessageType;
import accord.messages.MessageType.StandardMessage;
import accord.messages.Request;
import accord.primitives.Ballot;
import accord.primitives.SaveStatus;
import accord.primitives.TxnId;
import accord.utils.ArrayBuffers.BufferList;
import accord.utils.IntrusivePriorityHeap;
@ -69,6 +77,8 @@ import org.apache.cassandra.concurrent.DebuggableTask;
import org.apache.cassandra.concurrent.DebuggableTask.DebuggableTaskRunner;
import org.apache.cassandra.concurrent.ExecutorLocals;
import org.apache.cassandra.concurrent.Shutdownable;
import org.apache.cassandra.config.AccordConfig.QueuePriorityModel;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.metrics.AccordCacheMetrics;
import org.apache.cassandra.metrics.AccordExecutorMetrics;
import org.apache.cassandra.metrics.AccordReplicaMetrics;
@ -80,6 +90,9 @@ import org.apache.cassandra.metrics.ShardedDecayingHistograms.DecayingHistograms
import org.apache.cassandra.service.accord.AccordCacheEntry.LoadExecutor;
import org.apache.cassandra.service.accord.AccordCacheEntry.SaveExecutor;
import org.apache.cassandra.service.accord.AccordCacheEntry.UniqueSave;
import org.apache.cassandra.service.accord.debug.DebugExecution.DebugExecutor;
import org.apache.cassandra.service.accord.debug.DebugExecution.DebugSequentialExecutor;
import org.apache.cassandra.service.accord.debug.DebugExecution.DebugTask;
import org.apache.cassandra.utils.Closeable;
import org.apache.cassandra.utils.MonotonicClock;
import org.apache.cassandra.utils.WithResources;
@ -90,6 +103,7 @@ import org.apache.cassandra.utils.concurrent.Future;
import io.netty.util.concurrent.FastThreadLocal;
import static accord.utils.Invariants.createIllegalState;
import static org.apache.cassandra.config.AccordConfig.QueuePriorityModel.PHASE_HLC_FIFO;
import static org.apache.cassandra.service.accord.AccordCache.CommandAdapter.COMMAND_ADAPTER;
import static org.apache.cassandra.service.accord.AccordCache.CommandsForKeyAdapter.CFK_ADAPTER;
import static org.apache.cassandra.service.accord.AccordCache.registerJfrListener;
@ -99,6 +113,8 @@ import static org.apache.cassandra.service.accord.AccordTask.State.RUNNING;
import static org.apache.cassandra.service.accord.AccordTask.State.SCANNING_RANGES;
import static org.apache.cassandra.service.accord.AccordTask.State.WAITING_TO_LOAD;
import static org.apache.cassandra.service.accord.AccordTask.State.WAITING_TO_RUN;
import static org.apache.cassandra.service.accord.debug.DebugExecution.DEBUG_EXECUTION;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
/**
* NOTE: We assume that NO BLOCKING TASKS are submitted to this executor AND WAITED ON by another task executing on this executor.
@ -107,7 +123,12 @@ import static org.apache.cassandra.service.accord.AccordTask.State.WAITING_TO_RU
public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTask<?>, Boolean>, SaveExecutor, Shutdownable, AbstractAsyncExecutor
{
private static final Logger logger = LoggerFactory.getLogger(AccordExecutor.class);
private static final long PRIORITY_BITS = 0x7000000000000000L;
private static final QueuePriorityModel PRIORITY_MODEL = DatabaseDescriptor.getAccord().queue_priority_model;
private static final long AGE_TO_FIFO = DatabaseDescriptor.getAccord().queue_priority_age_to_fifo.to(TimeUnit.MICROSECONDS);
public static final ShardedDecayingHistograms HISTOGRAMS = new ShardedDecayingHistograms();
private static final FastThreadLocal<Lock> paranoidPriorityInversionCheck = new FastThreadLocal<>();
public interface AccordExecutorFactory
{
@ -130,7 +151,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
@Override
public void close()
{
executor.beforeUnlock();
executor.beforeUnlockExternal();
global.tryShrinkOrEvict(executor.lock);
executor.unlock();
}
@ -150,6 +171,25 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
}
}
private static class WaitForCompletion
{
final long position;
long maybeNotify;
final Runnable run;
private WaitForCompletion(long position, Runnable run)
{
this.position = position;
this.maybeNotify = position - 1;
this.run = run;
}
public String toString()
{
return run.toString() + " @" + position;
}
}
private final Lock lock;
final Agent agent;
final int executorId;
@ -173,28 +213,10 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
final LogLinearDecayingHistogram elapsedPreparingToRun;
final LogLinearDecayingHistogram elapsedWaitingToRun;
final LogLinearDecayingHistogram elapsedRunning;
final LogLinearDecayingHistogram elapsed;
final LogLinearDecayingHistogram keys;
public final AccordReplicaMetrics.Shard replicaMetrics;
private static class WaitForCompletion
{
final int position;
int maybeNotify;
final Runnable run;
private WaitForCompletion(int position, Runnable run)
{
this.position = position;
this.maybeNotify = position - 1;
this.run = run;
}
public String toString()
{
return run.toString() + " @" + position;
}
}
/**
* The maximum total number of loads we can queue at once - this includes loads for range transactions,
* which are subject to this limit as well as that imposed by {@link #maxQueuedRangeLoads}
@ -207,11 +229,12 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
private long maxWorkingSetSizeInBytes;
private long maxWorkingCapacityInBytes;
private int nextPosition;
private long minPosition, nextPosition;
private int activeLoads, activeRangeLoads;
private boolean hasPausedLoading;
int tasks;
int runningThreads;
final DebugExecutor debug = DebugExecutor.maybeDebug();
AccordExecutor(Lock lock, int executorId, Agent agent)
{
@ -235,6 +258,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
this.elapsedPreparingToRun = AccordExecutorMetrics.INSTANCE.elapsedPreparingToRun.forShard(histogramsShard);
this.elapsedWaitingToRun = AccordExecutorMetrics.INSTANCE.elapsedWaitingToRun.forShard(histogramsShard);
this.elapsedRunning = AccordExecutorMetrics.INSTANCE.elapsedRunning.forShard(histogramsShard);
this.elapsed = AccordExecutorMetrics.INSTANCE.elapsed.forShard(histogramsShard);
this.keys = AccordExecutorMetrics.INSTANCE.keys.forShard(histogramsShard);
this.replicaMetrics = new AccordReplicaMetrics.Shard(histogramsShard);
}
@ -250,7 +274,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
return caches;
}
private static final FastThreadLocal<Lock> paranoidPriorityInversionCheck = new FastThreadLocal<>();
abstract boolean isInLoop();
final Lock unsafeLock()
{
@ -259,24 +283,34 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
final void lock()
{
if (Invariants.isParanoid())
{
Lock locked = paranoidPriorityInversionCheck.getAndSet(lock);
Invariants.require(locked == null || locked == lock, "Tried to take multiple AccordExecutor locks on same thread - this is dangerous for progress");
}
if (Invariants.isParanoid()) paranoidLockExclusive();
//noinspection LockAcquiredButNotSafelyReleased
lock.lock();
if (DEBUG_EXECUTION) debug.onEnterLock();
}
final void unlock()
{
if (Invariants.isParanoid()) paranoidUnlockExclusive();
if (DEBUG_EXECUTION) debug.onExitLock();
lock.unlock();
}
final void paranoidLockExclusive()
{
Lock locked = paranoidPriorityInversionCheck.getAndSet(lock);
Invariants.require(locked == null || locked == lock, "Tried to take multiple AccordExecutor locks on same thread - this is dangerous for progress");
}
final void paranoidUnlockExclusive()
{
paranoidPriorityInversionCheck.set(null);
}
final boolean tryLock()
{
boolean result = lock.tryLock();
if (DEBUG_EXECUTION && result) debug.onEnterLock();
if (Invariants.isParanoid())
{
if (result)
@ -310,26 +344,30 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
return cache;
}
boolean hasWaitingToRun()
final boolean hasWaitingToRun()
{
updateWaitingToRunExclusive();
return !waitingToRun.isEmpty();
}
Task pollWaitingToRunExclusive()
void updateWaitingToRunExclusive()
{
// TODO (expected): this should not be invoked on every update of waiting to run
maybeUnpauseLoading();
}
final Task pollWaitingToRunExclusive()
{
updateWaitingToRunExclusive();
Task next = waitingToRun.poll();
if (next != null)
{
if (DEBUG_EXECUTION) next.debug.onPolled();
next.addToQueue(running);
}
return next;
}
void updateWaitingToRunExclusive()
{
maybeUnpauseLoading();
}
public Stream<? extends DebuggableTaskRunner> active()
{
return Stream.of();
@ -387,7 +425,8 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
if (waitingForCompletion == null)
waitingForCompletion = new ArrayDeque<>();
int position = nextPosition;
long position = nextPosition;
minPosition = position;
waitingForCompletion.add(new WaitForCompletion(position, run));
}
finally
@ -409,7 +448,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
}
public abstract boolean hasTasks();
abstract void beforeUnlock();
abstract void beforeUnlockExternal();
abstract boolean isOwningThread();
private void enqueueLoadsExclusive()
@ -569,27 +608,29 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
private void waitingToRun(AccordTask task)
{
task.onWaitingToRun();
task.addToQueue(task.commandStore.exclusiveExecutor);
}
private void waitingToRun(SubmittableTask task, @Nullable SequentialExecutor queue)
{
task.onWaitingToRun();
task.addToQueue(queue == null ? waitingToRun : queue);
}
public SequentialExecutor executor()
{
return new SequentialExecutor();
return new SequentialExecutor(this);
}
public SequentialExecutor executor(int commandStoreId)
{
return new SequentialExecutor(commandStoreId);
return new SequentialExecutor(this, commandStoreId);
}
public SequentialAsyncExecutor newSequentialExecutor()
{
return new SequentialExecutor();
return new SequentialExecutor(this);
}
public <R> void cancel(AccordTask<R> task)
@ -639,9 +680,25 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
submit(AccordExecutor::submitExclusive, i -> i, operation);
}
<R> void submitPriority(AccordTask<R> operation)
{
submit(AccordExecutor::submitPriorityExclusive, i -> i, operation);
}
void submitExclusive(AccordTask<?> task)
{
assignQueuePosition(task);
submitInternalExclusive(task);
}
void submitPriorityExclusive(AccordTask<?> task)
{
assignMinQueuePosition(task);
submitInternalExclusive(task);
}
private void submitInternalExclusive(AccordTask<?> task)
{
task.setupExclusive();
++tasks;
updateQueue(task);
@ -670,21 +727,126 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
Invariants.require(isOwningThread());
++tasks;
if (parent != null) inheritQueuePosition(parent, task);
else assignNewQueuePosition(task);
else assignFifoQueuePosition(task);
task.onWaitingToRun();
waitingToRun.append(task);
return task;
}
private void assignQueuePosition(Task task)
{
if (task.queuePosition == 0)
assignNewQueuePosition(task);
if (task.queuePosition != 0) updateNextPosition(task);
else assignFifoQueuePosition(task);
}
private void assignNewQueuePosition(Task task)
private void assignQueuePosition(AccordTask<?> task)
{
if (nextPosition == 0) nextPosition++;
task.queuePosition = nextPosition++;
if (task.queuePosition != 0) updateNextPosition(task);
else
{
long priority_bits = PRIORITY_BITS;
TxnId txnId = null;
switch (PRIORITY_MODEL)
{
case ORIG_PHASE_HLC_FIFO:
case PHASE_HLC_FIFO:
{
// TODO (expected): we should process messages for a TxnId together, to avoid processing delayed messages out of order
PreLoadContext context = task.preLoadContext();
if (context instanceof Request)
{
MessageType type = ((Request) context).type();
if (type instanceof StandardMessage)
{
TxnId txnId0 = context.primaryTxnId();
switch ((StandardMessage)type)
{
case APPLY_REQ:
{
priority_bits = 0L;
txnId = txnId0;
break;
}
case READ_EPHEMERAL_REQ:
case READ_REQ:
case STABLE_THEN_READ_REQ:
{
priority_bits = 1000000000000000L;
txnId = txnId0;
break;
}
case COMMIT_REQ:
{
Commit commit = (Commit) context;
if (PRIORITY_MODEL == PHASE_HLC_FIFO || commit.ballot.equals(Ballot.ZERO))
txnId = commit.txnId;
if (commit.kind.saveStatus == SaveStatus.Stable) priority_bits = 1000000000000L;
else priority_bits = 2000000000000L;
break;
}
case ACCEPT_REQ:
{
Accept accept = (Accept) context;
if (PRIORITY_MODEL == PHASE_HLC_FIFO || accept.ballot.equals(Ballot.ZERO))
txnId = accept.txnId;
priority_bits = 3000000000000L;
break;
}
case GET_EPHEMERAL_READ_DEPS_REQ:
case PRE_ACCEPT_REQ:
{
txnId = txnId0;
break;
}
}
}
}
break;
}
case HLC_FIFO:
{
txnId = task.preLoadContext().primaryTxnId();
break;
}
case FIFO:
{
break;
}
}
if (txnId != null)
{
long hlc = txnId.hlc();
long delta = nextPosition - hlc;
if (delta < AGE_TO_FIFO)
{
long position = hlc;
if (delta <= 0) nextPosition = position + 1;
else if (position < minPosition) position = minPosition;
position |= priority_bits;
task.queuePosition = position;
return;
}
}
assignFifoQueuePosition(task);
}
}
private void assignMinQueuePosition(Task task)
{
task.queuePosition = minPosition | PRIORITY_BITS;
}
private void assignFifoQueuePosition(Task task)
{
task.queuePosition = nextPosition++ | PRIORITY_BITS;
}
private void updateNextPosition(Task task)
{
nextPosition = Math.max(nextPosition, (task.queuePosition & ~PRIORITY_BITS) + 1);
}
private void inheritQueuePosition(Task parent, Task task)
@ -698,18 +860,19 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
// - first take the position so that represents the just-executed task
// - call cleanup to submit any following task on the relevant sub-queue
// - remove the previous task from the running collection only if still present (SequentialExecutor will have removed it)
int position = task.queuePosition;
long position = task.queuePosition;
try
{
task.cleanupExclusive();
task.cleanupExclusive(this);
}
finally
{
if (DEBUG_EXECUTION) task.debug.onCompleted(task, debug);
--tasks;
if (running.contains(task))
running.remove(task);
if (waitingForCompletion != null && waitingForCompletion.peek().maybeNotify - position >= 0)
if (waitingForCompletion != null && waitingForCompletion.peek().maybeNotify <= position)
maybeNotifyWaitingForCompletion();
cache.tryShrinkOrEvict(lock);
@ -718,12 +881,12 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
private void maybeNotifyWaitingForCompletion()
{
int min = minPosition(waitingToRun.peek(),
long min = minPosition(waitingToRun.peek(),
minPosition(waitingToLoad.peek(),
minPosition(waitingToLoadRangeTxns.peek(),
minPosition(running.peek(),
minPosition(loading.peek(),
minPosition(scanningRanges.peek(), Integer.MAX_VALUE))))));
minPosition(scanningRanges.peek(), Long.MAX_VALUE))))));
while (!waitingForCompletion.isEmpty() && waitingForCompletion.peek().position - min <= 0)
waitingForCompletion.poll().run.run();
@ -733,20 +896,21 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
waitingForCompletion.peek().maybeNotify = min;
}
private static int minPosition(@Nullable Task task, int min)
private static long minPosition(@Nullable Task task, long min)
{
return task == null ? min : Integer.min(task.queuePosition, min);
return task == null ? min : Long.min(task.queuePosition, min);
}
void cancelExclusive(AccordTask<?> task)
{
switch (task.state())
AccordTask.State state = task.state();
switch (state)
{
default: throw new UnhandledEnum(task.state());
default: throw new UnhandledEnum(state);
case INITIALIZED:
// we could be cancelled before we even reach the queue
try { task.cancelExclusive(); }
finally { task.cleanupExclusive(); }
finally { task.cleanupExclusive(this); }
break;
case SCANNING_RANGES:
@ -759,6 +923,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
finally { completeTaskExclusive(task); }
break;
case ASSIGNED:
case RUNNING:
case PERSISTING:
case FINISHED:
@ -869,7 +1034,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
}
finally
{
beforeUnlock();
beforeUnlockExternal();
unlock();
}
}
@ -947,24 +1112,31 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
public static abstract class Task extends IntrusivePriorityHeap.Node
{
int queuePosition;
Thread assigned;
long queuePosition;
public long createdAt = nanoTime(), waitingToRunAt, runningAt, cleanupAt;
public final DebugTask debug = DebugTask.maybeDebug();
protected Task()
{
}
public void onWaitingToRun()
{
waitingToRunAt = nanoTime();
}
public DebuggableTask debuggable() { return null; }
/**
* Prepare to run while holding the state cache lock
*/
abstract protected void preRunExclusive();
abstract protected void preRunExclusive(Thread assigned);
/**
* Run the command; the state cache lock may or may not be held depending on the executor implementation
*/
abstract protected void runInternal();
protected abstract void runInternal();
/**
* Fail the command; the state cache lock may or may not be held depending on the executor implementation
*/
@ -973,7 +1145,19 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
/**
* Cleanup the command while holding the state cache lock
*/
abstract protected void cleanupExclusive();
protected void cleanupExclusive(AccordExecutor executor)
{
cleanupAt = nanoTime();
if (runningAt != 0)
{
if (waitingToRunAt == 0)
waitingToRunAt = runningAt;
executor.elapsedWaitingToRun.increment(runningAt - waitingToRunAt, runningAt);
executor.elapsedPreparingToRun.increment(waitingToRunAt - createdAt, runningAt);
executor.elapsedRunning.increment(cleanupAt - runningAt, cleanupAt);
executor.elapsed.increment(cleanupAt - createdAt, cleanupAt);
}
}
void cancelExclusive(AccordExecutor owner) {}
@ -1011,9 +1195,9 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
}
@Override
protected void preRunExclusive()
protected void preRunExclusive(Thread assigned)
{
queue.preRunTask();
queue.preRunTask(assigned);
}
@Override
@ -1029,7 +1213,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
}
@Override
protected void cleanupExclusive()
protected void cleanupExclusive(AccordExecutor executor)
{
queue.cleanupTask();
}
@ -1054,51 +1238,54 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
private volatile boolean visibleStopped;
private boolean terminated;
SequentialExecutor()
final DebugSequentialExecutor debug;
SequentialExecutor(AccordExecutor executor)
{
this(-1);
this(executor, -1);
}
SequentialExecutor(int commandStoreId)
SequentialExecutor(AccordExecutor executor, int commandStoreId)
{
super(WAITING_TO_RUN);
super(WAITING_TO_RUN, commandStoreId < 0);
this.commandStoreId = commandStoreId;
this.selfTask = new SequentialQueueTask(this);
this.debug = DebugSequentialExecutor.maybeDebug(executor.debug, commandStoreId);
}
void preRunTask()
void preRunTask(Thread assigned)
{
Invariants.require(task != null);
assigned = Thread.currentThread();
task.preRunExclusive();
this.assigned = assigned;
task.preRunExclusive(assigned);
}
void runTask()
{
outer: while (!ownerUpdater.compareAndSet(this, null, assigned))
if (!ownerUpdater.compareAndSet(this, null, assigned))
{
if (DEBUG_EXECUTION) debug.onWaiting();
Invariants.require(assigned == Thread.currentThread());
waiting = assigned;
while (true)
outer: do
{
Thread owner = this.owner;
if (owner == assigned) break outer;
if (owner == null) continue outer;
LockSupport.park();
while (true)
{
Thread owner = this.owner;
if (owner == assigned) break outer;
if (owner == null) continue outer;
LockSupport.park();
}
}
while (!ownerUpdater.compareAndSet(this, null, assigned));
}
waiting = null;
try
{
if (stopped && reject(task))
task.fail(new RejectedExecutionException(commandStoreId + " is terminated. Cannot execute " + ((AccordTask<?>) task).preLoadContext()));
else
task.runInternal();
}
finally
{
owner = null;
}
if (stopped && reject(task))
task.fail(new RejectedExecutionException(commandStoreId + " is terminated. Cannot execute " + ((AccordTask<?>) task).preLoadContext()));
else
task.runInternal();
// NOTE: cannot safely release owner here, in case an immediate-execution runs before we can release our references and store their changes to the cache
}
private boolean reject(Task task)
@ -1118,11 +1305,13 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
void cleanupTask()
{
try { task.cleanupExclusive(); }
try { task.cleanupExclusive(AccordExecutor.this); }
finally
{
assigned = null;
owner = null;
task = super.poll();
if (DEBUG_EXECUTION) debug.onSetTask(task);
// it should only be possible for this method to be invoked once we're on the running queue
AccordExecutor.this.running.remove(selfTask);
@ -1149,6 +1338,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
task = newTask;
selfTask.queuePosition = newTask.queuePosition;
waitingToRun.append(selfTask);
if (DEBUG_EXECUTION) debug.onSetTask(newTask);
}
}
@ -1176,6 +1366,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
// but can for other tasks that don't track their own state
task = super.poll();
if (DEBUG_EXECUTION) debug.onSetTask(task);
if (waitingToRun.contains(selfTask))
{
if (task == null) waitingToRun.remove(selfTask);
@ -1242,7 +1433,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
@Override
public AsyncChain<Void> chain(Runnable run)
{
int position = inheritQueuePosition();
long position = inheritQueuePosition();
return new AsyncChains.Head<>()
{
@Override
@ -1256,7 +1447,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
@Override
public <T> AsyncChain<T> chain(Callable<T> call)
{
int position = inheritQueuePosition();
long position = inheritQueuePosition();
return new AsyncChains.Head<>()
{
@Override
@ -1270,7 +1461,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
@Override
public <T> AsyncChain<T> flatChain(Callable<? extends AsyncChain<T>> call)
{
int position = inheritQueuePosition();
long position = inheritQueuePosition();
return new AsyncChains.Head<>()
{
@Override
@ -1287,12 +1478,12 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
return execute(runOrFail, inheritQueuePosition());
}
private int inheritQueuePosition()
private long inheritQueuePosition()
{
return inExecutor() && task != null ? task.queuePosition : 0;
}
private Cancellable execute(RunOrFail runOrFail, int queuePosition)
private Cancellable execute(RunOrFail runOrFail, long queuePosition)
{
PlainChain submit = new PlainChain(runOrFail, SequentialExecutor.this, queuePosition);
return AccordExecutor.this.submit(submit);
@ -1319,12 +1510,13 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
{
if (owner == null)
{
this.owner = null;
Thread waiting = this.waiting;
Invariants.require(waiting != self);
this.owner = waiting;
if (waiting == null) // recheck, to ensure happens-before relation with a new waiter that expects any non-null owner to notify it
waiting = this.waiting;
if (waiting != null)
{
LockSupport.unpark(waiting);
ownerUpdater.compareAndSet(this, null, waiting);
}
}
}
return true;
@ -1340,6 +1532,12 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
this.kind = kind;
}
TaskQueue(AccordTask.State kind, boolean tiny)
{
super(tiny);
this.kind = kind;
}
@Override
public int compare(T o1, T o2)
{
@ -1421,10 +1619,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
abstract SequentialExecutor executor();
@Override
protected void preRunExclusive() {}
@Override
protected void cleanupExclusive() {}
protected void preRunExclusive(Thread assigned) {}
@Override
protected final void addToQueue(TaskQueue queue)
@ -1475,7 +1670,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
this(result, run, null, 0);
}
PlainRunnable(AsyncPromise<Void> result, Runnable run, @Nullable SequentialExecutor executor, int queuePosition)
PlainRunnable(AsyncPromise<Void> result, Runnable run, @Nullable SequentialExecutor executor, long queuePosition)
{
this.result = result;
this.run = run;
@ -1486,12 +1681,14 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
@Override
protected void runInternal()
{
runningAt = nanoTime();
try (Closeable close = locals.get())
{
run.run();
}
if (result != null)
result.trySuccess(null);
if (DEBUG_EXECUTION) debug.onRunComplete();
}
@Override
@ -1518,14 +1715,15 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
abstract void postRunExclusive();
@Override
protected void preRunExclusive()
protected void preRunExclusive(Thread assigned)
{
startedAtNanos = MonotonicClock.Global.approxTime.now();
}
@Override
protected void cleanupExclusive()
protected void cleanupExclusive(AccordExecutor executor)
{
super.cleanupExclusive(executor);
postRunExclusive();
}
@ -1586,10 +1784,12 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
@Override
public void runInternal()
{
runningAt = nanoTime();
try (Closeable close = locals.get())
{
result = entry.owner.parent().adapter().load(entry.owner.commandStore, entry.key());
}
if (DEBUG_EXECUTION) debug.onRunComplete();
}
@Override
@ -1631,10 +1831,12 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
@Override
protected void runInternal()
{
runningAt = nanoTime();
try (Closeable close = locals.get())
{
wrapped.runInternal();
}
if (DEBUG_EXECUTION) debug.onRunComplete();
}
@Override
@ -1680,10 +1882,12 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
@Override
public void runInternal()
{
runningAt = nanoTime();
try (Closeable close = locals.get())
{
run.run();
}
if (DEBUG_EXECUTION) debug.onRunComplete();
failure = null;
}
@ -1710,7 +1914,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
this(runOrFail, null, 0);
}
PlainChain(RunOrFail runOrFail, @Nullable SequentialExecutor executor, int queuePosition)
PlainChain(RunOrFail runOrFail, @Nullable SequentialExecutor executor, long queuePosition)
{
this.runOrFail = runOrFail;
this.executor = executor;
@ -1726,6 +1930,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
@Override
protected void runInternal()
{
runningAt = nanoTime();
try (Closeable close = locals.get())
{
runOrFail.run();
@ -1735,6 +1940,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
// shouldn't throw exceptions
agent.onException(t);
}
if (DEBUG_EXECUTION) debug.onRunComplete();
}
@Override
@ -1778,16 +1984,11 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
}
@Override
protected void preRunExclusive()
protected void preRunExclusive(Thread assigned)
{
startedAtNanos = MonotonicClock.Global.approxTime.now();
}
@Override
protected void cleanupExclusive()
{
}
@Override
public String description()
{
@ -1829,7 +2030,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
return commandStoreId >= 0 ? commandStoreId : null;
}
public int position()
public long position()
{
return task.queuePosition;
}
@ -1858,7 +2059,7 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
public int compareTo(TaskInfo that)
{
int c = this.status.compareTo(that.status);
if (c == 0) c = Integer.compare(this.position(), that.position());
if (c == 0) c = Long.compare(this.position(), that.position());
return c;
}
}
@ -1918,4 +2119,5 @@ public abstract class AccordExecutor implements CacheSize, LoadExecutor<AccordTa
{
return running.size();
}
}

View File

@ -26,15 +26,18 @@ import accord.utils.QuadFunction;
import accord.utils.QuintConsumer;
import org.apache.cassandra.concurrent.DebuggableTask.DebuggableTaskRunner;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.service.accord.AccordExecutorLoops.LoopTask;
import org.apache.cassandra.service.accord.debug.DebugExecution.DebugExecutorLoop;
import org.apache.cassandra.utils.concurrent.ConcurrentLinkedStack;
import static org.apache.cassandra.service.accord.AccordExecutor.Mode.RUN_WITH_LOCK;
import static org.apache.cassandra.service.accord.debug.DebugExecution.DEBUG_EXECUTION;
abstract class AccordExecutorAbstractLockLoop extends AccordExecutor
{
private static final int YIELD_INTERVAL = DatabaseDescriptor.getAccord().queue_yield_interval;
final ConcurrentLinkedStack<Submittable> submitted = new ConcurrentLinkedStack<>();
boolean isHeldByExecutor;
boolean shutdown;
AccordExecutorAbstractLockLoop(Lock lock, int executorId, Agent agent)
@ -44,9 +47,9 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutor
abstract void notifyWork();
abstract void notifyWorkExclusive();
void loopYieldExclusive() throws InterruptedException {}
abstract void awaitExclusive() throws InterruptedException;
abstract AccordExecutorLoops loops();
abstract boolean isInLoop();
abstract <P1s, P1a, P2, P3, P4> void submitExternal(QuintConsumer<AccordExecutor, P1s, P2, P3, P4> sync, QuadFunction<P1a, P2, P3, P4, Submittable> async, P1s p1s, P1a p1a, P2 p2, P3 p3, P4 p4);
<P1s, P1a, P2, P3, P4> void submit(QuintConsumer<AccordExecutor, P1s, P2, P3, P4> sync, QuadFunction<P1a, P2, P3, P4, Submittable> async, P1s p1s, P1a p1a, P2 p2, P3 p3, P4 p4)
@ -57,7 +60,7 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutor
else submitExternal(sync, async, p1s, p1a, p2, p3, p4);
}
<P1s, P1a, P2, P3, P4> void submitExternalExclusive(QuintConsumer<AccordExecutor, P1s, P2, P3, P4> sync, QuadFunction<P1a, P2, P3, P4, Submittable> async, P1s p1s, P1a p1a, P2 p2, P3 p3, P4 p4)
<P1s, P2, P3, P4> void submitExternalExclusive(QuintConsumer<AccordExecutor, P1s, P2, P3, P4> sync, P1s p1s, P2 p2, P3 p3, P4 p4)
{
try
{
@ -95,50 +98,54 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutor
}
}
@Override
void beforeUnlock()
{
if (!isInLoop())
notifyIfMoreWorkExclusive();
}
void updateWaitingToRunExclusive()
final void updateWaitingToRunExclusive()
{
drainSubmittedExclusive();
super.updateWaitingToRunExclusive();
}
void drainSubmittedExclusive()
final void drainSubmittedExclusive()
{
submitted.drain(AccordExecutor::consumeExclusive, this, true);
}
void notifyIfMoreWorkExclusive()
final void notifyIfMoreWorkExclusive()
{
if (hasWaitingToRun())
notifyWorkExclusive();
}
private void enterLockExclusive()
@Override
final void beforeUnlockExternal()
{
isHeldByExecutor = true;
beforeUnlockLoop();
}
private void exitLockExclusive()
final void beforeUnlockLoop()
{
notifyIfMoreWorkExclusive();
}
private void pauseExclusive()
private void enterLockLoop()
{
resumeLoop();
}
private void exitLockLoop()
{
pauseLoop();
notifyIfMoreWorkExclusive();
}
final void pauseLoop()
{
isHeldByExecutor = false;
if (--runningThreads == 0 && tasks == 0)
notifyQuiescentExclusive();
}
private void resumeExclusive()
final void resumeLoop()
{
isHeldByExecutor = true;
if (DEBUG_EXECUTION) debug.onEnterLock();
++runningThreads;
}
@ -154,14 +161,14 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutor
@Override
public void run()
{
Thread self = Thread.currentThread();
Task task;
while (true)
{
lock();
try
{
resumeExclusive();
enterLockExclusive();
enterLockLoop();
while (true)
{
task = pollWaitingToRunExclusive();
@ -171,7 +178,7 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutor
setRunning(task);
try
{
task.preRunExclusive();
task.preRunExclusive(self);
task.runInternal();
}
catch (Throwable t)
@ -188,22 +195,21 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutor
{
if (shutdown)
{
pauseExclusive();
exitLockExclusive();
pauseLoop();
exitLockLoop();
notifyWorkExclusive(); // always notify on shutdown
return;
}
pauseExclusive();
pauseLoop();
awaitExclusive();
resumeExclusive();
resumeLoop();
}
}
}
catch (Throwable t)
{
pauseExclusive();
exitLockExclusive();
exitLockLoop();
try { agent.onException(t); }
catch (Throwable t2) { }
@ -221,15 +227,21 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutor
{
return new LoopTask(name)
{
final DebugExecutorLoop debug = DEBUG_EXECUTION ? new DebugExecutorLoop(AccordExecutorAbstractLockLoop.this.debug) : null;
@Override
public void run()
{
Thread self = Thread.currentThread();
int count = 0;
Task task = null;
while (true)
{
if (DEBUG_EXECUTION) debug.onLock();
lock();
try
{
if (DEBUG_EXECUTION) debug.onEnterLock();
enterLockLoop();
if (task != null)
{
Task tmp = task;
@ -237,31 +249,40 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutor
completeTaskExclusive(tmp);
clearRunning();
}
else resumeExclusive();
enterLockExclusive();
if (count >= YIELD_INTERVAL)
{
loopYieldExclusive();
count = 0;
}
while (true)
{
task = pollWaitingToRunExclusive();
if (task != null)
{
setRunning(task);
task.preRunExclusive();
exitLockExclusive();
task.preRunExclusive(self);
if (DEBUG_EXECUTION) debug.onExitLock();
exitLockLoop();
break;
}
pauseExclusive();
if (shutdown)
{
exitLockExclusive();
if (DEBUG_EXECUTION) debug.onExitLock();
exitLockLoop();
notifyWorkExclusive();
return;
}
pauseLoop();
if (DEBUG_EXECUTION) debug.onExitLock();
awaitExclusive();
resumeExclusive();
if (DEBUG_EXECUTION) debug.onEnterLock();
resumeLoop();
count = 0;
}
}
catch (Throwable t)
@ -281,18 +302,18 @@ abstract class AccordExecutorAbstractLockLoop extends AccordExecutor
try { agent.onException(t); }
catch (Throwable t2) { /* nothing we can sensibly do after already reporting */ }
}
if (isHeldByExecutor)
pauseExclusive();
exitLockExclusive();
exitLockLoop();
continue;
}
finally
{
if (DEBUG_EXECUTION) debug.onExitLock();
unlock();
}
try
{
++count;
task.runInternal();
}
catch (Throwable t)

View File

@ -35,20 +35,7 @@ abstract class AccordExecutorAbstractSemiSyncSubmit extends AccordExecutorAbstra
<P1s, P1a, P2, P3, P4> void submitExternal(QuintConsumer<AccordExecutor, P1s, P2, P3, P4> sync, QuadFunction<P1a, P2, P3, P4, Submittable> async, P1s p1s, P1a p1a, P2 p2, P3 p3, P4 p4)
{
if (!tryLock())
{
submitted.push(async.apply(p1a, p2, p3, p4));
if (submitted.push(async.apply(p1a, p2, p3, p4)) && !isInLoop())
notifyWork();
return;
}
try
{
submitExternalExclusive(sync, async, p1s, p1a, p2, p3, p4);
}
finally
{
unlock();
}
}
}

View File

@ -31,6 +31,7 @@ class AccordExecutorSemiSyncSubmit extends AccordExecutorAbstractSemiSyncSubmit
private final AccordExecutorLoops loops;
private final ReentrantLock lock;
private final Condition hasWork;
private int waiting;
public AccordExecutorSemiSyncSubmit(int executorId, Mode mode, int threads, IntFunction<String> name, Agent agent)
{
@ -45,11 +46,30 @@ class AccordExecutorSemiSyncSubmit extends AccordExecutorAbstractSemiSyncSubmit
this.loops = new AccordExecutorLoops(mode, threads, name, this::task);
}
@Override
void loopYieldExclusive() throws InterruptedException
{
if (waiting > 0 && hasWaitingToRun())
{
pauseLoop();
hasWork.signal();
awaitWork();
resumeLoop();
}
}
@Override
void awaitExclusive() throws InterruptedException
{
if (submitted.isEmpty())
hasWork.await();
awaitWork();
}
private void awaitWork() throws InterruptedException
{
waiting++;
try { hasWork.await(); }
finally { waiting--; }
}
@Override
@ -67,19 +87,9 @@ class AccordExecutorSemiSyncSubmit extends AccordExecutorAbstractSemiSyncSubmit
@Override
void notifyWork()
{
// we check running both sides of tryLock for ordering guarantees
boolean hadRunning = isHeldByExecutor;
if (lock.tryLock())
{
try { hasWork.signal(); }
finally { lock.unlock(); }
}
else if (!hadRunning || !isHeldByExecutor)
{
lock.lock();
try { hasWork.signal(); }
finally { lock.unlock(); }
}
lock.lock();
try { hasWork.signal(); }
finally { lock.unlock(); }
}
@Override

View File

@ -62,6 +62,12 @@ class AccordExecutorSimple extends AccordExecutor
this.executor = executorFactory().sequential(name.apply(0));
}
@Override
boolean isInLoop()
{
return executor.inExecutor();
}
@Override
public boolean hasTasks()
{
@ -69,7 +75,7 @@ class AccordExecutorSimple extends AccordExecutor
}
@Override
void beforeUnlock()
void beforeUnlockExternal()
{
if (hasWaitingToRun())
executor.execute(this::run);
@ -77,6 +83,7 @@ class AccordExecutorSimple extends AccordExecutor
protected void run()
{
Thread self = Thread.currentThread();
lock.lock();
try
{
@ -92,7 +99,7 @@ class AccordExecutorSimple extends AccordExecutor
return;
}
try { task.preRunExclusive(); task.runInternal(); }
try { task.preRunExclusive(self); task.runInternal(); }
catch (Throwable t) { task.fail(t); }
finally
{

View File

@ -79,14 +79,8 @@ class AccordExecutorSyncSubmit extends AccordExecutorAbstractLockLoop
void notifyWork()
{
lock.lock();
try
{
hasWork.signal();
}
finally
{
lock.unlock();
}
try { hasWork.signal(); }
finally { lock.unlock(); }
}
@Override
@ -100,7 +94,7 @@ class AccordExecutorSyncSubmit extends AccordExecutorAbstractLockLoop
lock.lock();
try
{
submitExternalExclusive(sync, async, p1s, p1a, p2, p3, p4);
submitExternalExclusive(sync, p1s, p2, p3, p4);
}
finally
{

View File

@ -116,7 +116,7 @@ import org.apache.cassandra.utils.vint.VIntCoding;
import static java.lang.String.format;
import static java.util.Collections.emptyMap;
import static org.apache.cassandra.config.AccordSpec.RangeIndexMode.journal_sai;
import static org.apache.cassandra.config.AccordConfig.RangeIndexMode.journal_sai;
import static org.apache.cassandra.db.partitions.PartitionUpdate.singleRowUpdate;
import static org.apache.cassandra.db.rows.BTreeRow.singleCellRow;
import static org.apache.cassandra.schema.SchemaConstants.ACCORD_KEYSPACE_NAME;
@ -307,7 +307,7 @@ public class AccordKeyspace
return null;
// TODO (desired): consider whether better to not compact any validation failures, since we expect is already overwritten
CommandsForKey updated = current.withGcBeforeAtLeast(redundantBefore.gcBefore(), false);
CommandsForKey updated = current.withCleanCfkBeforeAtLeast(redundantBefore.cleanCfkBefore(), false);
if (current == updated)
return row;

View File

@ -28,7 +28,9 @@ import com.google.common.collect.ImmutableMap;
import accord.api.AsyncExecutor;
import accord.api.MessageSink;
import accord.api.Tracing;
import accord.impl.RequestCallbacks;
import accord.local.MapReduceCommandStores;
import accord.local.Node;
import accord.messages.Callback;
import accord.messages.MessageType;
@ -36,19 +38,19 @@ import accord.messages.Reply;
import accord.messages.ReplyContext;
import accord.messages.Request;
import accord.primitives.TxnId;
import accord.utils.Invariants;
import accord.utils.async.Cancellable;
import org.apache.cassandra.exceptions.RequestFailureReason;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessageDelivery;
import org.apache.cassandra.net.MessageFlag;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.net.ParamType;
import org.apache.cassandra.net.ResponseContext;
import org.apache.cassandra.net.Verb;
import org.apache.cassandra.service.TimeoutStrategy;
import org.apache.cassandra.service.accord.topology.AccordEndpointMapper;
import org.apache.cassandra.utils.Clock;
import static accord.messages.MessageType.StandardMessage.ACCEPT_REQ;
import static accord.messages.MessageType.StandardMessage.ACCEPT_RSP;
@ -86,6 +88,7 @@ import static accord.messages.MessageType.StandardMessage.READ_REQ;
import static accord.messages.MessageType.StandardMessage.READ_RSP;
import static accord.messages.MessageType.StandardMessage.RECOVER_AWAIT_REQ;
import static accord.messages.MessageType.StandardMessage.RECOVER_AWAIT_RSP;
import static accord.messages.MessageType.StandardMessage.REMOTE_SUCCESS_REQ;
import static accord.messages.MessageType.StandardMessage.SET_GLOBALLY_DURABLE_REQ;
import static accord.messages.MessageType.StandardMessage.SET_SHARD_DURABLE_REQ;
import static accord.messages.MessageType.StandardMessage.SIMPLE_RSP;
@ -96,6 +99,7 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.service.accord.api.AccordWaitStrategies.expire;
import static org.apache.cassandra.service.accord.api.AccordWaitStrategies.slowPreaccept;
import static org.apache.cassandra.service.accord.api.AccordWaitStrategies.slowRead;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
public class AccordMessageSink implements MessageSink
{
@ -167,6 +171,7 @@ public class AccordMessageSink implements MessageSink
builder.put(SET_GLOBALLY_DURABLE_REQ, Verb.ACCORD_SET_GLOBALLY_DURABLE_REQ);
builder.put(GET_DURABLE_BEFORE_REQ, Verb.ACCORD_GET_DURABLE_BEFORE_REQ);
builder.put(GET_DURABLE_BEFORE_RSP, Verb.ACCORD_GET_DURABLE_BEFORE_RSP);
builder.put(REMOTE_SUCCESS_REQ, Verb.ACCORD_REMOTE_SUCCESS_REQ);
builder.put(FAILURE_RSP, Verb.FAILURE_RSP);
Map<StandardMessage, Verb> mapping = builder.build();
StandardMessage.initialise(mapping);
@ -209,6 +214,7 @@ public class AccordMessageSink implements MessageSink
{
Verb verb = VerbMapping.getVerb(request);
Preconditions.checkNotNull(verb, "Verb is null for type %s", request.type());
Message<Request> message = Message.out(verb, request);
InetAddressAndPort endpoint = endpointMapper.mappedEndpointOrNull(to, message);
if (endpoint == null)
@ -224,7 +230,7 @@ public class AccordMessageSink implements MessageSink
Verb verb = VerbMapping.getVerb(request);
Preconditions.checkNotNull(verb, "Verb is null for type %s", request.type());
long nowNanos = Clock.Global.nanoTime();
long nowNanos = nanoTime();
TxnId txnId = request.primaryTxnId();
long slowAtNanos = Long.MAX_VALUE;
long expiresAtNanos = nowNanos + expire(txnId, verb).computeWait(attempt, NANOSECONDS);
@ -251,6 +257,13 @@ public class AccordMessageSink implements MessageSink
}
Message<Request> message = Message.out(verb, request, expiresAtNanos);
if (request instanceof MapReduceCommandStores<?, ?>)
{
Tracing tracing = ((MapReduceCommandStores<?, ?>) request).tracing();
if (tracing != null) tracing = tracing.send();
if (tracing != null) message = message.withParam(ParamType.ACCORD_TRACING, tracing);
}
InetAddressAndPort endpoint = endpointMapper.mappedEndpointOrNull(to, message);
if (endpoint == null)
{
@ -263,26 +276,24 @@ public class AccordMessageSink implements MessageSink
return cancellable;
}
@Override
public void reply(Node.Id replyingTo, ReplyContext replyContext, Reply reply)
public void reply(Node.Id replyingTo, ReplyContext replyContext, Reply reply, Throwable failure)
{
ResponseContext respondTo = (ResponseContext) replyContext;
Message<?> message = Message.responseWith(reply, respondTo);
if (!reply.isFinal())
message = message.withFlag(MessageFlag.NOT_FINAL);
checkReplyType(reply, respondTo);
InetAddressAndPort endpoint = endpointMapper.mappedEndpointOrNull(replyingTo, message);
if (endpoint == null)
return;
Message<?> message;
if (failure != null) message = Message.failureResponse(RequestFailureReason.UNKNOWN, failure, respondTo);
else
{
message = Message.responseWith(reply, respondTo);
if (Invariants.isParanoid()) checkReplyType(reply, respondTo);
}
Object tracing = respondTo.params().get(ParamType.ACCORD_TRACING);
if (tracing != null)
{
tracing = ((Tracing)tracing).send();
if (tracing != null)
message = message.withParam(ParamType.ACCORD_TRACING, tracing);
}
messaging.send(message, endpoint);
}
@Override
public void replyWithUnknownFailure(Node.Id replyingTo, ReplyContext replyContext, Throwable failure)
{
ResponseContext respondTo = (ResponseContext) replyContext;
Message<?> message = Message.failureResponse(RequestFailureReason.UNKNOWN, failure, respondTo);
InetAddressAndPort endpoint = endpointMapper.mappedEndpointOrNull(replyingTo, message);
if (endpoint == null)
return;

View File

@ -25,7 +25,7 @@ import accord.api.Key;
import accord.api.Result;
import accord.api.RoutingKey;
import accord.local.Command;
import accord.local.ICommand;
import accord.local.CommandBuilder;
import accord.local.Node;
import accord.local.StoreParticipants;
import accord.local.cfk.CommandsForKey;
@ -39,11 +39,9 @@ import accord.primitives.FullKeyRoute;
import accord.primitives.FullRangeRoute;
import accord.primitives.KeyDeps;
import accord.primitives.Keys;
import accord.primitives.PartialDeps;
import accord.primitives.PartialKeyRoute;
import accord.primitives.PartialRangeRoute;
import accord.primitives.PartialTxn;
import accord.primitives.Participants;
import accord.primitives.Range;
import accord.primitives.RangeDeps;
import accord.primitives.Ranges;
@ -61,6 +59,7 @@ import accord.utils.ImmutableBitSet;
import accord.utils.UnhandledEnum;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.BufferDecoratedKey;
import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.accord.api.PartitionKey;
@ -73,18 +72,15 @@ import org.apache.cassandra.service.accord.txn.TxnQuery;
import org.apache.cassandra.service.accord.txn.TxnRead;
import org.apache.cassandra.service.accord.txn.TxnResult;
import org.apache.cassandra.service.accord.txn.TxnWrite;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.ObjectSizes;
import static accord.local.Command.Accepted.accepted;
import static accord.local.Command.Committed.committed;
import static accord.local.Command.Executed.executed;
import static accord.local.Command.NotAcceptedWithoutDefinition.notAccepted;
import static accord.local.Command.NotDefined.notDefined;
import static accord.local.Command.PreAccepted.preaccepted;
import static accord.local.Command.Truncated.WaitingOn;
import static accord.local.Command.Truncated.invalidated;
import static accord.local.Command.Truncated.vestigial;
import static accord.local.cfk.CommandsForKey.InternalStatus.ACCEPTED;
import static accord.primitives.SaveStatus.Invalidated;
import static accord.primitives.SaveStatus.NotDefined;
import static accord.primitives.SaveStatus.PreAccepted;
import static accord.primitives.SaveStatus.TruncatedUnapplied;
import static accord.primitives.Status.Durability.NotDurable;
import static accord.primitives.TxnId.NO_TXNIDS;
import static org.apache.cassandra.utils.ObjectSizes.measure;
@ -306,23 +302,24 @@ public class AccordObjectSizes
private static class CommandEmptySizes
{
private final static TokenKey EMPTY_KEY = new TokenKey(EMPTY_ID, null);
private final static PartitionKey EMPTY_KEY = new PartitionKey(EMPTY_ID, new BufferDecoratedKey(new Murmur3Partitioner.LongToken(1), ByteBufferUtil.EMPTY_BYTE_BUFFER));
private final static TokenKey EMPTY_TOKEN_KEY = new TokenKey(EMPTY_ID, new Murmur3Partitioner.LongToken(1));
private final static TxnId EMPTY_TXNID = new TxnId(42, 42, 0, Kind.Read, Domain.Key, new Node.Id(42));
private static ICommand attrs(boolean hasDeps, boolean hasTxn, boolean executes)
private static Command build(SaveStatus saveStatus, boolean hasDeps, boolean hasTxn, boolean executes)
{
FullKeyRoute route = new FullKeyRoute(EMPTY_KEY, new RoutingKey[]{ EMPTY_KEY });
Participants<?> empty = route.slice(0, 0);
ICommand.Builder builder = new ICommand.Builder(EMPTY_TXNID)
.setParticipants(StoreParticipants.create(route, empty, executes ? empty : null, executes ? empty : null, empty, route))
Keys keys = Keys.of(EMPTY_KEY);
FullKeyRoute route = new FullKeyRoute(EMPTY_TOKEN_KEY, new RoutingKey[]{ EMPTY_TOKEN_KEY });
CommandBuilder builder = new CommandBuilder(EMPTY_TXNID)
.participants(StoreParticipants.create(route, route, executes ? route : null, executes ? route : null, route, route))
.durability(NotDurable)
.executeAt(EMPTY_TXNID)
.promised(Ballot.ZERO);
if (hasDeps)
builder.partialDeps(PartialDeps.NONE);
builder.partialDeps(new Deps(KeyDeps.none(route.toParticipants()), RangeDeps.NONE).intersecting(route));
if (hasTxn)
builder.partialTxn(new PartialTxn.InMemory(Kind.Read, null, null, null, null, TableMetadatasAndKeys.none(Domain.Key)));
builder.partialTxn(new PartialTxn.InMemory(Kind.Read, keys, TxnRead.empty(Domain.Key), null, null, TableMetadatasAndKeys.none(Domain.Key)));
if (executes)
{
@ -330,18 +327,20 @@ public class AccordObjectSizes
builder.result(new TxnData());
}
return builder;
return builder.build(saveStatus);
}
final static long NOT_DEFINED = measure(notDefined(attrs(false, false, false)));
final static long PREACCEPTED = measure(preaccepted(attrs(false, true, false), SaveStatus.PreAccepted));
final static long NOTACCEPTED = measure(notAccepted(attrs(false, false, false), SaveStatus.AcceptedInvalidate));
final static long ACCEPTED = measure(accepted(attrs(true, false, false), SaveStatus.AcceptedMedium));
final static long COMMITTED = measure(committed(attrs(true, true, false), SaveStatus.Committed));
final static long EXECUTED = measure(executed(attrs(true, true, true), SaveStatus.Applied));
final static long NOT_DEFINED = measure(build(NotDefined, false, false, false));
final static long PREACCEPTED = measure(build(PreAccepted, false, true, false));
final static long NOTACCEPTED = measure(build(SaveStatus.AcceptedInvalidate, false, false, false));
final static long ACCEPTED = measure(build(SaveStatus.AcceptedMedium, true, false, false));
final static long COMMITTED = measure(build(SaveStatus.Committed, true, true, false));
final static long EXECUTED = measure(build(SaveStatus.Applied, true, true, true));
// TODO (expected): TruncatedAwaitsOnlyDeps
final static long TRUNCATED = measure(vestigial(EMPTY_TXNID, attrs(false, false, false).participants()));
final static long INVALIDATED = measure(invalidated(EMPTY_TXNID, attrs(false, false, false).participants()));
final static long TRUNCATED = measure(build(TruncatedUnapplied, false, false, false).participants());
final static long INVALIDATED = measure(build(Invalidated, false, false, false).participants());
private static void touch() {}
private static long emptySize(Command command)
{

View File

@ -81,11 +81,11 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
return commandStore.unsafeGetRangesForEpoch();
}
public static AccordSafeCommandStore create(AccordTask<?> operation,
public static AccordSafeCommandStore create(AccordTask<?> task,
@Nullable CommandSummaries commandsForRanges,
AccordCommandStore commandStore)
{
return new AccordSafeCommandStore(operation, commandsForRanges, commandStore);
return new AccordSafeCommandStore(task, commandsForRanges, commandStore);
}
@VisibleForTesting

View File

@ -24,10 +24,21 @@ import com.google.common.annotations.VisibleForTesting;
import accord.api.RoutingKey;
import accord.local.cfk.CommandsForKey;
import accord.local.cfk.NotifySink;
import accord.local.cfk.SafeCommandsForKey;
public class AccordSafeCommandsForKey extends SafeCommandsForKey implements AccordSafeState<RoutingKey, CommandsForKey>
{
public static class CommandsForKeyCacheEntry extends AccordCacheEntry<RoutingKey, CommandsForKey>
{
private NotifySink overrideSink;
CommandsForKeyCacheEntry(RoutingKey key, AccordCache.Type<RoutingKey, CommandsForKey, ?>.Instance owner)
{
super(key, owner);
}
}
private boolean invalidated;
private final AccordCacheEntry<RoutingKey, CommandsForKey> global;
private CommandsForKey original;
@ -39,6 +50,8 @@ public class AccordSafeCommandsForKey extends SafeCommandsForKey implements Acco
this.global = global;
this.original = null;
this.current = null;
// if (overrideSink() == null)
// overrideSink(new RecordingNotifySink());
}
@Override
@ -103,6 +116,18 @@ public class AccordSafeCommandsForKey extends SafeCommandsForKey implements Acco
this.current = cfk;
}
@Override
public void overrideSink(NotifySink overrideSink)
{
((CommandsForKeyCacheEntry)global).overrideSink = overrideSink;
}
@Override
public NotifySink overrideSink()
{
return ((CommandsForKeyCacheEntry)global).overrideSink;
}
public CommandsForKey original()
{
checkNotInvalidated();

View File

@ -32,6 +32,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -72,6 +73,8 @@ import accord.primitives.Seekables;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.primitives.TxnId.FastPath;
import accord.primitives.TxnId.FastPaths;
import accord.topology.ActiveEpochs;
import accord.topology.EpochReady;
import accord.topology.Shard;
@ -87,9 +90,9 @@ import accord.utils.async.AsyncResult;
import accord.utils.async.AsyncResults;
import org.apache.cassandra.concurrent.Shutdownable;
import org.apache.cassandra.config.AccordSpec;
import org.apache.cassandra.config.AccordSpec.CatchupMode;
import org.apache.cassandra.config.AccordSpec.JournalSpec.ReplaySavePoint;
import org.apache.cassandra.config.AccordConfig;
import org.apache.cassandra.config.AccordConfig.CatchupMode;
import org.apache.cassandra.config.AccordConfig.JournalConfig.ReplaySavePoint;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
@ -158,23 +161,24 @@ import org.apache.cassandra.utils.concurrent.ImmediateFuture;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import static accord.api.Journal.TopologyUpdate;
import static accord.api.ProtocolModifiers.Toggles.FastExec.MAY_BYPASS_SAFESTORE;
import static accord.api.ProtocolModifiers.FastExecution.MAY_BYPASS_SAFESTORE;
import static accord.impl.progresslog.DefaultProgressLog.ModeFlag.CATCH_UP;
import static accord.local.durability.DurabilityService.SyncLocal.Self;
import static accord.local.durability.DurabilityService.SyncRemote.All;
import static accord.messages.SimpleReply.Ok;
import static accord.primitives.Txn.Kind.ExclusiveSyncPoint;
import static accord.primitives.Txn.Kind.Write;
import static accord.primitives.TxnId.MediumPath.NoMediumPath;
import static accord.primitives.TxnId.MediumPath.TrackStable;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
import static org.apache.cassandra.concurrent.ExecutorFactory.SimulatorThreadTag.JOB;
import static org.apache.cassandra.concurrent.ExecutorFactory.SystemThreadTag.DAEMON;
import static org.apache.cassandra.config.AccordSpec.CatchupMode.DISABLED;
import static org.apache.cassandra.config.AccordSpec.CatchupMode.FALLBACK_TO_HARD;
import static org.apache.cassandra.config.AccordSpec.CatchupMode.HARD;
import static org.apache.cassandra.config.AccordSpec.JournalSpec.ReplayMode.RESET;
import static org.apache.cassandra.config.AccordConfig.CatchupMode.FALLBACK_TO_HARD;
import static org.apache.cassandra.config.AccordConfig.CatchupMode.HARD;
import static org.apache.cassandra.config.AccordConfig.JournalConfig.ReplayMode.RESET;
import static org.apache.cassandra.config.DatabaseDescriptor.getAccord;
import static org.apache.cassandra.config.DatabaseDescriptor.getAccordCommandStoreShardCount;
import static org.apache.cassandra.config.DatabaseDescriptor.getAccordGlobalDurabilityCycle;
@ -276,12 +280,10 @@ public class AccordService implements IAccordService, Shutdownable
private static final Future<Void> EPOCH_READY = ImmediateFuture.success(null);
static
{
ProtocolModifiers.Toggles.setPermitLocalExecution(true);
ProtocolModifiers.Toggles.setRequiresUniqueHlcs(true);
ProtocolModifiers.Toggles.setFastReadExecMayResendTxn(true);
ProtocolModifiers.Toggles.setFastReadExec(MAY_BYPASS_SAFESTORE);
ProtocolModifiers.Toggles.setFastWriteExec(MAY_BYPASS_SAFESTORE);
ProtocolModifiers.Toggles.setDataStoreDetectsFutureReads(true);
ProtocolModifiers.Configure.setDataStoreRequiresUniqueHlcs(true);
ProtocolModifiers.Configure.setDataStoreDetectsFutureReads(true);
ProtocolModifiers.Configure.setFastReadExecution(MAY_BYPASS_SAFESTORE);
ProtocolModifiers.Configure.setFastWriteExecution(MAY_BYPASS_SAFESTORE);
}
private enum State { INIT, STARTING, STARTED, STOPPED, SHUTTING_DOWN, SHUTDOWN }
@ -322,6 +324,10 @@ public class AccordService implements IAccordService, Shutdownable
unsafeInstance = instance = requestInstance = replyInstance = NOOP_SERVICE;
}
public static void touch()
{
}
public static IAccordService tryGetUnsafe()
{
return unsafeInstance;
@ -345,8 +351,7 @@ public class AccordService implements IAccordService, Shutdownable
public static IVerbHandler<Void> watermarkHandlerOrNoop()
{
if (!isSetup()) return ignore -> {};
AccordService i = (AccordService) instance();
return i.topologyService().watermarkCollector().handler;
return instance().topologyService().watermarkCollector().handler;
}
public static IVerbHandler<? extends Request> requestHandlerOrNoop()
@ -448,14 +453,14 @@ public class AccordService implements IAccordService, Shutdownable
AccordAgent agent = FBUtilities.construct(CassandraRelevantProperties.ACCORD_AGENT_CLASS.getString(AccordAgent.class.getName()), "AccordAgent");
agent.setup(localId);
AccordTimeService time = new AccordTimeService();
final RequestCallbacks callbacks = new RequestCallbacks(time);
this.scheduler = new AccordScheduler();
final RequestCallbacks callbacks = new RequestCallbacks(time, scheduler);
this.dataStore = new AccordDataStore();
this.journal = new AccordJournal(DatabaseDescriptor.getAccord().journal);
this.endpointMapper = new EndpointMapping.Updateable();
this.messageSink = new AccordMessageSink(endpointMapper, callbacks);
this.topologyService = new AccordTopologyService(localId, endpointMapper);
this.fastPathCoordinator = AccordFastPathCoordinator.create(localId, endpointMapper);
this.messageSink = new AccordMessageSink(endpointMapper, callbacks);
this.node = new Node(localId,
messageSink,
topologyService,
@ -479,6 +484,38 @@ public class AccordService implements IAccordService, Shutdownable
this.responseHandler = new AccordResponseVerbHandler<>(callbacks, endpointMapper);
}
public static void applyProtocolModifiers(AccordConfig config)
{
if (config.coordinator_backlog_execution != null)
ProtocolModifiers.Configure.setCoordinatorBacklogExecution(config.coordinator_backlog_execution);
if (config.permit_coordinator_local_execution != null)
ProtocolModifiers.Configure.setPermitCoordinatorLocalExecution(config.permit_coordinator_local_execution);
if (config.permit_local_delivery != null)
ProtocolModifiers.Configure.setPermitLocalDelivery(config.permit_local_delivery);
if (config.permit_fast_path != null)
ProtocolModifiers.Configure.setPermittedFastPaths(new FastPaths(Stream.of(FastPath.values()).filter(fp -> fp.compareTo(config.permit_fast_path) <= 0).toArray(FastPath[]::new)));
if (config.permit_track_stable_medium_path != null)
ProtocolModifiers.Configure.setDefaultMediumPath(config.permit_track_stable_medium_path ? TrackStable : NoMediumPath);
if (config.permit_fast_quorum_medium_path != null)
ProtocolModifiers.Configure.setPermitFastQuorumMediumPath(config.permit_fast_quorum_medium_path);
if (config.always_inform_durable_single_key != null)
ProtocolModifiers.Configure.setInformOfSingleKeyDurabilityIfDepsSizeAtLeast(0);
if (config.replica_execution != null)
ProtocolModifiers.Configure.setReplicaExecution(config.replica_execution);
if (config.replica_execution_distributed_persist_chance != null)
ProtocolModifiers.Configure.setReplicaExecuteDistributedPersistChance(config.replica_execution_distributed_persist_chance);
if (config.fast_read_execution != null)
ProtocolModifiers.Configure.setFastReadExecution(config.fast_read_execution);
if (config.fast_write_execution != null)
ProtocolModifiers.Configure.setFastWriteExecution(config.fast_write_execution);
if (config.clean_cfk_before != null)
ProtocolModifiers.Configure.setCleanCfkBefore(config.clean_cfk_before);
if (config.send_stable != null)
ProtocolModifiers.Configure.setSendStableMessages(config.send_stable);
if (config.send_minimal != null)
ProtocolModifiers.Configure.setSendMinimalCommits(config.send_minimal);
}
@Override
public synchronized void localStartup()
{
@ -497,6 +534,7 @@ public class AccordService implements IAccordService, Shutdownable
case EXIT:
throw new RuntimeException("Stop marker is older than start marker (" + stopMarker + '<' + startMarker + ") , so cannot assume we have a complete log of our votes in any consensus groups. Exiting.");
case ALLOW_UNSAFE_STARTUP:
case UNSAFE_STARTUP:
logger.warn("Stop marker is older than start marker ({}<{}), so cannot assume we have a complete log of our votes in any consensus groups. Continuing to startup as configured.", stopMarker, startMarker);
break;
@ -611,7 +649,11 @@ public class AccordService implements IAccordService, Shutdownable
private void distributedStartupInternal()
{
finishTopologyInitialization();
WatermarkCollector.fetchAndReportWatermarksAsync(topology());
WatermarkCollector.fetchAndReportWatermarksAsync(topology())
.addCallback((success, failure) -> {
topologyService.afterStartup(node);
});
fastPathCoordinator.start();
ClusterMetadataService.instance().log().addListener(fastPathCoordinator);
@ -638,8 +680,8 @@ public class AccordService implements IAccordService, Shutdownable
void catchup()
{
AccordSpec spec = DatabaseDescriptor.getAccord();
if (spec.catchup_on_start == DISABLED)
AccordConfig spec = DatabaseDescriptor.getAccord();
if (spec.catchup_on_start == CatchupMode.DISABLED)
{
logger.info("Catchup disabled; continuing to startup");
return;

View File

@ -85,9 +85,8 @@ import static accord.local.LoadKeysFor.RECOVERY;
import static accord.local.LoadKeysFor.WRITE;
import static accord.primitives.Routable.Domain.Key;
import static accord.primitives.Txn.Kind.EphemeralRead;
import static accord.utils.Invariants.illegalState;
import static org.apache.cassandra.config.CassandraRelevantProperties.DTEST_ACCORD_JOURNAL_SANITY_CHECK_ENABLED;
import static org.apache.cassandra.config.DatabaseDescriptor.getPartitioner;
import static org.apache.cassandra.service.accord.AccordTask.State.ASSIGNED;
import static org.apache.cassandra.service.accord.AccordTask.State.CANCELLED;
import static org.apache.cassandra.service.accord.AccordTask.State.FAILED;
import static org.apache.cassandra.service.accord.AccordTask.State.FINISHED;
@ -99,13 +98,14 @@ import static org.apache.cassandra.service.accord.AccordTask.State.SCANNING_RANG
import static org.apache.cassandra.service.accord.AccordTask.State.WAITING_TO_LOAD;
import static org.apache.cassandra.service.accord.AccordTask.State.WAITING_TO_RUN;
import static org.apache.cassandra.service.accord.AccordTask.State.WAITING_TO_SCAN_RANGES;
import static org.apache.cassandra.service.accord.debug.DebugExecution.DEBUG_EXECUTION;
import static org.apache.cassandra.service.accord.debug.DebugExecution.DebugTask.SANITY_CHECK;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
public abstract class AccordTask<R> extends SubmittableTask implements Function<SafeCommandStore, R>, Cancellable, DebuggableTask
{
private static final Logger logger = LoggerFactory.getLogger(AccordTask.class);
private static final NoSpamLogger noSpamLogger = NoSpamLogger.getLogger(logger, 1, TimeUnit.MINUTES);
private static final boolean SANITY_CHECK = DTEST_ACCORD_JOURNAL_SANITY_CHECK_ENABLED.getBoolean();
static class ForFunction<R> extends AccordTask<R>
{
@ -161,11 +161,12 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
WAITING_TO_LOAD(INITIALIZED, SCANNING_RANGES),
LOADING(INITIALIZED, SCANNING_RANGES, WAITING_TO_LOAD),
WAITING_TO_RUN(INITIALIZED, SCANNING_RANGES, WAITING_TO_LOAD, LOADING),
RUNNING(WAITING_TO_RUN),
ASSIGNED(WAITING_TO_RUN),
RUNNING(ASSIGNED),
PERSISTING(RUNNING),
FINISHED(RUNNING, PERSISTING),
CANCELLED(WAITING_TO_SCAN_RANGES, SCANNING_RANGES, WAITING_TO_LOAD, LOADING, WAITING_TO_RUN),
FAILED(WAITING_TO_SCAN_RANGES, SCANNING_RANGES, WAITING_TO_LOAD, LOADING, WAITING_TO_RUN, RUNNING, PERSISTING);
CANCELLED(WAITING_TO_SCAN_RANGES, SCANNING_RANGES, WAITING_TO_LOAD, LOADING, WAITING_TO_RUN, ASSIGNED),
FAILED(WAITING_TO_SCAN_RANGES, SCANNING_RANGES, WAITING_TO_LOAD, LOADING, WAITING_TO_RUN, ASSIGNED, RUNNING, PERSISTING);
private final int permittedFrom;
@ -218,8 +219,6 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
@Nullable private TaskQueue queued;
private BiConsumer<? super R, Throwable> callback;
private List<Command> sanityCheck;
public long createdAt = nanoTime(), waitingToRunAt, runningAt, completedAt;
public AccordTask(@Nonnull AccordCommandStore commandStore, PreLoadContext preLoadContext)
{
@ -311,23 +310,6 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
{
Invariants.require(rangeScanner == null || rangeScanner.scanned);
Invariants.require(loading == null && waitingToLoad == null, "WAITING_TO_RUN => no loading or waiting; found %s", this, AccordTask::toDescription);
waitingToRunAt = nanoTime();
commandStore.executor().elapsedPreparingToRun.increment(waitingToRunAt - createdAt, runningAt);
}
else if (state == RUNNING)
{
runningAt = nanoTime();
if (waitingToRunAt == 0)
{
waitingToRunAt = runningAt;
commandStore.executor().elapsedPreparingToRun.increment(waitingToRunAt - createdAt, runningAt);
}
commandStore.executor().elapsedWaitingToRun.increment(runningAt - waitingToRunAt, runningAt);
commandStore.executor().keys.increment(commandsForKey == null ? 0 : commandsForKey.size(), runningAt);
}
else if (state.isExecuted() && completedAt == 0)
{
completedAt = nanoTime();
}
}
@ -345,15 +327,34 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
@Override
protected Cancellable start(BiConsumer<? super R, Throwable> callback)
{
Invariants.require(AccordTask.this.callback == null);
AccordTask.this.callback = callback;
commandStore.tryPreSetup(AccordTask.this);
preSetup(callback);
commandStore.executor().submit(AccordTask.this);
return AccordTask.this;
}
};
}
public AsyncChain<R> priorityChain()
{
return new AsyncChains.Head<>()
{
@Override
protected Cancellable start(BiConsumer<? super R, Throwable> callback)
{
preSetup(callback);
commandStore.executor().submitPriority(AccordTask.this);
return AccordTask.this;
}
};
}
private void preSetup(BiConsumer<? super R, Throwable> callback)
{
Invariants.require(this.callback == null);
this.callback = callback;
commandStore.tryPreSetup(this);
}
// to be invoked only by the CommandStore owning thread, to take references to objects already in use by the current execution
public void presetup(AccordTask<?> parent)
{
@ -628,22 +629,21 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
{
if (SANITY_CHECK)
{
if (sanityCheck == null)
sanityCheck = new ArrayList<>(commands.size());
sanityCheck.add(safeCommand.current());
if (debug.sanityCheck == null)
debug.sanityCheck = new ArrayList<>(commands.size());
debug.sanityCheck.add(safeCommand.current());
}
}
private void save(List<Journal.CommandUpdate> diffs, Runnable onFlush)
{
if (sanityCheck != null)
if (SANITY_CHECK && debug.sanityCheck != null)
{
Invariants.require(SANITY_CHECK);
Condition condition = Condition.newOneTimeCondition();
this.commandStore.appendCommands(diffs, condition::signal);
condition.awaitUninterruptibly();
for (Command check : sanityCheck)
for (Command check : debug.sanityCheck)
this.commandStore.sanityCheckCommand(commandStore.unsafeGetRedundantBefore(), check);
if (onFlush != null) onFlush.run();
@ -655,9 +655,9 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
}
@Override
protected void preRunExclusive()
protected void preRunExclusive(Thread assigned)
{
state(RUNNING);
state(ASSIGNED);
queued = null;
if (rangeScanner != null)
{
@ -673,6 +673,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
@Override
public void runInternal()
{
runningAt = nanoTime();
logger.trace("Running {} with state {}", this, state);
AccordSafeCommandStore safeStore = null;
try (Closeable close = locals.get())
@ -680,8 +681,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
if (Tracing.isTracing())
Tracing.trace(preLoadContext.describe());
if (state != RUNNING)
throw illegalState("Unexpected state " + toDescription());
state(RUNNING);
safeStore = commandStore.begin(this, commandsForRanges);
R result = apply(safeStore);
@ -717,6 +717,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
commandStore.complete(safeStore);
safeStore = null;
if (DEBUG_EXECUTION) debug.onRunComplete();
if (!flush)
finish(result, null);
}
@ -729,10 +730,6 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
}
throw t;
}
finally
{
logger.trace("Exiting {}", this);
}
}
public void fail(Throwable throwable)
@ -742,8 +739,8 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
try
{
commandStore.agent().onException(throwable);
state(FAILED);
commandStore.agent().onException(throwable);
}
finally
{
@ -757,17 +754,16 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
fail(throwable);
}
protected void cleanupExclusive()
@Override
protected void cleanupExclusive(AccordExecutor executor)
{
super.cleanupExclusive(executor);
Invariants.expect(state.isExecuted());
releaseResources(commandStore.cachesExclusive());
if (runningAt != 0)
{
commandStore.executor().elapsedRunning.increment(completedAt - runningAt, completedAt);
}
executor.keys.increment(commandsForKey == null ? 0 : commandsForKey.size(), runningAt);
if (histogramBuffer != null)
{
histogramBuffer.flush(completedAt);
histogramBuffer.flush(cleanupAt);
histogramBuffer = null;
}
}
@ -786,16 +782,21 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
@Override
public void cancel()
{
commandStore.executor().cancel(this);
if (!state.isComplete())
commandStore.executor().cancel(this);
}
public void cancelExclusive()
void cancelExclusive()
{
logger.info("Cancelling {}", preLoadContext);
state(CANCELLED);
if (rangeScanner != null)
rangeScanner.cancelled = true;
if (callback != null)
commandStore.executor().submit(() -> callback.accept(null, new CancellationException()));
{
if (commandStore.executor().isInLoop()) callback.accept(null, new CancellationException());
else commandStore.executor().submit(() -> callback.accept(null, new CancellationException()));
}
}
void cancelExclusive(AccordExecutor owner)
@ -1064,7 +1065,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
public class RangeTxnScanner extends AccordExecutor.AbstractIOTask
{
final Map<Timestamp, Summary> summaries = new HashMap<>();
final Map<Timestamp, Summary> mutexSummaries = Collections.synchronizedMap(summaries);
final Map<Timestamp, Summary> guardedSummaries = Collections.synchronizedMap(summaries);
RangeIndex.Loader loader;
boolean scanned;
@ -1074,7 +1075,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
protected void runInternal()
{
loader.load(mutexSummaries, () -> cancelled);
loader.load(guardedSummaries, () -> cancelled);
}
PreLoadContext preLoadContext()
@ -1105,7 +1106,7 @@ public abstract class AccordTask<R> extends SubmittableTask implements Function<
void startInternal(Caches caches)
{
loader = commandStore.rangeIndex().loader(preLoadContext.primaryTxnId(), preLoadContext.executeAt(), preLoadContext.loadKeysFor(), preLoadContext.keys());
loader.loadExclusive(mutexSummaries, caches);
loader.loadExclusive(guardedSummaries, caches);
}
public void scannedExclusive()

View File

@ -23,14 +23,21 @@ import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import accord.api.Tracing;
import accord.local.MapReduceCommandStores;
import accord.local.Node;
import accord.messages.AbstractRequest;
import accord.messages.Request;
import accord.utils.Invariants;
import org.apache.cassandra.net.IVerbHandler;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.service.accord.debug.AccordRemoteTracing;
import org.apache.cassandra.service.accord.topology.AccordEndpointMapper;
import org.apache.cassandra.utils.NoSpamLogger;
import static org.apache.cassandra.net.ParamType.ACCORD_TRACING;
public class AccordVerbHandler<T extends Request> implements IVerbHandler<T>
{
private static final Logger logger = LoggerFactory.getLogger(AccordVerbHandler.class);
@ -51,6 +58,12 @@ public class AccordVerbHandler<T extends Request> implements IVerbHandler<T>
logger.trace("Receiving {} from {}", message.payload, message.from());
T request = message.payload;
Tracing tracing = (Tracing) message.header.params().get(ACCORD_TRACING);
if (tracing != null && request instanceof AbstractRequest<?, ?>)
{
Invariants.require(tracing instanceof AccordRemoteTracing);
((MapReduceCommandStores<?, ?>) request).setTracing(tracing);
}
/*
* TODO (desired): messages are retained on heap until the node catches up to waitForEpoch,

View File

@ -27,6 +27,8 @@ import accord.local.Command;
import accord.local.CommandSummaries;
import accord.local.LoadKeysFor;
import accord.local.MaxDecidedRX;
import accord.local.MinimalCommand;
import accord.local.MinimalCommand.MinimalWithDeps;
import accord.local.RedundantBefore;
import accord.primitives.Ranges;
import accord.primitives.Routable;
@ -66,13 +68,13 @@ public interface RangeIndex
{
if (loadKeysFor != RECOVERY)
{
Command.Minimal cmd = commandStore().loadMinimal(txnId);
MinimalCommand cmd = commandStore().loadMinimal(txnId);
if (cmd != null)
return ifRelevant(cmd);
}
else
{
Command.MinimalWithDeps cmd = commandStore().loadMinimalWithDeps(txnId);
MinimalWithDeps cmd = commandStore().loadMinimalWithDeps(txnId);
if (cmd != null)
return ifRelevant(cmd);
}

View File

@ -22,7 +22,6 @@ import java.io.IOException;
import com.google.common.annotations.VisibleForTesting;
import accord.api.ProtocolModifiers.RangeSpec;
import accord.api.RoutingKey;
import accord.primitives.Range;
import accord.utils.Invariants;
@ -38,12 +37,14 @@ import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.accord.api.TokenKey;
import org.apache.cassandra.utils.ObjectSizes;
import static accord.api.ProtocolModifiers.isRangeEndInclusive;
public class TokenRange extends Range
{
public static final long EMPTY_SIZE = ObjectSizes.measure(new TokenRange(TokenKey.min(TableId.fromLong(0), Murmur3Partitioner.instance), TokenKey.max(TableId.fromLong(0), Murmur3Partitioner.instance)));
static
{
Invariants.require(RangeSpec.isEndInclusive());
Invariants.require(isRangeEndInclusive());
}
// Don't make this public use create or createUnsafe

View File

@ -35,6 +35,7 @@ import accord.api.CoordinatorEventListener;
import accord.api.OwnershipEventListener;
import accord.api.ProgressLog.BlockedUntil;
import accord.api.ReplicaEventListener;
import accord.api.Result;
import accord.api.RoutingKey;
import accord.api.Tracing;
import accord.coordinate.Coordination;
@ -47,12 +48,11 @@ import accord.local.Node;
import accord.local.SafeCommand;
import accord.local.SafeCommandStore;
import accord.local.TimeService;
import accord.messages.ReplyContext;
import accord.messages.MessageType;
import accord.primitives.Keys;
import accord.primitives.Participants;
import accord.primitives.Ranges;
import accord.primitives.Routable;
import accord.primitives.Status;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.Txn.Kind;
@ -68,19 +68,19 @@ import accord.utils.async.AsyncChain;
import accord.utils.async.AsyncChains;
import accord.utils.async.Cancellable;
import org.apache.cassandra.config.AccordSpec;
import org.apache.cassandra.config.AccordConfig;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.exceptions.RequestTimeoutException;
import org.apache.cassandra.metrics.AccordReplicaMetrics;
import org.apache.cassandra.metrics.AccordSystemMetrics;
import org.apache.cassandra.net.ResponseContext;
import org.apache.cassandra.service.RetryStrategy;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.debug.AccordTracing;
import org.apache.cassandra.service.accord.serializers.TableMetadatasAndKeys;
import org.apache.cassandra.service.accord.txn.TxnQuery;
import org.apache.cassandra.service.accord.txn.TxnRead;
import org.apache.cassandra.service.accord.txn.TxnResult;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.JVMStabilityInspector;
import org.apache.cassandra.utils.NoSpamLogger;
@ -95,6 +95,8 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.cassandra.config.DatabaseDescriptor.getAccordScheduleDurabilityTxnIdLag;
import static org.apache.cassandra.config.DatabaseDescriptor.getReadRpcTimeout;
import static org.apache.cassandra.service.accord.api.AccordWaitStrategies.expireEpochWait;
import static org.apache.cassandra.service.accord.api.AccordWaitStrategies.expireSyncPoint;
import static org.apache.cassandra.service.accord.api.AccordWaitStrategies.expireTxn;
import static org.apache.cassandra.service.accord.api.AccordWaitStrategies.fetch;
import static org.apache.cassandra.service.accord.api.AccordWaitStrategies.recover;
import static org.apache.cassandra.service.accord.api.AccordWaitStrategies.retryBootstrap;
@ -104,7 +106,7 @@ import static org.apache.cassandra.service.accord.api.AccordWaitStrategies.retry
import static org.apache.cassandra.service.accord.api.AccordWaitStrategies.retrySyncPoint;
import static org.apache.cassandra.service.accord.api.AccordWaitStrategies.slowRead;
import static org.apache.cassandra.service.accord.api.AccordWaitStrategies.slowTxnPreaccept;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
import static org.apache.cassandra.service.accord.txn.TxnResult.Kind.txn_data;
// TODO (expected): merge with AccordService
public class AccordAgent implements Agent, OwnershipEventListener
@ -124,7 +126,7 @@ public class AccordAgent implements Agent, OwnershipEventListener
private final AccordTracing tracing = new AccordTracing();
private final RandomSource random = new DefaultRandom();
protected Node.Id self;
protected AccordSpec config;
protected AccordConfig config;
public AccordAgent()
{
@ -248,27 +250,19 @@ public class AccordAgent implements Agent, OwnershipEventListener
@Override
public long cfkHlcPruneDelta()
{
return SECONDS.toMicros(10L);
return config.commands_for_key_prune_delta.to(MICROSECONDS);
}
@Override
public int cfkPruneInterval()
{
return 32;
return config.commands_for_key_prune_interval;
}
// TODO (expected): we probably want additional configuration here
@Override
public long maxConflictsHlcPruneDelta()
{
return SECONDS.toMicros(1);
}
// TODO (expected): I don't think we even need this - just prune each time we have doubled in size
@Override
public long maxConflictsPruneInterval()
{
return 1024;
return config.max_conflicts_prune_delta.to(MICROSECONDS);
}
@Override
@ -456,40 +450,29 @@ public class AccordAgent implements Agent, OwnershipEventListener
}
@Override
public long expiresAt(ReplyContext replyContext, TimeUnit unit)
public long selfSlowAt(TxnId txnId, MessageType type, TimeUnit unit)
{
return unit.convert(((ResponseContext)replyContext).expiresAtNanos(), NANOSECONDS);
if (type.getClass() == MessageType.StandardMessage.class)
{
switch ((MessageType.StandardMessage)type)
{
case PRE_ACCEPT_REQ:
return unit.convert(slowTxnPreaccept.computeWaitUntil(1), unit);
case READ_EPHEMERAL_REQ:
case READ_REQ:
case STABLE_THEN_READ_REQ:
return unit.convert(slowRead.computeWaitUntil(1), NANOSECONDS);
}
}
return -1;
}
@Override
public long selfSlowAt(TxnId txnId, Status.Phase phase, TimeUnit unit)
public long selfExpiresAt(TxnId txnId, MessageType type, TimeUnit unit)
{
switch (phase)
{
default: throw new UnhandledEnum(phase);
case PreAccept: return unit.convert(slowTxnPreaccept.computeWaitUntil(1), NANOSECONDS);
case Execute: return unit.convert(slowRead.computeWaitUntil(1), NANOSECONDS);
}
}
@Override
public long selfExpiresAt(TxnId txnId, Status.Phase phase, TimeUnit unit)
{
long delayNanos;
switch (txnId.kind())
{
default: throw new UnhandledEnum(txnId.kind());
case Write:
delayNanos = DatabaseDescriptor.getWriteRpcTimeout(NANOSECONDS);
break;
case EphemeralRead:
case Read:
delayNanos = DatabaseDescriptor.getReadRpcTimeout(NANOSECONDS);
break;
case ExclusiveSyncPoint:
delayNanos = DatabaseDescriptor.getAccordRangeSyncPointTimeoutNanos();
}
return unit.convert(nanoTime() + delayNanos, NANOSECONDS);
if (txnId == null || txnId.equals(TxnId.NONE))
return DatabaseDescriptor.getRpcTimeout(unit);
return unit.convert((txnId.isSyncPoint() ? expireSyncPoint : expireTxn).computeWaitUntil(1), NANOSECONDS);
}
@Override
@ -516,4 +499,10 @@ public class AccordAgent implements Agent, OwnershipEventListener
{
return node.now() - (100 + getAccordScheduleDurabilityTxnIdLag(MICROSECONDS));
}
@Override
public boolean reportRemoteSuccess(Result success)
{
return success instanceof TxnResult && ((TxnResult) success).kind() == txn_data;
}
}

View File

@ -19,6 +19,7 @@
package org.apache.cassandra.service.accord.api;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@ -29,7 +30,7 @@ import org.apache.cassandra.concurrent.ExecutorFactory;
import org.apache.cassandra.concurrent.ScheduledExecutorPlus;
import org.apache.cassandra.concurrent.Shutdownable;
public class AccordScheduler implements Scheduler, Shutdownable
public class AccordScheduler implements Scheduler, Shutdownable, Executor
{
private final ScheduledExecutorPlus scheduledExecutor = ExecutorFactory.Global.executorFactory().scheduled(false, "AccordScheduled");
@ -112,4 +113,10 @@ public class AccordScheduler implements Scheduler, Shutdownable
{
return scheduledExecutor.awaitTermination(timeout, units);
}
@Override
public void execute(Runnable command)
{
scheduledExecutor.execute(command);
}
}

View File

@ -22,13 +22,22 @@ import java.util.concurrent.TimeUnit;
import accord.local.TimeService;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.utils.FBUtilities;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.utils.Clock.Global.currentTimeMicros;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
public class AccordTimeService implements TimeService
{
private static final boolean PRECISE_MICROS;
static
{
Boolean precise = DatabaseDescriptor.getAccord().precise_micros;
PRECISE_MICROS = precise == null || precise;
}
@Override
public long now()
{
@ -37,7 +46,7 @@ public class AccordTimeService implements TimeService
public static long nowMicros()
{
return TimeUnit.MILLISECONDS.toMicros(Clock.Global.currentTimeMillis());
return PRECISE_MICROS ? currentTimeMicros() : FBUtilities.timestampMicros();
}
@Override

View File

@ -22,7 +22,7 @@ import javax.annotation.Nullable;
import accord.primitives.TxnId;
import org.apache.cassandra.config.AccordSpec;
import org.apache.cassandra.config.AccordConfig;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.StringRetryStrategy;
import org.apache.cassandra.net.Verb;
@ -84,7 +84,7 @@ public class AccordWaitStrategies
static
{
AccordSpec config = DatabaseDescriptor.getAccord();
AccordConfig config = DatabaseDescriptor.getAccord();
setSlowRead(config.slow_read);
setSlowTxnPreaccept(config.slow_txn_preaccept);
setSlowSyncPointPreaccept(config.slow_syncpoint_preaccept);

View File

@ -0,0 +1,385 @@
/*
* 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.io.IOException;
import java.util.ArrayList;
import java.util.List;
import accord.api.Tracing;
import accord.local.CommandStore;
import accord.primitives.TxnId;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.IVerbHandler;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.net.ParamType;
import org.apache.cassandra.net.Verb;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.api.AccordAgent;
import org.apache.cassandra.service.accord.api.AccordTimeService;
import org.apache.cassandra.service.accord.debug.AccordTracing.TxnEvent;
import org.apache.cassandra.service.accord.serializers.CommandSerializers;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.membership.NodeId;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
import static org.apache.cassandra.utils.MonotonicClock.Global.preciseTime;
public class AccordRemoteTracing implements Tracing
{
public static class AccordRemoteTrace {}
public static final class AccordTraceOut extends AccordRemoteTrace
{
final TxnId txnId;
final long idMicros;
final long receivedAtMicros;
final long nanosSinceReceived;
final int commandStoreId;
final String message;
AccordTraceOut(TxnId txnId, long idMicros, long receivedAtMicros, long nanosSinceReceived, int commandStoreId, String message)
{
this.txnId = txnId;
this.idMicros = idMicros;
this.receivedAtMicros = receivedAtMicros;
this.nanosSinceReceived = nanosSinceReceived;
this.commandStoreId = commandStoreId;
this.message = message;
}
}
public static final class AccordTraceIn extends AccordRemoteTrace
{
final TxnId txnId;
final long idMicros;
final long atNanos;
final int commandStoreId;
final String message;
AccordTraceIn(TxnId txnId, long idMicros, long atNanos, int commandStoreId, String message)
{
this.txnId = txnId;
this.idMicros = idMicros;
this.atNanos = atNanos;
this.commandStoreId = commandStoreId;
this.message = message;
}
}
public static final class AccordTracingIn implements Tracing
{
final TxnId txnId;
final long idMicros;
final long onWireAtNanos;
final List<AccordTraceIn> messages;
public AccordTracingIn(TxnId txnId, long idMicros, long onWireAtNanos, List<AccordTraceIn> messages)
{
this.txnId = txnId;
this.idMicros = idMicros;
this.onWireAtNanos = onWireAtNanos;
this.messages = messages;
}
void report(NodeId from)
{
AccordTracing tracing = ((AccordAgent)AccordService.unsafeInstance().agent()).tracing();
long offWireAtNanos = nanoTime();
long onWireAtNanos = Math.min(offWireAtNanos - 1, this.onWireAtNanos);
int fromId = from == null ? Integer.MAX_VALUE : from.id();
for (AccordTraceIn message : messages)
tracing.report(message, fromId);
tracing.report(new AccordTraceIn(txnId, idMicros, onWireAtNanos, -1, "Reply on wire"), fromId);
tracing.report(new AccordTraceIn(txnId, idMicros, offWireAtNanos, -1, "Reply off wire from " + from) , -1);
}
@Override
public void trace(CommandStore commandStore, String message)
{
throw new UnsupportedOperationException();
}
}
public static final class AccordTracingOut implements Tracing
{
final TxnId txnId;
final long idMicros;
final long receivedAtMicros;
final long receivedAtNanos;
final List<BufferedMessage> messages;
AccordTracingOut(TxnId txnId, long idMicros, long receivedAtMicros, long receivedAtNanos, List<BufferedMessage> messages)
{
this.txnId = txnId;
this.idMicros = idMicros;
this.receivedAtMicros = receivedAtMicros;
this.receivedAtNanos = receivedAtNanos;
this.messages = messages;
}
@Override
public void trace(CommandStore commandStore, String message)
{
if (message.length() > 100)
message = message.substring(0, 100);
messages.add(new BufferedMessage(nanoTime(), commandStore == null ? -1 : commandStore.id(), message));
}
}
static class BufferedMessage
{
final long atNanos;
final int commandStoreId;
final String message;
BufferedMessage(long atNanos, int commandStoreId, String message)
{
this.atNanos = atNanos;
this.commandStoreId = commandStoreId;
this.message = message;
}
}
final TxnId txnId;
final long idMicros;
private InetAddressAndPort replyTo;
final long receivedAtMicros = AccordTimeService.nowMicros();
final long receivedAtNanos = nanoTime();
List<BufferedMessage> messages = new ArrayList<>();
private AccordRemoteTracing(TxnId txnId, long idMicros)
{
this.txnId = txnId;
this.idMicros = idMicros;
}
public void setReplyTo(InetAddressAndPort from)
{
replyTo = from;
}
@Override
public synchronized void trace(CommandStore commandStore, String message)
{
long atNanos = nanoTime();
int commandStoreId = commandStore == null ? -1 : commandStore.id();
// TODO (expected): make this configurable
if (message.length() > 100)
message = message.substring(0, 100);
if (messages == null)
{
long nanosSinceReceived = atNanos - receivedAtNanos;
Message<?> reply = Message.out(Verb.ACCORD_REMOTE_TRACE, new AccordTraceOut(txnId, idMicros, receivedAtMicros, nanosSinceReceived, commandStoreId, message));
MessagingService.instance().send(reply, replyTo);
}
else
{
messages.add(new BufferedMessage(atNanos, commandStoreId, message));
}
}
@Override
public synchronized Tracing send()
{
List<BufferedMessage> messages = this.messages;
if (messages == null) messages = new ArrayList<>();
else this.messages = null;
return new AccordTracingOut(txnId, idMicros, receivedAtMicros, receivedAtNanos, messages);
}
public static final IVerbHandler<AccordRemoteTrace> traceMessageHandler = message -> {
AccordTracing tracing = ((AccordAgent)AccordService.unsafeInstance().agent()).tracing();
AccordTraceIn trace = (AccordTraceIn) message.payload;
NodeId nodeId = ClusterMetadata.current().directory.peerId(message.from());
tracing.report(trace, nodeId == null ? Integer.MAX_VALUE : nodeId.id());
};
static final int REPLY_FLAG = 1;
public static final IVersionedSerializer<Tracing> tracingSerializer = new IVersionedSerializer<>()
{
@Override
public void serialize(Tracing t, DataOutputPlus out, int version) throws IOException
{
int flags = 0;
if (t.getClass() == TxnEvent.class)
{
TxnEvent event = (TxnEvent) t;
out.writeUnsignedVInt32(flags);
CommandSerializers.txnId.serialize(event.txnId(), out);
out.writeLong(event.idMicros);
}
else
{
AccordTracingOut tracing = (AccordTracingOut) t;
flags |= REPLY_FLAG;
out.writeUnsignedVInt32(flags);
CommandSerializers.txnId.serialize(tracing.txnId, out);
out.writeLong(tracing.idMicros);
out.writeLong(tracing.receivedAtMicros);
out.writeLong(nanoTime() - tracing.receivedAtNanos);
out.writeUnsignedVInt32(tracing.messages.size());
for (BufferedMessage message : tracing.messages)
{
out.writeUnsignedVInt(message.atNanos - tracing.receivedAtNanos);
out.writeUnsignedVInt32(1 + message.commandStoreId);
out.writeUTF(message.message);
}
}
}
@Override
public Tracing deserialize(DataInputPlus in, int version) throws IOException
{
int flags = in.readUnsignedVInt32();
TxnId txnId = CommandSerializers.txnId.deserialize(in);
long idMicros = in.readLong();
if ((flags & REPLY_FLAG) == 0)
return new AccordRemoteTracing(txnId, idMicros);
long remoteReceivedAtMicros = in.readLong();
long remoteReceivedAtNanos = preciseTime.translate().fromMicrosSinceEpoch(remoteReceivedAtMicros);
long onWireAtNanos = remoteReceivedAtNanos + in.readLong();
int messageCount = in.readUnsignedVInt32();
List<AccordTraceIn> messages = new ArrayList<>(messageCount);
for (int i = 0 ; i < messageCount ; ++i)
{
long atNanos = remoteReceivedAtNanos + in.readUnsignedVInt();
int commandStoreId = in.readUnsignedVInt32() - 1;
String message = in.readUTF();
messages.add(new AccordTraceIn(txnId, idMicros, atNanos, commandStoreId, message));
}
return new AccordTracingIn(txnId, idMicros, onWireAtNanos, messages);
}
@Override
public long serializedSize(Tracing t, int version)
{
int flags = 0;
if (t.getClass() == TxnEvent.class)
{
TxnEvent event = (TxnEvent) t;
long size = TypeSizes.sizeofUnsignedVInt(flags);
size += CommandSerializers.txnId.serializedSize(event.txnId());
size += TypeSizes.LONG_SIZE;
return size;
}
else
{
AccordTracingOut tracing = (AccordTracingOut) t;
flags |= REPLY_FLAG;
long size = TypeSizes.sizeofUnsignedVInt(flags);
size += CommandSerializers.txnId.serializedSize(tracing.txnId);
size += 3 * TypeSizes.LONG_SIZE;
size += TypeSizes.sizeofUnsignedVInt(tracing.messages.size());
for (BufferedMessage message : tracing.messages)
{
size += TypeSizes.sizeofUnsignedVInt(message.atNanos - tracing.receivedAtNanos);
size += TypeSizes.sizeofUnsignedVInt(1 + message.commandStoreId);
size += TypeSizes.sizeof(message.message);
}
return size;
}
}
};
public static final IVersionedSerializer<AccordRemoteTrace> traceSerializer = new IVersionedSerializer<>()
{
@Override
public void serialize(AccordRemoteTrace t, DataOutputPlus out, int version) throws IOException
{
out.writeUnsignedVInt32(0);
AccordTraceOut trace = (AccordTraceOut) t;
CommandSerializers.txnId.serialize(trace.txnId, out);
out.writeUnsignedVInt(trace.idMicros);
out.writeUnsignedVInt(trace.receivedAtMicros);
out.writeUnsignedVInt(trace.nanosSinceReceived);
out.writeUnsignedVInt32(1 + trace.commandStoreId);
out.writeUTF(trace.message);
}
@Override
public AccordRemoteTrace deserialize(DataInputPlus in, int version) throws IOException
{
in.readUnsignedVInt32();
TxnId txnId = CommandSerializers.txnId.deserialize(in);
long idMicros = in.readUnsignedVInt();
long receivedAtMicros = in.readUnsignedVInt();
long nanosSinceReceived = in.readUnsignedVInt();
long atNanos = nanosSinceReceived + preciseTime.translate().fromMicrosSinceEpoch(receivedAtMicros);
int commandStoreId = 1 + in.readUnsignedVInt32();
String message = in.readUTF();
return new AccordTraceIn(txnId, idMicros, atNanos, commandStoreId, message);
}
@Override
public long serializedSize(AccordRemoteTrace t, int version)
{
AccordTraceOut trace = (AccordTraceOut) t;
long size = TypeSizes.sizeofUnsignedVInt(0);
size += CommandSerializers.txnId.serializedSize(trace.txnId);
size += TypeSizes.sizeofUnsignedVInt(trace.idMicros);
size += TypeSizes.sizeofUnsignedVInt(trace.receivedAtMicros);
size += TypeSizes.sizeofUnsignedVInt(trace.nanosSinceReceived);
size += TypeSizes.sizeofUnsignedVInt(1 + trace.commandStoreId);
size += TypeSizes.sizeof(trace.message);
return size;
}
};
public static void traceOnWire(Message.Header out, InetAddressAndPort to)
{
Object obj = out.params().get(ParamType.ACCORD_TRACING);
if (obj == null)
return;
Tracing tracing = (Tracing) obj;
if (tracing instanceof TxnEvent)
{
NodeId id = ClusterMetadata.current().directory.peerId(to);
tracing.trace(null, "Request on wire to %s", id);
}
}
public static void traceOffWire(Message.Header in)
{
Object obj = in.params().get(ParamType.ACCORD_TRACING);
if (obj == null)
return;
if (obj instanceof AccordRemoteTracing)
{
AccordRemoteTracing tracing = (AccordRemoteTracing) obj;
tracing.setReplyTo(in.from);
tracing.trace(null, "Request off wire");
}
else
{
AccordTracingIn tracing = (AccordTracingIn) obj;
NodeId id = ClusterMetadata.current().directory.peerId(in.from);
tracing.report(id);
}
}
}

View File

@ -22,8 +22,11 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadLocalRandom;
@ -46,6 +49,7 @@ import accord.coordinate.Coordination.CoordinationKind;
import accord.local.CommandStore;
import accord.local.Node;
import accord.primitives.Participants;
import accord.primitives.Routables;
import accord.primitives.TxnId;
import accord.utils.Invariants;
import accord.utils.SortedListMap;
@ -54,11 +58,14 @@ import accord.utils.UnhandledEnum;
import org.apache.cassandra.metrics.AccordCoordinatorMetrics;
import org.apache.cassandra.service.ClientWarn;
import org.apache.cassandra.service.accord.debug.AccordRemoteTracing.AccordTraceIn;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.NoSpamLogger;
import static org.apache.cassandra.service.accord.debug.AccordTracing.BucketMode.LEAKY;
import static org.apache.cassandra.service.accord.debug.AccordTracing.BucketMode.SAMPLE;
import static org.apache.cassandra.service.accord.debug.AccordTracing.BucketMode.SLOWEST;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
public class AccordTracing extends AccordCoordinatorMetrics.Listener
{
@ -68,16 +75,20 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
public enum BucketMode
{
LEAKY, SAMPLE, RING;
LEAKY, SAMPLE, RING, SLOWEST;
int position(int permits, int total)
{
switch (this)
{
default: throw UnhandledEnum.unknown(this);
case LEAKY: return Integer.MAX_VALUE;
case RING: return total % permits;
case SAMPLE: return ThreadLocalRandom.current().nextInt(total);
case SLOWEST:
case LEAKY:
return Integer.MAX_VALUE;
case RING:
return total % permits;
case SAMPLE:
return ThreadLocalRandom.current().nextInt(total);
}
}
}
@ -90,14 +101,26 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
public static class Message
{
public final long atNanos;
public final int nodeId;
public final int commandStoreId;
public final String message;
Message(int commandStoreId, String message, long atLeastNanos)
static Message withAtLeastNanos(int nodeId, int commandStoreId, String message, long atLeastNanos)
{
return new Message(nodeId, commandStoreId, message, Math.max(atLeastNanos, nanoTime()));
}
static Message withExactNanos(int nodeId, int commandStoreId, String message, long atNanos)
{
return new Message(nodeId, commandStoreId, message, atNanos);
}
private Message(int nodeId, int commandStoreId, String message, long atNanos)
{
this.atNanos = atNanos;
this.nodeId = nodeId;
this.commandStoreId = commandStoreId;
this.message = message;
this.atNanos = Math.max(atLeastNanos, Clock.Global.nanoTime());
}
@Override
@ -109,25 +132,29 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
public static class TxnEvent implements Tracing, Comparable<TxnEvent>
{
public final TxnEvents parent;
public final CoordinationKind kind;
public final long idMicros = uniqueNowMicros();
public final long atNanos = Clock.Global.nanoTime();
public final long atNanos = nanoTime();
final List<Message> messages = new ArrayList<>();
int index = -1, subIndex = -1;
long elapsedNanos;
public TxnEvent(CoordinationKind kind)
public TxnEvent(TxnEvents parent, CoordinationKind kind)
{
this.parent = parent;
this.kind = kind;
}
@Override
public void trace(CommandStore commandStore, String s)
public synchronized void trace(CommandStore commandStore, String s)
{
long prevNanos = messages.isEmpty() ? 0 : messages.get(messages.size() - 1).atNanos;
int id = commandStore == null ? -1 : commandStore.id();
// TODO (expected): make this configurable
if (s.length() > 1000)
s = s.substring(0, 1000);
messages.add(new Message(id, s, prevNanos + 1));
messages.add(Message.withAtLeastNanos(-1, id, s, prevNanos + 1));
}
@Override
@ -140,6 +167,79 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
{
return Collections.unmodifiableList(messages);
}
@Override
public Tracing send()
{
return this;
}
public long elapsedNanos()
{
return elapsedNanos;
}
public long doneAtNanos()
{
return atNanos + elapsedNanos;
}
public long doneAtMicros()
{
return idMicros + elapsedNanos/1000;
}
@Override
public void done()
{
elapsedNanos = nanoTime() - atNanos;
TracePatternState pattern = parent.owner;
if (pattern != null && pattern.bucketMode == SLOWEST && !pattern.updateSlowest(parent, elapsedNanos))
return;
if (index < 0)
{
parent.parent.txnIdMap.compute(parent.txnId, (id, events) -> {
TracePatternState owner = parent.owner;
if (owner != null)
{
synchronized (owner)
{
owner.detachedEvent.remove(idMicros);
}
}
if (events != parent)
return events;
TxnEventsList subList = events.subLists.get(kind);
TxnEventsList list = subList == null ? events : subList;
if (list.size < events.bucketSize && (subList == null || subList.size < events.bucketSubSize))
{
if (events.parent.globalCount.admit())
events.add(kind, subList, this);
return events;
}
for (int i = 0 ; i < list.size ; ++i)
{
TxnEvent event = list.get(i);
if (event.elapsedNanos < elapsedNanos)
{
events.remove(event.index);
events.add(kind, subList, this);
break;
}
}
return events;
});
}
}
public TxnId txnId()
{
return parent.txnId;
}
}
private static class TxnEventsList
@ -189,11 +289,28 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
public static class TxnEvents extends TxnEventsList
{
private final EnumMap<CoordinationKind, TxnEventsList> subLists = new EnumMap<>(CoordinationKind.class);
final AccordTracing parent;
final TxnId txnId;
private CoordinationKinds traceEvents = CoordinationKinds.ALL;
private BucketMode mode = LEAKY;
private TracePatternState owner;
private int bucketSize, bucketSubSize;
private float chance = 1.0f;
private boolean detached;
public TxnEvents(AccordTracing parent, TxnId txnId)
{
this.parent = parent;
this.txnId = txnId;
}
private TxnEvents setDetached()
{
detached = true;
return this;
}
void remove(int index)
{
@ -287,7 +404,11 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
subList.incrementSeen();
int position = mode.position(bucketSubSize, subList.bucketSeen);
if (position >= bucketSubSize)
{
if (mode == SLOWEST)
return new TxnEvent(this, kind);
return null;
}
remove(subList.get(position).index);
}
@ -296,13 +417,17 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
incrementSeen();
int position = mode.position(bucketSize, bucketSeen);
if (position >= bucketSize)
{
if (mode == SLOWEST)
return new TxnEvent(this, kind);
return null;
}
remove(position);
}
else
{
if (!globalCount.admit())
if (globalCount != null && !globalCount.admit())
return null;
}
@ -320,16 +445,21 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
}
private TxnEvent newTrace(CoordinationKind kind, TxnEventsList subList)
{
TxnEvent event = new TxnEvent(this, kind);
add(kind, subList, event);
return event;
}
private void add(CoordinationKind kind, TxnEventsList subList, TxnEvent event)
{
if (subList == null)
subLists.put(kind, subList = new TxnEventsList());
TxnEvent event = new TxnEvent(kind);
event.subIndex = subList.size;
subList.addInternal(event);
event.index = size;
addInternal(event);
return event;
}
public boolean hasOwner()
@ -370,7 +500,12 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
public void forEach(Consumer<TxnEvent> forEach)
{
for (int i = 0 ; i < size ; ++i)
forEach.accept(events[i]);
{
synchronized (events[i])
{
forEach.accept(events[i]);
}
}
}
}
@ -423,7 +558,7 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
return new TracePattern(kinds, intersects, traceNew, traceFailures, chance);
}
boolean matches(TxnId txnId, @Nullable Participants<?> participants, CoordinationKind kind, NewOrFailure newOrFailure)
boolean matches(TxnId txnId, @Nullable Routables<?> participants, CoordinationKind kind, NewOrFailure newOrFailure)
{
if (kinds != null && !kinds.matches(txnId))
return false;
@ -432,13 +567,33 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
if (testKind == null || !testKind.contains(kind))
return false;
if (intersects != null && (participants == null || !intersects.intersects(participants)))
if (intersects != null && (participants == null || !participants.intersects(intersects)))
return false;
return chance >= 1.0f || ThreadLocalRandom.current().nextFloat() <= chance;
}
}
private static class SlowestTxnId implements Comparable<SlowestTxnId>
{
final TxnId txnId;
long elapsedNanos;
private SlowestTxnId(TxnId txnId, long elapsedNanos)
{
this.txnId = txnId;
this.elapsedNanos = elapsedNanos;
}
@Override
public int compareTo(SlowestTxnId that)
{
int c = Long.compare(this.elapsedNanos, that.elapsedNanos);
if (c == 0) c = this.txnId.compareTo(that.txnId);
return c;
}
}
public class TracePatternState
{
final int id;
@ -452,6 +607,10 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
private CoordinationKinds traceEvents = new CoordinationKinds(false, 0);
private final List<TxnId> txnIds = new ArrayList<>();
private final WeakHashMap<TxnId, TxnEvents> detachedEvents = new WeakHashMap<>();
private final WeakHashMap<Long, TxnEvent> detachedEvent = new WeakHashMap<>();
private final Map<TxnId, SlowestTxnId> slowestLookup = new HashMap<>();
private final TreeSet<SlowestTxnId> slowest = new TreeSet<>();
public TracePatternState(int id)
{
@ -479,7 +638,7 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
return txnIds.get(index);
}
TxnEvents maybeAdd(TxnId txnId, @Nullable Participants<?> participants, CoordinationKind kind, NewOrFailure newOrFailure)
TxnEvents maybeAdd(TxnId txnId, @Nullable Routables<?> participants, CoordinationKind kind, NewOrFailure newOrFailure)
{
if (!pattern.matches(txnId, participants, kind, newOrFailure))
return null;
@ -487,37 +646,160 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
return maybeAdd(txnId);
}
private synchronized TxnEvents maybeAdd(TxnId txnId)
private TxnEvents maybeAdd(TxnId txnId)
{
if (bucketSize == 0)
return null;
if (++bucketSeen < 0)
bucketSeen = Integer.MAX_VALUE;
if (bucketSize > txnIds.size())
class MaybeAdd implements BiFunction<TxnId, TxnEvents, TxnEvents>
{
TxnEvents added = trace(txnId);
if (added != null)
txnIds.add(txnId);
return added;
TxnEvents result;
TxnId untrace;
@Override
public TxnEvents apply(TxnId txnId, TxnEvents in)
{
synchronized (TracePatternState.this)
{
if (in != null)
return null; // already tracing
if (bucketSize == 0)
return null;
if (++bucketSeen < 0)
bucketSeen = Integer.MAX_VALUE;
if (bucketSize > txnIds.size())
{
result = newEvents(txnId);
txnIds.add(txnId);
if (bucketMode == SLOWEST)
initSlowest(result);
return result;
}
int position = bucketMode.position(bucketSize, bucketSeen);
if (position >= bucketSize)
{
if (bucketMode == SLOWEST)
result = detachedEvents.computeIfAbsent(txnId, id -> newEvents(id).setDetached());
return null;
}
result = newEvents(txnId);
untrace = txnIds.get(position);
txnIds.set(position, txnId);
if (bucketMode == SLOWEST)
initSlowest(result);
return result;
}
}
}
int position = bucketMode.position(bucketSize, bucketSeen);
if (position >= bucketSize)
return null;
TxnEvents added = trace(txnId);
if (added == null)
return null;
untrace(txnIds.get(position));
txnIds.set(position, txnId);
return added;
MaybeAdd maybeAdd = new MaybeAdd();
txnIdMap.compute(txnId, maybeAdd);
if (maybeAdd.untrace != null)
untrace(maybeAdd.untrace);
return maybeAdd.result;
}
private synchronized void untrace(TxnId txnId)
private boolean initSlowest(TxnEvents events)
{
SlowestTxnId entry = new SlowestTxnId(events.txnId, Long.MAX_VALUE);
if (null != slowestLookup.putIfAbsent(events.txnId, entry))
return false;
slowest.add(entry);
return true;
}
private boolean updateSlowest(TxnEvents events, long elapsedNanos)
{
synchronized (this)
{
SlowestTxnId entry = slowestLookup.get(events.txnId);
if (entry != null)
{
if (entry.elapsedNanos < elapsedNanos)
{
slowest.remove(entry);
entry.elapsedNanos = elapsedNanos;
slowest.add(entry);
}
return true;
}
}
class UpdateSlowest implements BiFunction<TxnId, TxnEvents, TxnEvents>
{
boolean result;
TxnId untrace;
@Override
public TxnEvents apply(TxnId txnId, TxnEvents cur)
{
if (cur != null && cur != events)
return cur;
synchronized (TracePatternState.this)
{
SlowestTxnId entry = slowestLookup.get(events.txnId);
if (entry != null)
{
if (entry.elapsedNanos < elapsedNanos)
{
slowest.remove(entry);
entry.elapsedNanos = elapsedNanos;
slowest.add(entry);
}
result = true;
return events;
}
int index = txnIds.size();
if (index >= bucketSize && !slowest.isEmpty())
{
SlowestTxnId leastSlow = slowest.first();
if (leastSlow.elapsedNanos >= elapsedNanos)
return cur;
SlowestTxnId removed;
removed = slowest.pollFirst();
Invariants.expect(leastSlow.equals(removed), "%s != %s", entry, removed);
removed = slowestLookup.remove(leastSlow.txnId);
Invariants.expect(leastSlow.equals(removed), "%s != %s", entry, removed);
index = txnIds.indexOf(leastSlow.txnId);
Invariants.expect(index >= 0);
if (index < 0)
return cur;
untrace = leastSlow.txnId;
txnIds.set(index, events.txnId);
}
else
{
if (index > bucketSize)
txnIds.remove(0);
txnIds.add(events.txnId);
}
if (null != slowestLookup.put(events.txnId, entry = new SlowestTxnId(events.txnId, elapsedNanos)))
return cur;
slowest.add(entry);
detachedEvents.remove(entry.txnId);
events.detached = false;
result = true;
return events;
}
}
}
UpdateSlowest updateSlowest = new UpdateSlowest();
txnIdMap.compute(events.txnId, updateSlowest);
if (updateSlowest.untrace != null)
untrace(updateSlowest.untrace);
return updateSlowest.result;
}
private void untrace(TxnId txnId)
{
txnIdMap.compute(txnId, (ignore, cur) -> {
if (cur == null || cur.owner != this)
@ -528,16 +810,14 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
});
}
private synchronized TxnEvents trace(TxnId txnId)
private TxnEvents newEvents(TxnId txnId)
{
TxnEvents events = new TxnEvents();
TxnEvents events = new TxnEvents(AccordTracing.this, txnId);
events.mode = traceBucketMode;
events.bucketSize = traceBucketSize;
events.bucketSubSize = traceBucketSubSize;
events.owner = this;
if (null == txnIdMap.putIfAbsent(txnId, events))
return events;
return null;
return events;
}
synchronized void set(Function<TracePattern, TracePattern> pattern, BucketMode newBucketMode, int newBucketSeen, int newBucketSize, BucketMode newTraceBucketMode, int newTraceBucketSize, int newTraceBucketSubSize, CoordinationKinds newTraceEvents)
@ -545,8 +825,16 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
Invariants.require(newBucketSize != 0);
Invariants.require(newTraceBucketSize != 0);
this.pattern = pattern.apply(this.pattern);
if (newBucketMode != null)
if (newBucketMode != null && newBucketMode != bucketMode)
{
if (bucketMode == SLOWEST)
{
slowest.clear();
slowestLookup.clear();
detachedEvents.clear();
}
this.bucketMode = newBucketMode;
}
if (newBucketSize >= 0)
this.bucketSize = newBucketSize;
if (newBucketSeen >= 0)
@ -561,11 +849,19 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
this.traceEvents = newTraceEvents;
}
synchronized void clear()
void clear()
{
for (TxnId txnId : txnIds)
List<TxnId> untrace;
synchronized (this)
{
untrace = new ArrayList<>(txnIds);
txnIds.clear();
slowest.clear();
slowestLookup.clear();
detachedEvents.clear();
}
for (TxnId txnId : untrace)
untrace(txnId);
txnIds.clear();
}
}
@ -602,20 +898,54 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
final CopyOnWriteArrayList<TracePatternState> traceNewPatterns = new CopyOnWriteArrayList<>();
final GlobalCount globalCount = new GlobalCount();
public Tracing trace(TxnId txnId, @Nullable Participants<?> participants, CoordinationKind kind)
public Tracing trace(TxnId txnId, @Nullable Routables<?> participants, CoordinationKind kind)
{
if (kind == CoordinationKind.FetchDurableBefore)
return (cs, msg) -> logger.info("Catchup/FetchDurableBefore: {}", msg.length() <= 100 ? msg : msg.substring(0, 100));
if (!txnIdMap.containsKey(txnId) && null == maybeTrace(txnId, participants, kind, NewOrFailure.NEW, traceNewPatterns))
return null;
if (!txnIdMap.containsKey(txnId))
{
TxnEvents events = maybeTrace(txnId, participants, kind, NewOrFailure.NEW, traceNewPatterns);
if (events == null)
return null;
if (events.detached)
{
class RegisterDetached implements BiFunction<TxnId, TxnEvents, TxnEvents>
{
TxnEvent event;
@Override
public TxnEvents apply(TxnId txnId, TxnEvents state)
{
if (state != null) event = state.trace(kind, globalCount);
else
{
TracePatternState owner = events.owner;
if (owner != null)
{
synchronized (owner)
{
event = new TxnEvent(events, kind);
owner.detachedEvent.put(event.idMicros, event);
}
}
}
return state;
}
}
RegisterDetached register = new RegisterDetached();
txnIdMap.compute(txnId, register);
return register.event;
}
}
class Register implements BiFunction<TxnId, TxnEvents, TxnEvents>
{
TxnEvent event;
@Override
public TxnEvents apply(TxnId id, TxnEvents state)
public TxnEvents apply(TxnId txnId, TxnEvents state)
{
if (state == null)
return null;
@ -629,6 +959,65 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
return register.event;
}
public void report(AccordTraceIn in, int nodeId)
{
txnIdMap.compute(in.txnId, (ignore, events) -> {
if (events != null)
{
report(in, nodeId, events);
return events;
}
else
{
for (TracePatternState patternState : allPatterns)
{
synchronized (patternState)
{
TxnEvent detached = patternState.detachedEvent.get(in.idMicros);
if (detached != null)
{
report(in, nodeId, detached);
return null;
}
}
}
return null;
}
});
}
private boolean report(AccordTraceIn in, int nodeId, TxnEvents events)
{
for (int i = 0 ; i < events.size ; ++i)
{
TxnEvent event = events.get(i);
if (event.idMicros == in.idMicros)
{
report(in, nodeId, event);
return true;
}
}
return false;
}
private void report(AccordTraceIn in, int nodeId, TxnEvent event)
{
synchronized (event)
{
long atNanos = Math.max(in.atNanos, event.atNanos);
int j = event.messages.size();
while (j > 0 && event.messages.get(j - 1).atNanos >= atNanos)
--j;
while (j < event.messages.size() && event.messages.get(j).atNanos == atNanos)
{
atNanos++;
j++;
}
event.messages.add(j, Message.withExactNanos(nodeId, in.commandStoreId, in.message, atNanos));
}
}
// null values, or values < 0, are ignored
public void set(TxnId txnId, CoordinationKinds trace, BucketMode newBucketMode, int newBucketSize, int newBucketSubSize, int newBucketSeen, float newChance, boolean unsetManagedByPattern)
{
@ -641,7 +1030,7 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
if (newBucketSize < 0)
throw new IllegalArgumentException("Must specify bucket size for new trace config.");
cur = new TxnEvents();
cur = new TxnEvents(this, id);
if (newBucketSubSize < 0)
cur.bucketSubSize = newBucketSize;
}
@ -714,6 +1103,15 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
txnIdMap.keySet().forEach(this::stopTracing);
}
public void eraseAll()
{
allPatterns.forEach(TracePatternState::clear);
txnIdMap.keySet().forEach(txnId -> {
eraseEvents(txnId);
stopTracing(txnId);
});
}
public void forEach(Predicate<TxnId> include, ConsumeState forEach)
{
txnIdMap.forEach((txnId, state) -> {
@ -721,13 +1119,24 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
{
// ensure lock is held for duration of callback
txnIdMap.compute(txnId, (id, cur) -> {
forEach.accept(txnId, cur);
if (cur != null)
forEach.accept(txnId, cur);
return cur;
});
}
});
}
public void forEach(TxnId txnId, Consumer<TxnEvents> forEach)
{
// ensure lock is held for duration of callback
txnIdMap.compute(txnId, (id, cur) -> {
if (cur != null)
forEach.accept(cur);
return cur;
});
}
public void setPattern(int id, Function<TracePattern, TracePattern> pattern, BucketMode newBucketMode, int newBucketSeen, int newBucketSize, BucketMode newTraceBucketMode, int newTraceBucketSize, int newTraceBucketSubSize, CoordinationKinds newTraceEvents)
{
synchronized (allPatterns)
@ -815,6 +1224,7 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
if (cur != tracing)
return cur;
// TODO (desired): assign an idMicros that is based on coordinationId
TxnEvent event = tracing.forceTrace(coordination.kind(), globalCount);
if (event == null) // we still honour global limit
return cur;
@ -833,7 +1243,7 @@ public class AccordTracing extends AccordCoordinatorMetrics.Listener
}
}
private TxnEvents maybeTrace(TxnId txnId, @Nullable Participants<?> participants, CoordinationKind kind, NewOrFailure newOrFailure, List<TracePatternState> patterns)
private TxnEvents maybeTrace(TxnId txnId, @Nullable Routables<?> participants, CoordinationKind kind, NewOrFailure newOrFailure, List<TracePatternState> patterns)
{
if (patterns.isEmpty())
return null;

View File

@ -0,0 +1,242 @@
/*
* 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.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import accord.local.Command;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.metrics.LogLinearHistogram;
import org.apache.cassandra.service.accord.AccordExecutor;
import static org.apache.cassandra.config.CassandraRelevantProperties.DTEST_ACCORD_JOURNAL_SANITY_CHECK_ENABLED;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
public class DebugExecution
{
private static final Logger logger = LoggerFactory.getLogger(DebugExecution.class);
public static final boolean DEBUG_EXECUTION = CassandraRelevantProperties.ACCORD_DEBUG_EXECUTION.getBoolean(false);
private static final long REPORT_MIN_LATENCY_MICROS = 10_000;
private static final long REPORT_CPU_RATIO = 2;
private static final long REPORT_MAX_LATENCY_MICROS = 50_000;
// TODO (expected): use sharded histogram so we can report global stats
public static class DebugExecutor
{
public static DebugExecutor maybeDebug() { return DEBUG_EXECUTION ? new DebugExecutor() : null; }
private DebugExecutor() {}
final LogLinearHistogram waitingToLock = new LogLinearHistogram(REPORT_MAX_LATENCY_MICROS);
final LogLinearHistogram locked = new LogLinearHistogram(REPORT_MAX_LATENCY_MICROS);
final LogLinearHistogram sequentialExecutorWaitingToRunLatency = new LogLinearHistogram(REPORT_MAX_LATENCY_MICROS);
final LogLinearHistogram sequentialExecutorSetHeadToRunLatency = new LogLinearHistogram(REPORT_MAX_LATENCY_MICROS);
final LogLinearHistogram pollToRun = new LogLinearHistogram(REPORT_MAX_LATENCY_MICROS);
final LogLinearHistogram applying = new LogLinearHistogram(REPORT_MAX_LATENCY_MICROS);
final LogLinearHistogram running = new LogLinearHistogram(REPORT_MAX_LATENCY_MICROS);
final LogLinearHistogram cleanup = new LogLinearHistogram(REPORT_MAX_LATENCY_MICROS);
final LogLinearHistogram taskTotal = new LogLinearHistogram(REPORT_MAX_LATENCY_MICROS);
long lockedAt, lockedAtCpu;
long unlockedAt, unlockedAtCpu;
public void onEnterLock()
{
onEnterLock(0);
}
public void onEnterLock(long lockAt)
{
lockedAt = nanoTime();
lockedAtCpu = nowCpu();
if (lockAt > 0)
{
long waitingToLockForMicros = (lockedAt - lockAt)/1000;
waitingToLock.increment(waitingToLockForMicros);
if (waitingToLockForMicros > REPORT_MAX_LATENCY_MICROS)
{
report("Took {}us to aquire executor lock", waitingToLockForMicros);
}
}
}
public void onExitLock()
{
unlockedAt = nanoTime();
unlockedAtCpu = nowCpu();
long lockedForMicros = (unlockedAt - lockedAt)/1000;
long lockedForCpuMicros = (unlockedAtCpu - lockedAtCpu)/1000;
if (lockedForMicros >= REPORT_MAX_LATENCY_MICROS)
{
report("Held lock for {}us (cpu:{}us)\n", lockedForMicros, lockedForCpuMicros);
}
else if (lockedForMicros >= REPORT_MIN_LATENCY_MICROS && (lockedForMicros / lockedForCpuMicros) >= REPORT_CPU_RATIO)
{
report("Held lock for {}us with cpu time only {}us\n", lockedForMicros, lockedForCpuMicros);
}
locked.increment(lockedForMicros);
}
}
public static class DebugExecutorLoop
{
final DebugExecutor owner;
long lockAt;
public DebugExecutorLoop(DebugExecutor owner)
{
this.owner = owner;
}
public void onLock()
{
lockAt = nanoTime();
}
public void onEnterLock()
{
owner.onEnterLock(lockAt);
lockAt = 0;
}
public void onExitLock()
{
owner.onExitLock();
}
}
public static class DebugSequentialExecutor
{
public static DebugSequentialExecutor maybeDebug(DebugExecutor owner, int commandStoreId)
{
return DEBUG_EXECUTION ? new DebugSequentialExecutor(owner, commandStoreId) : null;
}
final DebugExecutor owner;
final int commandStoreId;
long setTaskAt, waitingAt;
AccordExecutor.Task prev;
public DebugSequentialExecutor(DebugExecutor owner, int commandStoreId)
{
this.owner = owner;
this.commandStoreId = commandStoreId;
}
public void onSetTask(AccordExecutor.Task next)
{
if (next == null) setTaskAt = 0;
else setTaskAt = nanoTime();
}
public void onComplete(AccordExecutor.Task completed)
{
long readyAt = setTaskAt;
if (waitingAt > setTaskAt)
{
readyAt = waitingAt;
long waitingMicros = (completed.runningAt - waitingAt)/1000;
owner.sequentialExecutorWaitingToRunLatency.increment(waitingMicros);
if (waitingMicros > REPORT_MAX_LATENCY_MICROS)
report("{} spent {}us blocked by a direct execution on queue {}", completed, waitingMicros, commandStoreId);
}
long atHeadMicros = (completed.runningAt - readyAt)/1000;
owner.sequentialExecutorSetHeadToRunLatency.increment(atHeadMicros);
if (atHeadMicros > REPORT_MAX_LATENCY_MICROS)
{
report("{} spent {}us at head of queue {}", completed, atHeadMicros, commandStoreId);
}
this.prev = completed;
}
public void onWaiting()
{
waitingAt = nanoTime();
}
}
public static class DebugTask
{
public static final boolean SANITY_CHECK = DTEST_ACCORD_JOURNAL_SANITY_CHECK_ENABLED.getBoolean();
private static final boolean DEBUG = DEBUG_EXECUTION || SANITY_CHECK;
public static DebugTask maybeDebug() { return DEBUG ? new DebugTask() : null; }
public List<Command> sanityCheck; // for AccordTask only
long polledAt, appliedAt, completedAt;
long polledAtCpu, completedAtCpu;
public void onPolled()
{
polledAt = nanoTime();
polledAtCpu = ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime();
}
public void onRunComplete()
{
appliedAt = nanoTime();
}
public void onCompleted(AccordExecutor.Task task, DebugExecutor owner)
{
completedAt = nanoTime();
completedAtCpu = ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime();
if (task.runningAt > 0 && polledAt > 0)
{
long pollToRunMicros = (task.runningAt - polledAt)/1000;
owner.pollToRun.increment(pollToRunMicros);
long applyingMicros = -1;
if (appliedAt > 0)
{
applyingMicros = (appliedAt - task.runningAt)/1000;
owner.applying.increment(applyingMicros);
}
long runningMicros = (task.cleanupAt - task.runningAt)/1000;
owner.running.increment(runningMicros);
long cleanupMicros = (completedAt - task.cleanupAt)/1000;
owner.cleanup.increment(cleanupMicros);
long totalMicros = (completedAt - polledAt)/1000;
owner.taskTotal.increment(totalMicros);
long totalCpu = (completedAtCpu - polledAtCpu)/1000;
if (totalMicros > REPORT_MAX_LATENCY_MICROS || (totalMicros > REPORT_MIN_LATENCY_MICROS && (totalMicros/totalCpu) >= REPORT_CPU_RATIO))
{
report("{}: total {}us {}cpu, running {}us{}, cleanup {}us, pollToRun {}us", task, totalMicros, totalCpu,
runningMicros, (applyingMicros >= 0 ? ", applying " + applyingMicros + "us" : ""), cleanupMicros, pollToRunMicros);
}
}
}
}
private static void report(String message, Object ... params)
{
logger.warn(Thread.currentThread() + " " + message, params);
}
private static final ThreadMXBean runtime = ManagementFactory.getThreadMXBean();
private static long nowCpu()
{
return runtime.getCurrentThreadCpuTime();
}
}

View File

@ -165,7 +165,7 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL
// and prevents races where we respond before dispatching all the required reads (if the reads are
// completing faster than the reads can be setup on all required shards)
if (-1 == --waitingOnCount)
node.reply(replyTo, replyContext, ApplyReply.Applied, null);
node.reply(replyTo, replyContext, ApplyReply.Applied, null, tracing());
}
@Override
@ -175,11 +175,11 @@ public class AccordInteropApply extends Apply implements LocalListeners.ComplexL
{
// Respond with insufficient which should make the coordinator send us the commit
// we need to respond
node.reply(replyTo, replyContext, reply, failure);
node.reply(replyTo, replyContext, reply, failure, tracing());
}
else if (failure != null)
{
node.reply(replyTo, replyContext, null, failure);
node.reply(replyTo, replyContext, null, failure, tracing());
node.agent().onException(failure);
fail();
}

View File

@ -219,7 +219,7 @@ public class AccordInteropExecution implements ReadCoordinator
// Also this read scope doesn't reflect the contents of this particular read and is larger than it needs to be
// TODO (required): understand interop and whether StableFastPath is appropriate
AccordInteropStableThenRead commit = new AccordInteropStableThenRead(id, allTopologies, txnId, Kind.StableFastPath, executeAt, txn, deps, route, message.payload);
node.send(id, commit, executor, new AccordInteropRead.ReadCallback(id, to, message, callback, this));
node.send(id, commit, executor, new AccordInteropRead.ReadCallback(id, to, message, callback, this), null);
}
@Override
@ -235,7 +235,7 @@ public class AccordInteropExecution implements ReadCoordinator
}
Participants<?> readScope = Participants.singleton(txn.read().keys().domain(), new TokenKey(message.payload.getTableIds().iterator().next(), message.payload.key().getToken()));
AccordInteropReadRepair readRepair = new AccordInteropReadRepair(id, executes, txnId, readScope, executeAt.epoch(), message.payload);
node.send(id, readRepair, executor, new AccordInteropReadRepair.ReadRepairCallback(id, to, message, callback, this));
node.send(id, readRepair, executor, new AccordInteropReadRepair.ReadRepairCallback(id, to, message, callback, this), null);
}
private List<AsyncChain<Data>> readChains(Dispatcher.RequestTime requestTime)
@ -376,7 +376,7 @@ public class AccordInteropExecution implements ReadCoordinator
{
InetAddressAndPort endpoint = endpointMapper.mappedEndpointOrNull(to);
if (endpoint != null && !contacted.contains(endpoint))
node.send(to, new Commit(Kind.StableFastPath, to, allTopologies, txnId, txn, route, Ballot.ZERO, executeAt, deps));
node.send(to, new Commit(Kind.StableFastPath, to, allTopologies, txnId, txn, route, Ballot.ZERO, executeAt, deps), null);
}
}
@ -387,7 +387,7 @@ public class AccordInteropExecution implements ReadCoordinator
for (Node.Id to : allTopologies.nodes())
{
if (!executeTopology.contains(to))
node.send(to, new Commit(Kind.StableFastPath, to, allTopologies, txnId, txn, route, Ballot.ZERO, executeAt, deps));
node.send(to, new Commit(Kind.StableFastPath, to, allTopologies, txnId, txn, route, Ballot.ZERO, executeAt, deps), null);
}
}
AsyncChain<Data> result;
@ -423,7 +423,7 @@ public class AccordInteropExecution implements ReadCoordinator
// and can be extended similar to MessageType which allows additional types not from Accord to be added
// This commit won't necessarily execute before the interop read repair message so there could be an insufficient which is fine
for (Node.Id to : executeTopology.nodes())
node.send(to, new Commit(Kind.StableFastPath, to, allTopologies, txnId, txn, route, Ballot.ZERO, executeAt, deps));
node.send(to, new Commit(Kind.StableFastPath, to, allTopologies, txnId, txn, route, Ballot.ZERO, executeAt, deps), null);
repairUpdate.runBRR(AccordInteropExecution.this);
return new TxnData();
});
@ -455,7 +455,7 @@ public class AccordInteropExecution implements ReadCoordinator
// Provide request callbacks with a way to send maximal commits on Insufficient responses
public void sendMaximalCommit(Id to)
{
node.send(to, new Commit(Kind.StableWithTxnAndDeps, to, allTopologies, txnId, txn, route, ballot, executeAt, deps));
node.send(to, new Commit(Kind.StableWithTxnAndDeps, to, allTopologies, txnId, txn, route, ballot, executeAt, deps), null);
}
public void maybeUpdateUniqueHlc(long uniqueHlc)

View File

@ -25,8 +25,6 @@ import accord.coordinate.ExecuteFlag;
import accord.coordinate.Persist;
import accord.coordinate.tracking.AllTracker;
import accord.coordinate.tracking.QuorumTracker;
import accord.coordinate.tracking.RequestStatus;
import accord.coordinate.tracking.ResponseTracker;
import accord.local.Node;
import accord.local.SequentialAsyncExecutor;
import accord.messages.Apply;
@ -40,10 +38,13 @@ import accord.primitives.TxnId;
import accord.primitives.Writes;
import accord.topology.Topologies;
import accord.utils.Invariants;
import accord.utils.UnhandledEnum;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.utils.Throwables;
import static org.apache.cassandra.db.ConsistencyLevel.ALL;
import static org.apache.cassandra.db.ConsistencyLevel.ONE;
import static org.apache.cassandra.db.ConsistencyLevel.QUORUM;
import static org.apache.cassandra.db.ConsistencyLevel.SERIAL;
/**
* Similar to Accord persist, but can wait on a configurable number of responses and sends AccordInteropApply messages
@ -52,110 +53,13 @@ import org.apache.cassandra.utils.Throwables;
*/
public class AccordInteropPersist extends Persist
{
private static class CallbackHolder
public AccordInteropPersist(Node node, SequentialAsyncExecutor executor, Topologies topologies, TxnId txnId, Route<?> sendTo, Ballot ballot, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, FullRoute<?> fullRoute, ConsistencyLevel consistencyLevel, ExecuteFlag.CoordinationFlags flags, boolean informDurableOnDone, Apply.Kind applyKind, BiConsumer<? super Result, Throwable> callback)
{
boolean isDone = false;
private final ResponseTracker tracker;
private final Result result;
private final BiConsumer<? super Result, Throwable> clientCallback;
private Throwable failure = null;
public CallbackHolder(ResponseTracker tracker, Result result, BiConsumer<? super Result, Throwable> clientCallback)
{
this.tracker = tracker;
this.result = result;
this.clientCallback = clientCallback;
}
private void handleStatus(RequestStatus status)
{
if (isDone)
return;
switch (status)
{
default: throw new IllegalStateException("Unhandled request status " + status);
case Success:
isDone = true;
clientCallback.accept(result, null);
return;
case Failed:
isDone = true;
clientCallback.accept(null, failure);
return;
case NoChange:
// noop
}
}
public void recordSuccess(Node.Id node)
{
handleStatus(tracker.recordSuccess(node));
}
public void recordFailure(Node.Id node, Throwable throwable)
{
failure = Throwables.merge(failure, throwable);
handleStatus(tracker.recordFailure(node));
}
boolean recordCallbackFailure(Throwable throwable)
{
if (isDone)
return false;
isDone = true;
failure = Throwables.merge(failure, throwable);
clientCallback.accept(null, failure);
return true;
}
}
private final ConsistencyLevel consistencyLevel;
private CallbackHolder callback;
public AccordInteropPersist(Node node, SequentialAsyncExecutor executor, Topologies topologies, TxnId txnId, Route<?> sendTo, Ballot ballot, Txn txn, Timestamp executeAt, Deps deps, Writes writes, Result result, FullRoute<?> fullRoute, ConsistencyLevel consistencyLevel, ExecuteFlag.CoordinationFlags flags, boolean informDurableOnDone, Apply.Kind applyKind, BiConsumer<? super Result, Throwable> clientCallback)
{
super(node, executor, topologies, txnId, ballot, sendTo, txn, executeAt, deps, writes, result, fullRoute, flags, informDurableOnDone, AccordInteropApply.FACTORY, applyKind);
Invariants.requireArgument(consistencyLevel == ConsistencyLevel.QUORUM || consistencyLevel == ConsistencyLevel.ALL || consistencyLevel == ConsistencyLevel.SERIAL || consistencyLevel == ConsistencyLevel.ONE);
this.consistencyLevel = consistencyLevel;
registerClientCallback(result, clientCallback);
}
public void registerClientCallback(Result result, BiConsumer<? super Result, Throwable> clientCallback)
{
Invariants.require(callback == null);
switch (consistencyLevel)
{
case ONE: // Can safely upgrade ONE to QUORUM/SERIAL to get a synchronous commit
case SERIAL:
case QUORUM:
callback = new CallbackHolder(new QuorumTracker(tracker.topologies()), result, clientCallback);
break;
case ALL:
callback = new CallbackHolder(new AllTracker(tracker.topologies()), result, clientCallback);
break;
default:
throw new IllegalArgumentException("Unhandled consistency level: " + consistencyLevel);
}
}
@Override
public void onSuccess(Node.Id from, Apply.ApplyReply reply)
{
super.onSuccess(from, reply);
switch (reply.kind)
{
case InsufficientEpochs: throw UnhandledEnum.invalid(reply.kind);
case Redundant:
case Applied:
callback.recordSuccess(from);
return;
case Insufficient:
// On insufficient Persist will send a commit with the missing information
// which will allow a final response to be returned later that could be successful
return;
default: throw UnhandledEnum.unknown(reply.kind);
}
super(node, executor, topologies, txnId, ballot, sendTo, txn, executeAt, deps, writes, result, fullRoute, flags, informDurableOnDone, AccordInteropApply.FACTORY, applyKind, consistencyLevel == ALL ? AllTracker::new : QuorumTracker::new, (ignore, fail) -> {
if (fail != null) callback.accept(null, fail);
else callback.accept(result, null);
});
Invariants.requireArgument(consistencyLevel == QUORUM || consistencyLevel == ALL || consistencyLevel == SERIAL || consistencyLevel == ONE);
}
@Override
@ -163,18 +67,4 @@ public class AccordInteropPersist extends Persist
{
super.start();
}
@Override
public void onFailure(Node.Id from, Throwable failure)
{
callback.recordFailure(from, failure);
super.onFailure(from, failure);
}
@Override
public boolean onCallbackFailure(Node.Id from, Throwable failure)
{
super.onCallbackFailure(from, failure);
return callback.recordCallbackFailure(failure);
}
}

View File

@ -245,6 +245,8 @@ public class AccordInteropRead extends ReadData
@Override
ReadResponse convertResponse(ReadOk ok)
{
LocalReadData data = (LocalReadData) ok.data;
data.ensureRemoteResponse();
return ((LocalReadData)ok.data).remoteResponse;
}
}

View File

@ -41,6 +41,8 @@ import accord.local.CommandStore;
import accord.local.CommandStores;
import accord.local.CommandStores.RangesForEpoch;
import accord.local.DurableBefore;
import accord.local.MinimalCommand;
import accord.local.MinimalCommand.MinimalWithDeps;
import accord.local.Node;
import accord.local.RedundantBefore;
import accord.primitives.EpochSupplier;
@ -50,8 +52,8 @@ import accord.primitives.TxnId;
import accord.utils.Invariants;
import accord.utils.PersistentField;
import org.apache.cassandra.config.AccordSpec.JournalSpec;
import org.apache.cassandra.config.AccordSpec.JournalSpec.ReplayMode;
import org.apache.cassandra.config.AccordConfig.JournalConfig;
import org.apache.cassandra.config.AccordConfig.JournalConfig.ReplayMode;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.marshal.LongType;
@ -77,7 +79,7 @@ import org.apache.cassandra.utils.concurrent.OpOrder;
import static accord.api.Journal.Load.MINIMAL;
import static accord.api.Journal.Load.MINIMAL_WITH_DEPS;
import static accord.local.Cleanup.Input.FULL;
import static org.apache.cassandra.config.AccordSpec.RangeIndexMode.journal_sai;
import static org.apache.cassandra.config.AccordConfig.RangeIndexMode.journal_sai;
import static org.apache.cassandra.config.DatabaseDescriptor.getAccord;
import static org.apache.cassandra.config.DatabaseDescriptor.getAccordJournalDirectory;
import static org.apache.cassandra.service.accord.JournalKey.Type.COMMAND_DIFF;
@ -230,14 +232,14 @@ public class AccordJournal implements accord.api.Journal, RangeSearcher.Supplier
}
@Override
public Command.Minimal loadMinimal(int commandStoreId, TxnId txnId, RedundantBefore redundantBefore, DurableBefore durableBefore)
public MinimalCommand loadMinimal(int commandStoreId, TxnId txnId, RedundantBefore redundantBefore, DurableBefore durableBefore)
{
CommandChanges builder = CommandChanges.cleanupAndFilter(loadDiffs(commandStoreId, txnId, MINIMAL), redundantBefore, durableBefore);
return builder == null ? null : builder.asMinimal();
}
@Override
public Command.MinimalWithDeps loadMinimalWithDeps(int commandStoreId, TxnId txnId, RedundantBefore redundantBefore, DurableBefore durableBefore)
public MinimalWithDeps loadMinimalWithDeps(int commandStoreId, TxnId txnId, RedundantBefore redundantBefore, DurableBefore durableBefore)
{
CommandChanges builder = CommandChanges.cleanupAndFilter(loadDiffs(commandStoreId, txnId, MINIMAL_WITH_DEPS), redundantBefore, durableBefore);
return builder == null ? null : builder.asMinimalWithDeps();
@ -596,8 +598,8 @@ public class AccordJournal implements accord.api.Journal, RangeSearcher.Supplier
@Override
public boolean replay(CommandStores commandStores, Object param)
{
ReplayMode mode = params instanceof JournalSpec ? ((JournalSpec)params).replay
: getAccord().journal.replay;
ReplayMode mode = params instanceof JournalConfig ? ((JournalConfig)params).replay
: getAccord().journal.replay;
return Replay.replay(this, mode, commandStores.all(), param);
}

View File

@ -36,7 +36,7 @@ import accord.primitives.TxnId;
import accord.utils.Invariants;
import accord.utils.UnhandledEnum;
import org.apache.cassandra.config.AccordSpec.JournalSpec.ReplayMode;
import org.apache.cassandra.config.AccordConfig.JournalConfig.ReplayMode;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.journal.Journal;
import org.apache.cassandra.service.accord.AccordCommandStore;

View File

@ -39,7 +39,6 @@ import accord.local.DurableBefore;
import accord.local.MaxConflicts;
import accord.local.MaxDecidedRX;
import accord.local.RedundantBefore;
import accord.local.RejectBefore;
import accord.primitives.Range;
import accord.primitives.Ranges;
import accord.primitives.SaveStatus;
@ -77,7 +76,7 @@ public class CommandStoreSerializers
public static final UnversionedSerializer<MaxDecidedRX> maxDecidedRX = new ReducingRangeMapSerializer<>(new DecidedRXSerializer(), MaxDecidedRX.DecidedRX[]::new, MaxDecidedRX.SerializerSupport::create, MaxDecidedRX.EMPTY);
public static final UnversionedSerializer<RedundantBefore.Bounds> redundantBeforeShortBounds = new RedundantBeforeShortBoundsSerializer();
public static final UnversionedSerializer<RedundantBefore> redundantBefore = new ReducingRangeMapSerializer<>(redundantBeforeShortBounds, RedundantBefore.Bounds[]::new, RedundantBefore.SerializerSupport::create, RedundantBefore.EMPTY);
public static final UnversionedSerializer<RejectBefore> rejectBefore = new ReducingRangeMapSerializer<>(CommandSerializers.timestamp, Timestamp[]::new, RejectBefore.SerializerSupport::create, RejectBefore.EMPTY);
public static final UnversionedSerializer<MaxConflicts> rejectBefore = new RejectBeforeSerializer();
public static final UnversionedSerializer<NavigableMap<TxnId, Ranges>> bootstrapBeganAt = new TimestampToRangesMapSerializer<>(CommandSerializers.txnId);
public static final UnversionedSerializer<NavigableMap<Timestamp, Ranges>> safeToRead = new TimestampToRangesMapSerializer<>(CommandSerializers.timestamp);
public static final UnversionedSerializer<TxnListener> txnListener = new TxnListenerSerializer();
@ -578,14 +577,15 @@ public class CommandStoreSerializers
private static final class MaxConflictsSerializer extends BTreeReducingRangeMapSerializer<MaxConflicts.Entry, MaxConflicts>
{
// use top bits of a single byte vint, to leave room for base impl to fill other way
private static final int SEPARATE_WRITES = 0x40;
private static final int INCLUDES_WRITE = 0x40;
private static final int INCLUDES_REJECT = 0x20;
private MaxConflictsSerializer() {}
@Override
protected int mapFlags()
{
return SEPARATE_WRITES;
return INCLUDES_WRITE | INCLUDES_REJECT;
}
@Override
@ -605,30 +605,78 @@ public class CommandStoreSerializers
{
CommandSerializers.timestamp.serialize(entry.any, out);
CommandSerializers.timestamp.serialize(entry.write, out);
CommandSerializers.timestamp.serialize(entry.reject, out);
}
@Override
long serializedSizeWithoutRange(MaxConflicts.Entry entry)
{
return CommandSerializers.timestamp.serializedSize(entry.any)
+ CommandSerializers.timestamp.serializedSize(entry.write);
+ CommandSerializers.timestamp.serializedSize(entry.write)
+ CommandSerializers.timestamp.serializedSize(entry.reject);
}
@Override
MaxConflicts.Entry deserialize(RoutingKey start, RoutingKey end, DataInputPlus in, int mapFlags) throws IOException
{
Timestamp all = CommandSerializers.timestamp.deserialize(in);
Timestamp writes = all;
if ((mapFlags & SEPARATE_WRITES) != 0)
writes = CommandSerializers.timestamp.deserialize(in);
return new MaxConflicts.Entry(start, end, all, writes);
Timestamp any = CommandSerializers.timestamp.deserialize(in);
Timestamp write = any;
if ((mapFlags & INCLUDES_WRITE) != 0)
write = CommandSerializers.timestamp.deserialize(in);
Timestamp reject = Timestamp.NONE;
if ((mapFlags & INCLUDES_REJECT) != 0)
reject = CommandSerializers.timestamp.deserialize(in);
return MaxConflicts.Entry.create(start, end, any, write, reject);
}
@Override
MaxConflicts.Entry deserializeArrayModeWithoutRange(DataInputPlus in) throws IOException
{
Timestamp all = CommandSerializers.timestamp.deserialize(in);
return new MaxConflicts.Entry(all, all);
return MaxConflicts.Entry.create(all, all, Timestamp.NONE);
}
}
// legacy version that deserializes RejectsBefore into MaxConflicts
private static final class RejectBeforeSerializer extends BTreeReducingRangeMapSerializer<MaxConflicts.Entry, MaxConflicts>
{
private RejectBeforeSerializer() {}
@Override
MaxConflicts empty()
{
return MaxConflicts.EMPTY;
}
@Override
BTreeReducingRangeMap.Builder<MaxConflicts.Entry, MaxConflicts> builder()
{
return new MaxConflicts.Builder();
}
@Override
void serializeWithoutRange(MaxConflicts.Entry entry, DataOutputPlus out)
{
throw new UnsupportedOperationException();
}
@Override
long serializedSizeWithoutRange(MaxConflicts.Entry entry)
{
throw new UnsupportedOperationException();
}
@Override
MaxConflicts.Entry deserialize(RoutingKey start, RoutingKey end, DataInputPlus in, int mapFlags) throws IOException
{
throw new UnsupportedOperationException();
}
@Override
MaxConflicts.Entry deserializeArrayModeWithoutRange(DataInputPlus in) throws IOException
{
Timestamp reject = CommandSerializers.timestamp.deserialize(in);
return MaxConflicts.Entry.create(Timestamp.NONE, Timestamp.NONE, reject);
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.serializers;
import java.io.IOException;
import accord.messages.RemoteSuccess;
import accord.primitives.TxnId;
import org.apache.cassandra.io.VersionedSerializer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.service.accord.txn.TxnData;
public class RemoteSuccessSerializers
{
public static final VersionedSerializer<RemoteSuccess, Version> remoteSuccess = new VersionedSerializer<>()
{
@Override
public void serialize(RemoteSuccess msg, DataOutputPlus out, Version version) throws IOException
{
CommandSerializers.txnId.serialize(msg.txnId, out);
TxnData.serializer.serialize((TxnData) msg.result, out, version);
}
@Override
public RemoteSuccess deserialize(DataInputPlus in, Version version) throws IOException
{
TxnId txnId = CommandSerializers.txnId.deserialize(in);
TxnData data = TxnData.serializer.deserialize(in, version);
return new RemoteSuccess(txnId, data);
}
@Override
public long serializedSize(RemoteSuccess msg, Version version)
{
return CommandSerializers.txnId.serializedSize(msg.txnId)
+ TxnData.serializer.serializedSize((TxnData) msg.result, version);
}
};
}

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.service.accord.topology;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import javax.annotation.concurrent.GuardedBy;
@ -42,6 +43,8 @@ import accord.utils.async.AsyncResults.SettableByCallback;
import org.apache.cassandra.concurrent.ScheduledExecutorPlus;
import org.apache.cassandra.concurrent.ScheduledExecutors;
import org.apache.cassandra.concurrent.Stage;
import org.apache.cassandra.config.AccordConfig;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.MessageDelivery;
import org.apache.cassandra.net.MessagingService;
@ -94,10 +97,40 @@ public class AccordTopologyService implements TopologyService, TopologyListener
previouslyRemovedIds = removed;
}
node.topology().addListener(watermarkCollector);
node.topology().addListener(syncPropagator);
syncPropagator.onNodesRemoved(removed);
if (DatabaseDescriptor.getAccord().topology_sync_propagator_enabled_pre_start)
{
node.topology().addListener(syncPropagator);
syncPropagator.onNodesRemoved(removed);
}
}
public void afterStartup(Node node)
{
AccordConfig config = DatabaseDescriptor.getAccord();
if (config.topology_sync_propagator_enabled_post_startup && !config.topology_sync_propagator_enabled_pre_start)
{
node.topology().addListener(syncPropagator);
syncPropagator.onNodesRemoved(previouslyRemovedIds);
}
long watermarkIntervalNanos = config.topology_watermark_interval.toNanoseconds();
if (watermarkIntervalNanos > 0)
fetchAndReportWatermarksRecurring(node, watermarkIntervalNanos);
}
public void fetchAndReportWatermarksRecurring(Node node, long intervalNanos)
{
if (state == State.SHUTDOWN)
return;
ScheduledExecutors.scheduledFastTasks.scheduleSelfRecurring(() -> {
WatermarkCollector.fetchAndReportWatermarksAsync(node.topology()).addCallback((success, fail) -> {
fetchAndReportWatermarksRecurring(node, intervalNanos);
});
}, intervalNanos, TimeUnit.NANOSECONDS);
}
public synchronized void shutdown()
{
state = State.SHUTDOWN;

View File

@ -44,7 +44,7 @@ public interface FastPathStrategy
{
enum Kind
{
SIMPLE, PARAMETERIZED, INHERIT_KEYSPACE;
SIMPLE, PARAMETERIZED, INHERIT_KEYSPACE, UP;
static final String KEY = "kind";
private static final Map<String, Kind> LOOKUP;
@ -54,6 +54,7 @@ public interface FastPathStrategy
builder.put(SIMPLE.name(), SIMPLE);
builder.put(PARAMETERIZED.name(), PARAMETERIZED);
builder.put(INHERIT_KEYSPACE.name(), INHERIT_KEYSPACE);
builder.put(UP.name(), UP);
LOOKUP = builder.build();
}
@ -115,6 +116,8 @@ public interface FastPathStrategy
return ParameterizedFastPathStrategy.fromMap(map);
case INHERIT_KEYSPACE:
return inheritKeyspace();
case UP:
return up();
default:
throw new IllegalArgumentException("Unhandled strategy kind: " + kind);
}
@ -127,8 +130,10 @@ public interface FastPathStrategy
return InheritKeyspaceFastPathStrategy.instance;
if (s.equals("simple"))
return SimpleFastPathStrategy.instance;
if (s.equals("up"))
return UpFastPathStrategy.instance;
throw new ConfigurationException("Fast path strategy must either be 'keyspace', `default` or a map size and optional dcs {'size':n, 'dcs': dc0,dc1...");
throw new ConfigurationException("Fast path strategy must either be 'keyspace', 'simple', 'up' or a map size and optional dcs {'size':n, 'dcs': dc0,dc1...");
}
static FastPathStrategy keyspaceStrategyFromString(String s)
@ -136,8 +141,10 @@ public interface FastPathStrategy
s = toLowerCaseLocalized(s).trim();
if (s.equals("simple"))
return SimpleFastPathStrategy.instance;
if (s.equals("up"))
return UpFastPathStrategy.instance;
throw new ConfigurationException("Fast path strategy must either be `default` or a map size and optional dcs {'size':n, 'dcs': dc0,dc1...");
throw new ConfigurationException("Fast path strategy must either be 'simple', 'up' or a map size and optional dcs {'size':n, 'dcs': dc0,dc1...");
}
static FastPathStrategy simple()
@ -145,6 +152,11 @@ public interface FastPathStrategy
return SimpleFastPathStrategy.instance;
}
static FastPathStrategy up()
{
return UpFastPathStrategy.instance;
}
static FastPathStrategy inheritKeyspace()
{
return InheritKeyspaceFastPathStrategy.instance;
@ -171,6 +183,8 @@ public interface FastPathStrategy
return ParameterizedFastPathStrategy.serializer.deserialize(in, version);
case INHERIT_KEYSPACE:
return inheritKeyspace();
case UP:
return up();
default:
throw new IllegalArgumentException("Unhandled type: " + type);
}

View File

@ -24,9 +24,6 @@ import java.util.Set;
import com.google.common.collect.ImmutableMap;
import accord.local.Node;
import accord.topology.Shard;
import accord.utils.ArrayBuffers;
import accord.utils.Invariants;
import accord.utils.SortedArrays.SortedArrayList;
public class SimpleFastPathStrategy implements FastPathStrategy
@ -40,27 +37,7 @@ public class SimpleFastPathStrategy implements FastPathStrategy
@Override
public SortedArrayList<Node.Id> calculateFastPath(SortedArrayList<Node.Id> nodes, Set<Node.Id> unavailable, Map<Node.Id, String> dcMap)
{
int maxFailures = Shard.maxToleratedFailures(nodes.size());
int discarded = 0;
if (unavailable.isEmpty())
return nodes;
Object[] tmp = ArrayBuffers.cachedAny().get(nodes.size());
for (int i=0,mi=nodes.size(); i<mi; i++)
{
Node.Id node = nodes.get(i);
if (unavailable.contains(node) && discarded < maxFailures)
discarded++;
else
tmp[i - discarded] = node;
}
Node.Id[] array = new Node.Id[nodes.size() - discarded];
System.arraycopy(tmp, 0, array, 0, nodes.size() - discarded);
SortedArrayList<Node.Id> fastPath = new SortedArrayList<>(array);
Invariants.require(fastPath.size() >= Shard.slowQuorumSize(nodes.size()));
return fastPath;
return nodes;
}
@Override

View File

@ -0,0 +1,89 @@
/*
* 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.topology;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableMap;
import accord.local.Node;
import accord.topology.Shard;
import accord.utils.ArrayBuffers;
import accord.utils.Invariants;
import accord.utils.SortedArrays.SortedArrayList;
public class UpFastPathStrategy implements FastPathStrategy
{
public static final UpFastPathStrategy instance = new UpFastPathStrategy();
private static final Map<String, String> SCHEMA_PARAMS = ImmutableMap.of(Kind.KEY, Kind.UP.name());
private UpFastPathStrategy() {}
@Override
public SortedArrayList<Node.Id> calculateFastPath(SortedArrayList<Node.Id> nodes, Set<Node.Id> unavailable, Map<Node.Id, String> dcMap)
{
int maxFailures = Shard.maxToleratedFailures(nodes.size());
int discarded = 0;
if (unavailable.isEmpty())
return nodes;
Object[] tmp = ArrayBuffers.cachedAny().get(nodes.size());
for (int i=0,mi=nodes.size(); i<mi; i++)
{
Node.Id node = nodes.get(i);
if (unavailable.contains(node) && discarded < maxFailures)
discarded++;
else
tmp[i - discarded] = node;
}
Node.Id[] array = new Node.Id[nodes.size() - discarded];
System.arraycopy(tmp, 0, array, 0, nodes.size() - discarded);
ArrayBuffers.cachedAny().forceDiscard(tmp, array.length);
SortedArrayList<Node.Id> fastPath = new SortedArrayList<>(array);
Invariants.require(fastPath.size() >= Shard.slowQuorumSize(nodes.size()));
return fastPath;
}
@Override
public Kind kind()
{
return Kind.UP;
}
@Override
public String toString()
{
return "up";
}
public Map<String, String> asMap()
{
return SCHEMA_PARAMS;
}
@Override
public String asCQL()
{
return "'up'";
}
}

View File

@ -20,12 +20,11 @@ package org.apache.cassandra.service.accord.topology;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import javax.annotation.Nullable;
@ -62,6 +61,7 @@ import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.TokenRange;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.concurrent.Future;
import static org.apache.cassandra.service.accord.api.AccordWaitStrategies.retryFetchWatermarks;
@ -124,14 +124,18 @@ public class WatermarkCollector implements TopologyListener
};
@VisibleForTesting
public static void fetchAndReportWatermarksAsync(TopologyManager topologyManager)
public static Future<?> fetchAndReportWatermarksAsync(TopologyManager topologyManager)
{
SharedContext context = SharedContext.Global.instance;
Set<InetAddressAndPort> peers = new HashSet<>();
peers.addAll(ClusterMetadata.current().directory.allAddresses());
peers.remove(FBUtilities.getBroadcastAddressAndPort());
List<InetAddressAndPort> peers = new ArrayList<>();
for (InetAddressAndPort peer : ClusterMetadata.current().directory.allAddresses())
{
if (!peer.equals(FBUtilities.getBroadcastAddressAndPort()))
peers.add(peer);
}
Collections.shuffle(peers);
context.messaging().<NoPayload, Snapshot>sendWithRetries(retryFetchWatermarks(),
return context.messaging().<NoPayload, Snapshot>sendWithRetries(retryFetchWatermarks(),
context.optionalTasks()::schedule,
Verb.ACCORD_FETCH_WATERMARKS_REQ,
NoPayload.noPayload,

View File

@ -222,13 +222,13 @@ public class TxnData extends Int2ObjectHashMap<TxnDataValue> implements TxnResul
}
@Override
public boolean validateReply(TxnId txnId, Timestamp executeAt, boolean futureReadPossible)
public boolean validateReply(TxnId txnId, Timestamp executeAt, long safeToReadHlc)
{
if (futureReadPossible)
if (safeToReadHlc > 0)
{
for (TxnDataValue value : values())
{
if (value.maxTimestamp() >= executeAt.hlc())
if (value.maxTimestamp() >= safeToReadHlc)
return false;
}
}
@ -238,7 +238,7 @@ public class TxnData extends Int2ObjectHashMap<TxnDataValue> implements TxnResul
@Override
public long estimatedSizeOnHeap()
{
long size = EMPTY_SIZE + (size() * TypeSizes.INT_SIZE);
long size = EMPTY_SIZE + ObjectSizes.sizeOfReferenceArray(capacity()) + (capacity() * TypeSizes.INT_SIZE);
for (TxnDataValue value : values())
size += value.estimatedSizeOnHeap();
return size;

View File

@ -759,7 +759,7 @@ public final class TxnUpdate extends AccordUpdate
public void serialize(TxnUpdate update, TableMetadatasAndKeys tablesAndKeys, DataOutputPlus out, Version version) throws IOException
{
// Serializing it with the condition result set shouldn't be needed
checkState(update.anyConditionResult == null, "Can't serialize if conditionResult is set without adding it to serialization");
// checkState(update.anyConditionResult == null, "Can't serialize if conditionResult is set without adding it to serialization");
// Once in accord "mixedTimeSource" and "yes" are the same, so only care about the side effect: that the timestamp is preserved or not
out.writeByte(update.preserveTimestamps.preserve ? FLAG_PRESERVE_TIMESTAMPS : 0);
tablesAndKeys.serializeKeys(update.keys, out);

View File

@ -111,7 +111,9 @@ public class TxnWrite extends AbstractKeySorted<TxnWrite.Update> implements Writ
public long estimatedSizeOnHeap()
{
// we don't measure the key, as this is shared
return EMPTY_SIZE + ByteBufferUtil.estimatedSizeOnHeap(unsafeBytes());
if (latestVersionBytes == null)
return EMPTY_SIZE;
return EMPTY_SIZE + ByteBufferUtil.estimatedSizeOnHeap(latestVersionBytes);
}
@Override
@ -518,8 +520,8 @@ public class TxnWrite extends AbstractKeySorted<TxnWrite.Update> implements Writ
public long estimatedSizeOnHeap()
{
long size = EMPTY_SIZE;
for (Update update : this)
size += update.estimatedSizeOnHeap();
for (int i = 0 ; i < size() ; ++i)
size += get(i).estimatedSizeOnHeap();
return size;
}

View File

@ -36,7 +36,7 @@ import com.google.common.collect.Streams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.AccordSpec;
import org.apache.cassandra.config.AccordConfig;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.statements.schema.AlterSchemaStatement;
import org.apache.cassandra.dht.Range;
@ -312,7 +312,7 @@ public class AlterSchema implements Transformation
if (!started.isEmpty())
{
List<Range<Token>> ranges;
AccordSpec.TransactionalRangeMigration migration = DatabaseDescriptor.getTransactionalRangeMigration();
AccordConfig.TransactionalRangeMigration migration = DatabaseDescriptor.getTransactionalRangeMigration();
switch (migration)
{
default: throw new IllegalStateException("Unhandled transactional range migration: " + migration);

View File

@ -35,7 +35,7 @@ import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import accord.utils.Invariants;
import org.apache.cassandra.config.AccordSpec;
import org.apache.cassandra.config.AccordConfig;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.db.ColumnFamilyStore;
@ -266,7 +266,7 @@ public class StandaloneJournalUtil implements Runnable
setAccordJournalDirectory(journalSegments);
Keyspace.setInitialized();
AccordJournal journal = new AccordJournal(new AccordSpec.JournalSpec().setFlushPeriod(new DurationSpec.IntMillisecondsBound("1500ms")), new File(journalSegments).parent(), Keyspace.open(SchemaConstants.ACCORD_KEYSPACE_NAME).getColumnFamilyStore(AccordKeyspace.JOURNAL));
AccordJournal journal = new AccordJournal(new AccordConfig.JournalConfig().setFlushPeriod(new DurationSpec.IntMillisecondsBound("1500ms")), new File(journalSegments).parent(), Keyspace.open(SchemaConstants.ACCORD_KEYSPACE_NAME).getColumnFamilyStore(AccordKeyspace.JOURNAL));
Keyspace ks = Schema.instance.getKeyspaceInstance("system_accord");
ColumnFamilyStore cfs = ks.getColumnFamilyStore("journal");
@ -388,7 +388,7 @@ public class StandaloneJournalUtil implements Runnable
setAccordJournalDirectory(journalSegments);
Keyspace.setInitialized();
AccordJournal journal = new AccordJournal(new AccordSpec.JournalSpec().setFlushPeriod(new DurationSpec.IntMillisecondsBound("1500ms")), new File(journalSegments).parent(), Keyspace.open(SchemaConstants.ACCORD_KEYSPACE_NAME).getColumnFamilyStore(AccordKeyspace.JOURNAL));
AccordJournal journal = new AccordJournal(new AccordConfig.JournalConfig().setFlushPeriod(new DurationSpec.IntMillisecondsBound("1500ms")), new File(journalSegments).parent(), Keyspace.open(SchemaConstants.ACCORD_KEYSPACE_NAME).getColumnFamilyStore(AccordKeyspace.JOURNAL));
Keyspace ks = Schema.instance.getKeyspaceInstance("system_accord");
ColumnFamilyStore cfs = ks.getColumnFamilyStore("journal");

View File

@ -52,7 +52,7 @@ import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUID;
*/
public abstract class Tracing extends ExecutorLocals.Impl
{
public static final IVersionedSerializer<TraceType> traceTypeSerializer = new IVersionedSerializer<TraceType>()
public static final IVersionedSerializer<TraceType> traceTypeSerializer = new IVersionedSerializer<>()
{
public void serialize(TraceType traceType, DataOutputPlus out, int version) throws IOException
{
@ -258,7 +258,7 @@ public abstract class Tracing extends ExecutorLocals.Impl
TraceType traceType = header.traceType();
if (header.verb.isResponse())
if (header.verb.isManagedResponse())
{
// received a message for a session we've already closed out. see CASSANDRA-5668
return new ExpiredTraceState(newTraceState(header.from, sessionId, traceType));
@ -294,7 +294,7 @@ public abstract class Tracing extends ExecutorLocals.Impl
else
{
state.trace(logMessage);
if (message.verb().isResponse())
if (message.verb().isManagedResponse())
doneWithNonLocalSession(state);
}
}

View File

@ -17,10 +17,13 @@
*/
package org.apache.cassandra.utils;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import accord.utils.Invariants;
import static org.apache.cassandra.config.CassandraRelevantProperties.CLOCK_GLOBAL;
import static org.apache.cassandra.utils.Shared.Scope.SIMULATION;
@ -108,6 +111,15 @@ public interface Clock
return instance.currentTimeMillis();
}
/**
* Semantically equivalent to {@link System#currentTimeMillis()},
* but yields a time with microseconds granularity
*/
public static long currentTimeMicros()
{
return instance.currentTimeMicros();
}
/**
* Semantically equivalent to {@link FBUtilities#nowInSeconds()}
*/
@ -134,6 +146,19 @@ public interface Clock
{
return System.currentTimeMillis(); // checkstyle: permit system clock
}
@Override
public long currentTimeMicros()
{
Instant now = Instant.now(); // checkstyle: permit this invocation
long seconds = now.getEpochSecond();
long nanos = now.getNano();
Invariants.require(seconds >= 0);
long micros = Math.multiplyExact(seconds, 1000_000);
micros = Math.addExact(micros, nanos/1000);
return micros;
}
}
/**
@ -146,6 +171,11 @@ public interface Clock
*/
public long currentTimeMillis();
public default long currentTimeMicros()
{
return currentTimeMillis() * 1000L;
}
public default long nowInSeconds()
{
return currentTimeMillis() / 1000L;

View File

@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.concurrent.ScheduledExecutors;
import static java.util.concurrent.TimeUnit.MICROSECONDS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.apache.cassandra.config.CassandraRelevantProperties.APPROXIMATE_TIME_PRECISION_MS;
import static org.apache.cassandra.config.CassandraRelevantProperties.CLOCK_MONOTONIC_APPROX;
@ -146,21 +147,28 @@ public interface MonotonicClock
@VisibleForTesting
public static class AlmostSameTime implements MonotonicClockTranslation
{
final long microsSinceEpoch;
final long millisSinceEpoch;
final long monotonicNanos;
final long error; // maximum error of millis measurement (in nanos)
@VisibleForTesting
public AlmostSameTime(long millisSinceEpoch, long monotonicNanos, long errorNanos)
public AlmostSameTime(long sinceEpoch, TimeUnit unitsSinceEpoch, long monotonicNanos, long errorNanos)
{
this.millisSinceEpoch = millisSinceEpoch;
this.microsSinceEpoch = unitsSinceEpoch.toMicros(sinceEpoch);
this.millisSinceEpoch = unitsSinceEpoch.toMillis(sinceEpoch);
this.monotonicNanos = monotonicNanos;
this.error = errorNanos;
}
public long fromMillisSinceEpoch(long currentTimeMillis)
public long fromMillisSinceEpoch(long millisSinceEpoch)
{
return monotonicNanos + MILLISECONDS.toNanos(currentTimeMillis - millisSinceEpoch);
return monotonicNanos + MILLISECONDS.toNanos(millisSinceEpoch - this.millisSinceEpoch);
}
public long fromMicrosSinceEpoch(long microsSinceEpoch)
{
return monotonicNanos + MICROSECONDS.toNanos(microsSinceEpoch - this.microsSinceEpoch);
}
public long toMillisSinceEpoch(long nanoTime)
@ -174,15 +182,17 @@ public interface MonotonicClock
}
}
final LongSupplier millisSinceEpoch;
final LongSupplier sinceEpoch;
final TimeUnit sinceEpochUnits;
private volatile AlmostSameTime almostSameTime = new AlmostSameTime(0L, 0L, Long.MAX_VALUE);
private volatile AlmostSameTime almostSameTime = new AlmostSameTime(0L, MILLISECONDS, 0L, Long.MAX_VALUE);
private Future<?> almostSameTimeUpdater;
private static double failedAlmostSameTimeUpdateModifier = 1.0;
AbstractEpochSamplingClock(LongSupplier millisSinceEpoch)
AbstractEpochSamplingClock(LongSupplier sinceEpoch, TimeUnit sinceEpochUnits)
{
this.millisSinceEpoch = millisSinceEpoch;
this.sinceEpoch = sinceEpoch;
this.sinceEpochUnits = sinceEpochUnits;
resumeEpochSampling();
}
@ -217,7 +227,7 @@ public interface MonotonicClock
samples[0] = nanoTime();
for (int i = 1 ; i < samples.length ; i += 2)
{
samples[i] = millisSinceEpoch.getAsLong();
samples[i] = sinceEpoch.getAsLong();
samples[i + 1] = now();
}
@ -229,12 +239,12 @@ public interface MonotonicClock
best = i;
}
long millis = samples[best];
long since = samples[best];
long nanos = (samples[best+1] / 2) + (samples[best-1] / 2);
long error = (samples[best+1] / 2) - (samples[best-1] / 2);
AlmostSameTime prev = almostSameTime;
AlmostSameTime next = new AlmostSameTime(millis, nanos, error);
AlmostSameTime next = new AlmostSameTime(since, sinceEpochUnits, nanos, error);
if (next.error > prev.error && next.error > prev.error * failedAlmostSameTimeUpdateModifier)
{
@ -258,7 +268,7 @@ public interface MonotonicClock
// class to ACC_PUBLIC, and ensured proper testing relationship from both the surrounding and nested class.
public SystemClock()
{
super(Clock.Global::currentTimeMillis);
super(Clock.Global::currentTimeMicros, MICROSECONDS);
}
@Override

Some files were not shown because too many files have changed in this diff Show More