ExclusiveSyncPoints should always wait for a simple quorum

split JournalKey in journal table so we can index it
reorder journal fields so we can easily index on route (when present)
use Message.expiresAtNanos for callback expiration
do not notify slow for range barriers

Accord: Do not contact faulty replicas, and promptly report slow replies for preaccept/read. Do not wait for stale or left nodes for durability.
This commit is contained in:
Benedict Elliott Smith 2024-10-05 12:32:06 +01:00 committed by David Capwell
parent cc321720e3
commit 82ae3adcb2
44 changed files with 1919 additions and 1301 deletions

@ -1 +1 @@
Subproject commit 08ee5ce1c6301201ccaf7d580a6af289ab4c5765
Subproject commit 841e139bc8a974ac674ce8eae847bd52255ca544

View File

@ -40,6 +40,8 @@ public class AccordSpec
// TODO (expected): we should be able to support lower recover delays, at least for txns
public volatile DurationSpec.IntMillisecondsBound recover_delay = new DurationSpec.IntMillisecondsBound(5000);
public volatile DurationSpec.IntMillisecondsBound range_sync_recover_delay = new DurationSpec.IntMillisecondsBound(10000);
public String slowPreAccept = "30ms <= p50*2 <= 100ms";
public String slowRead = "30ms <= p50*2 <= 100ms";
public long recoveryDelayFor(TxnId txnId, TimeUnit unit)
{

View File

@ -85,7 +85,6 @@ import org.apache.cassandra.io.sstable.ISSTableScanner;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.journal.KeySupport;
import org.apache.cassandra.metrics.TopPartitionTracker;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.CompactionParams.TombstoneOption;
@ -110,7 +109,6 @@ import org.apache.cassandra.service.paxos.uncommitted.PaxosRows;
import org.apache.cassandra.utils.TimeUUID;
import static accord.local.Cleanup.ERASE;
import static accord.local.Cleanup.TRUNCATE;
import static accord.local.Cleanup.TRUNCATE_WITH_OUTCOME;
import static accord.local.Cleanup.shouldCleanupPartial;
import static com.google.common.base.Preconditions.checkState;
@ -1020,7 +1018,6 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
final Int2ObjectHashMap<CommandStores.RangesForEpoch> ranges;
final ColumnMetadata recordColumn;
final ColumnMetadata versionColumn;
final KeySupport<JournalKey> keySupport = JournalKey.SUPPORT;
final AccordService service;
JournalKey key = null;
@ -1051,7 +1048,7 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
@Override
protected void beginPartition(UnfilteredRowIterator partition)
{
key = keySupport.deserialize(partition.partitionKey().getKey(), 0, userVersion);
key = AccordKeyspace.JournalColumns.getJournalKey(partition.partitionKey());
serializer = (AccordJournalValueSerializers.FlyweightSerializer<Object, Object>) key.type.serializer;
builder = serializer.mergerFor(key);
maxSeenTimestamp = -1;

View File

@ -45,7 +45,7 @@ public final class Compactor<K, V> implements Runnable, Shutdownable
synchronized void start()
{
if (!journal.params.enableCompaction())
if (journal.params.enableCompaction())
schedule(journal.params.compactionPeriodMillis(), TimeUnit.MILLISECONDS);
}

View File

@ -27,6 +27,7 @@ import javax.annotation.Nullable;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileOutputStreamPlus;
import org.apache.cassandra.journal.StaticSegment.SequentialReader;
/**
* An index for a segment that's still being updated by journal writers concurrently.
@ -138,17 +139,10 @@ final class InMemoryIndex<K> extends Index<K>
{
InMemoryIndex<K> index = new InMemoryIndex<>(keySupport, new TreeMap<>(keySupport));
try (StaticSegment.SequentialReader<K> reader = StaticSegment.sequentialReader(descriptor, keySupport, fsyncedLimit))
try (SequentialReader<K> reader = StaticSegment.sequentialReader(descriptor, keySupport, fsyncedLimit))
{
int last = -1;
while (reader.advance())
{
int current = reader.offset();
if (last >= 0)
index.update(reader.key(), last, current);
last = current;
}
index.update(reader.key(), reader.offset, reader.buffer.position() - reader.offset);
}
return index;
}

View File

@ -93,6 +93,7 @@ public abstract class Segment<K, V> implements Closeable, RefCounted<Segment<K,
int size = Index.readSize(all[i]);
Invariants.checkState(offset < prevOffset);
Invariants.checkState(read(offset, size, into), "Read should always return true");
Invariants.checkState(id.equals(into.key), "Index for %s read incorrect key: expected %s but read %s", descriptor, id, into.key);
onEntry.accept(descriptor.timestamp, offset, into.key, into.value, into.hosts, into.userVersion);
}
}

View File

@ -28,6 +28,7 @@ import java.util.Collection;
import java.util.List;
import java.util.concurrent.locks.LockSupport;
import accord.utils.Invariants;
import org.agrona.collections.IntHashSet;
import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.io.util.File;
@ -260,7 +261,11 @@ public final class StaticSegment<K, V> extends Segment<K, V>
ByteBuffer duplicate = buffer.duplicate().position(offset).limit(offset + size);
try (DataInputBuffer in = new DataInputBuffer(duplicate, false))
{
return EntrySerializer.tryRead(into, keySupport, duplicate, in, syncedOffsets.syncedOffset(), descriptor.userVersion);
if (!EntrySerializer.tryRead(into, keySupport, duplicate, in, syncedOffsets.syncedOffset(), descriptor.userVersion))
return false;
Invariants.checkState(in.available() == 0);
return true;
}
catch (IOException e)
{

View File

@ -31,7 +31,7 @@ import com.codahale.metrics.Counting;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Timer;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.api.AccordTimeService;
import static java.util.concurrent.TimeUnit.MICROSECONDS;
import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics;
@ -206,7 +206,7 @@ public class AccordMetrics
@Override
public void onStable(Command cmd)
{
long now = AccordService.now();
long now = AccordTimeService.nowMicros();
AccordMetrics metrics = forTransaction(cmd.txnId());
if (metrics != null)
{
@ -218,7 +218,7 @@ public class AccordMetrics
@Override
public void onExecuted(Command cmd)
{
long now = AccordService.now();
long now = AccordTimeService.nowMicros();
AccordMetrics metrics = forTransaction(cmd.txnId());
if (metrics != null)
{
@ -232,7 +232,7 @@ public class AccordMetrics
@Override
public void onApplied(Command cmd, long applyStartTimestamp)
{
long now = AccordService.now();
long now = AccordTimeService.nowMicros();
AccordMetrics metrics = forTransaction(cmd.txnId());
if (metrics != null)
{
@ -270,7 +270,7 @@ public class AccordMetrics
AccordMetrics metrics = forTransaction(txnId);
if (metrics != null)
{
long now = AccordService.now();
long now = AccordTimeService.nowMicros();
metrics.recoveryDuration.update(now - recoveryTimestamp.hlc(), MICROSECONDS);
metrics.recoveryDelay.update(recoveryTimestamp.hlc() - txnId.hlc(), MICROSECONDS);

View File

@ -309,56 +309,57 @@ public enum Verb
DATA_MOVEMENT_EXECUTED_REQ (817, P1, rpcTimeout, MISC, () -> DataMovement.Status.serializer, () -> DataMovements.instance, DATA_MOVEMENT_EXECUTED_RSP ),
// accord
ACCORD_SIMPLE_RSP (119, P2, writeTimeout, IMMEDIATE, () -> EnumSerializer.simpleReply, RESPONSE_HANDLER ),
ACCORD_PRE_ACCEPT_RSP (120, P2, writeTimeout, IMMEDIATE, () -> PreacceptSerializers.reply, RESPONSE_HANDLER ),
ACCORD_PRE_ACCEPT_REQ (121, P2, writeTimeout, IMMEDIATE, () -> PreacceptSerializers.request, AccordService::verbHandlerOrNoop, ACCORD_PRE_ACCEPT_RSP ),
ACCORD_ACCEPT_RSP (122, P2, writeTimeout, IMMEDIATE, () -> AcceptSerializers.reply, RESPONSE_HANDLER ),
ACCORD_ACCEPT_REQ (123, P2, writeTimeout, IMMEDIATE, () -> AcceptSerializers.request, AccordService::verbHandlerOrNoop, ACCORD_ACCEPT_RSP ),
ACCORD_ACCEPT_INVALIDATE_REQ (124, P2, writeTimeout, IMMEDIATE, () -> AcceptSerializers.invalidate, AccordService::verbHandlerOrNoop, ACCORD_ACCEPT_RSP ),
ACCORD_READ_RSP (125, P2, writeTimeout, IMMEDIATE, () -> ReadDataSerializers.reply, RESPONSE_HANDLER ),
ACCORD_READ_REQ (126, P2, writeTimeout, IMMEDIATE, () -> ReadDataSerializers.readData, AccordService::verbHandlerOrNoop, ACCORD_READ_RSP ),
ACCORD_COMMIT_REQ (127, P2, writeTimeout, IMMEDIATE, () -> CommitSerializers.request, AccordService::verbHandlerOrNoop, ACCORD_READ_RSP ),
ACCORD_COMMIT_INVALIDATE_REQ (128, P2, writeTimeout, IMMEDIATE, () -> CommitSerializers.invalidate, AccordService::verbHandlerOrNoop ),
ACCORD_APPLY_RSP (129, P2, writeTimeout, IMMEDIATE, () -> ApplySerializers.reply, RESPONSE_HANDLER ),
ACCORD_APPLY_REQ (130, P2, writeTimeout, IMMEDIATE, () -> ApplySerializers.request, AccordService::verbHandlerOrNoop, ACCORD_APPLY_RSP ),
ACCORD_BEGIN_RECOVER_RSP (131, P2, writeTimeout, IMMEDIATE, () -> RecoverySerializers.reply, RESPONSE_HANDLER ),
ACCORD_BEGIN_RECOVER_REQ (132, P2, writeTimeout, IMMEDIATE, () -> RecoverySerializers.request, AccordService::verbHandlerOrNoop, ACCORD_BEGIN_RECOVER_RSP ),
ACCORD_BEGIN_INVALIDATE_RSP (133, P2, writeTimeout, IMMEDIATE, () -> BeginInvalidationSerializers.reply, RESPONSE_HANDLER ),
ACCORD_BEGIN_INVALIDATE_REQ (134, P2, writeTimeout, IMMEDIATE, () -> BeginInvalidationSerializers.request, AccordService::verbHandlerOrNoop, ACCORD_BEGIN_INVALIDATE_RSP ),
ACCORD_AWAIT_RSP (136, P2, writeTimeout, IMMEDIATE, () -> AwaitSerializer.syncReply, RESPONSE_HANDLER ),
ACCORD_AWAIT_REQ (135, P2, writeTimeout, IMMEDIATE, () -> AwaitSerializer.request, AccordService::verbHandlerOrNoop, ACCORD_AWAIT_RSP ),
ACCORD_AWAIT_ASYNC_RSP_REQ (137, P2, writeTimeout, IMMEDIATE, () -> AwaitSerializer.asyncReply, AccordService::verbHandlerOrNoop ),
ACCORD_WAIT_UNTIL_APPLIED_REQ (138, P2, writeTimeout, IMMEDIATE, () -> ReadDataSerializers.waitUntilApplied, AccordService::verbHandlerOrNoop, ACCORD_READ_RSP ),
ACCORD_INFORM_DURABLE_REQ (140, P2, writeTimeout, IMMEDIATE, () -> InformDurableSerializers.request, AccordService::verbHandlerOrNoop, ACCORD_SIMPLE_RSP ),
ACCORD_CHECK_STATUS_RSP (141, P2, writeTimeout, IMMEDIATE, () -> CheckStatusSerializers.reply, RESPONSE_HANDLER ),
ACCORD_CHECK_STATUS_REQ (142, P2, writeTimeout, IMMEDIATE, () -> CheckStatusSerializers.request, AccordService::verbHandlerOrNoop, ACCORD_CHECK_STATUS_RSP ),
ACCORD_CALCULATE_DEPS_RSP (143, P2, writeTimeout, IMMEDIATE, () -> CalculateDepsSerializers.reply, RESPONSE_HANDLER ),
ACCORD_CALCULATE_DEPS_REQ (144, P2, longTimeout, IMMEDIATE, () -> CalculateDepsSerializers.request, AccordService::verbHandlerOrNoop, ACCORD_CALCULATE_DEPS_RSP),
ACCORD_GET_EPHMRL_READ_DEPS_RSP (161, P2, writeTimeout, IMMEDIATE, () -> GetEphmrlReadDepsSerializers.reply, RESPONSE_HANDLER ),
ACCORD_GET_EPHMRL_READ_DEPS_REQ (162, P2, writeTimeout, IMMEDIATE, () -> GetEphmrlReadDepsSerializers.request, AccordService::verbHandlerOrNoop, ACCORD_GET_EPHMRL_READ_DEPS_RSP),
ACCORD_GET_MAX_CONFLICT_RSP (163, P2, writeTimeout, IMMEDIATE, () -> GetMaxConflictSerializers.reply, RESPONSE_HANDLER ),
ACCORD_GET_MAX_CONFLICT_REQ (164, P2, writeTimeout, IMMEDIATE, () -> GetMaxConflictSerializers.request, AccordService::verbHandlerOrNoop, ACCORD_GET_MAX_CONFLICT_RSP),
ACCORD_FETCH_DATA_RSP (145, P2, writeTimeout, IMMEDIATE, () -> FetchSerializers.reply, RESPONSE_HANDLER ),
ACCORD_FETCH_DATA_REQ (146, P2, writeTimeout, IMMEDIATE, () -> FetchSerializers.request, AccordService::verbHandlerOrNoop, ACCORD_FETCH_DATA_RSP ),
ACCORD_SET_SHARD_DURABLE_REQ (147, P2, writeTimeout, MISC, () -> SetDurableSerializers.shardDurable, AccordService::verbHandlerOrNoop, ACCORD_SIMPLE_RSP ),
ACCORD_SET_GLOBALLY_DURABLE_REQ (148, P2, writeTimeout, MISC, () -> SetDurableSerializers.globallyDurable,AccordService::verbHandlerOrNoop, ACCORD_SIMPLE_RSP ),
ACCORD_QUERY_DURABLE_BEFORE_RSP (149, P2, writeTimeout, IMMEDIATE, () -> QueryDurableBeforeSerializers.reply, RESPONSE_HANDLER ),
ACCORD_QUERY_DURABLE_BEFORE_REQ (150, P2, writeTimeout, IMMEDIATE, () -> QueryDurableBeforeSerializers.request,AccordService::verbHandlerOrNoop, ACCORD_QUERY_DURABLE_BEFORE_RSP ),
ACCORD_SIMPLE_RSP (119, P2, writeTimeout, IMMEDIATE, () -> EnumSerializer.simpleReply, AccordService::responseHandlerOrNoop ),
ACCORD_PRE_ACCEPT_RSP (120, P2, writeTimeout, IMMEDIATE, () -> PreacceptSerializers.reply, AccordService::responseHandlerOrNoop ),
ACCORD_PRE_ACCEPT_REQ (121, P2, writeTimeout, IMMEDIATE, () -> PreacceptSerializers.request, AccordService::requestHandlerOrNoop, ACCORD_PRE_ACCEPT_RSP ),
ACCORD_ACCEPT_RSP (122, P2, writeTimeout, IMMEDIATE, () -> AcceptSerializers.reply, AccordService::responseHandlerOrNoop ),
ACCORD_ACCEPT_REQ (123, P2, writeTimeout, IMMEDIATE, () -> AcceptSerializers.request, AccordService::requestHandlerOrNoop, ACCORD_ACCEPT_RSP ),
ACCORD_ACCEPT_INVALIDATE_REQ (124, P2, writeTimeout, IMMEDIATE, () -> AcceptSerializers.invalidate, AccordService::requestHandlerOrNoop, ACCORD_ACCEPT_RSP ),
ACCORD_READ_RSP (125, P2, readTimeout, IMMEDIATE, () -> ReadDataSerializers.reply, AccordService::responseHandlerOrNoop ),
ACCORD_READ_REQ (126, P2, readTimeout, IMMEDIATE, () -> ReadDataSerializers.readData, AccordService::requestHandlerOrNoop, ACCORD_READ_RSP ),
ACCORD_COMMIT_REQ (127, P2, writeTimeout, IMMEDIATE, () -> CommitSerializers.request, AccordService::requestHandlerOrNoop, ACCORD_READ_RSP ),
ACCORD_COMMIT_INVALIDATE_REQ (128, P2, writeTimeout, IMMEDIATE, () -> CommitSerializers.invalidate, AccordService::requestHandlerOrNoop ),
ACCORD_APPLY_RSP (129, P2, writeTimeout, IMMEDIATE, () -> ApplySerializers.reply, AccordService::responseHandlerOrNoop ),
ACCORD_APPLY_REQ (130, P2, writeTimeout, IMMEDIATE, () -> ApplySerializers.request, AccordService::requestHandlerOrNoop, ACCORD_APPLY_RSP ),
ACCORD_BEGIN_RECOVER_RSP (131, P2, writeTimeout, IMMEDIATE, () -> RecoverySerializers.reply, AccordService::responseHandlerOrNoop ),
ACCORD_BEGIN_RECOVER_REQ (132, P2, writeTimeout, IMMEDIATE, () -> RecoverySerializers.request, AccordService::requestHandlerOrNoop, ACCORD_BEGIN_RECOVER_RSP ),
ACCORD_BEGIN_INVALIDATE_RSP (133, P2, writeTimeout, IMMEDIATE, () -> BeginInvalidationSerializers.reply, AccordService::responseHandlerOrNoop ),
ACCORD_BEGIN_INVALIDATE_REQ (134, P2, writeTimeout, IMMEDIATE, () -> BeginInvalidationSerializers.request, AccordService::requestHandlerOrNoop, ACCORD_BEGIN_INVALIDATE_RSP ),
ACCORD_AWAIT_RSP (136, P2, writeTimeout, IMMEDIATE, () -> AwaitSerializer.syncReply, AccordService::responseHandlerOrNoop ),
ACCORD_AWAIT_REQ (135, P2, writeTimeout, IMMEDIATE, () -> AwaitSerializer.request, AccordService::requestHandlerOrNoop, ACCORD_AWAIT_RSP ),
ACCORD_AWAIT_ASYNC_RSP_REQ (137, P2, writeTimeout, IMMEDIATE, () -> AwaitSerializer.asyncReply, AccordService::requestHandlerOrNoop ),
ACCORD_WAIT_UNTIL_APPLIED_REQ (138, P2, writeTimeout, IMMEDIATE, () -> ReadDataSerializers.waitUntilApplied, AccordService::requestHandlerOrNoop, ACCORD_READ_RSP ),
ACCORD_INFORM_DURABLE_REQ (140, P2, writeTimeout, IMMEDIATE, () -> InformDurableSerializers.request, AccordService::requestHandlerOrNoop, ACCORD_SIMPLE_RSP ),
ACCORD_CHECK_STATUS_RSP (141, P2, writeTimeout, IMMEDIATE, () -> CheckStatusSerializers.reply, AccordService::responseHandlerOrNoop ),
ACCORD_CHECK_STATUS_REQ (142, P2, writeTimeout, IMMEDIATE, () -> CheckStatusSerializers.request, AccordService::requestHandlerOrNoop, ACCORD_CHECK_STATUS_RSP ),
ACCORD_CALCULATE_DEPS_RSP (143, P2, writeTimeout, IMMEDIATE, () -> CalculateDepsSerializers.reply, AccordService::responseHandlerOrNoop ),
ACCORD_CALCULATE_DEPS_REQ (144, P2, longTimeout, IMMEDIATE, () -> CalculateDepsSerializers.request, AccordService::requestHandlerOrNoop, ACCORD_CALCULATE_DEPS_RSP),
ACCORD_GET_EPHMRL_READ_DEPS_RSP (161, P2, writeTimeout, IMMEDIATE, () -> GetEphmrlReadDepsSerializers.reply, AccordService::responseHandlerOrNoop ),
ACCORD_GET_EPHMRL_READ_DEPS_REQ (162, P2, writeTimeout, IMMEDIATE, () -> GetEphmrlReadDepsSerializers.request, AccordService::requestHandlerOrNoop, ACCORD_GET_EPHMRL_READ_DEPS_RSP),
ACCORD_GET_MAX_CONFLICT_RSP (163, P2, writeTimeout, IMMEDIATE, () -> GetMaxConflictSerializers.reply, AccordService::responseHandlerOrNoop ),
ACCORD_GET_MAX_CONFLICT_REQ (164, P2, writeTimeout, IMMEDIATE, () -> GetMaxConflictSerializers.request, AccordService::requestHandlerOrNoop, ACCORD_GET_MAX_CONFLICT_RSP),
ACCORD_FETCH_DATA_RSP (145, P2, writeTimeout, IMMEDIATE, () -> FetchSerializers.reply, AccordService::responseHandlerOrNoop ),
ACCORD_FETCH_DATA_REQ (146, P2, writeTimeout, IMMEDIATE, () -> FetchSerializers.request, AccordService::requestHandlerOrNoop, ACCORD_FETCH_DATA_RSP ),
ACCORD_SET_SHARD_DURABLE_REQ (147, P2, writeTimeout, MISC, () -> SetDurableSerializers.shardDurable, AccordService::requestHandlerOrNoop, ACCORD_SIMPLE_RSP ),
ACCORD_SET_GLOBALLY_DURABLE_REQ (148, P2, writeTimeout, MISC, () -> SetDurableSerializers.globallyDurable,AccordService::requestHandlerOrNoop, ACCORD_SIMPLE_RSP ),
ACCORD_QUERY_DURABLE_BEFORE_RSP (149, P2, writeTimeout, IMMEDIATE, () -> QueryDurableBeforeSerializers.reply, AccordService::responseHandlerOrNoop ),
ACCORD_QUERY_DURABLE_BEFORE_REQ (150, P2, writeTimeout, IMMEDIATE, () -> QueryDurableBeforeSerializers.request,AccordService::requestHandlerOrNoop, ACCORD_QUERY_DURABLE_BEFORE_RSP ),
ACCORD_SYNC_NOTIFY_REQ (151, P2, writeTimeout, IMMEDIATE, () -> Notification.listSerializer, () -> AccordSyncPropagator.verbHandler, ACCORD_SIMPLE_RSP ),
ACCORD_SYNC_NOTIFY_RSP (151, P2, writeTimeout, IMMEDIATE, () -> EnumSerializer.simpleReply, RESPONSE_HANDLER),
ACCORD_SYNC_NOTIFY_REQ (152, P2, writeTimeout, IMMEDIATE, () -> Notification.listSerializer, () -> AccordSyncPropagator.verbHandler, ACCORD_SYNC_NOTIFY_RSP ),
ACCORD_APPLY_AND_WAIT_REQ (152, P2, writeTimeout, IMMEDIATE, () -> ReadDataSerializers.readData, AccordService::verbHandlerOrNoop, ACCORD_READ_RSP),
ACCORD_APPLY_AND_WAIT_REQ (153, P2, writeTimeout, IMMEDIATE, () -> ReadDataSerializers.readData, AccordService::requestHandlerOrNoop, ACCORD_READ_RSP),
CONSENSUS_KEY_MIGRATION (153, P1, writeTimeout, MUTATION, () -> ConsensusKeyMigrationFinished.serializer,() -> ConsensusKeyMigrationState.consensusKeyMigrationFinishedHandler),
CONSENSUS_KEY_MIGRATION (154, P1, writeTimeout, MUTATION, () -> ConsensusKeyMigrationFinished.serializer,() -> ConsensusKeyMigrationState.consensusKeyMigrationFinishedHandler),
ACCORD_INTEROP_READ_RSP (154, P2, writeTimeout, IMMEDIATE, () -> AccordInteropRead.replySerializer, RESPONSE_HANDLER),
ACCORD_INTEROP_READ_REQ (155, P2, writeTimeout, IMMEDIATE, () -> AccordInteropRead.requestSerializer, AccordService::verbHandlerOrNoop, ACCORD_INTEROP_READ_RSP),
ACCORD_INTEROP_COMMIT_REQ (156, P2, writeTimeout, IMMEDIATE, () -> AccordInteropCommit.serializer, AccordService::verbHandlerOrNoop, ACCORD_INTEROP_READ_RSP),
ACCORD_INTEROP_READ_REPAIR_RSP (157, P2, writeTimeout, IMMEDIATE, () -> AccordInteropReadRepair.replySerializer, RESPONSE_HANDLER),
ACCORD_INTEROP_READ_REPAIR_REQ (158, P2, writeTimeout, IMMEDIATE, () -> AccordInteropReadRepair.requestSerializer, AccordService::verbHandlerOrNoop, ACCORD_INTEROP_READ_REPAIR_RSP),
ACCORD_INTEROP_APPLY_REQ (160, P2, writeTimeout, IMMEDIATE, () -> AccordInteropApply.serializer, AccordService::verbHandlerOrNoop, ACCORD_APPLY_RSP),
ACCORD_FETCH_MIN_EPOCH_RSP (166, P2, writeTimeout, IMMEDIATE, () -> FetchMinEpoch.Response.serializer, RESPONSE_HANDLER),
ACCORD_FETCH_MIN_EPOCH_REQ (165, P2, writeTimeout, IMMEDIATE, () -> FetchMinEpoch.serializer, () -> FetchMinEpoch.handler, ACCORD_FETCH_MIN_EPOCH_RSP),
ACCORD_INTEROP_READ_RSP (155, P2, writeTimeout, IMMEDIATE, () -> AccordInteropRead.replySerializer, AccordService::responseHandlerOrNoop),
ACCORD_INTEROP_READ_REQ (156, P2, writeTimeout, IMMEDIATE, () -> AccordInteropRead.requestSerializer, AccordService::requestHandlerOrNoop, ACCORD_INTEROP_READ_RSP),
ACCORD_INTEROP_COMMIT_REQ (157, P2, writeTimeout, IMMEDIATE, () -> AccordInteropCommit.serializer, AccordService::requestHandlerOrNoop, ACCORD_INTEROP_READ_RSP),
ACCORD_INTEROP_READ_REPAIR_RSP (158, P2, writeTimeout, IMMEDIATE, () -> AccordInteropReadRepair.replySerializer, AccordService::responseHandlerOrNoop),
ACCORD_INTEROP_READ_REPAIR_REQ (159, P2, writeTimeout, IMMEDIATE, () -> AccordInteropReadRepair.requestSerializer, AccordService::requestHandlerOrNoop, ACCORD_INTEROP_READ_REPAIR_RSP),
ACCORD_INTEROP_APPLY_REQ (160, P2, writeTimeout, IMMEDIATE, () -> AccordInteropApply.serializer, AccordService::requestHandlerOrNoop, ACCORD_APPLY_RSP),
ACCORD_FETCH_MIN_EPOCH_RSP (166, P2, writeTimeout, IMMEDIATE, () -> FetchMinEpoch.Response.serializer, RESPONSE_HANDLER),
ACCORD_FETCH_MIN_EPOCH_REQ (165, P2, writeTimeout, IMMEDIATE, () -> FetchMinEpoch.serializer, () -> FetchMinEpoch.handler, ACCORD_FETCH_MIN_EPOCH_RSP),
// generic failure response
FAILURE_RSP (99, P0, noTimeout, REQUEST_RESPONSE, () -> RequestFailure.serializer, RESPONSE_HANDLER ),
@ -490,6 +491,11 @@ public enum Verb
return expiration.applyAsLong(NANOSECONDS);
}
public long expiresAfter(TimeUnit units)
{
return expiration.applyAsLong(units);
}
// this is a little hacky, but reduces the number of parameters up top
public boolean isResponse()
{

View File

@ -0,0 +1,299 @@
/*
* 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;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.service.TimeoutStrategy.LatencySourceFactory;
import org.apache.cassandra.service.TimeoutStrategy.Wait;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.DoubleSupplier;
import java.util.function.LongBinaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.lang.Math.*;
import static java.util.Arrays.stream;
import static java.util.concurrent.TimeUnit.*;
import static org.apache.cassandra.service.TimeoutStrategy.parseWait;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
/**
* <p>A strategy for making retry timing decisions for operations.
* The strategy is defined by four factors: <ul>
* <li> {@link #min}
* <li> {@link #max}
* <li> {@link #spread}
* <li> {@link #waitRandomizer}
* </ul>
*
* <p>The first three represent time periods, and may be defined dynamically based on a simple calculation over: <ul>
* <li> {@code pX()} recent experienced latency distribution for successful operations,
* e.g. {@code p50(rw)} the maximum of read and write median latencies,
* {@code p999(r)} the 99.9th percentile of read latencies
* <li> {@code attempts} the number of failed attempts made by the operation so far
* <li> {@code constant} a user provided floating point constant
* </ul>
*
* <p>Their calculation may take any of these forms
* <li> constant {@code $constant$[mu]s}
* <li> dynamic constant {@code pX() * constant}
* <li> dynamic linear {@code pX() * constant * attempts}
* <li> dynamic exponential {@code pX() * constant ^ attempts}
*
* <p>Furthermore, the dynamic calculations can be bounded with a min/max, like so:
* {@code min[mu]s <= dynamic expr <= max[mu]s}
*
* e.g.
* <li> {@code 10ms <= p50(rw)*0.66}
* <li> {@code 10ms <= p95(rw)*1.8^attempts <= 100ms}
* <li> {@code 5ms <= p50(rw)*0.5}
*
* <p>These calculations are put together to construct a range from which we draw a random number.
* The period we wait for {@code X} will be drawn so that {@code min <= X < max}.
*
* <p>With the constraint that {@code max} must be {@code spread} greater than {@code min},
* but no greater than its expression-defined maximum. {@code max} will be increased up until
* this point, after which {@code min} will be decreased until this gap is imposed.
*
* <p>The {@link #waitRandomizer} property specifies the manner in which a random value is drawn from the range.
* It is defined using one of the following specifiers:
* <li> uniform
* <li> exp($power$) or exponential($power$)
* <li> qexp($power$) or qexponential($power$) or quantizedexponential($power$)
*
* The uniform specifier is self-explanatory, selecting all values in the range with equal probability.
* The exponential specifier draws values towards the end of the range with higher probability, raising
* a floating point number in the range [0..1.0) to the power provided, and translating the resulting value
* to a uniform value in the range.
* The quantized exponential specifier partitions the range into {@code attempts} buckets, then applies the pure
* exponential approach to draw values from [0..attempts), before drawing a uniform value from the corresponding bucket
*/
public class RetryStrategy
{
private static final Pattern RANDOMIZER = Pattern.compile(
"uniform|exp(onential)?[(](?<exp>[0-9.]+)[)]|q(uantized)?exp(onential)?[(](?<qexp>[0-9.]+)[)]");
final static WaitRandomizerFactory randomizers = new WaitRandomizerFactory(){};
protected interface WaitRandomizer
{
long wait(long min, long max, int attempts);
}
interface WaitRandomizerFactory
{
default LongBinaryOperator uniformLongSupplier() { return (min, max) -> ThreadLocalRandom.current().nextLong(min, max); } // DO NOT USE METHOD HANDLES (want to fetch afresh each time)
default DoubleSupplier uniformDoubleSupplier() { return () -> ThreadLocalRandom.current().nextDouble(); }
default WaitRandomizer uniform() { return new Uniform(uniformLongSupplier()); }
default WaitRandomizer exponential(double power) { return new Exponential(uniformLongSupplier(), uniformDoubleSupplier(), power); }
default WaitRandomizer quantizedExponential(double power) { return new QuantizedExponential(uniformLongSupplier(), uniformDoubleSupplier(), power); }
static class Uniform implements WaitRandomizer
{
final LongBinaryOperator uniformLong;
public Uniform(LongBinaryOperator uniformLong)
{
this.uniformLong = uniformLong;
}
@Override
public long wait(long min, long max, int attempts)
{
return uniformLong.applyAsLong(min, max);
}
}
static abstract class AbstractExponential implements WaitRandomizer
{
final LongBinaryOperator uniformLong;
final DoubleSupplier uniformDouble;
final double power;
public AbstractExponential(LongBinaryOperator uniformLong, DoubleSupplier uniformDouble, double power)
{
this.uniformLong = uniformLong;
this.uniformDouble = uniformDouble;
this.power = power;
}
}
static class Exponential extends AbstractExponential
{
public Exponential(LongBinaryOperator uniformLong, DoubleSupplier uniformDouble, double power)
{
super(uniformLong, uniformDouble, power);
}
@Override
public long wait(long min, long max, int attempts)
{
if (attempts == 1)
return uniformLong.applyAsLong(min, max);
double p = uniformDouble.getAsDouble();
long delta = max - min;
delta *= Math.pow(p, power);
return max - delta;
}
}
static class QuantizedExponential extends AbstractExponential
{
public QuantizedExponential(LongBinaryOperator uniformLong, DoubleSupplier uniformDouble, double power)
{
super(uniformLong, uniformDouble, power);
}
@Override
public long wait(long min, long max, int attempts)
{
long quanta = (max - min) / attempts;
if (attempts == 1 || quanta == 0)
return uniformLong.applyAsLong(min, max);
double p = uniformDouble.getAsDouble();
int base = (int) (attempts * Math.pow(p, power));
return max - ThreadLocalRandom.current().nextLong(quanta * base, quanta * (base + 1));
}
}
}
public final WaitRandomizer waitRandomizer;
public final Wait min, max, spread;
public RetryStrategy(String waitRandomizer, String min, String max, String spread, LatencySourceFactory latencies)
{
this.waitRandomizer = parseWaitRandomizer(waitRandomizer);
this.min = parseBound(min, true, latencies);
this.max = parseBound(max, false, latencies);
this.spread = parseBound(spread, true, latencies);
}
protected RetryStrategy(WaitRandomizer waitRandomizer, Wait min, Wait max, Wait spread)
{
this.waitRandomizer = waitRandomizer;
this.min = min;
this.max = max;
this.spread = spread;
}
protected Wait parseBound(String spec, boolean isMin, LatencySourceFactory latencies)
{
long defaultMaxMicros = DatabaseDescriptor.getRpcTimeout(MICROSECONDS);
return parseWait(spec, 0, defaultMaxMicros, isMin ? 0 : defaultMaxMicros, latencies);
}
protected long computeWaitUntil(int attempts)
{
long wait = computeWait(attempts);
return nanoTime() + MICROSECONDS.toNanos(wait);
}
protected long computeWait(int attempts)
{
long minWaitMicros = min.get(attempts);
long maxWaitMicros = max.get(attempts);
long spreadMicros = spread.get(attempts);
if (minWaitMicros + spreadMicros > maxWaitMicros)
{
maxWaitMicros = minWaitMicros + spreadMicros;
if (maxWaitMicros > this.max.max)
{
maxWaitMicros = this.max.max;
minWaitMicros = max(this.min.min, min(this.min.max, maxWaitMicros - spreadMicros));
}
}
return waitRandomizer.wait(minWaitMicros, maxWaitMicros, attempts);
}
public static class ParsedStrategy
{
public final String waitRandomizer, min, max, spread;
public final RetryStrategy strategy;
protected ParsedStrategy(String waitRandomizer, String min, String max, String spread, RetryStrategy strategy)
{
this.waitRandomizer = waitRandomizer;
this.min = min;
this.max = max;
this.spread = spread;
this.strategy = strategy;
}
public String toString()
{
return "min=" + min + ",max=" + max + ",spread=" + spread + ",random=" + waitRandomizer;
}
}
@VisibleForTesting
public static ParsedStrategy parseStrategy(String spec, LatencySourceFactory latencies, ParsedStrategy defaultStrategy)
{
String[] args = spec.split(",");
String waitRandomizer = find(args, "random");
String min = find(args, "min");
String max = find(args, "max");
String spread = find(args, "spread");
if (spread == null)
spread = find(args, "delta");
if (waitRandomizer == null) waitRandomizer = defaultStrategy.waitRandomizer;
if (min == null) min = defaultStrategy.min;
if (max == null) max = defaultStrategy.max;
if (spread == null) spread = defaultStrategy.spread;
RetryStrategy strategy = new RetryStrategy(waitRandomizer, min, max, spread, latencies);
return new ParsedStrategy(waitRandomizer, min, max, spread, strategy);
}
protected static String find(String[] args, String param)
{
return stream(args).filter(s -> s.startsWith(param + '='))
.map(s -> s.substring(param.length() + 1))
.findFirst().orElse(null);
}
static WaitRandomizer parseWaitRandomizer(String input)
{
return parseWaitRandomizer(input, randomizers);
}
static WaitRandomizer parseWaitRandomizer(String input, WaitRandomizerFactory randomizers)
{
Matcher m = RANDOMIZER.matcher(input);
if (!m.matches())
throw new IllegalArgumentException(input + " does not match" + RANDOMIZER);
String exp;
exp = m.group("exp");
if (exp != null)
return randomizers.exponential(Double.parseDouble(exp));
exp = m.group("qexp");
if (exp != null)
return randomizers.quantizedExponential(Double.parseDouble(exp));
return randomizers.uniform();
}
}

View File

@ -0,0 +1,368 @@
/*
* 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;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.codahale.metrics.Snapshot;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.metrics.ClientRequestMetrics;
import org.apache.cassandra.utils.NoSpamLogger;
import static java.lang.Double.parseDouble;
import static java.lang.Integer.parseInt;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.pow;
import static java.util.concurrent.TimeUnit.MICROSECONDS;
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.utils.Clock.Global.nanoTime;
/**
* <p>A strategy for making timeout decisions for operations. This is a simplified single-value version of
* the RetryStrategy
*
* <p>This represent a computed time period, that may be defined dynamically based on a simple calculation over: <ul>
* <li> {@code pX()} recent experienced latency distribution for successful operations,
* e.g. {@code p50(rw)} the maximum of read and write median latencies,
* {@code p999(r)} the 99.9th percentile of read latencies
* <li> {@code attempts} the number of failed attempts made by the operation so far
* <li> {@code constant} a user provided floating point constant
* </ul>
*
* <p>The calculation may take any of these forms
* <li> constant {@code $constant$[mu]s}
* <li> dynamic constant {@code pX() * constant}
* <li> dynamic linear {@code pX() * constant * attempts}
* <li> dynamic exponential {@code pX() * constant ^ attempts}
*
* <p>Furthermore, the dynamic calculations can be bounded with a min/max, like so:
* {@code min[mu]s <= dynamic expr <= max[mu]s}
*
* e.g.
* <li> {@code 10ms <= p50(rw)*0.66}
* <li> {@code 10ms <= p95(rw)*1.8^attempts <= 100ms}
* <li> {@code 5ms <= p50(rw)*0.5}
*
* TODO (expected): permit simple constant addition (e.g. p50+5ms)
* TODO (required): track separate stats per-DC as inputs to these decisions
*/
public class TimeoutStrategy
{
private static final Logger logger = LoggerFactory.getLogger(TimeoutStrategy.class);
static final Pattern BOUND = Pattern.compile(
"(?<const>0|[0-9]+[mu]s)" +
"|((?<min>0|[0-9]+[mu]s) *<= *)?" +
"(p(?<perc>[0-9]+)(\\((?<rw>r|w|rw|wr)\\))?|(?<constbase>0|[0-9]+[mu]s))" +
"\\s*([*]\\s*(?<mod>[0-9.]+)?\\s*(?<modkind>[*^]\\s*attempts)?)?" +
"( *<= *(?<max>0|[0-9]+[mu]s))?");
static final Pattern TIME = Pattern.compile(
"0|([0-9]+)ms|([0-9]+)us");
// Factories can be useful for testing purposes, to supply custom implementations of selectors and modifiers.
final static LatencySupplierFactory selectors = new LatencySupplierFactory(){};
final static LatencyModifierFactory modifiers = new LatencyModifierFactory(){};
interface LatencyModifierFactory
{
default LatencyModifier identity() { return (l, a) -> l; }
default LatencyModifier multiply(double constant) { return (l, a) -> saturatedCast(l * constant); }
default LatencyModifier multiplyByAttempts(double multiply) { return (l, a) -> saturatedCast(l * multiply * a); }
default LatencyModifier multiplyByAttemptsExp(double base) { return (l, a) -> saturatedCast(l * pow(base, a)); }
}
interface LatencySupplier
{
long get();
}
public interface LatencySource
{
long get(double percentile);
}
interface LatencySupplierFactory
{
default LatencySupplier constant(long latency) { return () -> latency; }
default LatencySupplier percentile(double percentile, LatencySource latencies) { return () -> latencies.get(percentile); }
}
public interface LatencySourceFactory
{
LatencySource source(String params);
static LatencySourceFactory rw(ClientRequestMetrics reads, ClientRequestMetrics writes)
{
return new ReadWriteLatencySourceFactory(reads, writes);
}
static LatencySourceFactory of(ClientRequestMetrics latencies)
{
LatencySource source = new TimeLimitedLatencySupplier(latencies.latency::getSnapshot, 10, SECONDS);
return ignore -> source;
}
}
public static class ReadWriteLatencySourceFactory implements LatencySourceFactory
{
final LatencySource reads, writes;
public ReadWriteLatencySourceFactory(ClientRequestMetrics reads, ClientRequestMetrics writes)
{
this(reads.latency::getSnapshot, writes.latency::getSnapshot);
}
public ReadWriteLatencySourceFactory(Supplier<Snapshot> reads, Supplier<Snapshot> writes)
{
this.reads = new TimeLimitedLatencySupplier(reads, 10, SECONDS);
this.writes = new TimeLimitedLatencySupplier(writes, 10, SECONDS);
}
@Override
public LatencySource source(String rw)
{
if (rw.length() == 2)
return percentile -> Math.max(reads.get(percentile), writes.get(percentile));
else if ("r".equals(rw))
return reads;
else
return writes;
}
}
interface LatencyModifier
{
long modify(long latency, int attempts);
}
static class SnapshotAndTime
{
final long validUntil;
final Snapshot snapshot;
SnapshotAndTime(long validUntil, Snapshot snapshot)
{
this.validUntil = validUntil;
this.snapshot = snapshot;
}
}
static class TimeLimitedLatencySupplier extends AtomicReference<SnapshotAndTime> implements LatencySource
{
final Supplier<Snapshot> snapshotSupplier;
final long validForNanos;
TimeLimitedLatencySupplier(Supplier<Snapshot> snapshotSupplier, long time, TimeUnit units)
{
this.snapshotSupplier = snapshotSupplier;
this.validForNanos = units.toNanos(time);
}
private Snapshot getSnapshot()
{
long now = nanoTime();
SnapshotAndTime cur = get();
if (cur != null && cur.validUntil > now)
return cur.snapshot;
Snapshot newSnapshot = snapshotSupplier.get();
SnapshotAndTime next = new SnapshotAndTime(now + validForNanos, newSnapshot);
if (compareAndSet(cur, next))
return next.snapshot;
return accumulateAndGet(next, (a, b) -> a.validUntil > b.validUntil ? a : b).snapshot;
}
@Override
public long get(double percentile)
{
return (long)getSnapshot().getValue(percentile);
}
}
public static class Wait
{
final long min, max, onFailure;
final LatencyModifier modifier;
final LatencySupplier supplier;
Wait(long min, long max, long onFailure, LatencyModifier modifier, LatencySupplier supplier)
{
Preconditions.checkArgument(min<=max, "min (%s) must be less than or equal to max (%s)", min, max);
this.min = min;
this.max = max;
this.onFailure = onFailure;
this.modifier = modifier;
this.supplier = supplier;
}
long get(int attempts)
{
try
{
long base = supplier.get();
return max(min, min(max, modifier.modify(base, attempts)));
}
catch (Throwable t)
{
NoSpamLogger.getLogger(logger, 1L, MINUTES).info("", t);
return onFailure;
}
}
public String toString()
{
return "Bound{" +
"min=" + min +
", max=" + max +
", onFailure=" + onFailure +
", modifier=" + modifier +
", supplier=" + supplier +
'}';
}
}
final Wait wait;
public TimeoutStrategy(String spec, LatencySourceFactory latencies)
{
this.wait = parseWait(spec, latencies);
}
public long computeWait(int attempts, TimeUnit units)
{
return units.convert(wait.get(attempts), MICROSECONDS);
}
public long computeWaitUntil(int attempts)
{
long nanos = computeWait(attempts, NANOSECONDS);
return nanoTime() + nanos;
}
protected Wait parseWait(String spec, LatencySourceFactory latencies)
{
long defaultMicros = DatabaseDescriptor.getRpcTimeout(MICROSECONDS);
return parseWait(spec, 0, defaultMicros, defaultMicros, latencies);
}
private static LatencySupplier parseLatencySupplier(Matcher m, LatencySupplierFactory selectors, LatencySourceFactory latenciesFactory)
{
String perc = m.group("perc");
if (perc == null)
return selectors.constant(parseInMicros(m.group("constbase")));
LatencySource latencies = latenciesFactory.source(m.group("rw"));
double percentile = parseDouble("0." + perc);
return selectors.percentile(percentile, latencies);
}
private static LatencyModifier parseLatencyModifier(Matcher m, LatencyModifierFactory modifiers)
{
String mod = m.group("mod");
if (mod == null)
return modifiers.identity();
double modifier = parseDouble(mod);
String modkind = m.group("modkind");
if (modkind == null)
return modifiers.multiply(modifier);
if (modkind.startsWith("*"))
return modifiers.multiplyByAttempts(modifier);
else if (modkind.startsWith("^"))
return modifiers.multiplyByAttemptsExp(modifier);
else
throw new IllegalArgumentException("Unrecognised attempt modifier: " + modkind);
}
static long saturatedCast(double v)
{
if (v > Long.MAX_VALUE)
return Long.MAX_VALUE;
return (long) v;
}
public static Wait parseWait(String input, long defaultMin, long defaultMax, long onFailure, LatencySourceFactory latencies)
{
return parseWait(input, defaultMin, defaultMax, onFailure, latencies, selectors, modifiers);
}
@VisibleForTesting
public static Wait parseWait(String input, long defaultMinMicros, long defaultMaxMicros, long onFailure, LatencySourceFactory latencies, LatencySupplierFactory selectors, LatencyModifierFactory modifiers)
{
Matcher m = BOUND.matcher(input);
if (!m.matches())
throw new IllegalArgumentException(input + " does not match " + BOUND);
String maybeConst = m.group("const");
if (maybeConst != null)
{
long v = parseInMicros(maybeConst);
return new Wait(v, v, v, modifiers.identity(), selectors.constant(v));
}
long min = parseInMicros(m.group("min"), defaultMinMicros);
long max = parseInMicros(m.group("max"), defaultMaxMicros);
return new Wait(min, max, onFailure, parseLatencyModifier(m, modifiers), parseLatencySupplier(m, selectors, latencies));
}
private static long parseInMicros(String input, long orElse)
{
if (input == null)
return orElse;
return parseInMicros(input);
}
private static long parseInMicros(String input)
{
Matcher m = TIME.matcher(input);
if (!m.matches())
throw new IllegalArgumentException(input + " does not match " + TIME);
String text;
if (null != (text = m.group(1)))
return parseInt(text) * 1000;
else if (null != (text = m.group(2)))
return parseInt(text);
else
return 0;
}
private static String orElse(Supplier<String> get, String orElse)
{
String result = get.get();
return result != null ? result : orElse;
}
}

View File

@ -80,11 +80,8 @@ import org.apache.cassandra.utils.concurrent.AsyncPromise;
import org.apache.cassandra.utils.concurrent.Promise;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import static accord.primitives.SaveStatus.Applying;
import static accord.primitives.Status.Committed;
import static accord.primitives.Status.Invalidated;
import static accord.primitives.Status.PreApplied;
import static accord.primitives.Status.Stable;
import static accord.primitives.Status.Truncated;
import static accord.utils.Invariants.checkState;
@ -310,7 +307,6 @@ public class AccordCommandStore extends CommandStore
journal.persistStoreState(id, fieldUpdates, onFlush);
}
@Nullable
@VisibleForTesting
public void appendToLog(Command before, Command after, Runnable onFlush)
@ -626,10 +622,7 @@ public class AccordCommandStore extends CommandStore
safeStore -> {
SafeCommand safeCommand = safeStore.unsafeGet(txnId);
Command local = safeCommand.current();
if (local.is(Stable) || local.is(PreApplied))
Commands.maybeExecute(safeStore, safeCommand, local, true, true);
else if (local.saveStatus().compareTo(Applying) >= 0 && !local.hasBeen(Truncated))
Commands.applyWrites(safeStore, context, local).begin(agent);
Commands.maybeExecute(safeStore, safeCommand, local, true, true);
})
.begin((unused, throwable) -> {
if (throwable != null)

View File

@ -20,7 +20,6 @@ package org.apache.cassandra.service.accord;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.NavigableMap;
import java.util.Set;
@ -32,6 +31,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import accord.impl.ErasedSafeCommand;
import accord.impl.TimestampsForKey;
import accord.local.Cleanup;
import accord.local.Command;
import accord.local.CommandStores;
@ -113,7 +113,7 @@ public class AccordJournal implements IJournal, Shutdownable
throw new UnsupportedOperationException();
}
},
new AccordSegmentCompactor<>(JournalKey.SUPPORT, params.userVersion()));
new AccordSegmentCompactor<>(params.userVersion()));
this.journalTable = new AccordJournalTable<>(journal, JournalKey.SUPPORT, params.userVersion());
this.params = params;
}
@ -151,7 +151,7 @@ public class AccordJournal implements IJournal, Shutdownable
@Override
public void shutdown()
{
Invariants.checkState(status == Status.REPLAY || status == Status.STARTED);
Invariants.checkState(status == Status.REPLAY || status == Status.STARTED, "%s", status);
status = Status.TERMINATING;
journal.shutdown();
status = Status.TERMINATED;
@ -357,23 +357,10 @@ public class AccordJournal implements IJournal, Shutdownable
public void replay()
{
logger.info("Starting journal replay.");
TimestampsForKey.unsafeSetReplay(true);
CommandsForKey.disableLinearizabilityViolationsReporting();
AccordKeyspace.truncateAllCaches();
// TODO (expected): optimize replay memory footprint
class ToApply
{
final JournalKey key;
final Command command;
ToApply(JournalKey key, Command command)
{
this.key = key;
this.command = command;
}
}
List<ToApply> toApply = new ArrayList<>();
try (AccordJournalTable.KeyOrderIterator<JournalKey> iter = journalTable.readAll())
{
JournalKey key;
@ -406,23 +393,17 @@ public class AccordJournal implements IJournal, Shutdownable
{
Command command = builder.construct();
AccordCommandStore commandStore = (AccordCommandStore) node.commandStores().forId(key.commandStoreId);
commandStore.loader().load(command).get();
AccordCommandStore.Loader loader = commandStore.loader();
loader.load(command).get();
if (command.saveStatus().compareTo(SaveStatus.Stable) >= 0 && !command.hasBeen(Truncated))
toApply.add(new ToApply(key, command));
loader.apply(command);
}
}
toApply.sort(Comparator.comparing(v -> v.command.executeAt()));
for (ToApply apply : toApply)
{
AccordCommandStore commandStore = (AccordCommandStore) node.commandStores().forId(apply.key.commandStoreId);
logger.info("Apply {}", apply.command);
commandStore.loader().apply(apply.command);
}
logger.info("Waiting for command stores to quiesce.");
((AccordCommandStores)node.commandStores()).waitForQuiescense();
CommandsForKey.enableLinearizabilityViolationsReporting();
TimestampsForKey.unsafeSetReplay(false);
logger.info("Finished journal replay.");
status = Status.STARTED;
}
@ -488,4 +469,9 @@ public class AccordJournal implements IJournal, Shutdownable
}
}
}
public void unsafeSetStarted()
{
status = Status.STARTED;
}
}

View File

@ -49,7 +49,6 @@ import org.apache.cassandra.io.sstable.ISSTableScanner;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.journal.EntrySerializer.EntryHolder;
import org.apache.cassandra.journal.Journal;
import org.apache.cassandra.journal.KeySupport;
@ -58,7 +57,7 @@ import org.apache.cassandra.schema.ColumnMetadata;
import static org.apache.cassandra.io.sstable.SSTableReadsListener.NOOP_LISTENER;
public class AccordJournalTable<K, V>
public class AccordJournalTable<K extends JournalKey, V>
{
private static final IntHashSet SENTINEL_HOSTS = new IntHashSet();
@ -170,8 +169,7 @@ public class AccordJournalTable<K, V>
private void readAllFromTable(K key, TableRecordConsumer onEntry)
{
DecoratedKey pk = makePartitionKey(cfs, key, keySupport, accordJournalVersion);
DecoratedKey pk = AccordKeyspace.JournalColumns.decorate(key);
try (RefViewFragment view = cfs.selectAndReference(View.select(SSTableSet.LIVE, pk)))
{
if (view.sstables.isEmpty())
@ -209,20 +207,6 @@ public class AccordJournalTable<K, V>
onEntry.accept(descriptor, position, into.key, into.value, into.hosts, into.userVersion);
}
public static <K> DecoratedKey makePartitionKey(ColumnFamilyStore cfs, K key, KeySupport<K> keySupport, int version)
{
try (DataOutputBuffer out = new DataOutputBuffer(keySupport.serializedSize(version)))
{
keySupport.serialize(key, out, version);
return cfs.decorateKey(out.buffer(false));
}
catch (IOException e)
{
// can only throw if (key) serializer is buggy
throw new RuntimeException("Could not serialize key " + key + ", this shouldn't be possible", e);
}
}
@SuppressWarnings("resource") // Auto-closeable iterator will release related resources
public KeyOrderIterator<K> readAll()
{
@ -249,7 +233,7 @@ public class AccordJournalTable<K, V>
: UnfilteredPartitionIterators.merge(scanners, UnfilteredPartitionIterators.MergeListener.NOOP);
}
public K key()
public JournalKey key()
{
if (partition == null)
{
@ -259,7 +243,7 @@ public class AccordJournalTable<K, V>
return null;
}
return keySupport.deserialize(partition.partitionKey().getKey(), 0, accordJournalVersion);
return AccordKeyspace.JournalColumns.getJournalKey(partition.partitionKey());
}
protected void readAllForKey(K key, RecordConsumer<K> recordConsumer)
@ -318,7 +302,8 @@ public class AccordJournalTable<K, V>
@Override
public K key()
{
K tableKey = tableIterator.key();
// TODO (expected): fix generics mismatch here
K tableKey = (K)tableIterator.key();
K journalKey = staticSegmentIterator.key();
if (tableKey == null)
return journalKey;
@ -331,7 +316,7 @@ public class AccordJournalTable<K, V>
@Override
public void readAllForKey(K key, RecordConsumer<K> reader)
{
K tableKey = tableIterator.key();
K tableKey = (K)tableIterator.key();
K journalKey = staticSegmentIterator.key();
if (journalKey != null && keySupport.compare(journalKey, key) == 0)
staticSegmentIterator.readAllForKey(key, (segment, position, key1, buffer, hosts, userVersion) -> {

View File

@ -83,6 +83,7 @@ import org.apache.cassandra.db.filter.DataLimits;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.marshal.ByteArrayAccessor;
import org.apache.cassandra.db.marshal.ByteBufferAccessor;
import org.apache.cassandra.db.marshal.ByteType;
import org.apache.cassandra.db.marshal.BytesType;
import org.apache.cassandra.db.marshal.CompositeType;
import org.apache.cassandra.db.marshal.Int32Type;
@ -230,12 +231,14 @@ public class AccordKeyspace
parse(JOURNAL,
"accord journal",
"CREATE TABLE %s ("
+ "key blob,"
+ "store_id int,"
+ "type tinyint,"
+ "id blob,"
+ "descriptor bigint,"
+ "offset int,"
+ "user_version int,"
+ "record blob,"
+ "PRIMARY KEY(key, descriptor, offset)"
+ "PRIMARY KEY((store_id, type, id), descriptor, offset)"
+ ") WITH CLUSTERING ORDER BY (descriptor DESC, offset DESC) WITH compression = {'class':'NoopCompressor'};")
.partitioner(new LocalPartitioner(BytesType.instance))
.build();
@ -1350,6 +1353,68 @@ public class AccordKeyspace
}
}
public static class JournalColumns
{
static final ClusteringComparator keyComparator = Journal.partitionKeyAsClusteringComparator();
static final CompositeType partitionKeyType = (CompositeType) Journal.partitionKeyType;
public static final ColumnMetadata store_id = getColumn(Journal, "store_id");
public static final ColumnMetadata type = getColumn(Journal, "type");
public static final ColumnMetadata id = getColumn(Journal, "id");
public static final ColumnMetadata record = getColumn(Journal, "record");
public static DecoratedKey decorate(JournalKey key)
{
ByteBuffer id = ByteBuffer.allocate(CommandSerializers.txnId.serializedSize());
CommandSerializers.txnId.serialize(key.id, id);
id.flip();
ByteBuffer pk = keyComparator.make(key.commandStoreId, (byte)key.type.id, id).serializeAsPartitionKey();
Invariants.checkState(getTxnId(splitPartitionKey(pk)).equals(key.id));
return Journal.partitioner.decorateKey(pk);
}
public static ByteBuffer[] splitPartitionKey(DecoratedKey key)
{
return JournalColumns.partitionKeyType.split(key.getKey());
}
public static ByteBuffer[] splitPartitionKey(ByteBuffer key)
{
return JournalColumns.partitionKeyType.split(key);
}
public static int getStoreId(DecoratedKey pk)
{
return getStoreId(splitPartitionKey(pk));
}
public static int getStoreId(ByteBuffer[] partitionKeyComponents)
{
return Int32Type.instance.compose(partitionKeyComponents[store_id.position()]);
}
public static JournalKey.Type getType(ByteBuffer[] partitionKeyComponents)
{
return JournalKey.Type.fromId(ByteType.instance.compose(partitionKeyComponents[type.position()]));
}
public static TxnId getTxnId(DecoratedKey key)
{
return getTxnId(splitPartitionKey(key));
}
public static TxnId getTxnId(ByteBuffer[] partitionKeyComponents)
{
ByteBuffer buffer = partitionKeyComponents[id.position()];
return CommandSerializers.txnId.deserialize(buffer, buffer.position());
}
public static JournalKey getJournalKey(DecoratedKey key)
{
ByteBuffer[] parts = splitPartitionKey(key);
return new JournalKey(getTxnId(parts), getType(parts), getStoreId(parts));
}
}
private static EpochDiskState saveEpochDiskState(EpochDiskState diskState)
{
String cql = "INSERT INTO " + ACCORD_KEYSPACE_NAME + '.' + EPOCH_METADATA + ' ' +

View File

@ -26,12 +26,18 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import accord.impl.RequestCallbacks;
import accord.messages.*;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import org.apache.cassandra.config.AccordSpec;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.metrics.ClientRequestsMetricsHolder;
import org.apache.cassandra.service.TimeoutStrategy;
import org.apache.cassandra.service.TimeoutStrategy.LatencySourceFactory;
import org.apache.cassandra.utils.Clock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -50,6 +56,7 @@ import org.apache.cassandra.net.ResponseContext;
import org.apache.cassandra.net.Verb;
import static accord.messages.MessageType.Kind.REMOTE;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
public class AccordMessageSink implements MessageSink
{
@ -201,17 +208,28 @@ public class AccordMessageSink implements MessageSink
private final Agent agent;
private final MessageDelivery messaging;
private final AccordEndpointMapper endpointMapper;
private final RequestCallbacks callbacks;
// TODO (required): make hot property
private TimeoutStrategy slowPreaccept, slowRead;
public AccordMessageSink(Agent agent, MessageDelivery messaging, AccordEndpointMapper endpointMapper)
public AccordMessageSink(Agent agent, MessageDelivery messaging, AccordEndpointMapper endpointMapper, RequestCallbacks callbacks)
{
AccordSpec config = DatabaseDescriptor.getAccord();
if (config != null)
{
// TODO (expected): introduce better metrics, esp. for preaccept, but also to disambiguate DC latencies
slowPreaccept = new TimeoutStrategy(config.slowPreAccept, LatencySourceFactory.of(ClientRequestsMetricsHolder.accordReadMetrics));
slowRead = new TimeoutStrategy(config.slowRead, LatencySourceFactory.of(ClientRequestsMetricsHolder.accordReadMetrics));
}
this.agent = agent;
this.messaging = messaging;
this.endpointMapper = endpointMapper;
this.callbacks = callbacks;
}
public AccordMessageSink(Agent agent, AccordConfigurationService endpointMapper)
public AccordMessageSink(Agent agent, AccordConfigurationService endpointMapper, RequestCallbacks callbacks)
{
this(agent, MessagingService.instance(), endpointMapper);
this(agent, MessagingService.instance(), endpointMapper, callbacks);
}
@Override
@ -237,24 +255,42 @@ public class AccordMessageSink implements MessageSink
return txnRequest.txnId.domain().isRange();
}
// TODO (expected): permit bulk send to save esp. on callback registration (and combine records)
@Override
public void send(Node.Id to, Request request, AgentExecutor executor, Callback callback)
{
Verb verb = getVerb(request);
Preconditions.checkNotNull(verb, "Verb is null for type %s", request.type());
Message<Request> message;
if (isRangeBarrier(request))
long nowNanos = Clock.Global.nanoTime();
long expiresAtNanos;
if (isRangeBarrier(request)) expiresAtNanos = nowNanos + DatabaseDescriptor.getAccordRangeBarrierTimeoutNanos();
else expiresAtNanos = nowNanos + verb.expiresAfterNanos();
long delayedAtNanos = Long.MAX_VALUE;
switch (verb)
{
long nowNanos = Clock.Global.nanoTime();
message = Message.out(verb, request, nowNanos + DatabaseDescriptor.getAccordRangeBarrierTimeoutNanos());
}
else
{
message = Message.out(verb, request);
case ACCORD_COMMIT_REQ:
if (((Commit)request).readData == null)
break;
case ACCORD_READ_REQ:
if (slowRead == null || isRangeBarrier(request))
break;
case ACCORD_CHECK_STATUS_REQ:
delayedAtNanos = nowNanos + slowRead.computeWait(1, NANOSECONDS);
break;
case ACCORD_PRE_ACCEPT_REQ:
if (slowPreaccept == null || isRangeBarrier(request))
break;
delayedAtNanos = nowNanos + slowPreaccept.computeWait(1, NANOSECONDS);
}
Message<Request> message = Message.out(verb, request, expiresAtNanos);
InetAddressAndPort endpoint = endpointMapper.mappedEndpoint(to);
logger.trace("Sending {} {} to {}", verb, message.payload, endpoint);
messaging.sendWithCallback(message, endpoint, new AccordCallback<>(executor, (Callback<Reply>) callback, endpointMapper));
callbacks.registerAt(message.id(), executor, callback, to, nowNanos, delayedAtNanos, expiresAtNanos, NANOSECONDS);
messaging.send(message, endpoint);
}
@Override

View File

@ -0,0 +1,74 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.service.accord;
import accord.coordinate.Timeout;
import accord.impl.RequestCallbacks;
import accord.local.Node;
import accord.messages.Reply;
import org.apache.cassandra.exceptions.RequestFailure;
import org.apache.cassandra.exceptions.RequestFailureReason;
import org.apache.cassandra.net.IVerbHandler;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.tracing.Tracing;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime;
class AccordResponseVerbHandler<T extends Reply> implements IVerbHandler<T>
{
private final RequestCallbacks callbacks;
private final AccordEndpointMapper endpointMapper;
AccordResponseVerbHandler(RequestCallbacks callbacks, AccordEndpointMapper endpointMapper)
{
this.callbacks = callbacks;
this.endpointMapper = endpointMapper;
}
@Override
public void doVerb(Message message)
{
Node.Id from = endpointMapper.mappedId(message.from());
if (message.isFailureResponse())
{
Tracing.trace("Processing failure response from {}", message.from());
callbacks.onFailure(message.id(), from, convertFailureMessage((RequestFailure) message.payload));
}
else
{
Tracing.trace("Processing response from {}", message.from());
boolean remove = !(message.payload instanceof Reply) || ((Reply) message.payload).isFinal();
RequestCallbacks.CallbackEntry cbe = callbacks.onSuccess(message.id(), from, message.payload, remove);
if (cbe == null)
return;
long latencyNanos = approxTime.now() - cbe.registeredAt(NANOSECONDS);
MessagingService.instance().latencySubscribers.add(message.from(), latencyNanos, NANOSECONDS);
}
}
private static Throwable convertFailureMessage(RequestFailure failure)
{
return failure.reason == RequestFailureReason.TIMEOUT ?
new Timeout(null, null) :
new RuntimeException(failure.failure);
}
}

View File

@ -35,7 +35,7 @@ import accord.impl.AbstractSafeCommandStore;
import accord.impl.CommandsSummary;
import accord.local.CommandStores;
import accord.local.CommandStores.RangesForEpoch;
import accord.local.NodeTimeService;
import accord.local.NodeCommandStoreService;
import accord.local.PreLoadContext;
import accord.local.RedundantBefore;
import accord.local.cfk.CommandsForKey;
@ -179,7 +179,7 @@ public class AccordSafeCommandStore extends AbstractSafeCommandStore<AccordSafeC
}
@Override
public NodeTimeService time()
public NodeCommandStoreService node()
{
// TODO: safe command store should not have arbitrary time
return commandStore.node();

View File

@ -36,7 +36,6 @@ import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.SSTableTxnWriter;
import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.journal.KeySupport;
import org.apache.cassandra.journal.SegmentCompactor;
import org.apache.cassandra.journal.StaticSegment;
import org.apache.cassandra.journal.StaticSegment.KeyOrderReader;
@ -49,12 +48,10 @@ public class AccordSegmentCompactor<V> implements SegmentCompactor<JournalKey, V
{
private static final Logger logger = LoggerFactory.getLogger(AccordSegmentCompactor.class);
private final int userVersion;
private final KeySupport<JournalKey> keySupport;
public AccordSegmentCompactor(KeySupport<JournalKey> keySupport, int userVersion)
public AccordSegmentCompactor(int userVersion)
{
this.userVersion = userVersion;
this.keySupport = keySupport;
}
@Override
@ -148,7 +145,7 @@ public class AccordSegmentCompactor<V> implements SegmentCompactor<JournalKey, V
{
if (builder != null)
{
SimpleBuilder partitionBuilder = PartitionUpdate.simpleBuilder(AccordKeyspace.Journal, AccordJournalTable.makePartitionKey(cfs, key, keySupport, userVersion));
SimpleBuilder partitionBuilder = PartitionUpdate.simpleBuilder(AccordKeyspace.Journal, AccordKeyspace.JournalColumns.decorate(key));
try (DataOutputBuffer out = DataOutputBuffer.scratchBuffer.get())
{
serializer.reserialize(key, builder, out, userVersion);

View File

@ -69,7 +69,7 @@ import accord.impl.AbstractConfigurationService;
import accord.impl.CoordinateDurabilityScheduling;
import accord.impl.DefaultLocalListeners;
import accord.impl.DefaultRemoteListeners;
import accord.impl.DefaultRequestTimeouts;
import accord.impl.RequestCallbacks;
import accord.impl.SizeOfIntersectionSorter;
import accord.impl.progresslog.DefaultProgressLogs;
import accord.local.Command;
@ -80,13 +80,13 @@ import accord.local.DurableBefore;
import accord.local.KeyHistory;
import accord.local.Node;
import accord.local.Node.Id;
import accord.local.NodeTimeService;
import accord.local.PreLoadContext;
import accord.local.RedundantBefore;
import accord.local.ShardDistributor.EvenSplit;
import accord.local.cfk.CommandsForKey;
import accord.messages.Callback;
import accord.messages.ReadData;
import accord.messages.Reply;
import accord.messages.Request;
import accord.messages.WaitUntilApplied;
import accord.primitives.FullRoute;
@ -142,6 +142,7 @@ import org.apache.cassandra.service.accord.api.AccordAgent;
import org.apache.cassandra.service.accord.api.AccordRoutingKey.KeyspaceSplitter;
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
import org.apache.cassandra.service.accord.api.AccordScheduler;
import org.apache.cassandra.service.accord.api.AccordTimeService;
import org.apache.cassandra.service.accord.api.AccordTopologySorter;
import org.apache.cassandra.service.accord.api.CompositeTopologySorter;
import org.apache.cassandra.service.accord.api.PartitionKey;
@ -161,7 +162,6 @@ import org.apache.cassandra.tcm.ownership.DataPlacement;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.Blocking;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.ExecutorUtils;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.concurrent.AsyncPromise;
@ -200,6 +200,7 @@ public class AccordService implements IAccordService, Shutdownable
private final AccordJournal journal;
private final CoordinateDurabilityScheduling durabilityScheduling;
private final AccordVerbHandler<? extends Request> requestHandler;
private final AccordResponseVerbHandler<? extends Reply> responseHandler;
private final LocalConfig configuration;
@GuardedBy("this")
@ -208,7 +209,13 @@ public class AccordService implements IAccordService, Shutdownable
private static final IAccordService NOOP_SERVICE = new IAccordService()
{
@Override
public IVerbHandler<? extends Request> verbHandler()
public IVerbHandler<? extends Request> requestHandler()
{
return null;
}
@Override
public IVerbHandler<? extends Reply> responseHandler()
{
return null;
}
@ -346,10 +353,16 @@ public class AccordService implements IAccordService, Shutdownable
return instance != null;
}
public static IVerbHandler<? extends Request> verbHandlerOrNoop()
public static IVerbHandler<? extends Request> requestHandlerOrNoop()
{
if (!isSetup()) return ignore -> {};
return instance().verbHandler();
return instance().requestHandler();
}
public static IVerbHandler<? extends Reply> responseHandlerOrNoop()
{
if (!isSetup()) return ignore -> {};
return instance().responseHandler();
}
public synchronized static void startup(NodeId tcmId)
@ -395,20 +408,17 @@ public class AccordService implements IAccordService, Shutdownable
return i;
}
public static long now()
{
return TimeUnit.MILLISECONDS.toMicros(Clock.Global.currentTimeMillis());
}
private AccordService(Id localId)
{
Invariants.checkState(localId != null, "static localId must be set before instantiating AccordService");
logger.info("Starting accord with nodeId {}", localId);
AccordAgent agent = FBUtilities.construct(CassandraRelevantProperties.ACCORD_AGENT_CLASS.getString(AccordAgent.class.getName()), "AccordAgent");
agent.setNodeId(localId);
AccordTimeService time = new AccordTimeService();
final RequestCallbacks callbacks = new RequestCallbacks(time);
this.configService = new AccordConfigurationService(localId);
this.fastPathCoordinator = AccordFastPathCoordinator.create(localId, configService);
this.messageSink = new AccordMessageSink(agent, configService);
this.messageSink = new AccordMessageSink(agent, configService, callbacks);
this.scheduler = new AccordScheduler();
this.dataStore = new AccordDataStore();
this.configuration = new AccordConfiguration(DatabaseDescriptor.getRawConfig());
@ -416,8 +426,7 @@ public class AccordService implements IAccordService, Shutdownable
this.node = new Node(localId,
messageSink,
configService,
AccordService::now,
NodeTimeService.elapsedWrapperFromMonotonicSource(NANOSECONDS, Clock.Global::nanoTime),
time,
() -> dataStore,
new KeyspaceSplitter(new EvenSplit<>(DatabaseDescriptor.getAccordShardCount(), getPartitioner().accordSplitter())),
agent,
@ -426,7 +435,7 @@ public class AccordService implements IAccordService, Shutdownable
CompositeTopologySorter.create(SizeOfIntersectionSorter.SUPPLIER,
new AccordTopologySorter.Supplier(configService, DatabaseDescriptor.getNodeProximity())),
DefaultRemoteListeners::new,
DefaultRequestTimeouts::new,
ignore -> callbacks,
DefaultProgressLogs::new,
DefaultLocalListeners.Factory::new,
AccordCommandStores.factory(journal),
@ -436,6 +445,7 @@ public class AccordService implements IAccordService, Shutdownable
this.nodeShutdown = toShutdownable(node);
this.durabilityScheduling = new CoordinateDurabilityScheduling(node);
this.requestHandler = new AccordVerbHandler<>(node, configService);
this.responseHandler = new AccordResponseVerbHandler<>(callbacks, configService);
}
@Override
@ -568,11 +578,17 @@ public class AccordService implements IAccordService, Shutdownable
}
@Override
public IVerbHandler<? extends Request> verbHandler()
public IVerbHandler<? extends Request> requestHandler()
{
return requestHandler;
}
@Override
public IVerbHandler<? extends Reply> responseHandler()
{
return responseHandler;
}
private Seekables<?, ?> barrier(@Nonnull Seekables<?, ?> keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, BiFunction<Node, FullRoute<?>, AsyncSyncPoint> syncPoint)
{
Stopwatch sw = Stopwatch.createStarted();

View File

@ -291,7 +291,6 @@ public class AccordStateCache extends IntrusiveLinkedList<AccordCachingState<?,?
private final BiFunction<K, V, Boolean> validateFunction;
private final ToLongFunction<V> heapEstimator;
private long bytesCached;
// private int itemsCached;
@VisibleForTesting
final CacheAccessMetrics instanceMetrics;
@ -382,7 +381,6 @@ public class AccordStateCache extends IntrusiveLinkedList<AccordCachingState<?,?
return safeRefFactory.apply(acquireExisting(node, false));
}
public void maybeLoad(K key, V initial)
{
AccordCachingState<K, V> node = (AccordCachingState<K, V>) cache.get(key);

View File

@ -60,7 +60,7 @@ public class AccordVerbHandler<T extends Request> implements IVerbHandler<T>
Node.Id fromNodeId = endpointMapper.mappedId(message.from());
long waitForEpoch = request.waitForEpoch();
if (node.topology().hasEpoch(waitForEpoch))
if (node.topology().hasAtLeastEpoch(waitForEpoch))
request.process(node, fromNodeId, message);
else
node.withEpoch(waitForEpoch, (ignored, withEpochFailure) -> {

View File

@ -35,11 +35,11 @@ import accord.local.Command;
import accord.local.KeyHistory;
import accord.local.RedundantBefore;
import accord.primitives.PartialDeps;
import accord.primitives.Routable.Domain;
import accord.primitives.SaveStatus;
import accord.primitives.Status;
import accord.primitives.Range;
import accord.primitives.Ranges;
import accord.primitives.Routable;
import accord.primitives.Routables;
import accord.primitives.Seekables;
import accord.primitives.Timestamp;
@ -54,16 +54,34 @@ import org.apache.cassandra.utils.Pair;
import static accord.primitives.Txn.Kind.ExclusiveSyncPoint;
public class CommandsForRangesLoader
public class CommandsForRangesLoader implements AccordStateCache.Listener<TxnId, Command>
{
private final RoutesSearcher searcher = new RoutesSearcher();
//TODO (now, durability): find solution for this...
private final NavigableMap<TxnId, Ranges> historicalTransaction = new TreeMap<>();
private final AccordCommandStore store;
private final ObjectHashSet<TxnId> cachedRangeTxns = new ObjectHashSet<>();
public CommandsForRangesLoader(AccordCommandStore store)
{
this.store = store;
store.commandCache().register(this);
}
@Override
public void onAdd(AccordCachingState<TxnId, Command> state)
{
TxnId txnId = state.key();
if (txnId.is(Domain.Range))
cachedRangeTxns.add(txnId);
}
@Override
public void onEvict(AccordCachingState<TxnId, Command> state)
{
TxnId txnId = state.key();
if (txnId.is(Domain.Range))
cachedRangeTxns.remove(txnId);
}
public AsyncResult<Pair<Watcher, NavigableMap<TxnId, Summary>>> get(@Nullable TxnId primaryTxnId, KeyHistory keyHistory, Ranges ranges)
@ -136,7 +154,7 @@ public class CommandsForRangesLoader
@Override
public void onAdd(AccordCachingState<TxnId, Command> n)
{
if (n.key().domain() != Routable.Domain.Range)
if (n.key().domain() != Domain.Range)
return;
if (n.key().compareTo(minTxnId) < 0 || n.key().compareTo(maxTxnId) >= 0)
@ -197,7 +215,8 @@ public class CommandsForRangesLoader
private Watcher fromCache(@Nullable TxnId findAsDep, Ranges ranges, TxnId minTxnId, Timestamp maxTxnId, RedundantBefore redundantBefore)
{
Watcher watcher = new Watcher(ranges, findAsDep, minTxnId, maxTxnId, redundantBefore);
store.commandCache().stream().forEach(watcher::onAdd);
for (TxnId rangeTxnId : cachedRangeTxns)
watcher.onAdd(store.commandCache().getUnsafe(rangeTxnId));
store.commandCache().register(watcher);
return watcher;
}
@ -234,11 +253,12 @@ public class CommandsForRangesLoader
return null;
Seekables<?, ? extends Seekables<?, ?>> keysOrRanges = cmd.partialTxn().keys();
if (keysOrRanges.domain() != Routable.Domain.Range)
if (keysOrRanges.domain() != Domain.Range)
throw new AssertionError(String.format("Txn keys are not range for %s", cmd.partialTxn()));
Ranges ranges = (Ranges) keysOrRanges;
if (!ranges.intersects(cacheRanges))
ranges = ranges.slice(cacheRanges, Routables.Slice.Minimal);
if (ranges.isEmpty())
return null;
if (redundantBefore != null)

View File

@ -34,6 +34,7 @@ import accord.local.DurableBefore;
import accord.local.Node;
import accord.local.Node.Id;
import accord.local.RedundantBefore;
import accord.messages.Reply;
import accord.messages.Request;
import accord.primitives.Ranges;
import accord.primitives.Seekables;
@ -68,7 +69,8 @@ public interface IAccordService
Set<ConsistencyLevel> SUPPORTED_COMMIT_CONSISTENCY_LEVELS = ImmutableSet.of(ConsistencyLevel.ANY, ConsistencyLevel.ONE, ConsistencyLevel.LOCAL_ONE, ConsistencyLevel.QUORUM, ConsistencyLevel.SERIAL, ConsistencyLevel.ALL);
Set<ConsistencyLevel> SUPPORTED_READ_CONSISTENCY_LEVELS = ImmutableSet.of(ConsistencyLevel.ONE, ConsistencyLevel.QUORUM, ConsistencyLevel.SERIAL);
IVerbHandler<? extends Request> verbHandler();
IVerbHandler<? extends Request> requestHandler();
IVerbHandler<? extends Reply> responseHandler();
Seekables<?, ?> barrierWithRetries(Seekables<?, ?> keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException;

View File

@ -71,13 +71,13 @@ public final class JournalKey
public static final class JournalKeySupport implements KeySupport<JournalKey>
{
private static final int MSB_OFFSET = 0;
private static final int CS_ID_OFFSET = 0;
private static final int TYPE_OFFSET = INT_SIZE;
private static final int MSB_OFFSET = TYPE_OFFSET + BYTE_SIZE;
private static final int LSB_OFFSET = MSB_OFFSET + LONG_SIZE;
private static final int NODE_OFFSET = LSB_OFFSET + LONG_SIZE;
private static final int TYPE_OFFSET = NODE_OFFSET + INT_SIZE;
private static final int CS_ID_OFFSET = TYPE_OFFSET + BYTE_SIZE;
// TODO (required): revisit commandStoreId - this can go arbitrarily high so may want to use vint
public static final int TOTAL_SIZE = CS_ID_OFFSET + INT_SIZE;
public static final int TOTAL_SIZE = NODE_OFFSET + INT_SIZE;
@Override
public int serializedSize(int userVersion)
@ -88,33 +88,33 @@ public final class JournalKey
@Override
public void serialize(JournalKey key, DataOutputPlus out, int userVersion) throws IOException
{
serializeTxnId(key.id, out);
out.writeByte(key.type.id);
out.writeInt(key.commandStoreId);
out.writeByte(key.type.id);
serializeTxnId(key.id, out);
}
private void serialize(JournalKey key, byte[] out)
{
serializeTxnId(key.id, out);
out[TYPE_OFFSET] = (byte) (key.type.id & 0xFF);
ByteArrayUtil.putInt(out, CS_ID_OFFSET, key.commandStoreId);
out[TYPE_OFFSET] = (byte) (key.type.id & 0xFF);
serializeTxnId(key.id, out);
}
@Override
public JournalKey deserialize(DataInputPlus in, int userVersion) throws IOException
{
TxnId txnId = deserializeTxnId(in);
int type = in.readByte();
int commandStoreId = in.readInt();
int type = in.readByte();
TxnId txnId = deserializeTxnId(in);
return new JournalKey(txnId, Type.fromId(type), commandStoreId);
}
@Override
public JournalKey deserialize(ByteBuffer buffer, int position, int userVersion)
{
TxnId txnId = deserializeTxnId(buffer, position);
int type = buffer.get(position + TYPE_OFFSET);
int commandStoreId = buffer.getInt(position + CS_ID_OFFSET);
int type = buffer.get(position + TYPE_OFFSET);
TxnId txnId = deserializeTxnId(buffer, position);
return new JournalKey(txnId, Type.fromId(type), commandStoreId);
}
@ -159,15 +159,15 @@ public final class JournalKey
@Override
public int compareWithKeyAt(JournalKey k, ByteBuffer buffer, int position, int userVersion)
{
int cmp = compareWithTxnIdAt(k.id, buffer, position);
int commandStoreId = buffer.getInt(position + CS_ID_OFFSET);
int cmp = Integer.compare(k.commandStoreId, commandStoreId);
if (cmp != 0) return cmp;
byte type = buffer.get(position + TYPE_OFFSET);
cmp = Byte.compare((byte) k.type.id, type);
if (cmp != 0) return cmp;
int commandStoreId = buffer.getInt(position + CS_ID_OFFSET);
cmp = Integer.compare(k.commandStoreId, commandStoreId);
cmp = compareWithTxnIdAt(k.id, buffer, position);
return cmp;
}
@ -189,9 +189,9 @@ public final class JournalKey
@Override
public int compare(JournalKey k1, JournalKey k2)
{
int cmp = k1.id.compareTo(k2.id);
int cmp = Integer.compare(k1.commandStoreId, k2.commandStoreId);
if (cmp == 0) cmp = Byte.compare((byte) k1.type.id, (byte) k2.type.id);
if (cmp == 0) cmp = Integer.compare(k1.commandStoreId, k2.commandStoreId);
if (cmp == 0) cmp = k1.id.compareTo(k2.id);
return cmp;
}
};

View File

@ -35,6 +35,7 @@ import accord.local.StoreParticipants;
import accord.primitives.Ballot;
import accord.primitives.PartialDeps;
import accord.primitives.PartialTxn;
import accord.primitives.Route;
import accord.primitives.SaveStatus;
import accord.primitives.Status;
import accord.primitives.Timestamp;
@ -57,24 +58,25 @@ import static accord.primitives.Known.KnownDeps.DepsUnknown;
import static accord.primitives.Known.KnownDeps.NoDeps;
import static accord.primitives.Status.Durability.NotDurable;
import static accord.utils.Invariants.illegalState;
import static org.apache.cassandra.service.accord.SavedCommand.Fields.PARTICIPANTS;
public class SavedCommand
{
// This enum is order-dependent
public enum Fields
{
PARTICIPANTS, // stored first so we can index it
SAVE_STATUS,
PARTIAL_DEPS,
EXECUTE_AT,
EXECUTES_AT_LEAST,
SAVE_STATUS,
DURABILITY,
ACCEPTED,
PROMISED,
PARTICIPANTS,
PARTIAL_TXN,
PARTIAL_DEPS,
WAITING_ON,
PARTIAL_TXN,
WRITES,
CLEANUP
CLEANUP,
;
public static final Fields[] FIELDS = values();
@ -233,7 +235,7 @@ public class SavedCommand
flags = collectFlags(before, after, Command::acceptedOrCommitted, false, Fields.ACCEPTED, flags);
flags = collectFlags(before, after, Command::promised, false, Fields.PROMISED, flags);
flags = collectFlags(before, after, Command::participants, true, Fields.PARTICIPANTS, flags);
flags = collectFlags(before, after, Command::participants, true, PARTICIPANTS, flags);
flags = collectFlags(before, after, Command::partialTxn, false, Fields.PARTIAL_TXN, flags);
flags = collectFlags(before, after, Command::partialDeps, false, Fields.PARTIAL_DEPS, flags);
@ -540,7 +542,7 @@ public class SavedCommand
}
if (participants != null)
{
builder.flags = setFieldChanged(Fields.PARTICIPANTS, builder.flags);
builder.flags = setFieldChanged(PARTICIPANTS, builder.flags);
builder.participants = participants;
}
if (includeOutcome && builder.writes != null)
@ -579,6 +581,16 @@ public class SavedCommand
}
}
public static Route<?> deserializeRouteOrNull(DataInputPlus in, int userVersion) throws IOException
{
int flags = in.readInt();
if (!getFieldChanged(PARTICIPANTS, flags) || getFieldIsNull(PARTICIPANTS, flags))
return null;
return CommandSerializers.participants.deserializeRouteOnly(in, userVersion);
}
public void serialize(DataOutputPlus out, int userVersion) throws IOException
{
out.writeInt(flags);

View File

@ -0,0 +1,47 @@
/*
* 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.api;
import java.util.concurrent.TimeUnit;
import accord.local.TimeService;
import org.apache.cassandra.utils.Clock;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
public class AccordTimeService implements TimeService
{
@Override
public long now()
{
return nowMicros();
}
public static long nowMicros()
{
return TimeUnit.MILLISECONDS.toMicros(Clock.Global.currentTimeMillis());
}
@Override
public long elapsed(TimeUnit unit)
{
return unit.convert(nanoTime(), NANOSECONDS);
}
}

View File

@ -28,6 +28,10 @@ import accord.topology.ShardSelection;
import accord.topology.Topologies;
import accord.topology.Topology;
import accord.utils.SortedList;
import org.apache.cassandra.gms.ApplicationState;
import org.apache.cassandra.gms.EndpointState;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.gms.VersionedValue;
import org.apache.cassandra.locator.DynamicEndpointSnitch;
import org.apache.cassandra.locator.Endpoint;
import org.apache.cassandra.locator.InetAddressAndPort;
@ -71,8 +75,8 @@ public class AccordTopologySorter implements TopologySorter
}
private final AccordEndpointMapper mapper;
private final Comparator<Endpoint> comparator;
private AccordTopologySorter(AccordEndpointMapper mapper, Comparator<Endpoint> comparator)
{
this.mapper = mapper;
@ -95,6 +99,27 @@ public class AccordTopologySorter implements TopologySorter
return comparator.compare(() -> mapper.mappedEndpoint(node1), () -> mapper.mappedEndpoint(node2));
}
@Override
public boolean isFaulty(Node.Id node)
{
InetAddressAndPort ep = mapper.mappedEndpointOrNull(node);
if (ep == null)
return true;
EndpointState epState = Gossiper.instance.getEndpointStateForEndpoint(ep);
if (epState == null)
return true;
if (!epState.isAlive())
return true;
VersionedValue event = epState.getApplicationState(ApplicationState.SEVERITY);
if (event == null)
return false;
return Double.parseDouble(event.value) == 0.0;
}
private static class EndpointTuple implements Endpoint
{
final InetAddressAndPort endpoint;

View File

@ -81,4 +81,15 @@ public class CompositeTopologySorter implements TopologySorter
}
return 0;
}
@Override
public boolean isFaulty(Node.Id node)
{
for (int i = 0; i < delegates.length; i++)
{
if (delegates[i].isFaulty(node))
return true;
}
return false;
}
}

View File

@ -61,7 +61,7 @@ public class RepairSyncPointAdapter<U extends Unseekable> extends CoordinationAd
public void execute(Node node, Topologies all, FullRoute<?> route, ExecutePath path, TxnId txnId, Txn txn, Timestamp executeAt, Deps deps, BiConsumer<? super SyncPoint<U>, Throwable> callback)
{
RequiredResponseTracker tracker = new RequiredResponseTracker(requiredResponses, all);
ExecuteSyncPoint.ExecuteBlocking<U> execute = new ExecuteSyncPoint.ExecuteBlocking<>(node, new SyncPoint<U>(txnId, deps, (FullRoute<U>) route), tracker, executeAt);
ExecuteSyncPoint.ExecuteBlocking<U> execute = new ExecuteSyncPoint.ExecuteBlocking<>(node, new SyncPoint<>(txnId, deps, (FullRoute<U>) route), tracker, executeAt);
execute.addCallback(callback);
execute.start();
}

View File

@ -21,9 +21,9 @@ package org.apache.cassandra.service.accord.repair;
import java.util.HashSet;
import java.util.Set;
import accord.coordinate.tracking.AbstractSimpleTracker;
import accord.coordinate.tracking.RequestStatus;
import accord.coordinate.tracking.ShardTracker;
import accord.coordinate.tracking.SimpleTracker;
import accord.local.Node;
import accord.topology.Shard;
import accord.topology.Topologies;
@ -32,7 +32,7 @@ import static accord.coordinate.tracking.AbstractTracker.ShardOutcomes.Fail;
import static accord.coordinate.tracking.AbstractTracker.ShardOutcomes.NoChange;
import static accord.coordinate.tracking.AbstractTracker.ShardOutcomes.Success;
public class RequiredResponseTracker extends AbstractSimpleTracker<RequiredResponseTracker.RequiredResponseShardTracker>
public class RequiredResponseTracker extends SimpleTracker<RequiredResponseTracker.RequiredResponseShardTracker>
{
public static class RequiredResponseShardTracker extends ShardTracker
{

View File

@ -145,7 +145,7 @@ public class ReadDataSerializers
}
};
private static final ReadDataSerializer<ReadEphemeralTxnData> readEphemeralTxnData = new ReadDataSerializer<ReadEphemeralTxnData>()
public static final ReadDataSerializer<ReadEphemeralTxnData> readEphemeralTxnData = new ReadDataSerializer<>()
{
@Override
public void serialize(ReadEphemeralTxnData read, DataOutputPlus out, int version) throws IOException

View File

@ -19,347 +19,83 @@
package org.apache.cassandra.service.paxos;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.codahale.metrics.Snapshot;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.RetryStrategy;
import org.apache.cassandra.service.TimeoutStrategy;
import org.apache.cassandra.service.TimeoutStrategy.LatencySourceFactory;
import org.apache.cassandra.service.TimeoutStrategy.ReadWriteLatencySourceFactory;
import org.apache.cassandra.service.TimeoutStrategy.Wait;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.NoSpamLogger;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.DoubleSupplier;
import java.util.function.LongBinaryOperator;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.lang.Double.parseDouble;
import static java.lang.Integer.parseInt;
import static java.lang.Math.*;
import static java.util.Arrays.stream;
import static java.util.concurrent.TimeUnit.*;
import static org.apache.cassandra.config.DatabaseDescriptor.*;
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.casReadMetrics;
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.casWriteMetrics;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
import static org.apache.cassandra.utils.Clock.waitUntil;
import static org.apache.cassandra.utils.LocalizeString.toLowerCaseLocalized;
/**
* <p>A strategy for making back-off decisions for Paxos operations that fail to make progress because of other paxos operations.
* The strategy is defined by four factors: <ul>
* <li> {@link #min}
* <li> {@link #max}
* <li> {@link #minDelta}
* <li> {@link #waitRandomizer}
* </ul>
*
* <p>The first three represent time periods, and may be defined dynamically based on a simple calculation over: <ul>
* <li> {@code pX()} recent experienced latency distribution for successful operations,
* e.g. {@code p50(rw)} the maximum of read and write median latencies,
* {@code p999(r)} the 99.9th percentile of read latencies
* <li> {@code attempts} the number of failed attempts made by the operation so far
* <li> {@code constant} a user provided floating point constant
* </ul>
*
* <p>Their calculation may take any of these forms
* <li> constant {@code $constant$[mu]s}
* <li> dynamic constant {@code pX() * constant}
* <li> dynamic linear {@code pX() * constant * attempts}
* <li> dynamic exponential {@code pX() * constant ^ attempts}
*
* <p>Furthermore, the dynamic calculations can be bounded with a min/max, like so:
* {@code min[mu]s <= dynamic expr <= max[mu]s}
*
* e.g.
* <li> {@code 10ms <= p50(rw)*0.66}
* <li> {@code 10ms <= p95(rw)*1.8^attempts <= 100ms}
* <li> {@code 5ms <= p50(rw)*0.5}
*
* <p>These calculations are put together to construct a range from which we draw a random number.
* The period we wait for {@code X} will be drawn so that {@code min <= X < max}.
*
* <p>With the constraint that {@code max} must be {@code minDelta} greater than {@code min},
* but no greater than its expression-defined maximum. {@code max} will be increased up until
* this point, after which {@code min} will be decreased until this gap is imposed.
*
* <p>The {@link #waitRandomizer} property specifies the manner in which a random value is drawn from the range.
* It is defined using one of the following specifiers:
* <li> uniform
* <li> exp($power$) or exponential($power$)
* <li> qexp($power$) or qexponential($power$) or quantizedexponential($power$)
*
* The uniform specifier is self-explanatory, selecting all values in the range with equal probability.
* The exponential specifier draws values towards the end of the range with higher probability, raising
* a floating point number in the range [0..1.0) to the power provided, and translating the resulting value
* to a uniform value in the range.
* The quantized exponential specifier partitions the range into {@code attempts} buckets, then applies the pure
* exponential approach to draw values from [0..attempts), before drawing a uniform value from the corresponding bucket
*
* <p>Finally, there is also a {@link #traceAfterAttempts} property that permits initiating tracing of operations
* that experience a certain minimum number of failed paxos rounds due to contention. A setting of 0 or 1 will initiate
* a trace session after the first failed ballot.
* See {@link RetryStrategy}
*/
public class ContentionStrategy
public class ContentionStrategy extends RetryStrategy
{
private static final Logger logger = LoggerFactory.getLogger(ContentionStrategy.class);
private static final Logger logger = LoggerFactory.getLogger(RetryStrategy.class);
private static final Pattern BOUND = Pattern.compile(
"(?<const>0|[0-9]+[mu]s)" +
"|((?<min>0|[0-9]+[mu]s) *<= *)?" +
"(p(?<perc>[0-9]+)\\((?<rw>r|w|rw|wr)\\)|(?<constbase>0|[0-9]+[mu]s))" +
"\\s*([*]\\s*(?<mod>[0-9.]+)?\\s*(?<modkind>[*^]\\s*attempts)?)?" +
"( *<= *(?<max>0|[0-9]+[mu]s))?");
private static final Pattern TIME = Pattern.compile(
"0|([0-9]+)ms|([0-9]+)us");
private static final Pattern RANDOMIZER = Pattern.compile(
"uniform|exp(onential)?[(](?<exp>[0-9.]+)[)]|q(uantized)?exp(onential)?[(](?<qexp>[0-9.]+)[)]");
private static final String DEFAULT_WAIT_RANDOMIZER = "qexp(1.5)"; // at least 0ms, and at least 66% of median latency
private static final String DEFAULT_MIN = "0 <= p50(rw)*0.66"; // at least 0ms, and at least 66% of median latency
private static final String DEFAULT_MAX = "10ms <= p95(rw)*1.8^attempts <= 100ms"; // p95 latency with exponential back-off at rate of 1.8^attempts
private static final String DEFAULT_MIN_DELTA = "5ms <= p50(rw)*0.5"; // at least 5ms, and at least 50% of median latency
private static final String DEFAULT_WAIT_RANDOMIZER = "uniform";
private static final String DEFAULT_MIN = "0";
private static final String DEFAULT_MAX = "100ms";
private static final String DEFAULT_SPREAD = "100ms";
private static final LatencySourceFactory LATENCIES = new ReadWriteLatencySourceFactory(casReadMetrics, casWriteMetrics);
private static volatile ContentionStrategy current;
// Factories can be useful for testing purposes, to supply custom implementations of selectors and modifiers.
final static LatencySelectorFactory selectors = new LatencySelectorFactory(){};
final static LatencyModifierFactory modifiers = new LatencyModifierFactory(){};
final static WaitRandomizerFactory randomizers = new WaitRandomizerFactory(){};
private static volatile ParsedStrategy currentParsed;
private static final RetryStrategy.ParsedStrategy defaultStrategy;
static
{
current = new ContentionStrategy(defaultWaitRandomizer(), defaultMinWait(), defaultMaxWait(), defaultMinDelta(), Integer.MAX_VALUE);
defaultStrategy = new ParsedStrategy(DEFAULT_WAIT_RANDOMIZER, DEFAULT_MIN, DEFAULT_MAX, DEFAULT_SPREAD, Integer.MAX_VALUE,
new ContentionStrategy(DEFAULT_WAIT_RANDOMIZER, DEFAULT_MIN, DEFAULT_MAX, DEFAULT_SPREAD, Integer.MAX_VALUE));
String waitRandomizer = orElse(DatabaseDescriptor::getPaxosContentionWaitRandomizer, DEFAULT_WAIT_RANDOMIZER);
String min = orElse(DatabaseDescriptor::getPaxosContentionMinWait, DEFAULT_MIN);
String max = orElse(DatabaseDescriptor::getPaxosContentionMaxWait, DEFAULT_MAX);
String spread = orElse(DatabaseDescriptor::getPaxosContentionMinDelta, DEFAULT_SPREAD);
current = new ContentionStrategy(waitRandomizer, min, max, spread, Integer.MAX_VALUE);
currentParsed = new ParsedStrategy(waitRandomizer, min, max, spread, Integer.MAX_VALUE, current);
}
static interface LatencyModifierFactory
{
default LatencyModifier identity() { return (l, a) -> l; }
default LatencyModifier multiply(double constant) { return (l, a) -> saturatedCast(l * constant); }
default LatencyModifier multiplyByAttempts(double multiply) { return (l, a) -> saturatedCast(l * multiply * a); }
default LatencyModifier multiplyByAttemptsExp(double base) { return (l, a) -> saturatedCast(l * pow(base, a)); }
}
static interface LatencySupplier
{
abstract long get(double percentile);
}
static interface LatencySelector
{
abstract long select(LatencySupplier readLatencyHistogram, LatencySupplier writeLatencyHistogram);
}
static interface LatencySelectorFactory
{
default LatencySelector constant(long latency) { return (read, write) -> latency; }
default LatencySelector read(double percentile) { return (read, write) -> read.get(percentile); }
default LatencySelector write(double percentile) { return (read, write) -> write.get(percentile); }
default LatencySelector maxReadWrite(double percentile) { return (read, write) -> max(read.get(percentile), write.get(percentile)); }
}
static interface LatencyModifier
{
long modify(long latency, int attempts);
}
static interface WaitRandomizer
{
abstract long wait(long min, long max, int attempts);
}
static interface WaitRandomizerFactory
{
default LongBinaryOperator uniformLongSupplier() { return (min, max) -> ThreadLocalRandom.current().nextLong(min, max); } // DO NOT USE METHOD HANDLES (want to fetch afresh each time)
default DoubleSupplier uniformDoubleSupplier() { return () -> ThreadLocalRandom.current().nextDouble(); }
default WaitRandomizer uniform() { return new Uniform(uniformLongSupplier()); }
default WaitRandomizer exponential(double power) { return new Exponential(uniformLongSupplier(), uniformDoubleSupplier(), power); }
default WaitRandomizer quantizedExponential(double power) { return new QuantizedExponential(uniformLongSupplier(), uniformDoubleSupplier(), power); }
static class Uniform implements WaitRandomizer
{
final LongBinaryOperator uniformLong;
public Uniform(LongBinaryOperator uniformLong)
{
this.uniformLong = uniformLong;
}
@Override
public long wait(long min, long max, int attempts)
{
return uniformLong.applyAsLong(min, max);
}
}
static abstract class AbstractExponential implements WaitRandomizer
{
final LongBinaryOperator uniformLong;
final DoubleSupplier uniformDouble;
final double power;
public AbstractExponential(LongBinaryOperator uniformLong, DoubleSupplier uniformDouble, double power)
{
this.uniformLong = uniformLong;
this.uniformDouble = uniformDouble;
this.power = power;
}
}
static class Exponential extends AbstractExponential
{
public Exponential(LongBinaryOperator uniformLong, DoubleSupplier uniformDouble, double power)
{
super(uniformLong, uniformDouble, power);
}
@Override
public long wait(long min, long max, int attempts)
{
if (attempts == 1)
return uniformLong.applyAsLong(min, max);
double p = uniformDouble.getAsDouble();
long delta = max - min;
delta *= Math.pow(p, power);
return max - delta;
}
}
static class QuantizedExponential extends AbstractExponential
{
public QuantizedExponential(LongBinaryOperator uniformLong, DoubleSupplier uniformDouble, double power)
{
super(uniformLong, uniformDouble, power);
}
@Override
public long wait(long min, long max, int attempts)
{
long quanta = (max - min) / attempts;
if (attempts == 1 || quanta == 0)
return uniformLong.applyAsLong(min, max);
double p = uniformDouble.getAsDouble();
int base = (int) (attempts * Math.pow(p, power));
return max - ThreadLocalRandom.current().nextLong(quanta * base, quanta * (base + 1));
}
}
}
static class SnapshotAndTime
{
final long validUntil;
final Snapshot snapshot;
SnapshotAndTime(long validUntil, Snapshot snapshot)
{
this.validUntil = validUntil;
this.snapshot = snapshot;
}
}
static class TimeLimitedLatencySupplier extends AtomicReference<SnapshotAndTime> implements LatencySupplier
{
final Supplier<Snapshot> snapshotSupplier;
final long validForNanos;
TimeLimitedLatencySupplier(Supplier<Snapshot> snapshotSupplier, long time, TimeUnit units)
{
this.snapshotSupplier = snapshotSupplier;
this.validForNanos = units.toNanos(time);
}
private Snapshot getSnapshot()
{
long now = nanoTime();
SnapshotAndTime cur = get();
if (cur != null && cur.validUntil > now)
return cur.snapshot;
Snapshot newSnapshot = snapshotSupplier.get();
SnapshotAndTime next = new SnapshotAndTime(now + validForNanos, newSnapshot);
if (compareAndSet(cur, next))
return next.snapshot;
return accumulateAndGet(next, (a, b) -> a.validUntil > b.validUntil ? a : b).snapshot;
}
@Override
public long get(double percentile)
{
return (long)getSnapshot().getValue(percentile);
}
}
static class Bound
{
final long min, max, onFailure;
final LatencyModifier modifier;
final LatencySelector selector;
final LatencySupplier reads, writes;
Bound(long min, long max, long onFailure, LatencyModifier modifier, LatencySelector selector)
{
Preconditions.checkArgument(min<=max, "min (%s) must be less than or equal to max (%s)", min, max);
this.min = min;
this.max = max;
this.onFailure = onFailure;
this.modifier = modifier;
this.selector = selector;
this.reads = new TimeLimitedLatencySupplier(casReadMetrics.latency::getSnapshot, 10L, SECONDS);
this.writes = new TimeLimitedLatencySupplier(casWriteMetrics.latency::getSnapshot, 10L, SECONDS);
}
long get(int attempts)
{
try
{
long base = selector.select(reads, writes);
return max(min, min(max, modifier.modify(base, attempts)));
}
catch (Throwable t)
{
NoSpamLogger.getLogger(logger, 1L, MINUTES).info("", t);
return onFailure;
}
}
public String toString()
{
return "Bound{" +
"min=" + min +
", max=" + max +
", onFailure=" + onFailure +
", modifier=" + modifier +
", selector=" + selector +
'}';
}
}
final WaitRandomizer waitRandomizer;
final Bound min, max, minDelta;
final int traceAfterAttempts;
public ContentionStrategy(String waitRandomizer, String min, String max, String minDelta, int traceAfterAttempts)
public ContentionStrategy(String waitRandomizer, String min, String max, String spread, int traceAfterAttempts)
{
this.waitRandomizer = parseWaitRandomizer(waitRandomizer);
this.min = parseBound(min, true);
this.max = parseBound(max, false);
this.minDelta = parseBound(minDelta, true);
super(waitRandomizer, min, max, spread, LATENCIES);
this.traceAfterAttempts = traceAfterAttempts;
}
public ContentionStrategy(WaitRandomizer waitRandomizer, Wait min, Wait max, Wait spread, int traceAfterAttempts)
{
super(waitRandomizer, min, max, spread);
this.traceAfterAttempts = traceAfterAttempts;
}
@Override
protected Wait parseBound(String spec, boolean isMin, LatencySourceFactory latencies)
{
return TimeoutStrategy.parseWait(spec, 0, maxQueryTimeoutMicros(), isMin ? 0 : maxQueryTimeoutMicros(), latencies);
}
public enum Type
{
READ("Contended Paxos Read"), WRITE("Contended Paxos Write"), REPAIR("Contended Paxos Repair");
@ -395,25 +131,10 @@ public class ContentionStrategy
Tracing.instance.getSessionId());
}
long minWaitMicros = min.get(attempts);
long maxWaitMicros = max.get(attempts);
long minDeltaMicros = minDelta.get(attempts);
if (minWaitMicros + minDeltaMicros > maxWaitMicros)
{
maxWaitMicros = minWaitMicros + minDeltaMicros;
if (maxWaitMicros > this.max.max)
{
maxWaitMicros = this.max.max;
minWaitMicros = max(this.min.min, min(this.min.max, maxWaitMicros - minDeltaMicros));
}
}
long wait = waitRandomizer.wait(minWaitMicros, maxWaitMicros, attempts);
return nanoTime() + MICROSECONDS.toNanos(wait);
return super.computeWaitUntil(attempts);
}
boolean doWaitForContention(long deadline, int attempts, TableMetadata table, DecoratedKey partitionKey, ConsistencyLevel consistency, Type type)
public boolean doWaitForContention(long deadline, int attempts, TableMetadata table, DecoratedKey partitionKey, ConsistencyLevel consistency, Type type)
{
long until = computeWaitUntilForContention(attempts, table, partitionKey, consistency, type);
if (until >= deadline)
@ -441,201 +162,52 @@ public class ContentionStrategy
return current.computeWaitUntilForContention(attempts, table, partitionKey, consistency, type);
}
static class ParsedStrategy
public static class ParsedStrategy extends RetryStrategy.ParsedStrategy
{
final String waitRandomizer, min, max, minDelta;
final ContentionStrategy strategy;
public final int trace;
public final ContentionStrategy strategy;
ParsedStrategy(String waitRandomizer, String min, String max, String minDelta, ContentionStrategy strategy)
ParsedStrategy(String waitRandomizer, String min, String max, String minDelta, int trace, ContentionStrategy strategy)
{
this.waitRandomizer = waitRandomizer;
this.min = min;
this.max = max;
this.minDelta = minDelta;
super(waitRandomizer, min, max, minDelta, strategy);
this.trace = trace;
this.strategy = strategy;
}
@Override
public String toString()
{
return super.toString() + (trace == Integer.MAX_VALUE ? "" : ",trace=" + current.traceAfterAttempts);
}
}
@VisibleForTesting
static ParsedStrategy parseStrategy(String spec)
public static ParsedStrategy parseStrategy(String spec)
{
RetryStrategy.ParsedStrategy parsed = RetryStrategy.parseStrategy(spec, LATENCIES, defaultStrategy);
String[] args = spec.split(",");
String waitRandomizer = find(args, "random");
String min = find(args, "min");
String max = find(args, "max");
String minDelta = find(args, "delta");
String trace = find(args, "trace");
if (waitRandomizer == null) waitRandomizer = defaultWaitRandomizer();
if (min == null) min = defaultMinWait();
if (max == null) max = defaultMaxWait();
if (minDelta == null) minDelta = defaultMinDelta();
String trace = find(args, "trace");
int traceAfterAttempts = trace == null ? current.traceAfterAttempts: Integer.parseInt(trace);
ContentionStrategy strategy = new ContentionStrategy(waitRandomizer, min, max, minDelta, traceAfterAttempts);
return new ParsedStrategy(waitRandomizer, min, max, minDelta, strategy);
ContentionStrategy strategy = new ContentionStrategy(parsed.strategy.waitRandomizer, parsed.strategy.min, parsed.strategy.max, parsed.strategy.spread, traceAfterAttempts);
return new ParsedStrategy(parsed.waitRandomizer, parsed.min, parsed.max, parsed.spread, traceAfterAttempts, strategy);
}
public static void setStrategy(String spec)
public static synchronized void setStrategy(String spec)
{
ParsedStrategy parsed = parseStrategy(spec);
currentParsed = parsed;
current = parsed.strategy;
setPaxosContentionWaitRandomizer(parsed.waitRandomizer);
setPaxosContentionMinWait(parsed.min);
setPaxosContentionMaxWait(parsed.max);
setPaxosContentionMinDelta(parsed.minDelta);
setPaxosContentionMinDelta(parsed.spread);
}
public static String getStrategySpec()
{
return "min=" + defaultMinWait()
+ ",max=" + defaultMaxWait()
+ ",delta=" + defaultMinDelta()
+ ",random=" + defaultWaitRandomizer()
+ ",trace=" + current.traceAfterAttempts;
}
private static String find(String[] args, String param)
{
return stream(args).filter(s -> s.startsWith(param + '='))
.map(s -> s.substring(param.length() + 1))
.findFirst().orElse(null);
}
private static LatencySelector parseLatencySelector(Matcher m, LatencySelectorFactory selectors)
{
String perc = m.group("perc");
if (perc == null)
return selectors.constant(parseInMicros(m.group("constbase")));
double percentile = parseDouble("0." + perc);
String rw = m.group("rw");
if (rw.length() == 2)
return selectors.maxReadWrite(percentile);
else if ("r".equals(rw))
return selectors.read(percentile);
else
return selectors.write(percentile);
}
private static LatencyModifier parseLatencyModifier(Matcher m, LatencyModifierFactory modifiers)
{
String mod = m.group("mod");
if (mod == null)
return modifiers.identity();
double modifier = parseDouble(mod);
String modkind = m.group("modkind");
if (modkind == null)
return modifiers.multiply(modifier);
if (modkind.startsWith("*"))
return modifiers.multiplyByAttempts(modifier);
else if (modkind.startsWith("^"))
return modifiers.multiplyByAttemptsExp(modifier);
else
throw new IllegalArgumentException("Unrecognised attempt modifier: " + modkind);
}
static long saturatedCast(double v)
{
if (v > Long.MAX_VALUE)
return Long.MAX_VALUE;
return (long) v;
}
static WaitRandomizer parseWaitRandomizer(String input)
{
return parseWaitRandomizer(input, randomizers);
}
static WaitRandomizer parseWaitRandomizer(String input, WaitRandomizerFactory randomizers)
{
Matcher m = RANDOMIZER.matcher(input);
if (!m.matches())
throw new IllegalArgumentException(input + " does not match" + RANDOMIZER);
String exp;
exp = m.group("exp");
if (exp != null)
return randomizers.exponential(Double.parseDouble(exp));
exp = m.group("qexp");
if (exp != null)
return randomizers.quantizedExponential(Double.parseDouble(exp));
return randomizers.uniform();
}
static Bound parseBound(String input, boolean isMin)
{
return parseBound(input, isMin, selectors, modifiers);
}
@VisibleForTesting
static Bound parseBound(String input, boolean isMin, LatencySelectorFactory selectors, LatencyModifierFactory modifiers)
{
Matcher m = BOUND.matcher(input);
if (!m.matches())
throw new IllegalArgumentException(input + " does not match " + BOUND);
String maybeConst = m.group("const");
if (maybeConst != null)
{
long v = parseInMicros(maybeConst);
return new Bound(v, v, v, modifiers.identity(), selectors.constant(v));
}
long min = parseInMicros(m.group("min"), 0);
long max = parseInMicros(m.group("max"), maxQueryTimeoutMicros() / 2);
return new Bound(min, max, isMin ? min : max, parseLatencyModifier(m, modifiers), parseLatencySelector(m, selectors));
}
private static long parseInMicros(String input, long orElse)
{
if (input == null)
return orElse;
return parseInMicros(input);
}
private static long parseInMicros(String input)
{
Matcher m = TIME.matcher(input);
if (!m.matches())
throw new IllegalArgumentException(input + " does not match " + TIME);
String text;
if (null != (text = m.group(1)))
return parseInt(text) * 1000;
else if (null != (text = m.group(2)))
return parseInt(text);
else
return 0;
}
@VisibleForTesting
static String defaultWaitRandomizer()
{
return orElse(DatabaseDescriptor::getPaxosContentionWaitRandomizer, DEFAULT_WAIT_RANDOMIZER);
}
@VisibleForTesting
static String defaultMinWait()
{
return orElse(DatabaseDescriptor::getPaxosContentionMinWait, DEFAULT_MIN);
}
@VisibleForTesting
static String defaultMaxWait()
{
return orElse(DatabaseDescriptor::getPaxosContentionMaxWait, DEFAULT_MAX);
}
@VisibleForTesting
static String defaultMinDelta()
{
return orElse(DatabaseDescriptor::getPaxosContentionMinDelta, DEFAULT_MIN_DELTA);
return currentParsed.toString();
}
@VisibleForTesting

View File

@ -0,0 +1,36 @@
/*
* 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.distributed.test;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.Feature;
import java.io.IOException;
public class ForBenchmarks extends TestBaseImpl {
public static void main(String[] args) throws IOException, InterruptedException {
try (Cluster cluster = Cluster.build(3)
.withConfig(c -> c.with(Feature.values()))
.start()) {
cluster.get(1).nodetoolResult("cms", "reconfigure", "3").asserts().success();
Thread.currentThread().join();
}
}
}

View File

@ -27,6 +27,9 @@ import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@ -61,7 +64,8 @@ public class AccordLoadTest extends AccordTestBase
public static void setUp() throws IOException
{
CassandraRelevantProperties.SIMULATOR_STARTED.setString(Long.toString(MILLISECONDS.toSeconds(currentTimeMillis())));
AccordTestBase.setupCluster(builder -> builder, 2);
AccordTestBase.setupCluster(builder -> builder, 3);
// AccordTestBase.setupCluster(builder -> builder.withConfig(config -> config.with(Feature.values())), 3);
}
@Ignore
@ -71,151 +75,187 @@ public class AccordLoadTest extends AccordTestBase
test("CREATE TABLE " + qualifiedAccordTableName + " (k int, v int, PRIMARY KEY(k)) WITH transactional_mode = 'full'",
cluster -> {
final ConcurrentHashMap<Verb, AtomicInteger> verbs = new ConcurrentHashMap<>();
cluster.filters().outbound().messagesMatching(new IMessageFilters.Matcher()
try
{
@Override
public boolean matches(int i, int i1, IMessage iMessage)
final ConcurrentHashMap<Verb, AtomicInteger> verbs = new ConcurrentHashMap<>();
cluster.filters().outbound().messagesMatching(new IMessageFilters.Matcher()
{
verbs.computeIfAbsent(Verb.fromId(iMessage.verb()), ignore -> new AtomicInteger()).incrementAndGet();
return false;
}
}).drop();
@Override
public boolean matches(int i, int i1, IMessage iMessage)
{
verbs.computeIfAbsent(Verb.fromId(iMessage.verb()), ignore -> new AtomicInteger()).incrementAndGet();
return false;
}
}).drop();
cluster.forEach(i -> i.runOnInstance(() -> {
((AccordService) AccordService.instance()).journal().compactor().updateCompactionPeriod(1, SECONDS);
// ((AccordSpec.JournalSpec)((AccordService) AccordService.instance()).journal().configuration()).segmentSize = 128 << 10;
}));
ICoordinator coordinator = cluster.coordinator(1);
final int repairInterval = Integer.MAX_VALUE;
// final int repairInterval = 3000;
final int compactionInterval = Integer.MAX_VALUE;
// final int compactionInterval = 3000;
final int flushInterval = Integer.MAX_VALUE;
// final int flushInterval = 1000;
final int compactionPeriodSeconds = -1;
final int restartInterval = 150_000_000;
final int batchSizeLimit = 1000;
final long batchTime = TimeUnit.SECONDS.toNanos(10);
final int concurrency = 100;
final int ratePerSecond = 1000;
final int keyCount = 1000000;
final float readChance = 0.33f;
long nextRepairAt = repairInterval;
long nextCompactionAt = compactionInterval;
long nextFlushAt = flushInterval;
long nextRestartAt = restartInterval;
final ExecutorService restartExecutor = Executors.newSingleThreadExecutor();
final BitSet initialised = new BitSet();
ICoordinator coordinator = cluster.coordinator(1);
final int repairInterval = 3000;
final int compactionInterval = 3000;
final int flushInterval = 1000;
final int batchSizeLimit = 1000;
final long batchTime = TimeUnit.SECONDS.toNanos(10);
final int concurrency = 100;
final int ratePerSecond = 1000;
final int keyCount = 1000000;
final float readChance = 0.33f;
long nextRepairAt = repairInterval;
long nextCompactionAt = compactionInterval;
long nextFlushAt = flushInterval;
final BitSet initialised = new BitSet();
cluster.get(1).nodetoolResult("cms", "reconfigure", "3").asserts().success();
cluster.forEach(i -> i.runOnInstance(() -> {
if (compactionPeriodSeconds > 0)
((AccordService) AccordService.instance()).journal().compactor().updateCompactionPeriod(1, SECONDS);
// ((AccordSpec.JournalSpec)((AccordService) AccordService.instance()).journal().configuration()).segmentSize = 128 << 10;
}));
Random random = new Random();
// CopyOnWriteArrayList<Throwable> exceptions = new CopyOnWriteArrayList<>();
final Semaphore inFlight = new Semaphore(concurrency);
final RateLimiter rateLimiter = RateLimiter.create(ratePerSecond);
// long testStart = System.nanoTime();
// while (NANOSECONDS.toMinutes(System.nanoTime() - testStart) < 10 && exceptions.size() < 10000)
while (true)
{
final EstimatedHistogram histogram = new EstimatedHistogram(200);
long batchStart = System.nanoTime();
long batchEnd = batchStart + batchTime;
int batchSize = 0;
while (batchSize < batchSizeLimit)
Random random = new Random();
// CopyOnWriteArrayList<Throwable> exceptions = new CopyOnWriteArrayList<>();
final Semaphore inFlight = new Semaphore(concurrency);
final RateLimiter rateLimiter = RateLimiter.create(ratePerSecond);
// long testStart = System.nanoTime();
// while (NANOSECONDS.toMinutes(System.nanoTime() - testStart) < 10 && exceptions.size() < 10000)
while (true)
{
inFlight.acquire();
rateLimiter.acquire();
long commandStart = System.nanoTime();
int k = random.nextInt(keyCount);
if (random.nextFloat() < readChance)
final EstimatedHistogram histogram = new EstimatedHistogram(200);
long batchStart = System.nanoTime();
long batchEnd = batchStart + batchTime;
int batchSize = 0;
while (batchSize < batchSizeLimit)
{
coordinator.executeWithResult((success, fail) -> {
inFlight.release();
if (fail == null) histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart));
// else exceptions.add(fail);
}, "SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;", ConsistencyLevel.SERIAL, k);
inFlight.acquire();
rateLimiter.acquire();
long commandStart = System.nanoTime();
int k = random.nextInt(keyCount);
if (random.nextFloat() < readChance)
{
coordinator.executeWithResult((success, fail) -> {
inFlight.release();
if (fail == null) histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart));
// else exceptions.add(fail);
}, "SELECT * FROM " + qualifiedAccordTableName + " WHERE k = ?;", ConsistencyLevel.SERIAL, k);
}
else if (initialised.get(k))
{
coordinator.executeWithResult((success, fail) -> {
inFlight.release();
if (fail == null) histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart));
// else exceptions.add(fail);
}, "UPDATE " + qualifiedAccordTableName + " SET v += 1 WHERE k = ? IF EXISTS;", ConsistencyLevel.SERIAL, ConsistencyLevel.QUORUM, k);
}
else
{
initialised.set(k);
coordinator.executeWithResult((success, fail) -> {
inFlight.release();
if (fail == null) histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart));
// else exceptions.add(fail);
}, "UPDATE " + qualifiedAccordTableName + " SET v = 0 WHERE k = ? IF NOT EXISTS;", ConsistencyLevel.SERIAL, ConsistencyLevel.QUORUM, k);
}
batchSize++;
if (System.nanoTime() >= batchEnd)
break;
}
else if (initialised.get(k))
{
coordinator.executeWithResult((success, fail) -> {
inFlight.release();
if (fail == null) histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart));
// else exceptions.add(fail);
}, "UPDATE " + qualifiedAccordTableName + " SET v += 1 WHERE k = ? IF EXISTS;", ConsistencyLevel.SERIAL, ConsistencyLevel.QUORUM, k);
}
else
{
initialised.set(k);
coordinator.executeWithResult((success, fail) -> {
inFlight.release();
if (fail == null) histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart));
// else exceptions.add(fail);
}, "UPDATE " + qualifiedAccordTableName + " SET v = 0 WHERE k = ? IF NOT EXISTS;", ConsistencyLevel.SERIAL, ConsistencyLevel.QUORUM, k);
}
batchSize++;
if (System.nanoTime() >= batchEnd)
break;
}
if ((nextRepairAt -= batchSize) <= 0)
{
nextRepairAt += repairInterval;
System.out.println("repairing...");
cluster.coordinator(1).instance().nodetool("repair", qualifiedAccordTableName);
}
if ((nextRepairAt -= batchSize) <= 0)
{
nextRepairAt += repairInterval;
System.out.println("repairing...");
cluster.coordinator(1).instance().nodetool("repair", qualifiedAccordTableName);
}
if ((nextCompactionAt -= batchSize) <= 0)
{
nextCompactionAt += compactionInterval;
System.out.println("compacting accord...");
cluster.forEach(i -> {
i.nodetool("compact", "system_accord.journal");
i.runOnInstance(() -> {
((AccordService) AccordService.instance()).journal().checkAllCommands();
if ((nextCompactionAt -= batchSize) <= 0)
{
nextCompactionAt += compactionInterval;
System.out.println("compacting accord...");
cluster.forEach(i -> {
i.nodetool("compact", "system_accord.journal");
i.runOnInstance(() -> {
((AccordService) AccordService.instance()).journal().checkAllCommands();
});
});
});
}
if ((nextFlushAt -= batchSize) <= 0)
{
nextFlushAt += flushInterval;
System.out.println("flushing journal...");
cluster.forEach(i -> i.runOnInstance(() -> {
((AccordService) AccordService.instance()).journal().closeCurrentSegmentForTestingIfNonEmpty();
((AccordService) AccordService.instance()).journal().checkAllCommands();
}));
}
final Date date = new Date();
System.out.printf("%tT rate: %.2f/s (%d total)\n", date, (((float)batchSizeLimit * 1000) / NANOSECONDS.toMillis(System.nanoTime() - batchStart)), batchSize);
System.out.printf("%tT percentiles: %d %d %d %d\n", date, histogram.percentile(.25)/1000, histogram.percentile(.5)/1000, histogram.percentile(.75)/1000, histogram.percentile(1)/1000);
class VerbCount
{
final Verb verb;
final int count;
VerbCount(Verb verb, int count)
{
this.verb = verb;
this.count = count;
}
}
List<VerbCount> verbCounts = new ArrayList<>();
for (Map.Entry<Verb, AtomicInteger> e : verbs.entrySet())
{
int count = e.getValue().getAndSet(0);
if (count != 0) verbCounts.add(new VerbCount(e.getKey(), count));
}
verbCounts.sort(Comparator.comparing(v -> -v.count));
StringBuilder verbSummary = new StringBuilder();
for (VerbCount vs : verbCounts)
{
if ((nextFlushAt -= batchSize) <= 0)
{
if (verbSummary.length() > 0)
verbSummary.append(", ");
verbSummary.append(vs.verb);
verbSummary.append(": ");
verbSummary.append(vs.count);
nextFlushAt += flushInterval;
System.out.println("flushing journal...");
cluster.forEach(i -> i.runOnInstance(() -> {
((AccordService) AccordService.instance()).journal().closeCurrentSegmentForTestingIfNonEmpty();
((AccordService) AccordService.instance()).journal().checkAllCommands();
}));
}
if ((nextRestartAt -= batchSize) <= 0)
{
nextRestartAt += restartInterval;
int nodeIdx = random.nextInt(cluster.size());
restartExecutor.submit(() -> {
System.out.printf("restarting node %d...\n", nodeIdx);
try
{
cluster.get(nodeIdx).shutdown().get();
cluster.get(nodeIdx).startup();
return null;
}
catch (InterruptedException | ExecutionException e)
{
throw new RuntimeException(e);
}
});
}
final Date date = new Date();
System.out.printf("%tT rate: %.2f/s (%d total)\n", date, (((float)batchSizeLimit * 1000) / NANOSECONDS.toMillis(System.nanoTime() - batchStart)), batchSize);
System.out.printf("%tT percentiles: %d %d %d %d\n", date, histogram.percentile(.25)/1000, histogram.percentile(.5)/1000, histogram.percentile(.75)/1000, histogram.percentile(1)/1000);
class VerbCount
{
final Verb verb;
final int count;
VerbCount(Verb verb, int count)
{
this.verb = verb;
this.count = count;
}
}
List<VerbCount> verbCounts = new ArrayList<>();
for (Map.Entry<Verb, AtomicInteger> e : verbs.entrySet())
{
int count = e.getValue().getAndSet(0);
if (count != 0) verbCounts.add(new VerbCount(e.getKey(), count));
}
verbCounts.sort(Comparator.comparing(v -> -v.count));
StringBuilder verbSummary = new StringBuilder();
for (VerbCount vs : verbCounts)
{
{
if (verbSummary.length() > 0)
verbSummary.append(", ");
verbSummary.append(vs.verb);
verbSummary.append(": ");
verbSummary.append(vs.count);
}
}
System.out.printf("%tT verbs: %s\n", date, verbSummary);
}
System.out.printf("%tT verbs: %s\n", date, verbSummary);
}
}
catch (Throwable t)
{
t.printStackTrace();
}
}
);
}

View File

@ -117,6 +117,7 @@ public class AccordJournalCompactionTest
try
{
journal.start(null);
journal.unsafeSetStarted();
Timestamp timestamp = Timestamp.NONE;
RandomSource rs = new DefaultRandom(1);

View File

@ -136,8 +136,8 @@ public class IndexTest
{
assertArrayEquals(EMPTY, onDisk.lookUp(key0));
assertArrayEquals(new long[] { composeOffsetAndSize(val11, 1) }, onDisk.lookUp(key1));
assertArrayEquals(new long[] { composeOffsetAndSize(val21, 2), composeOffsetAndSize(val22, 3) }, onDisk.lookUp(key2));
assertArrayEquals(new long[] { composeOffsetAndSize(val31, 4), composeOffsetAndSize(val32, 5), composeOffsetAndSize(val33, 6) }, onDisk.lookUp(key3));
assertArrayEquals(new long[] { composeOffsetAndSize(val22, 3), composeOffsetAndSize(val21, 2) }, onDisk.lookUp(key2));
assertArrayEquals(new long[] { composeOffsetAndSize(val33, 6), composeOffsetAndSize(val32, 5), composeOffsetAndSize(val31, 4) }, onDisk.lookUp(key3));
assertArrayEquals(EMPTY, onDisk.lookUp(key4));
assertEquals(key1, onDisk.firstId());

View File

@ -0,0 +1,482 @@
///*
// * 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;
//
//import java.util.List;
//import java.util.Random;
//import java.util.concurrent.ThreadLocalRandom;
//import java.util.concurrent.TimeUnit;
//import java.util.concurrent.atomic.AtomicReference;
//import java.util.function.BiFunction;
//import java.util.function.Consumer;
//import java.util.function.DoubleSupplier;
//import java.util.function.LongBinaryOperator;
//
//import com.google.common.collect.ImmutableList;
//import org.junit.Assert;
//import org.junit.Test;
//
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//
//import net.nicoulaj.compilecommand.annotations.Inline;
//import org.apache.cassandra.config.DatabaseDescriptor;
//import org.apache.cassandra.service.TimeoutStrategy.LatencyModifier;
//import org.apache.cassandra.service.TimeoutStrategy.LatencyModifierFactory;
//import org.apache.cassandra.service.TimeoutStrategy.LatencySource;
//import org.apache.cassandra.service.TimeoutStrategy.LatencySupplierFactory;
//import org.apache.cassandra.service.TimeoutStrategy.LatencySupplier;
//import org.apache.cassandra.service.TimeoutStrategy.Wait;
//import org.apache.cassandra.service.paxos.ContentionStrategy;
//
//import static org.apache.cassandra.service.RetryStrategy.*;
//import static org.apache.cassandra.service.RetryStrategy.WaitRandomizerFactory.*;
//import static org.apache.cassandra.service.RetryStrategyTest.WaitRandomizerType.*;
//import static org.apache.cassandra.service.TimeoutStrategy.modifiers;
//import static org.apache.cassandra.service.TimeoutStrategy.parseWait;
//import static org.apache.cassandra.service.TimeoutStrategy.selectors;
//
//public class RetryStrategyTest
//{
// private static final Logger logger = LoggerFactory.getLogger(RetryStrategyTest.class);
//
// static
// {
// DatabaseDescriptor.daemonInitialization();
// }
//
// private static final long MAX = DatabaseDescriptor.getRpcTimeout(TimeUnit.MICROSECONDS);
//
// private static final String DEFAULT_WAIT_RANDOMIZER = "qexp(1.5)"; // at least 0ms, and at least 66% of median latency
// private static final String DEFAULT_MIN = "0 <= p50(rw)*0.66"; // at least 0ms, and at least 66% of median latency
// private static final String DEFAULT_MAX = "10ms <= p95(rw)*1.8^attempts <= 100ms"; // p95 latency with exponential back-off at rate of 1.8^attempts
// private static final String DEFAULT_SPREAD = "5ms <= p50(rw)*0.5"; // at least 5ms, and at least 50% of median latency
//
// private static final WaitRandomizerParseValidator DEFAULT_WAIT_RANDOMIZER_VALIDATOR = new WaitRandomizerParseValidator(DEFAULT_WAIT_RANDOMIZER, QEXP, 1.5);
// private static final WaitParseValidator DEFAULT_MIN_VALIDATOR = new WaitParseValidator(DEFAULT_MIN, true, assertWait(0, MAX, 0, selectors.maxReadWrite(0f).getClass(), 0.50, 0, modifiers.multiply(0f).getClass(), 0.66));
// private static final WaitParseValidator DEFAULT_MAX_VALIDATOR = new WaitParseValidator(DEFAULT_MAX, false, assertWait(10000, 100000, 100000, selectors.maxReadWrite(0f).getClass(), 0.95, 0, modifiers.multiplyByAttemptsExp(0f).getClass(), 1.8));
// private static final WaitParseValidator DEFAULT_MIN_DELTA_VALIDATOR = new WaitParseValidator(DEFAULT_SPREAD, true, assertWait(5000, MAX, 5000, selectors.maxReadWrite(0f).getClass(), 0.50, 0, modifiers.multiply(0f).getClass(), 0.5));
// private static final RetryStrategy.ParsedStrategy DEFAULT = new RetryStrategy.ParsedStrategy(DEFAULT_WAIT_RANDOMIZER, DEFAULT_MIN, DEFAULT_MAX, DEFAULT_SPREAD,
// new RetryStrategy(DEFAULT_WAIT_RANDOMIZER, DEFAULT_MIN, DEFAULT_MAX, DEFAULT_SPREAD));
//
// private static List<WaitParseValidator> VALIDATE = ImmutableList.of(
// new WaitParseValidator("p95(rw)", false, assertWait(0, MAX, MAX, selectors.maxReadWrite(0f).getClass(), 0.95, 0, modifiers.identity().getClass(), 1)),
// new WaitParseValidator("5ms<=p50(rw)*0.66", false, assertWait(5000, MAX, MAX, selectors.maxReadWrite(0f).getClass(), 0.50, 0, modifiers.multiply(0).getClass(), 0.66)),
// new WaitParseValidator("5us <= p50(r)*1.66*attempts", true, assertWait(5, MAX, 5, selectors.read(0f).getClass(), 0.50, 0, modifiers.multiplyByAttempts(0f).getClass(), 1.66)),
// new WaitParseValidator("0<=p50(w)*0.66^attempts", true, assertWait(0, MAX, 0, selectors.write(0f).getClass(), 0.50, 0, modifiers.multiplyByAttemptsExp(0f).getClass(), 0.66)),
// new WaitParseValidator("125us", true, assertWait(125, 125, 125, selectors.constant(0).getClass(), 0.0f, 125, modifiers.identity().getClass(), 1)),
// new WaitParseValidator("5us <= p95(r)*1.8^attempts <= 100us", true, assertWait(5, 100, 5, selectors.read(0f).getClass(), 0.95, 0, modifiers.multiplyByAttemptsExp(0f).getClass(), 1.8)),
// DEFAULT_MIN_VALIDATOR, DEFAULT_MAX_VALIDATOR, DEFAULT_MIN_DELTA_VALIDATOR
// );
//
// private static List<WaitRandomizerParseValidator> VALIDATE_RANDOMIZER = ImmutableList.of(
// new WaitRandomizerParseValidator("quantizedexponential(0.5)", QEXP, 0.5),
// new WaitRandomizerParseValidator("exponential(2.5)", EXP, 2.5),
// new WaitRandomizerParseValidator("exp(10)", EXP, 10),
// new WaitRandomizerParseValidator("uniform", UNIFORM, 0),
// DEFAULT_WAIT_RANDOMIZER_VALIDATOR
// );
//
// static class WaitParseValidator
// {
// final String spec;
// final boolean isMin;
// final Consumer<Wait> validator;
//
// WaitParseValidator(String spec, boolean isMin, Consumer<Wait> validator)
// {
// this.spec = spec;
// this.isMin = isMin;
// this.validator = validator;
// }
//
// void validate(Wait Wait)
// {
// validator.accept(Wait);
// }
// }
//
// enum WaitRandomizerType
// {
// UNIFORM(Uniform.class, (p, f) -> f.uniform()),
// EXP(Exponential.class, (p, f) -> f.exponential(p)),
// QEXP(QuantizedExponential.class, (p, f) -> f.quantizedExponential(p));
//
// final Class<? extends WaitRandomizer> clazz;
// final BiFunction<Double, WaitRandomizerFactory, WaitRandomizer> getter;
//
// WaitRandomizerType(Class<? extends WaitRandomizer> clazz, BiFunction<Double, WaitRandomizerFactory, WaitRandomizer> getter)
// {
// this.clazz = clazz;
// this.getter = getter;
// }
// }
//
// static class WaitRandomizerParseValidator
// {
// final String spec;
// final WaitRandomizerType type;
// final double power;
//
// WaitRandomizerParseValidator(String spec, WaitRandomizerType type, double power)
// {
// this.spec = spec;
// this.type = type;
// this.power = power;
// }
//
// void validate(WaitRandomizer randomizer)
// {
// Assert.assertSame(type.clazz, randomizer.getClass());
// if (AbstractExponential.class.isAssignableFrom(type.clazz))
// Assert.assertEquals(power, ((AbstractExponential) randomizer).power, 0.00001);
// }
// }
//
// private static class WaitRandomizerOutputValidator
// {
// static void validate(WaitRandomizerType type, long seed, int trials, int samplesPerTrial)
// {
// Random random = new Random(seed);
// WaitRandomizer randomizer = type.getter.apply(2d, new WaitRandomizerFactory()
// {
// @Override public LongBinaryOperator uniformLongSupplier() { return (min, max) -> min + random.nextInt((int) (max - min)); }
// @Override public DoubleSupplier uniformDoubleSupplier() { return random::nextDouble; }
// });
//
// for (int i = 0 ; i < trials ; ++i)
// {
// int min = random.nextInt(1 << 20);
// int max = min + 1024 + random.nextInt(1 << 20);
// double minMean = minMean(type, min, max);
// double maxMean = maxMean(type, min, max);
// double sampleMean = sampleMean(samplesPerTrial, min, max, randomizer);
// Assert.assertTrue(minMean <= sampleMean);
// Assert.assertTrue(maxMean >= sampleMean);
// }
// }
//
// private static double minMean(WaitRandomizerType type, int min, int max)
// {
// switch (type)
// {
// case UNIFORM: return min + (max - min) * (4d/10);
// case EXP: case QEXP: return min + (max - min) * (6d/10);
// default: throw new IllegalStateException();
// }
// }
//
// private static double maxMean(WaitRandomizerType type, int min, int max)
// {
// switch (type)
// {
// case UNIFORM: return min + (max - min) * (6d/10);
// case EXP: case QEXP: return min + (max - min) * (8d/10);
// default: throw new IllegalStateException();
// }
// }
//
// private static double sampleMean(int samples, int min, int max, WaitRandomizer randomizer)
// {
// double sum = 0;
// int attempts = 1;
// for (int i = 0 ; i < samples ; ++i)
// {
// long wait = randomizer.wait(min, max, attempts = (attempts & 15) + 1);
// Assert.assertTrue(wait >= min);
// Assert.assertTrue(wait <= max);
// sum += wait;
// }
// double mean = sum / samples;
// Assert.assertTrue(mean >= min);
// Assert.assertTrue(mean <= max);
// return mean;
// }
// }
//
// private static Consumer<Wait> assertWait(
// long min, long max, long onFailure,
// Class<? extends LatencySupplier> selectorClass,
// double selectorPercentile,
// long selectorConst,
// Class<? extends LatencyModifier> modifierClass,
// double modifierVal
// )
// {
// return Wait -> {
// Assert.assertEquals(min, Wait.min);
// Assert.assertEquals(max, Wait.max);
// Assert.assertEquals(onFailure, Wait.onFailure);
// Assert.assertSame(selectorClass, Wait.selector.getClass());
// if (selectorClass == selectors.constant(0).getClass())
// {
// LatencySupplier fail = v -> { throw new UnsupportedOperationException(); };
// Assert.assertEquals(selectorConst, Wait.selector.select(fail, fail));
// }
// else
// {
// AtomicReference<Double> percentile = new AtomicReference<>();
// LatencySource set = v -> { percentile.set(v); return 0; };
// Wait.selector.select(set, set);
// Assert.assertNotNull(percentile.get());
// Assert.assertEquals(selectorPercentile, percentile.get(), 0.00001);
// }
// Assert.assertSame(modifierClass, Wait.modifier.getClass());
// Assert.assertEquals(1000000L * modifierVal, Wait.modifier.modify(1000000, 1), 0.00001);
// };
// }
//
// private static void assertParseFailure(String spec)
// {
//
// try
// {
// Wait Wait = parseWait(spec, 0, 0, 0);
// Assert.fail("expected parse failure, but got " + Wait);
// }
// catch (IllegalArgumentException e)
// {
// // expected
// }
// }
//
// @Test
// public void strategyParseTest()
// {
// for (WaitParseValidator min : VALIDATE.stream().filter(v -> v.isMin).toArray(WaitParseValidator[]::new))
// {
// for (WaitParseValidator max : VALIDATE.stream().filter(v -> !v.isMin).toArray(WaitParseValidator[]::new))
// {
// for (WaitParseValidator minDelta : VALIDATE.stream().filter(v -> v.isMin).toArray(WaitParseValidator[]::new))
// {
// for (WaitRandomizerParseValidator random : VALIDATE_RANDOMIZER)
// {
// {
// ParsedStrategy parsed = parseStrategy("min=" + min.spec + ",max=" + max.spec + ",delta=" + minDelta.spec + ",random=" + random.spec, DEFAULT);
// Assert.assertEquals(parsed.min, min.spec);
// min.validate(parsed.strategy.min);
// Assert.assertEquals(parsed.max, max.spec);
// max.validate(parsed.strategy.max);
// Assert.assertEquals(parsed.spread, minDelta.spec);
// minDelta.validate(parsed.strategy.spread);
// Assert.assertEquals(parsed.waitRandomizer, random.spec);
// random.validate(parsed.strategy.waitRandomizer);
// }
// ParsedStrategy parsed = parseStrategy("random=" + random.spec, DEFAULT);
// Assert.assertEquals(parsed.min, DEFAULT_MIN_VALIDATOR.spec);
// DEFAULT_MIN_VALIDATOR.validate(parsed.strategy.min);
// Assert.assertEquals(parsed.max, DEFAULT_MAX_VALIDATOR.spec);
// DEFAULT_MAX_VALIDATOR.validate(parsed.strategy.max);
// Assert.assertEquals(parsed.spread, DEFAULT_MIN_DELTA_VALIDATOR.spec);
// DEFAULT_MIN_DELTA_VALIDATOR.validate(parsed.strategy.spread);
// Assert.assertEquals(parsed.waitRandomizer, random.spec);
// random.validate(parsed.strategy.waitRandomizer);
// }
// ParsedStrategy parsed = parseStrategy("delta=" + minDelta.spec, DEFAULT);
// Assert.assertEquals(parsed.min, DEFAULT_MIN_VALIDATOR.spec);
// DEFAULT_MIN_VALIDATOR.validate(parsed.strategy.min);
// Assert.assertEquals(parsed.max, DEFAULT_MAX_VALIDATOR.spec);
// DEFAULT_MAX_VALIDATOR.validate(parsed.strategy.max);
// Assert.assertEquals(parsed.spread, minDelta.spec);
// minDelta.validate(parsed.strategy.spread);
// }
// ParsedStrategy parsed = parseStrategy("max=" + max.spec, DEFAULT);
// Assert.assertEquals(parsed.min, DEFAULT_MIN_VALIDATOR.spec);
// DEFAULT_MIN_VALIDATOR.validate(parsed.strategy.min);
// Assert.assertEquals(parsed.max, max.spec);
// max.validate(parsed.strategy.max);
// Assert.assertEquals(parsed.spread, DEFAULT_MIN_DELTA_VALIDATOR.spec);
// DEFAULT_MIN_DELTA_VALIDATOR.validate(parsed.strategy.spread);
// }
// ParsedStrategy parsed = parseStrategy("min=" + min.spec, DEFAULT);
// Assert.assertEquals(parsed.min, min.spec);
// min.validate(parsed.strategy.min);
// Assert.assertEquals(parsed.max, DEFAULT_MAX_VALIDATOR.spec);
// DEFAULT_MAX_VALIDATOR.validate(parsed.strategy.max);
// Assert.assertEquals(parsed.spread, DEFAULT_MIN_DELTA_VALIDATOR.spec);
// DEFAULT_MIN_DELTA_VALIDATOR.validate(parsed.strategy.spread);
// }
// }
//
// @Test
// public void testParseRoundTrip()
// {
// LatencySupplierFactory selectorFactory = new LatencySupplierFactory()
// {
// LatencySupplierFactory delegate = TimeoutStrategy.selectors;
// public LatencySelector constant(long latency) { return selector(delegate.constant(latency), String.format("%dms", latency)); }
// public LatencySelector read(double percentile) { return selector(delegate.read(percentile), String.format("p%d(r)", (int) (percentile * 100))); }
// public LatencySelector write(double percentile) { return selector(delegate.write(percentile), String.format("p%d(w)", (int) (percentile * 100))); }
// public LatencySelector maxReadWrite(double percentile) { return selector(delegate.maxReadWrite(percentile), String.format("p%d(rw)", (int) percentile * 100)); }
//
// private LatencySelector selector(LatencySelector selector, String str) {
// return new LatencySelector()
// {
// public long select(LatencySupplier read, LatencySupplier write)
// {
// return selector.select(read, write);
// }
//
// public String toString()
// {
// return str;
// }
// };
// }
// };
//
// LatencyModifierFactory modifierFactory = new LatencyModifierFactory()
// {
// LatencyModifierFactory delegate = modifiers;
// public LatencyModifier identity() { return modifier(delegate.identity(), ""); }
// public LatencyModifier multiply(double constant) { return modifier(delegate.multiply(constant), String.format(" * %.2f", constant)); }
// public LatencyModifier multiplyByAttempts(double multiply) { return modifier(delegate.multiplyByAttempts(multiply), String.format(" * %.2f * attempts", multiply)); }
// public LatencyModifier multiplyByAttemptsExp(double base) { return modifier(delegate.multiplyByAttemptsExp(base), String.format(" * %.2f ^ attempts", base)); }
//
// private LatencyModifier modifier(LatencyModifier modifier, String str) {
// return new LatencyModifier()
// {
// @Inline
// public long modify(long latency, int attempts)
// {
// return modifier.modify(latency, attempts);
// }
//
// public String toString()
// {
// return str;
// }
// };
// }
// };
//
// LatencyModifier[] latencyModifiers = new LatencyModifier[]{
// modifierFactory.multiply(0.5),
// modifierFactory.multiplyByAttempts(0.5),
// modifierFactory.multiplyByAttemptsExp(0.5)
// };
//
// LatencySelector[] latencySelectors = new LatencySelector[]{
// selectorFactory.read(0.5),
// selectorFactory.write(0.5),
// selectorFactory.maxReadWrite(0.99)
// };
//
// for (boolean min : new boolean[] { true, false})
// {
// String left = min ? "10ms <= " : "";
// for (boolean max : new boolean[] { true, false})
// {
// String right = max ? " <= 10ms" : "";
//
// for (LatencySelector selector : latencySelectors)
// {
// for (LatencyModifier modifier : latencyModifiers)
// {
// String mid = String.format("%s%s", selector, modifier);
// String input = left + mid + right;
// Wait Wait = parseWait(input, 0, MAX, MAX, selectorFactory, modifierFactory);
// Assert.assertTrue(String.format("Wait: %d" , Wait.min), !min || Wait.min == 10000);
// Assert.assertTrue(String.format("Wait: %d" , Wait.max), !max || Wait.max == 10000);
// Assert.assertEquals(selector.toString(), Wait.selector.toString());
// Assert.assertEquals(modifier.toString(), Wait.modifier.toString());
// }
// }
// }
// }
// }
//
// @Test
// public void WaitParseTest()
// {
// VALIDATE.forEach(v -> v.validate(parseWait(v.spec, 0, MAX, v.isMin ? 0 : MAX)));
// }
//
// @Test
// public void waitRandomizerParseTest()
// {
// VALIDATE_RANDOMIZER.forEach(v -> v.validate(parseWaitRandomizer(v.spec)));
// }
//
// @Test
// public void waitRandomizerSampleTest()
// {
// waitRandomizerSampleTest(2);
// }
//
// private void waitRandomizerSampleTest(int count)
// {
// while (count-- > 0)
// {
// long seed = ThreadLocalRandom.current().nextLong();
// logger.info("Seed {}", seed);
// for (WaitRandomizerType type : WaitRandomizerType.values())
// {
// WaitRandomizerOutputValidator.validate(type, seed, 100, 1000000);
// }
// }
// }
//
// @Test
// public void WaitParseFailureTest()
// {
// assertParseFailure("10ms <= p95(r) <= 5ms");
// assertParseFailure("10 <= p95(r)");
// assertParseFailure("10 <= 20 <= 30");
// assertParseFailure("p95(r) < 5");
// assertParseFailure("p95(x)");
// assertParseFailure("p95()");
// assertParseFailure("p95");
// assertParseFailure("p50(rw)+0.66");
// }
//
// @Test
// public void testBackoffTime()
// {
// RetryStrategy strategy = parseStrategy("min=0ms,max=100ms,random=uniform", DEFAULT).strategy;
// double total = 0;
// int count = 100000;
// for (int i = 0 ; i < count ; ++i)
// {
// long now = System.nanoTime();
// long waitUntil = strategy.computeWaitUntil(1);
// long waitLength = Math.max(waitUntil - now, 0);
// total += waitLength;
// }
// Assert.assertTrue(Math.abs(TimeUnit.MILLISECONDS.toNanos(50) - (total / count)) < TimeUnit.MILLISECONDS.toNanos(1L));
// }
//
// @Test
// public void testBackoffTimeElapsed()
// {
// ContentionStrategy strategy = ContentionStrategy.parseStrategy("min=0ms,max=10ms,random=uniform").strategy;
// double total = 0;
// int count = 1000;
// for (int i = 0 ; i < count ; ++i)
// {
// long start = System.nanoTime();
// strategy.doWaitForContention(Long.MAX_VALUE, 1, null, null, null, null);
// long end = System.nanoTime();
// total += end - start;
// }
// // make sure we have slept at least 4ms on average, given a mean wait time of 5ms
// double avg = total / count;
// double nanos = avg - TimeUnit.MILLISECONDS.toNanos(4);
// Assert.assertTrue(nanos > 0);
// }
//}

View File

@ -21,9 +21,13 @@ package org.apache.cassandra.service.accord;
import org.junit.BeforeClass;
import org.junit.Test;
import accord.api.TopologySorter;
import accord.api.TopologySorter.StaticSorter;
import accord.impl.RequestCallbacks;
import accord.messages.ReadData;
import accord.messages.ReadData.CommitOrReadNack;
import accord.topology.TopologyUtils;
import org.apache.cassandra.service.accord.api.AccordTimeService;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
@ -56,10 +60,10 @@ public class AccordMessageSinkTest
private static final Node.Id node = new Node.Id(1);
private static final AccordEndpointMapper mapping = SimpleAccordEndpointMapper.INSTANCE;
private static final Topology topology = TopologyUtils.initialTopology(new Node.Id[] { node}, Ranges.of(IntKey.range(0, 100)), 1);
private static final Topologies topologies = new Topologies.Single((a, b, ignore) -> 0, topology);
private static final Topologies topologies = new Topologies.Single((TopologySorter) (StaticSorter)(a, b, ignore) -> 0, topology);
private static final MessageDelivery messaging = Mockito.mock(MessageDelivery.class);
private static final AccordMessageSink sink = new AccordMessageSink(Mockito.mock(Agent.class), messaging, mapping);
private static final AccordMessageSink sink = new AccordMessageSink(Mockito.mock(Agent.class), messaging, mapping, new RequestCallbacks(new AccordTimeService()));
@BeforeClass
public static void setup()

View File

@ -50,7 +50,7 @@ import accord.local.DurableBefore;
import accord.local.Node;
import accord.local.Node.Id;
import accord.local.NodeCommandStoreService;
import accord.local.NodeTimeService;
import accord.local.TimeService;
import accord.local.PreLoadContext;
import accord.local.SafeCommand;
import accord.local.SafeCommandStore;
@ -368,12 +368,14 @@ public class AccordTestUtils
Node.Id node = new Id(1);
NodeCommandStoreService time = new NodeCommandStoreService()
{
private ToLongFunction<TimeUnit> elapsed = NodeTimeService.elapsedWrapperFromNonMonotonicSource(TimeUnit.MICROSECONDS, this::now);
private ToLongFunction<TimeUnit> elapsed = TimeService.elapsedWrapperFromNonMonotonicSource(TimeUnit.MICROSECONDS, this::now);
@Override public Id id() { return node;}
@Override public DurableBefore durableBefore() { return DurableBefore.EMPTY; }
@Override public long epoch() {return 1; }
@Override public long now() {return now.getAsLong(); }
@Override public Timestamp uniqueNow() { return uniqueNow(Timestamp.NONE); }
@Override public Timestamp uniqueNow(Timestamp atLeast) { return Timestamp.fromValues(1, now.getAsLong(), node); }
@Override public long elapsed(TimeUnit timeUnit) { return elapsed.applyAsLong(timeUnit); }
};
@ -390,12 +392,13 @@ public class AccordTestUtils
{
NodeCommandStoreService time = new NodeCommandStoreService()
{
private ToLongFunction<TimeUnit> elapsed = NodeTimeService.elapsedWrapperFromNonMonotonicSource(TimeUnit.MICROSECONDS, this::now);
private ToLongFunction<TimeUnit> elapsed = TimeService.elapsedWrapperFromNonMonotonicSource(TimeUnit.MICROSECONDS, this::now);
@Override public DurableBefore durableBefore() { return DurableBefore.EMPTY; }
@Override public Id id() { return node;}
@Override public long epoch() {return 1; }
@Override public long now() {return now.getAsLong(); }
@Override public Timestamp uniqueNow() { return uniqueNow(Timestamp.NONE); }
@Override public Timestamp uniqueNow(Timestamp atLeast) { return Timestamp.fromValues(1, now.getAsLong(), node); }
@Override
public long elapsed(TimeUnit timeUnit) { return elapsed.applyAsLong(timeUnit); }

View File

@ -54,7 +54,7 @@ import accord.api.Scheduler;
import accord.impl.SizeOfIntersectionSorter;
import accord.impl.TestAgent;
import accord.local.Node;
import accord.local.NodeTimeService;
import accord.local.TimeService;
import accord.primitives.Ranges;
import accord.topology.Topology;
import accord.topology.TopologyManager;
@ -666,7 +666,7 @@ public class EpochSyncTest
this.token = token;
this.epoch = epoch;
// TODO (review): Should there be a real scheduler here? Is it possible to adapt the Scheduler interface to scheduler used in this test?
this.topology = new TopologyManager(SizeOfIntersectionSorter.SUPPLIER, new TestAgent.RethrowAgent(), id, Scheduler.NEVER_RUN_SCHEDULED, NodeTimeService.elapsedWrapperFromNonMonotonicSource(TimeUnit.MILLISECONDS, globalExecutor::currentTimeMillis), LocalConfig.DEFAULT);
this.topology = new TopologyManager(SizeOfIntersectionSorter.SUPPLIER, new TestAgent.RethrowAgent(), id, Scheduler.NEVER_RUN_SCHEDULED, TimeService.ofNonMonotonic(globalExecutor::currentTimeMillis, TimeUnit.MILLISECONDS), LocalConfig.DEFAULT);
AccordConfigurationService.DiskStateManager instance = MockDiskStateManager.instance;
config = new AccordConfigurationService(node, messagingService, failureDetector, instance, scheduler);
config.registerListener(new ConfigurationService.Listener()

View File

@ -41,7 +41,7 @@ import accord.local.CommandStores;
import accord.local.DurableBefore;
import accord.local.Node;
import accord.local.NodeCommandStoreService;
import accord.local.NodeTimeService;
import accord.local.TimeService;
import accord.local.PreLoadContext;
import accord.local.SafeCommand;
import accord.local.SafeCommandStore;
@ -118,10 +118,16 @@ public class SimulatedAccordCommandStore implements AutoCloseable
this.nodeId = AccordTopology.tcmIdToAccord(ClusterMetadata.currentNullable().myNodeId());
this.storeService = new NodeCommandStoreService()
{
private final ToLongFunction<TimeUnit> elapsed = NodeTimeService.elapsedWrapperFromNonMonotonicSource(TimeUnit.NANOSECONDS, this::now);
private final ToLongFunction<TimeUnit> elapsed = TimeService.elapsedWrapperFromNonMonotonicSource(TimeUnit.NANOSECONDS, this::now);
@Override public DurableBefore durableBefore() { return DurableBefore.EMPTY; }
@Override
public Timestamp uniqueNow()
{
return uniqueNow(Timestamp.NONE);
}
@Override
public Node.Id id()
{

View File

@ -27,6 +27,7 @@ import org.junit.BeforeClass;
import org.junit.Test;
import accord.api.TopologySorter;
import accord.api.TopologySorter.StaticSorter;
import accord.coordinate.tracking.RequestStatus;
import accord.local.Node;
import accord.topology.Topologies;
@ -58,7 +59,7 @@ public class RequiredResponseTrackerTest
private static final Location LOCATION = new Location("DC1", "RACK1");
private static final List<Range<Token>> RANGES = ImmutableList.of(range(-100, 0), range(0, 100), range(100, -100));
private static final TopologySorter TOPOLOGY_SORTER = (node1, node2, shards) -> node1.compareTo(node2);
private static final TopologySorter TOPOLOGY_SORTER = (StaticSorter)(node1, node2, shards) -> node1.compareTo(node2);
@BeforeClass
public static void beforeClass() throws Throwable

View File

@ -1,466 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.service.paxos;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.DoubleSupplier;
import java.util.function.LongBinaryOperator;
import com.google.common.collect.ImmutableList;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.nicoulaj.compilecommand.annotations.Inline;
import org.apache.cassandra.config.DatabaseDescriptor;
import static org.apache.cassandra.service.paxos.ContentionStrategy.*;
import static org.apache.cassandra.service.paxos.ContentionStrategy.WaitRandomizerFactory.*;
import static org.apache.cassandra.service.paxos.ContentionStrategyTest.WaitRandomizerType.*;
public class ContentionStrategyTest
{
private static final Logger logger = LoggerFactory.getLogger(ContentionStrategyTest.class);
static
{
DatabaseDescriptor.daemonInitialization();
}
private static final long MAX = maxQueryTimeoutMicros()/2;
private static final WaitParseValidator DEFAULT_WAIT_RANDOMIZER_VALIDATOR = new WaitParseValidator(defaultWaitRandomizer(), QEXP, 1.5);
private static final BoundParseValidator DEFAULT_MIN_VALIDATOR = new BoundParseValidator(defaultMinWait(), true, assertBound(0, MAX, 0, selectors.maxReadWrite(0f).getClass(), 0.50, 0, modifiers.multiply(0f).getClass(), 0.66));
private static final BoundParseValidator DEFAULT_MAX_VALIDATOR = new BoundParseValidator(defaultMaxWait(), false, assertBound(10000, 100000, 100000, selectors.maxReadWrite(0f).getClass(), 0.95, 0, modifiers.multiplyByAttemptsExp(0f).getClass(), 1.8));
private static final BoundParseValidator DEFAULT_MIN_DELTA_VALIDATOR = new BoundParseValidator(defaultMinDelta(), true, assertBound(5000, MAX, 5000, selectors.maxReadWrite(0f).getClass(), 0.50, 0, modifiers.multiply(0f).getClass(), 0.5));
private static List<BoundParseValidator> VALIDATE = ImmutableList.of(
new BoundParseValidator("p95(rw)", false, assertBound(0, MAX, MAX, selectors.maxReadWrite(0f).getClass(), 0.95, 0, modifiers.identity().getClass(), 1)),
new BoundParseValidator("5ms<=p50(rw)*0.66", false, assertBound(5000, MAX, MAX, selectors.maxReadWrite(0f).getClass(), 0.50, 0, modifiers.multiply(0).getClass(), 0.66)),
new BoundParseValidator("5us <= p50(r)*1.66*attempts", true, assertBound(5, MAX, 5, selectors.read(0f).getClass(), 0.50, 0, modifiers.multiplyByAttempts(0f).getClass(), 1.66)),
new BoundParseValidator("0<=p50(w)*0.66^attempts", true, assertBound(0, MAX, 0, selectors.write(0f).getClass(), 0.50, 0, modifiers.multiplyByAttemptsExp(0f).getClass(), 0.66)),
new BoundParseValidator("125us", true, assertBound(125, 125, 125, selectors.constant(0).getClass(), 0.0f, 125, modifiers.identity().getClass(), 1)),
new BoundParseValidator("5us <= p95(r)*1.8^attempts <= 100us", true, assertBound(5, 100, 5, selectors.read(0f).getClass(), 0.95, 0, modifiers.multiplyByAttemptsExp(0f).getClass(), 1.8)),
DEFAULT_MIN_VALIDATOR, DEFAULT_MAX_VALIDATOR, DEFAULT_MIN_DELTA_VALIDATOR
);
private static List<WaitParseValidator> VALIDATE_RANDOMIZER = ImmutableList.of(
new WaitParseValidator("quantizedexponential(0.5)", QEXP, 0.5),
new WaitParseValidator("exponential(2.5)", EXP, 2.5),
new WaitParseValidator("exp(10)", EXP, 10),
new WaitParseValidator("uniform", UNIFORM, 0),
DEFAULT_WAIT_RANDOMIZER_VALIDATOR
);
static class BoundParseValidator
{
final String spec;
final boolean isMin;
final Consumer<Bound> validator;
BoundParseValidator(String spec, boolean isMin, Consumer<Bound> validator)
{
this.spec = spec;
this.isMin = isMin;
this.validator = validator;
}
void validate(Bound bound)
{
validator.accept(bound);
}
}
enum WaitRandomizerType
{
UNIFORM(Uniform.class, (p, f) -> f.uniform()),
EXP(Exponential.class, (p, f) -> f.exponential(p)),
QEXP(QuantizedExponential.class, (p, f) -> f.quantizedExponential(p));
final Class<? extends WaitRandomizer> clazz;
final BiFunction<Double, WaitRandomizerFactory, WaitRandomizer> getter;
WaitRandomizerType(Class<? extends WaitRandomizer> clazz, BiFunction<Double, WaitRandomizerFactory, WaitRandomizer> getter)
{
this.clazz = clazz;
this.getter = getter;
}
}
static class WaitParseValidator
{
final String spec;
final WaitRandomizerType type;
final double power;
WaitParseValidator(String spec, WaitRandomizerType type, double power)
{
this.spec = spec;
this.type = type;
this.power = power;
}
void validate(WaitRandomizer randomizer)
{
Assert.assertSame(type.clazz, randomizer.getClass());
if (AbstractExponential.class.isAssignableFrom(type.clazz))
Assert.assertEquals(power, ((AbstractExponential) randomizer).power, 0.00001);
}
}
private static class WaitRandomizerOutputValidator
{
static void validate(WaitRandomizerType type, long seed, int trials, int samplesPerTrial)
{
Random random = new Random(seed);
WaitRandomizer randomizer = type.getter.apply(2d, new WaitRandomizerFactory()
{
@Override public LongBinaryOperator uniformLongSupplier() { return (min, max) -> min + random.nextInt((int) (max - min)); }
@Override public DoubleSupplier uniformDoubleSupplier() { return random::nextDouble; }
});
for (int i = 0 ; i < trials ; ++i)
{
int min = random.nextInt(1 << 20);
int max = min + 1024 + random.nextInt(1 << 20);
double minMean = minMean(type, min, max);
double maxMean = maxMean(type, min, max);
double sampleMean = sampleMean(samplesPerTrial, min, max, randomizer);
Assert.assertTrue(minMean <= sampleMean);
Assert.assertTrue(maxMean >= sampleMean);
}
}
private static double minMean(WaitRandomizerType type, int min, int max)
{
switch (type)
{
case UNIFORM: return min + (max - min) * (4d/10);
case EXP: case QEXP: return min + (max - min) * (6d/10);
default: throw new IllegalStateException();
}
}
private static double maxMean(WaitRandomizerType type, int min, int max)
{
switch (type)
{
case UNIFORM: return min + (max - min) * (6d/10);
case EXP: case QEXP: return min + (max - min) * (8d/10);
default: throw new IllegalStateException();
}
}
private static double sampleMean(int samples, int min, int max, WaitRandomizer randomizer)
{
double sum = 0;
int attempts = 1;
for (int i = 0 ; i < samples ; ++i)
{
long wait = randomizer.wait(min, max, attempts = (attempts & 15) + 1);
Assert.assertTrue(wait >= min);
Assert.assertTrue(wait <= max);
sum += wait;
}
double mean = sum / samples;
Assert.assertTrue(mean >= min);
Assert.assertTrue(mean <= max);
return mean;
}
}
private static Consumer<Bound> assertBound(
long min, long max, long onFailure,
Class<? extends LatencySelector> selectorClass,
double selectorPercentile,
long selectorConst,
Class<? extends LatencyModifier> modifierClass,
double modifierVal
)
{
return bound -> {
Assert.assertEquals(min, bound.min);
Assert.assertEquals(max, bound.max);
Assert.assertEquals(onFailure, bound.onFailure);
Assert.assertSame(selectorClass, bound.selector.getClass());
if (selectorClass == selectors.constant(0).getClass())
{
LatencySupplier fail = v -> { throw new UnsupportedOperationException(); };
Assert.assertEquals(selectorConst, bound.selector.select(fail, fail));
}
else
{
AtomicReference<Double> percentile = new AtomicReference<>();
LatencySupplier set = v -> { percentile.set(v); return 0; };
bound.selector.select(set, set);
Assert.assertNotNull(percentile.get());
Assert.assertEquals(selectorPercentile, percentile.get(), 0.00001);
}
Assert.assertSame(modifierClass, bound.modifier.getClass());
Assert.assertEquals(1000000L * modifierVal, bound.modifier.modify(1000000, 1), 0.00001);
};
}
private static void assertParseFailure(String spec)
{
try
{
Bound bound = parseBound(spec, false);
Assert.fail("expected parse failure, but got " + bound);
}
catch (IllegalArgumentException e)
{
// expected
}
}
@Test
public void strategyParseTest()
{
for (BoundParseValidator min : VALIDATE.stream().filter(v -> v.isMin).toArray(BoundParseValidator[]::new))
{
for (BoundParseValidator max : VALIDATE.stream().filter(v -> !v.isMin).toArray(BoundParseValidator[]::new))
{
for (BoundParseValidator minDelta : VALIDATE.stream().filter(v -> v.isMin).toArray(BoundParseValidator[]::new))
{
for (WaitParseValidator random : VALIDATE_RANDOMIZER)
{
{
ParsedStrategy parsed = parseStrategy("min=" + min.spec + ",max=" + max.spec + ",delta=" + minDelta.spec + ",random=" + random.spec);
Assert.assertEquals(parsed.min, min.spec);
min.validate(parsed.strategy.min);
Assert.assertEquals(parsed.max, max.spec);
max.validate(parsed.strategy.max);
Assert.assertEquals(parsed.minDelta, minDelta.spec);
minDelta.validate(parsed.strategy.minDelta);
Assert.assertEquals(parsed.waitRandomizer, random.spec);
random.validate(parsed.strategy.waitRandomizer);
}
ParsedStrategy parsed = parseStrategy("random=" + random.spec);
Assert.assertEquals(parsed.min, DEFAULT_MIN_VALIDATOR.spec);
DEFAULT_MIN_VALIDATOR.validate(parsed.strategy.min);
Assert.assertEquals(parsed.max, DEFAULT_MAX_VALIDATOR.spec);
DEFAULT_MAX_VALIDATOR.validate(parsed.strategy.max);
Assert.assertEquals(parsed.minDelta, DEFAULT_MIN_DELTA_VALIDATOR.spec);
DEFAULT_MIN_DELTA_VALIDATOR.validate(parsed.strategy.minDelta);
Assert.assertEquals(parsed.waitRandomizer, random.spec);
random.validate(parsed.strategy.waitRandomizer);
}
ParsedStrategy parsed = parseStrategy("delta=" + minDelta.spec);
Assert.assertEquals(parsed.min, DEFAULT_MIN_VALIDATOR.spec);
DEFAULT_MIN_VALIDATOR.validate(parsed.strategy.min);
Assert.assertEquals(parsed.max, DEFAULT_MAX_VALIDATOR.spec);
DEFAULT_MAX_VALIDATOR.validate(parsed.strategy.max);
Assert.assertEquals(parsed.minDelta, minDelta.spec);
minDelta.validate(parsed.strategy.minDelta);
}
ParsedStrategy parsed = parseStrategy("max=" + max.spec);
Assert.assertEquals(parsed.min, DEFAULT_MIN_VALIDATOR.spec);
DEFAULT_MIN_VALIDATOR.validate(parsed.strategy.min);
Assert.assertEquals(parsed.max, max.spec);
max.validate(parsed.strategy.max);
Assert.assertEquals(parsed.minDelta, DEFAULT_MIN_DELTA_VALIDATOR.spec);
DEFAULT_MIN_DELTA_VALIDATOR.validate(parsed.strategy.minDelta);
}
ParsedStrategy parsed = parseStrategy("min=" + min.spec);
Assert.assertEquals(parsed.min, min.spec);
min.validate(parsed.strategy.min);
Assert.assertEquals(parsed.max, DEFAULT_MAX_VALIDATOR.spec);
DEFAULT_MAX_VALIDATOR.validate(parsed.strategy.max);
Assert.assertEquals(parsed.minDelta, DEFAULT_MIN_DELTA_VALIDATOR.spec);
DEFAULT_MIN_DELTA_VALIDATOR.validate(parsed.strategy.minDelta);
}
}
@Test
public void testParseRoundTrip()
{
LatencySelectorFactory selectorFactory = new LatencySelectorFactory()
{
LatencySelectorFactory delegate = ContentionStrategy.selectors;
public LatencySelector constant(long latency) { return selector(delegate.constant(latency), String.format("%dms", latency)); }
public LatencySelector read(double percentile) { return selector(delegate.read(percentile), String.format("p%d(r)", (int) (percentile * 100))); }
public LatencySelector write(double percentile) { return selector(delegate.write(percentile), String.format("p%d(w)", (int) (percentile * 100))); }
public LatencySelector maxReadWrite(double percentile) { return selector(delegate.maxReadWrite(percentile), String.format("p%d(rw)", (int) percentile * 100)); }
private LatencySelector selector(LatencySelector selector, String str) {
return new LatencySelector()
{
public long select(LatencySupplier read, LatencySupplier write)
{
return selector.select(read, write);
}
public String toString()
{
return str;
}
};
}
};
LatencyModifierFactory modifierFactory = new LatencyModifierFactory()
{
LatencyModifierFactory delegate = ContentionStrategy.modifiers;
public LatencyModifier identity() { return modifier(delegate.identity(), ""); }
public LatencyModifier multiply(double constant) { return modifier(delegate.multiply(constant), String.format(" * %.2f", constant)); }
public LatencyModifier multiplyByAttempts(double multiply) { return modifier(delegate.multiplyByAttempts(multiply), String.format(" * %.2f * attempts", multiply)); }
public LatencyModifier multiplyByAttemptsExp(double base) { return modifier(delegate.multiplyByAttemptsExp(base), String.format(" * %.2f ^ attempts", base)); }
private LatencyModifier modifier(LatencyModifier modifier, String str) {
return new LatencyModifier()
{
@Inline
public long modify(long latency, int attempts)
{
return modifier.modify(latency, attempts);
}
public String toString()
{
return str;
}
};
}
};
LatencyModifier[] latencyModifiers = new LatencyModifier[]{
modifierFactory.multiply(0.5),
modifierFactory.multiplyByAttempts(0.5),
modifierFactory.multiplyByAttemptsExp(0.5)
};
LatencySelector[] latencySelectors = new LatencySelector[]{
selectorFactory.read(0.5),
selectorFactory.write(0.5),
selectorFactory.maxReadWrite(0.99)
};
for (boolean min : new boolean[] { true, false})
{
String left = min ? "10ms <= " : "";
for (boolean max : new boolean[] { true, false})
{
String right = max ? " <= 10ms" : "";
for (LatencySelector selector : latencySelectors)
{
for (LatencyModifier modifier : latencyModifiers)
{
String mid = String.format("%s%s", selector, modifier);
String input = left + mid + right;
Bound bound = parseBound(input, false, selectorFactory, modifierFactory);
Assert.assertTrue(String.format("Bound: %d" , bound.min), !min || bound.min == 10000);
Assert.assertTrue(String.format("Bound: %d" , bound.max), !max || bound.max == 10000);
Assert.assertEquals(selector.toString(), bound.selector.toString());
Assert.assertEquals(modifier.toString(), bound.modifier.toString());
}
}
}
}
}
@Test
public void boundParseTest()
{
VALIDATE.forEach(v -> v.validate(parseBound(v.spec, v.isMin)));
}
@Test
public void waitRandomizerParseTest()
{
VALIDATE_RANDOMIZER.forEach(v -> v.validate(parseWaitRandomizer(v.spec)));
}
@Test
public void waitRandomizerSampleTest()
{
waitRandomizerSampleTest(2);
}
private void waitRandomizerSampleTest(int count)
{
while (count-- > 0)
{
long seed = ThreadLocalRandom.current().nextLong();
logger.info("Seed {}", seed);
for (WaitRandomizerType type : WaitRandomizerType.values())
{
WaitRandomizerOutputValidator.validate(type, seed, 100, 1000000);
}
}
}
@Test
public void boundParseFailureTest()
{
assertParseFailure("10ms <= p95(r) <= 5ms");
assertParseFailure("10 <= p95(r)");
assertParseFailure("10 <= 20 <= 30");
assertParseFailure("p95(r) < 5");
assertParseFailure("p95(x)");
assertParseFailure("p95()");
assertParseFailure("p95");
assertParseFailure("p50(rw)+0.66");
}
@Test
public void testBackoffTime()
{
ContentionStrategy strategy = parseStrategy("min=0ms,max=100ms,random=uniform").strategy;
double total = 0;
int count = 100000;
for (int i = 0 ; i < count ; ++i)
{
long now = System.nanoTime();
long waitUntil = strategy.computeWaitUntilForContention(1, null, null, null, null);
long waitLength = Math.max(waitUntil - now, 0);
total += waitLength;
}
Assert.assertTrue(Math.abs(TimeUnit.MILLISECONDS.toNanos(50) - (total / count)) < TimeUnit.MILLISECONDS.toNanos(1L));
}
@Test
public void testBackoffTimeElapsed()
{
ContentionStrategy strategy = parseStrategy("min=0ms,max=10ms,random=uniform").strategy;
double total = 0;
int count = 1000;
for (int i = 0 ; i < count ; ++i)
{
long start = System.nanoTime();
strategy.doWaitForContention(Long.MAX_VALUE, 1, null, null, null, null);
long end = System.nanoTime();
total += end - start;
}
// make sure we have slept at least 4ms on average, given a mean wait time of 5ms
double avg = total / count;
double nanos = avg - TimeUnit.MILLISECONDS.toNanos(4);
Assert.assertTrue(nanos > 0);
}
}