Split accord migration into two phases

Patch by Ariel Weisberg; Reviewed by Benedict Elliott Smith for CASSANDRA-19436
This commit is contained in:
Ariel Weisberg 2024-07-17 14:33:06 -04:00 committed by David Capwell
parent 513509ee2c
commit b29eac16fe
46 changed files with 1544 additions and 993 deletions

View File

@ -517,7 +517,7 @@ public class CQL3CasRequest implements CASRequest
{
// Potentially ignore commit consistency level if TransactionalMode is full
// since it is safe to match what non-SERIAL writes do
commitConsistencyLevel = metadata.params.transactionalMode.commitCLForStrategy(commitConsistencyLevel);
commitConsistencyLevel = metadata.params.transactionalMode.commitCLForStrategy(commitConsistencyLevel, metadata.id, key.getToken());
// CAS requires using the new txn timestamp to correctly linearize some kinds of updates
return new TxnUpdate(createWriteFragments(clientState), createCondition(), commitConsistencyLevel, false);
}

View File

@ -48,7 +48,7 @@ import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.NormalizedRanges;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.Schema;
@ -145,6 +145,7 @@ public class AccordVirtualTables
" transactional_mode text,\n" +
" transactional_migration_from text,\n" +
" migrated_ranges frozen<list<text>>,\n" +
" repair_pending_ranges frozen<list<text>>,\n" +
" migrating_ranges_by_epoch frozen<map<bigint, list<text>>>,\n" +
" PRIMARY KEY (keyspace_name, table_name)" +
')'));
@ -201,9 +202,12 @@ public class AccordVirtualTables
List<String> primitiveMigratedRanges = state.migratedRanges.stream().map(Objects::toString).collect(toImmutableList());
result.column("migrated_ranges", primitiveMigratedRanges);
List<String> primitiveRepairPendingRanges = state.repairPendingRanges.stream().map(Objects::toString).collect(toImmutableList());
result.column("repair_pending_ranges", primitiveRepairPendingRanges);
Map<Long, List<String>> primitiveRangesByEpoch = new LinkedHashMap<>();
for (Map.Entry<org.apache.cassandra.tcm.Epoch, List<Range<Token>>> entry : state.migratingRangesByEpoch.entrySet())
for (Map.Entry<org.apache.cassandra.tcm.Epoch, NormalizedRanges<Token>> entry : state.migratingRangesByEpoch.entrySet())
primitiveRangesByEpoch.put(entry.getKey().getEpoch(), entry.getValue().stream().map(Objects::toString).collect(toImmutableList()));
result.column("migrating_ranges_by_epoch", primitiveRangesByEpoch);

View File

@ -0,0 +1,295 @@
/*
* 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.dht;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.RandomAccess;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import static com.google.common.base.Preconditions.checkState;
/*
* Immutable list of ranges that are statically known to be normalized
*/
public class NormalizedRanges<T extends RingPosition<T>> extends AbstractList<Range<T>> implements List<Range<T>>, RandomAccess
{
private static final NormalizedRanges EMPTY_NORMALIZED_RANGES = new NormalizedRanges(Collections.emptyList());
private static final Comparator NORMALIZED_TOKEN_RANGE_COMPARATOR = (o1, o2) -> {
Range range = (Range) o1;
RingPosition key = (RingPosition) o2;
boolean rangeRightIsMin = range.right.isMinimum();
boolean keyIsMinimum = key.isMinimum();
if (keyIsMinimum & rangeRightIsMin)
return 0;
int lc = key.compareTo(range.left);
int rc = key.compareTo(range.right);
if ((lc < 0 & !keyIsMinimum) | lc == 0) return 1;
if (rc > 0 & !rangeRightIsMin) return -1;
return 0;
};
public static <T extends RingPosition<T>> NormalizedRanges<T> empty()
{
return (NormalizedRanges<T>) EMPTY_NORMALIZED_RANGES;
}
/*
* Compares ranges by right token. Used for intersecting normalized ranges.
*
* Assumes no wrap around ranges except for RHS = minValue which is essentialy synonymous with the maximal value.
* This shows up coming out of unwrap because Range is not left inclusive so the only way to include minValue
* in the range is by wrapping from maxValue.
*/
private static <T extends RingPosition<T>> int compareNormalized(Range<T> lhs, Range<T> rhs)
{
// otherwise compare by right.
int cmp = lhs.right.compareTo(rhs.right);
// minValue on the RHS is maxValue, but doesn't work with compare so check for it explicitly
boolean rhsRMin = rhs.right.isMinimum();
boolean lhsRMin = lhs.right.isMinimum();
if (rhsRMin && lhsRMin)
return 0;
if (cmp < 0)
{
if (lhsRMin)
{
return 1;
}
return -1;
}
else if (cmp > 0)
{
if (rhsRMin)
{
return -1;
}
return 1;
}
return 0;
}
private final Object[] ranges;
private NormalizedRanges(Collection<Range<T>> ranges)
{
this.ranges = new Object[ranges.size()];
int index = 0;
for (Range<T> range : ranges)
this.ranges[index++] = range;
}
public static <T extends RingPosition<T>> NormalizedRanges<T> normalizedRanges(Collection<Range<T>> ranges)
{
if (ranges instanceof NormalizedRanges)
return (NormalizedRanges<T>) ranges;
return new NormalizedRanges<>(Range.normalize(ranges));
}
@Override
public Range<T> get(int index)
{
Objects.checkIndex(index, ranges.length);
return (Range<T>) ranges[index];
}
public boolean intersects(T token)
{
if (this.size() == 1 && this.get(0).isFull())
return true;
boolean isIn = Collections.binarySearch((List) this, token, NORMALIZED_TOKEN_RANGE_COMPARATOR) >= 0;
if (Range.EXPENSIVE_CHECKS)
checkState(Range.isInRanges(token, this) == isIn);
return isIn;
}
public NormalizedRanges<T> subtract(NormalizedRanges<T> b)
{
if (b.size() == 1 && b.get(0).isFull())
return NormalizedRanges.empty();
if (this.size() == 1 && this.get(0).isFull())
return b.invert();
List<Range<T>> remaining = new ArrayList<>();
Iterator<Range<T>> aIter = this.iterator();
Iterator<Range<T>> bIter = b.iterator();
Range<T> aRange = aIter.hasNext() ? aIter.next() : null;
Range<T> bRange = bIter.hasNext() ? bIter.next() : null;
while (aRange != null && bRange != null)
{
boolean aRMin = aRange.right.isMinimum();
boolean bRMin = bRange.right.isMinimum();
if (aRMin && bRMin)
{
if (aRange.left.compareTo(bRange.left) < 0)
remaining.add(new Range<>(aRange.left, bRange.left));
checkState(!aIter.hasNext() && !bIter.hasNext());
aRange = null;
break;
}
if (!aRMin && aRange.right.compareTo(bRange.left) <= 0)
{
remaining.add(aRange);
aRange = aIter.hasNext() ? aIter.next() : null;
}
else if (!bRMin && aRange.left.compareTo(bRange.right) >= 0)
{
bRange = bIter.hasNext() ? bIter.next() : null;
}
else
{
// Handle what remains to the left of the intersection
if (aRange.left.compareTo(bRange.left) < 0)
{
remaining.add(new Range(aRange.left, bRange.left));
}
// Handle what remains to the right of the intersection
if (!aRMin && (aRange.right.compareTo(bRange.right) <= 0 | bRMin))
aRange = aIter.hasNext() ? aIter.next() : null;
else
aRange = new Range(bRange.right, aRange.right);
}
}
while (aRange != null)
{
remaining.add(aRange);
aRange = aIter.hasNext() ? aIter.next() : null;
}
NormalizedRanges<T> result = normalizedRanges(remaining);
if (Range.EXPENSIVE_CHECKS)
checkState(result.equals(Range.normalize(Range.subtract(this, b))));
return result;
}
@VisibleForTesting
public NormalizedRanges<T> invert()
{
if (isEmpty())
return this;
List<Range<T>> result = new ArrayList<>(size() + 2);
T minValue = get(0).left.minValue();
T left = minValue;
for (Range<T> r : this)
{
if (!r.left.equals(left))
{
result.add(new Range<>(left, r.left));
}
left = r.right;
}
// Loop doesn't add the range to the right of the last one
Range<T> last = get(size() - 1);
if (!last.right.isMinimum())
result.add(new Range<>(last.right, minValue));
result = Range.normalize(result);
if (Range.EXPENSIVE_CHECKS)
checkState(result.equals(Range.normalize(Range.subtract(ImmutableList.of(new Range<>(minValue, minValue)), this))));
return new NormalizedRanges<>(result);
}
public NormalizedRanges<T> intersection(NormalizedRanges<T> b)
{
if (this.size() == 1 && this.get(0).isFull())
return b;
if (b.size() == 1 && b.get(0).isFull())
return this;
List<Range<T>> merged = new ArrayList<>();
PeekingIterator<Range<T>> aIter = Iterators.peekingIterator(this.iterator());
PeekingIterator<Range<T>> bIter = Iterators.peekingIterator(b.iterator());
while (aIter.hasNext() && bIter.hasNext())
{
Range<T> aRange = aIter.peek();
Range<T> bRange = bIter.peek();
int cmp = compareNormalized(aRange, bRange);
if (aRange.intersects(bRange))
{
merged.addAll(aRange.intersectionWith(bRange));
if (cmp == 0)
{
aIter.next();
bIter.next();
}
else if (cmp < 0)
{
aIter.next();
}
else
{
bIter.next();
}
}
else
{
if (cmp <= 0)
aIter.next();
if (cmp >= 0)
bIter.next();
}
}
NormalizedRanges<T> result = normalizedRanges(merged);
if (Range.EXPENSIVE_CHECKS)
{
List<Range<T>> expensiveResult = new ArrayList<>();
for (Range<T> r1 : this)
{
for (Range<T> r2 : b)
{
expensiveResult.addAll(r1.intersectionWith(r2));
}
}
checkState(result.equals(Range.normalize(expensiveResult)));
}
return result;
}
@Override
public int size()
{
return ranges.length;
}
}

View File

@ -30,11 +30,7 @@ import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.cassandra.config.DatabaseDescriptor;
@ -48,7 +44,6 @@ import org.apache.cassandra.tcm.serialization.MetadataSerializer;
import org.apache.cassandra.tcm.serialization.Version;
import org.apache.cassandra.utils.Pair;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Collections.emptyList;
import static org.apache.cassandra.config.CassandraRelevantProperties.TEST_RANGE_EXPENSIVE_CHECKS;
@ -395,43 +390,6 @@ public class Range<T extends RingPosition<T>> extends AbstractBounds<T> implemen
return right.compareTo(rhs.right);
}
/*
* Compares ranges by right token. Used for intersecting normalized ranges.
*
* Assumes no wrap around ranges except for RHS = minValue which is essentialy synonymous with the maximal value.
* This shows up coming out of unwrap because Range is not left inclusive so the only way to include minValue
* in the range is by wrapping from maxValue.
*/
private int compareNormalized(Range<T> rhs)
{
// otherwise compare by right.
int cmp = right.compareTo(rhs.right);
// minValue on the RHS is maxValue, but doesn't work with compare so check for it explicitly
boolean rhsRMin = rhs.right.isMinimum();
boolean lhsRMin = right.isMinimum();
if (rhsRMin && lhsRMin)
return 0;
if (cmp < 0)
{
if (lhsRMin)
{
return 1;
}
return -1;
}
else if (cmp > 0)
{
if (rhsRMin)
{
return -1;
}
return 1;
}
return 0;
}
/**
* Subtracts a portion of this range.
* @param contained The range to subtract from this. It must be totally
@ -555,190 +513,11 @@ public class Range<T extends RingPosition<T>> extends AbstractBounds<T> implemen
return false;
}
private static final Comparator NORMALIZED_TOKEN_RANGE_COMPARATOR = (o1, o2) -> {
Range range = (Range)o1;
RingPosition key = (RingPosition) o2;
boolean rangeRightIsMin = range.right.isMinimum();
boolean keyIsMinimum = key.isMinimum();
if (keyIsMinimum & rangeRightIsMin)
return 0;
int lc = key.compareTo(range.left);
int rc = key.compareTo(range.right);
if ((lc < 0 & !keyIsMinimum) | lc == 0) return 1;
if (rc > 0 & !rangeRightIsMin) return -1;
return 0;
};
public static <T extends RingPosition<T>> boolean isInNormalizedRanges(T token, List<Range<T>> ranges)
{
if (ranges.size() == 1 && ranges.get(0).isFull())
return true;
boolean isIn = Collections.binarySearch((List)ranges, token, NORMALIZED_TOKEN_RANGE_COMPARATOR) >= 0;
if (EXPENSIVE_CHECKS)
checkState(isInRanges(token, ranges) == isIn);
return isIn;
}
public static <T extends RingPosition<T>> List<Range<T>> subtractNormalizedRanges(List<Range<T>> a, List<Range<T>> b)
{
if (b.size() == 1 && b.get(0).isFull())
return emptyList();
if (a.size() == 1 && a.get(0).isFull())
return invertNormalizedRanges(b);
List<Range<T>> remaining = new ArrayList<>();
Iterator<Range<T>> aIter = a.iterator();
Iterator<Range<T>> bIter = b.iterator();
Range<T> aRange = aIter.hasNext() ? aIter.next() : null;
Range<T> bRange = bIter.hasNext() ? bIter.next() : null;
while (aRange != null && bRange != null)
{
boolean aRMin = aRange.right.isMinimum();
boolean bRMin = bRange.right.isMinimum();
if (aRMin && bRMin)
{
if (aRange.left.compareTo(bRange.left) < 0)
remaining.add(new Range<>(aRange.left, bRange.left));
checkState(!aIter.hasNext() && !bIter.hasNext());
aRange = null;
break;
}
if (!aRMin && aRange.right.compareTo(bRange.left) <= 0)
{
remaining.add(aRange);
aRange = aIter.hasNext() ? aIter.next() : null;
}
else if (!bRMin && aRange.left.compareTo(bRange.right) >= 0)
{
bRange = bIter.hasNext() ? bIter.next() : null;
}
else
{
// Handle what remains to the left of the intersection
if (aRange.left.compareTo(bRange.left) < 0)
{
remaining.add(new Range(aRange.left, bRange.left));
}
// Handle what remains to the right of the intersection
if (!aRMin && (aRange.right.compareTo(bRange.right) <= 0 | bRMin))
aRange = aIter.hasNext() ? aIter.next() : null;
else
aRange = new Range(bRange.right, aRange.right);
}
}
while (aRange != null)
{
remaining.add(aRange);
aRange = aIter.hasNext() ? aIter.next() : null;
}
List<Range<T>> result = ImmutableList.copyOf(normalize(remaining));
if (EXPENSIVE_CHECKS)
checkState(result.equals(normalize(subtract(a, b))));
return result;
}
private boolean isFull()
public boolean isFull()
{
return isFull(left, right);
}
@VisibleForTesting
static <T extends RingPosition<T>> List<Range<T>> invertNormalizedRanges(List<Range<T>> ranges)
{
if (ranges.isEmpty())
return ranges;
List<Range<T>> result = new ArrayList<>(ranges.size() + 2);
T minValue = ranges.get(0).left.minValue();
T left = minValue;
for (Range<T> r : ranges)
{
if (!r.left.equals(left))
{
result.add(new Range<>(left, r.left));
}
left = r.right;
}
// Loop doesn't add the range to the right of the last one
Range<T> last = ranges.get(ranges.size() - 1);
if (!last.right.isMinimum())
result.add(new Range<>(last.right, minValue));
result = normalize(result);
if (EXPENSIVE_CHECKS)
checkState(result.equals(normalize(subtract(ImmutableList.of(new Range<>(minValue, minValue)), ranges))));
return result;
}
public static <T extends RingPosition<T>> List<Range<T>> intersectionOfNormalizedRanges(List<Range<T>> a, List<Range<T>> b)
{
if (a.size() == 1 && a.get(0).isFull())
return b;
if (b.size() == 1 && b.get(0).isFull())
return a;
List<Range<T>> merged = new ArrayList<>();
PeekingIterator<Range<T>> aIter = Iterators.peekingIterator(a.iterator());
PeekingIterator<Range<T>> bIter = Iterators.peekingIterator(b.iterator());
while (aIter.hasNext() && bIter.hasNext())
{
Range<T> aRange = aIter.peek();
Range<T> bRange = bIter.peek();
int cmp = aRange.compareNormalized(bRange);
if (aRange.intersects(bRange))
{
merged.addAll(aRange.intersectionWith(bRange));
if (cmp == 0)
{
aIter.next();
bIter.next();
}
else if(cmp < 0)
{
aIter.next();
}
else
{
bIter.next();
}
}
else
{
if (cmp <= 0)
aIter.next();
if (cmp >= 0)
bIter.next();
}
}
List<Range<T>> result = ImmutableList.copyOf(normalize(merged));
if (EXPENSIVE_CHECKS)
{
List<Range<T>> expensiveResult = new ArrayList<>();
for (Range<T> r1 : a)
{
for (Range<T> r2 : b)
{
expensiveResult.addAll(r1.intersectionWith(r2));
}
}
checkState(result.equals(normalize(expensiveResult)));
}
return result;
}
@Override
public boolean equals(Object o)
{

View File

@ -75,11 +75,10 @@ public abstract class AbstractRepairTask implements RepairTask
options.isPullRepair(),
options.getPreviewKind(),
options.optimiseStreams(),
options.repairData(),
options.repairPaxos(),
options.paxosOnly(),
options.dontPurgeTombstones(),
options.accordOnly(),
options.isConsensusMigration(),
options.repairAccord(),
executor,
validationScheduler,
cfnames);

View File

@ -39,14 +39,6 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.cassandra.locator.RangesAtEndpoint;
import org.apache.cassandra.net.Verb;
import org.apache.cassandra.repair.messages.FailSession;
import org.apache.cassandra.repair.messages.RepairMessage;
import org.apache.cassandra.repair.state.ParticipateState;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.concurrent.Future;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -66,10 +58,15 @@ import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.RepairException;
import org.apache.cassandra.locator.EndpointsForRange;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.RangesAtEndpoint;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.metrics.StorageMetrics;
import org.apache.cassandra.net.Verb;
import org.apache.cassandra.repair.messages.FailSession;
import org.apache.cassandra.repair.messages.RepairMessage;
import org.apache.cassandra.repair.messages.RepairOption;
import org.apache.cassandra.repair.state.CoordinatorState;
import org.apache.cassandra.repair.state.ParticipateState;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.SystemDistributedKeyspace;
import org.apache.cassandra.schema.TableMetadata;
@ -81,12 +78,14 @@ import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tracing.TraceKeyspace;
import org.apache.cassandra.tracing.TraceState;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.Throwables;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.WrappedRunnable;
import org.apache.cassandra.utils.concurrent.Future;
import org.apache.cassandra.utils.progress.ProgressEvent;
import org.apache.cassandra.utils.progress.ProgressEventNotifier;
import org.apache.cassandra.utils.progress.ProgressEventType;
@ -499,7 +498,7 @@ public class RepairCoordinator implements Runnable, ProgressEventNotifier, Repai
{
task = new PreviewRepairTask(this, state.id, neighborsAndRanges.filterCommonRanges(state.keyspace, cfnames), neighborsAndRanges.shouldExcludeDeadParticipants, cfnames);
}
else if (state.options.isIncremental() && !state.options.isConsensusOnly())
else if (state.options.isIncremental())
{
task = new IncrementalRepairTask(this, state.id, neighborsAndRanges, cfnames);
}

View File

@ -113,10 +113,10 @@ public class RepairJob extends AsyncFuture<RepairResult> implements Runnable
this.state = new JobState(ctx.clock(), desc, session.state.commonRange.endpoints);
TableMetadata metadata = this.cfs.metadata();
if (session.paxosOnly && !metadata.supportsPaxosOperations())
if ((!session.repairData && !session.repairAccord) && !metadata.supportsPaxosOperations())
throw new IllegalArgumentException(String.format("Cannot run paxos only repair on %s.%s, which isn't configured for paxos operations", cfs.keyspace.getName(), cfs.name));
if (session.accordOnly && !metadata.requiresAccordSupport())
if ((!session.repairData && !session.repairPaxos) && !metadata.requiresAccordSupport())
throw new IllegalArgumentException(String.format("Cannot run accord only repair on %s.%s, which isn't configured for accord operations", cfs.keyspace.getName(), cfs.name));
}
@ -157,12 +157,11 @@ public class RepairJob extends AsyncFuture<RepairResult> implements Runnable
Future<Void> paxosRepair;
Epoch repairStartingEpoch = ClusterMetadata.current().epoch;
Preconditions.checkArgument(!session.paxosOnly || !session.accordOnly);
Preconditions.checkArgument(session.repairData || session.repairPaxos || session.repairAccord);
boolean doPaxosRepair = paxosRepairEnabled()
&& (((useV2() || isMetadataKeyspace()) && session.repairPaxos) || session.paxosOnly)
&& metadata.supportsPaxosOperations()
&& !session.accordOnly;
boolean doAccordRepair = metadata.requiresAccordSupport() && !session.paxosOnly;
&& ((useV2() || isMetadataKeyspace()) && session.repairPaxos)
&& metadata.supportsPaxosOperations();
boolean doAccordRepair = metadata.requiresAccordSupport() && session.repairAccord;
if (doPaxosRepair)
{
@ -175,33 +174,25 @@ public class RepairJob extends AsyncFuture<RepairResult> implements Runnable
paxosRepair = ImmediateFuture.success(null);
}
if (session.paxosOnly)
{
paxosRepair.addCallback(new FutureCallback<>()
{
public void onSuccess(Void ignored)
{
logger.info("{} {}.{} paxos repair completed", session.previewKind.logPrefix(session.getId()), desc.keyspace, desc.columnFamily);
trySuccess(new RepairResult(desc, Collections.emptyList(), ConsensusMigrationRepairResult.fromPaxosOnlyRepair(repairStartingEpoch, session.excludedDeadNodes)));
}
public void onFailure(Throwable t)
{
logger.warn("{} {}.{} paxos repair failed", session.previewKind.logPrefix(session.getId()), desc.keyspace, desc.columnFamily);
tryFailure(t);
}
}, taskExecutor);
return;
}
Future<Ranges> accordRepair;
if (doAccordRepair)
{
accordRepair = paxosRepair.flatMap(unused -> {
logger.info("{} {}.{} starting accord repair", session.previewKind.logPrefix(session.getId()), desc.keyspace, desc.columnFamily);
boolean requireAllEndpoints;
// If the session excluded dead nodes it's not eligible for migration and is not supposed to occur at ALL anyways
if (session.excludedDeadNodes)
requireAllEndpoints = false;
else
{
// If the session is doing a data repair (which flushes sstables if not incremental) we can do the barriers at QUORUM
if (session.repairData && !session.isIncremental)
requireAllEndpoints = false;
else
requireAllEndpoints = true;
}
logger.info("{} {}.{} starting accord repair, require all endpoints {}", session.previewKind.logPrefix(session.getId()), desc.keyspace, desc.columnFamily, requireAllEndpoints);
IPartitioner partitioner = metadata.partitioner;
AccordRepair repair = new AccordRepair(ctx, cfs, partitioner, desc.keyspace, desc.ranges, session.isConsensusMigration && session.accordOnly, allEndpoints);
AccordRepair repair = new AccordRepair(ctx, cfs, partitioner, desc.keyspace, desc.ranges, requireAllEndpoints, allEndpoints);
return repair.repair(taskExecutor);
}, taskExecutor);
}
@ -213,78 +204,72 @@ public class RepairJob extends AsyncFuture<RepairResult> implements Runnable
});
}
if (session.accordOnly)
Future<List<SyncStat>> syncResults;
if (session.repairData)
{
accordRepair.addCallback(new FutureCallback<>()
// Create a snapshot at all nodes unless we're using pure parallel repairs
final Future<?> allSnapshotTasks;
if (parallelismDegree != RepairParallelism.PARALLEL)
{
public void onSuccess(Ranges barrieredRanges)
if (session.isIncremental)
{
logger.info("{} {}.{} accord repair completed", session.previewKind.logPrefix(session.getId()), desc.keyspace, desc.columnFamily);
trySuccess(new RepairResult(desc, Collections.emptyList(), ConsensusMigrationRepairResult.fromAccordOnlyRepair(repairStartingEpoch, barrieredRanges, session.excludedDeadNodes)));
// consistent repair does it's own "snapshotting"
allSnapshotTasks = accordRepair.map(input -> allEndpoints);
}
public void onFailure(Throwable t)
else
{
logger.warn("{} {}.{} accord repair failed", session.previewKind.logPrefix(session.getId()), desc.keyspace, desc.columnFamily);
tryFailure(t);
// Request snapshot to all replica
allSnapshotTasks = accordRepair.flatMap(input -> {
List<Future<InetAddressAndPort>> snapshotTasks = new ArrayList<>(allEndpoints.size());
state.phase.snapshotsSubmitted();
for (InetAddressAndPort endpoint : allEndpoints)
{
SnapshotTask snapshotTask = new SnapshotTask(ctx, desc, endpoint);
snapshotTasks.add(snapshotTask);
taskExecutor.execute(snapshotTask);
}
return FutureCombiner.allOf(snapshotTasks).map(a -> {
state.phase.snapshotsCompleted();
return a;
});
});
}
}, taskExecutor);
return;
}
// Create a snapshot at all nodes unless we're using pure parallel repairs
final Future<?> allSnapshotTasks;
if (parallelismDegree != RepairParallelism.PARALLEL)
{
if (session.isIncremental)
{
// consistent repair does it's own "snapshotting"
allSnapshotTasks = accordRepair.map(input -> allEndpoints);
}
else
{
// Request snapshot to all replica
allSnapshotTasks = accordRepair.flatMap(input -> {
List<Future<InetAddressAndPort>> snapshotTasks = new ArrayList<>(allEndpoints.size());
state.phase.snapshotsSubmitted();
for (InetAddressAndPort endpoint : allEndpoints)
{
SnapshotTask snapshotTask = new SnapshotTask(ctx, desc, endpoint);
snapshotTasks.add(snapshotTask);
taskExecutor.execute(snapshotTask);
}
return FutureCombiner.allOf(snapshotTasks).map(a -> {
state.phase.snapshotsCompleted();
return a;
});
});
allSnapshotTasks = null;
}
// Run validations and the creation of sync tasks in the scheduler, so it can limit the number of Merkle trees
// that there are in memory at once. When all validations complete, submit sync tasks out of the scheduler.
syncResults = session.validationScheduler.schedule(() -> createSyncTasks(accordRepair, allSnapshotTasks, allEndpoints), taskExecutor)
.flatMap(this::executeTasks, taskExecutor);
}
else
{
allSnapshotTasks = null;
syncResults = accordRepair.flatMap(unused -> {
logger.info("{} {}.{} not running data repair", session.previewKind.logPrefix(session.getId()), desc.keyspace, desc.columnFamily);
return ImmediateFuture.success(Collections.emptyList());
});
}
// Run validations and the creation of sync tasks in the scheduler, so it can limit the number of Merkle trees
// that there are in memory at once. When all validations complete, submit sync tasks out of the scheduler.
Future<List<SyncStat>> syncResults = session.validationScheduler.schedule(() -> createSyncTasks(accordRepair, allSnapshotTasks, allEndpoints), taskExecutor)
.flatMap(this::executeTasks, taskExecutor);
// When all sync complete, set the final result
syncResults.addCallback(new FutureCallback<>()
{
@Override
public void onSuccess(List<SyncStat> stats)
{
logger.info("{} {}.{} Successfully did repair repairData {}, repairPaxos {}, repairAccord {}, excludedDeadNodes {}", session.previewKind.logPrefix(session.getId()), desc.keyspace, desc.columnFamily, session.repairData, session.repairPaxos, session.repairAccord, session.excludedDeadNodes);
state.phase.success();
if (!session.previewKind.isPreview())
if (!session.previewKind.isPreview() && session.repairData)
{
logger.info("{} {}.{} is fully synced", session.previewKind.logPrefix(session.getId()), desc.keyspace, desc.columnFamily);
SystemDistributedKeyspace.successfulRepairJob(session.getId(), desc.keyspace, desc.columnFamily);
}
cfs.metric.repairsCompleted.inc();
logger.info("Completing repair with excludedDeadNodes {}", session.excludedDeadNodes);
trySuccess(new RepairResult(desc, stats, ConsensusMigrationRepairResult.fromRepair(repairStartingEpoch, getUnchecked(accordRepair), doPaxosRepair, doAccordRepair, session.excludedDeadNodes)));
ConsensusMigrationRepairResult cmrs = ConsensusMigrationRepairResult.fromRepair(repairStartingEpoch, getUnchecked(accordRepair), session.repairData, doPaxosRepair, doAccordRepair, session.excludedDeadNodes);
trySuccess(new RepairResult(desc, stats, cmrs));
}
/**
@ -293,10 +278,11 @@ public class RepairJob extends AsyncFuture<RepairResult> implements Runnable
@Override
public void onFailure(Throwable t)
{
logger.info("{} {}.{} Failed repair repairData {}, repairPaxos {}, repairAccord {}, excludedDeadNodes {}", session.previewKind.logPrefix(session.getId()), desc.keyspace, desc.columnFamily, session.repairData, session.repairPaxos, session.repairAccord, session.excludedDeadNodes);
state.phase.fail(t);
abort(t);
if (!session.previewKind.isPreview())
if (!session.previewKind.isPreview() && session.repairData)
{
logger.warn("{} {}.{} sync failed", session.previewKind.logPrefix(session.getId()), desc.keyspace, desc.columnFamily);
SystemDistributedKeyspace.failedRepairJob(session.getId(), desc.keyspace, desc.columnFamily, t);

View File

@ -120,11 +120,10 @@ public class RepairSession extends AsyncFuture<RepairSessionResult> implements I
/** Range to repair */
public final boolean isIncremental;
public final PreviewKind previewKind;
public final boolean repairData;
public final boolean repairPaxos; // TODO (now): rename to repairPaxosIfSupported
public final boolean paxosOnly;
public final boolean repairAccord;
public final boolean dontPurgeTombstones;
public final boolean accordOnly;
public final boolean isConsensusMigration;
public final boolean excludedDeadNodes;
private final AtomicBoolean isFailed = new AtomicBoolean(false);
@ -153,9 +152,6 @@ public class RepairSession extends AsyncFuture<RepairSessionResult> implements I
* @param parallelismDegree specifies the degree of parallelism when calculating the merkle trees
* @param pullRepair true if the repair should be one way (from remote host to this host and only applicable between two hosts--see RepairOption)
* @param repairPaxos true if incomplete paxos operations should be completed as part of repair
* @param paxosOnly true if we should only complete paxos operations, not run a normal repair
* @param accordOnly true if we should only complete accord operations, not run a normal repair
* @param isConsensusMigration true if this repair is being run by the consensus migration tool (affects accord repair availability requirements)
* @param cfnames names of columnfamilies
*/
public RepairSession(SharedContext ctx,
@ -169,18 +165,17 @@ public class RepairSession extends AsyncFuture<RepairSessionResult> implements I
boolean pullRepair,
PreviewKind previewKind,
boolean optimiseStreams,
boolean repairData,
boolean repairPaxos,
boolean paxosOnly,
boolean dontPurgeTombstones,
boolean accordOnly,
boolean isConsensusMigration,
boolean repairAccord,
String... cfnames)
{
this.ctx = ctx;
this.validationScheduler = validationScheduler;
this.repairData = repairData;
this.repairPaxos = repairPaxos;
this.paxosOnly = paxosOnly;
this.isConsensusMigration = isConsensusMigration;
this.repairAccord = repairAccord;
assert cfnames.length > 0 : "Repairing no column families seems pointless, doesn't it";
this.state = new SessionState(ctx, parentRepairSession, keyspace, cfnames, commonRange);
this.parallelismDegree = parallelismDegree;
@ -190,7 +185,6 @@ public class RepairSession extends AsyncFuture<RepairSessionResult> implements I
this.optimiseStreams = optimiseStreams;
this.dontPurgeTombstones = dontPurgeTombstones;
this.taskExecutor = new SafeExecutor(createExecutor(ctx));
this.accordOnly = accordOnly;
this.excludedDeadNodes = excludedDeadNodes;
}
@ -314,7 +308,7 @@ public class RepairSession extends AsyncFuture<RepairSessionResult> implements I
logger.info("{} parentSessionId = {}: new session: will sync {} on range {} for {}.{}",
previewKind.logPrefix(getId()), state.parentRepairSession, repairedNodes(), state.commonRange, state.keyspace, Arrays.toString(state.cfnames));
Tracing.traceRepair("Syncing range {}", state.commonRange);
if (!previewKind.isPreview() && !paxosOnly && !accordOnly)
if (!previewKind.isPreview() && repairData)
{
SystemDistributedKeyspace.startRepairs(getId(), state.parentRepairSession, state.keyspace, state.cfnames, state.commonRange);
}
@ -468,6 +462,11 @@ public class RepairSession extends AsyncFuture<RepairSessionResult> implements I
}
}
public boolean accordOnly()
{
return repairData && repairAccord && !repairPaxos;
}
private boolean includesTables(Set<TableId> tableIds)
{
Keyspace ks = Keyspace.open(state.keyspace);

View File

@ -26,7 +26,6 @@ import java.util.Set;
import java.util.StringTokenizer;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -35,8 +34,10 @@ import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.locator.MetaStrategy;
import org.apache.cassandra.streaming.PreviewKind;
import org.apache.cassandra.repair.RepairParallelism;
import org.apache.cassandra.streaming.PreviewKind;
import static com.google.common.base.Preconditions.checkArgument;
/**
* Repair options.
@ -58,10 +59,10 @@ public class RepairOption
public static final String PREVIEW = "previewKind";
public static final String OPTIMISE_STREAMS_KEY = "optimiseStreams";
public static final String IGNORE_UNREPLICATED_KS = "ignoreUnreplicatedKeyspaces";
public static final String REPAIR_DATA_KEY = "repairData";
public static final String REPAIR_PAXOS_KEY = "repairPaxos";
public static final String PAXOS_ONLY_KEY = "paxosOnly";
public static final String NO_TOMBSTONE_PURGING = "nopurge";
public static final String ACCORD_ONLY_KEY = "accordOnly";
public static final String REPAIR_ACCORD_KEY = "repairAccord";
// we don't want to push nodes too much for repair
public static final int MAX_JOB_THREADS = 4;
@ -198,19 +199,21 @@ public class RepairOption
boolean force = Boolean.parseBoolean(options.get(FORCE_REPAIR_KEY));
boolean pullRepair = Boolean.parseBoolean(options.get(PULL_REPAIR_KEY));
boolean ignoreUnreplicatedKeyspaces = Boolean.parseBoolean(options.get(IGNORE_UNREPLICATED_KS));
// Default to true because historically it was a default and some tests were written to expect it
boolean repairData = Boolean.parseBoolean(options.getOrDefault(REPAIR_DATA_KEY, "true"));
boolean repairPaxos = Boolean.parseBoolean(options.get(REPAIR_PAXOS_KEY));
boolean paxosOnly = Boolean.parseBoolean(options.get(PAXOS_ONLY_KEY));
boolean dontPurgeTombstones = Boolean.parseBoolean(options.get(NO_TOMBSTONE_PURGING));
boolean accordOnly = Boolean.parseBoolean(options.get(ACCORD_ONLY_KEY));
if (paxosOnly && accordOnly)
throw new IllegalArgumentException("Cannot repair paxos and repair only");
boolean repairAccord = Boolean.parseBoolean(options.get(REPAIR_ACCORD_KEY));
if (repairAccord && !DatabaseDescriptor.getAccordTransactionsEnabled())
{
logger.info("Overriding and disabling Accord repair because Accord is not enabled");
repairAccord = false;
}
if (previewKind != PreviewKind.NONE)
{
Preconditions.checkArgument(!repairPaxos, "repairPaxos must be set to false for preview repairs");
Preconditions.checkArgument(!paxosOnly, "paxosOnly must be set to false for preview repairs");
Preconditions.checkArgument(!accordOnly, "accordOnly must be set to false for preview repairs");
checkArgument(!repairPaxos, "repairPaxos must be set to false for preview repairs");
checkArgument(!repairAccord, "repairAccord must be set to false for preview repairs");
}
int jobThreads = 1;
@ -230,7 +233,7 @@ public class RepairOption
boolean asymmetricSyncing = Boolean.parseBoolean(options.get(OPTIMISE_STREAMS_KEY));
RepairOption option = new RepairOption(parallelism, primaryRange, incremental, trace, jobThreads, ranges, pullRepair, force, previewKind, asymmetricSyncing, ignoreUnreplicatedKeyspaces, repairPaxos, paxosOnly, dontPurgeTombstones, accordOnly, false);
RepairOption option = new RepairOption(parallelism, primaryRange, incremental, trace, jobThreads, ranges, pullRepair, force, previewKind, asymmetricSyncing, ignoreUnreplicatedKeyspaces, repairData, repairPaxos, dontPurgeTombstones, repairAccord);
// data centers
String dataCentersStr = options.get(DATACENTERS_KEY);
@ -309,40 +312,34 @@ public class RepairOption
private final PreviewKind previewKind;
private final boolean optimiseStreams;
private final boolean ignoreUnreplicatedKeyspaces;
private final boolean repairData;
private final boolean repairPaxos;
private final boolean paxosOnly;
private final boolean dontPurgeTombstones;
private final boolean accordOnly;
private final boolean isConsensusMigration;
private final boolean repairAccord;
private final Collection<String> columnFamilies = new HashSet<>();
private final Collection<String> dataCenters = new HashSet<>();
private final Collection<String> hosts = new HashSet<>();
private final Collection<Range<Token>> ranges = new HashSet<>();
public RepairOption(RepairParallelism parallelism, boolean primaryRange, boolean incremental, boolean trace, int jobThreads,
Collection<Range<Token>> ranges, boolean pullRepair, boolean forceRepair,
PreviewKind previewKind, boolean optimiseStreams, boolean ignoreUnreplicatedKeyspaces, boolean repairPaxos,
boolean paxosOnly, boolean dontPurgeTombstones, boolean accordOnly, boolean isConsensusMigration)
public RepairOption(RepairParallelism parallelism, boolean primaryRange, boolean incremental, boolean trace, int jobThreads, Collection<Range<Token>> ranges, boolean pullRepair, boolean forceRepair, PreviewKind previewKind, boolean optimiseStreams, boolean ignoreUnreplicatedKeyspaces, boolean repairData, boolean repairPaxos, boolean dontPurgeTombstones, boolean repairAccord)
{
checkArgument(repairData || repairAccord || repairPaxos, "Repair needs to repair at least one of data, Paxos, or Accord");
this.parallelism = parallelism;
this.primaryRange = primaryRange;
this.incremental = incremental;
this.trace = trace;
this.jobThreads = jobThreads;
this.isConsensusMigration = isConsensusMigration;
this.ranges.addAll(ranges);
this.pullRepair = pullRepair;
this.forceRepair = forceRepair;
this.previewKind = previewKind;
this.optimiseStreams = optimiseStreams;
this.ignoreUnreplicatedKeyspaces = ignoreUnreplicatedKeyspaces;
this.repairData = repairData;
this.repairPaxos = repairPaxos;
this.paxosOnly = paxosOnly;
this.dontPurgeTombstones = dontPurgeTombstones;
this.accordOnly = accordOnly;
this.repairAccord = repairAccord;
}
public RepairParallelism getParallelism()
@ -443,6 +440,11 @@ public class RepairOption
return ignoreUnreplicatedKeyspaces;
}
public boolean repairData()
{
return repairData;
}
public boolean repairPaxos()
{
return repairPaxos;
@ -450,7 +452,12 @@ public class RepairOption
public boolean paxosOnly()
{
return paxosOnly;
return !repairAccord && !repairData && repairPaxos;
}
public boolean repairAccord()
{
return repairAccord;
}
public boolean dontPurgeTombstones()
@ -460,17 +467,7 @@ public class RepairOption
public boolean accordOnly()
{
return accordOnly;
}
public boolean isConsensusOnly()
{
return paxosOnly() || accordOnly();
}
public boolean isConsensusMigration()
{
return isConsensusMigration;
return !repairPaxos && !repairData && repairAccord;
}
@Override
@ -490,10 +487,10 @@ public class RepairOption
", force repair: " + forceRepair +
", optimise streams: "+ optimiseStreams() +
", ignore unreplicated keyspaces: "+ ignoreUnreplicatedKeyspaces +
", repairData: " + repairData +
", repairPaxos: " + repairPaxos +
", paxosOnly: " + paxosOnly +
", dontPurgeTombstones: " + dontPurgeTombstones +
", accordOnly: " + accordOnly +
", repairAccord: " + repairAccord +
')';
}
@ -513,10 +510,10 @@ public class RepairOption
options.put(FORCE_REPAIR_KEY, Boolean.toString(forceRepair));
options.put(PREVIEW, previewKind.toString());
options.put(OPTIMISE_STREAMS_KEY, Boolean.toString(optimiseStreams));
options.put(REPAIR_DATA_KEY, Boolean.toString(repairData));
options.put(REPAIR_PAXOS_KEY, Boolean.toString(repairPaxos));
options.put(PAXOS_ONLY_KEY, Boolean.toString(paxosOnly));
options.put(NO_TOMBSTONE_PURGING, Boolean.toString(dontPurgeTombstones));
options.put(ACCORD_ONLY_KEY, Boolean.toString(accordOnly));
options.put(REPAIR_ACCORD_KEY, Boolean.toString(repairAccord));
return options;
}
}

View File

@ -35,12 +35,12 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.management.openmbean.CompositeData;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.management.openmbean.CompositeData;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
@ -50,7 +50,6 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -131,7 +130,14 @@ import static org.apache.cassandra.config.CassandraRelevantProperties.PAXOS_REPA
import static org.apache.cassandra.config.CassandraRelevantProperties.SKIP_PAXOS_REPAIR_ON_TOPOLOGY_CHANGE;
import static org.apache.cassandra.config.CassandraRelevantProperties.SKIP_PAXOS_REPAIR_ON_TOPOLOGY_CHANGE_KEYSPACES;
import static org.apache.cassandra.config.Config.RepairCommandPoolFullStrategy.reject;
import static org.apache.cassandra.config.DatabaseDescriptor.*;
import static org.apache.cassandra.config.DatabaseDescriptor.getRepairCommandPoolFullStrategy;
import static org.apache.cassandra.config.DatabaseDescriptor.getRepairCommandPoolSize;
import static org.apache.cassandra.config.DatabaseDescriptor.getRepairRetrySpec;
import static org.apache.cassandra.config.DatabaseDescriptor.getRepairRpcTimeout;
import static org.apache.cassandra.config.DatabaseDescriptor.getRepairStateExpires;
import static org.apache.cassandra.config.DatabaseDescriptor.getRepairStateSize;
import static org.apache.cassandra.config.DatabaseDescriptor.getRpcTimeout;
import static org.apache.cassandra.config.DatabaseDescriptor.paxosRepairEnabled;
import static org.apache.cassandra.net.Verb.PREPARE_MSG;
import static org.apache.cassandra.repair.messages.RepairMessage.notDone;
import static org.apache.cassandra.utils.Simulate.With.MONITORS;
@ -455,11 +461,10 @@ public class ActiveRepairService implements IEndpointStateChangeSubscriber, IFai
boolean pullRepair,
PreviewKind previewKind,
boolean optimiseStreams,
boolean repairData,
boolean repairPaxos,
boolean paxosOnly,
boolean dontPurgeTombstones,
boolean accordOnly,
boolean isConsensusMigration,
boolean repairAccord,
ExecutorPlus executor,
Scheduler validationScheduler,
String... cfnames)
@ -476,8 +481,8 @@ public class ActiveRepairService implements IEndpointStateChangeSubscriber, IFai
final RepairSession session = new RepairSession(ctx, validationScheduler, parentRepairSession,
range, excludedDeadNodes, keyspace,
parallelismDegree, isIncremental, pullRepair,
previewKind, optimiseStreams, repairPaxos, paxosOnly,
dontPurgeTombstones, accordOnly, isConsensusMigration, cfnames);
previewKind, optimiseStreams, repairData, repairPaxos,
dontPurgeTombstones, repairAccord, cfnames);
repairs.getIfPresent(parentRepairSession).register(session.state);
sessions.put(session.getId(), session);

View File

@ -41,10 +41,12 @@ import accord.local.Node;
import accord.messages.Callback;
import accord.messages.Commit;
import accord.messages.MessageType;
import accord.messages.ReadData;
import accord.messages.Reply;
import accord.messages.ReplyContext;
import accord.messages.Request;
import accord.messages.TxnRequest;
import accord.primitives.TxnId;
import org.apache.cassandra.config.AccordSpec;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.exceptions.RequestFailureReason;
@ -251,14 +253,19 @@ public class AccordMessageSink implements MessageSink
private static boolean isRangeBarrier(Request request)
{
if (!(request instanceof TxnRequest))
return false;
TxnId txnId = null;
if (request instanceof TxnRequest)
{
TxnRequest<?> txnRequest = (TxnRequest<?>) request;
txnId = txnRequest.txnId;
}
else if (request instanceof ReadData)
{
ReadData epochRequest = (ReadData)request;
txnId = epochRequest.txnId;
}
TxnRequest<?> txnRequest = (TxnRequest<?>) request;
if (!txnRequest.txnId.isSyncPoint())
return false;
return txnRequest.txnId.is(Range);
return txnId != null && txnId.isSyncPoint() && txnId.is(Range);
}
// TODO (expected): permit bulk send to save esp. on callback registration (and combine records)

View File

@ -55,7 +55,7 @@ class AccordResponseVerbHandler<T extends Reply> implements IVerbHandler<T>
@Override
public void doVerb(Message message)
{
if (!((AccordService)AccordService.instance()).shouldAcceptMessages())
if (!AccordService.instance().shouldAcceptMessages())
{
dropping.debug(message.verb(), message.from());
return;

View File

@ -173,7 +173,6 @@ import org.apache.cassandra.utils.ExecutorUtils;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.concurrent.AsyncPromise;
import org.apache.cassandra.utils.concurrent.Future;
import org.apache.cassandra.utils.concurrent.ImmediateFuture;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import static accord.messages.SimpleReply.Ok;
@ -196,8 +195,6 @@ public class AccordService implements IAccordService, Shutdownable
private enum State {INIT, STARTED, SHUTTING_DOWN, SHUTDOWN}
private static final Future<Void> BOOTSTRAP_SUCCESS = ImmediateFuture.success(null);
private final Node node;
private final Shutdownable nodeShutdown;
private final AccordMessageSink messageSink;
@ -213,149 +210,14 @@ public class AccordService implements IAccordService, Shutdownable
@GuardedBy("this")
private State state = State.INIT;
private static final IAccordService NOOP_SERVICE = new IAccordService()
{
@Override
public IVerbHandler<? extends Request> requestHandler()
{
return null;
}
@Override
public IVerbHandler<? extends Reply> responseHandler()
{
return null;
}
@Override
public Seekables<?, ?> barrierWithRetries(Seekables<?, ?> keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException
{
throw new UnsupportedOperationException();
}
@Override
public Seekables<?, ?> barrier(@Nonnull Seekables<?, ?> keysOrRanges, long minEpoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite)
{
throw new UnsupportedOperationException("No accord barriers should be executed when accord.enabled = false in cassandra.yaml");
}
@Override
public Seekables<?, ?> repair(@Nonnull Seekables<?, ?> keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints)
{
throw new UnsupportedOperationException("No accord repairs should be executed when accord.enabled = false in cassandra.yaml");
}
@Override
public @Nonnull TxnResult coordinate(@Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, @Nonnull Dispatcher.RequestTime requestTime)
{
throw new UnsupportedOperationException("No accord transaction should be executed when accord.enabled = false in cassandra.yaml");
}
@Override
public @Nonnull AsyncTxnResult coordinateAsync(@Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
{
throw new UnsupportedOperationException("No accord transaction should be executed when accord.enabled = false in cassandra.yaml");
}
@Override
public TxnResult getTxnResult(AsyncTxnResult asyncTxnResult, boolean isWrite, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
{
throw new UnsupportedOperationException("No accord transaction should be executed when accord.enabled = false in cassandra.yaml");
}
@Override
public long currentEpoch()
{
throw new UnsupportedOperationException("Cannot return epoch when accord.enabled = false in cassandra.yaml");
}
@Override
public void setCacheSize(long kb) { }
@Override
public void setWorkingSetSize(long kb) { }
@Override
public TopologyManager topology()
{
throw new UnsupportedOperationException("Cannot return topology when accord.enabled = false in cassandra.yaml");
}
@Override
public void startup()
{
try
{
AccordTopologySorter.checkSnitchSupported(DatabaseDescriptor.getNodeProximity());
}
catch (Throwable t)
{
logger.warn("Current snitch is not compatable with Accord, make sure to fix the snitch before enabling Accord; {}", t.toString());
}
}
@Override
public void shutdownAndWait(long timeout, TimeUnit unit) { }
@Override
public AccordScheduler scheduler()
{
return null;
}
@Override
public Future<Void> epochReady(Epoch epoch)
{
return BOOTSTRAP_SUCCESS;
}
@Override
public void receive(Message<List<AccordSyncPropagator.Notification>> message) {}
@Override
public CompactionInfo getCompactionInfo()
{
return new CompactionInfo(new Int2ObjectHashMap<>(), new Int2ObjectHashMap<>(), new Int2ObjectHashMap<>());
}
@Override
public AccordAgent agent()
{
return null;
}
@Override
public List<CommandStoreTxnBlockedGraph> debugTxnBlockedGraph(TxnId txnId)
{
return Collections.emptyList();
}
@Nullable
@Override
public Long minEpoch(Collection<TokenRange> ranges)
{
return null;
}
@Override
public void tryMarkRemoved(Topology topology, Id node)
{
}
@Override
public Params journalConfiguration()
{
throw new UnsupportedOperationException("Cannot return configuration when accord.enabled = false in cassandra.yaml");
}
};
private static final IAccordService NOOP_SERVICE = new NoOpAccordService();
private static volatile IAccordService instance = null;
@VisibleForTesting
public static void unsafeSetNewAccordService()
public static void unsafeSetNewAccordService(IAccordService service)
{
instance = null;
instance = service;
}
@VisibleForTesting
@ -411,6 +273,7 @@ public class AccordService implements IAccordService, Shutdownable
i.shutdownAndWait(timeout, unit);
}
@Override
public boolean shouldAcceptMessages()
{
return state == State.STARTED && journal.started();
@ -643,7 +506,6 @@ public class AccordService implements IAccordService, Shutdownable
PartitionKey key = (PartitionKey)keysOrRanges.get(0);
maybeSaveAccordKeyMigrationLocally(key, Epoch.create(txnId.epoch()));
}
((AccordAgent) node.agent()).onSuccessfulBarrier(txnId, keysOrRanges);
logger.debug("Completed barrier attempt in {}ms, {}ms since attempts start, barrier key: {} epoch: {} barrierType: {} isForWrite {}",
sw.elapsed(MILLISECONDS),
NANOSECONDS.toMillis(nanoTime() - requestTime.startedAtNanos()),
@ -658,7 +520,7 @@ public class AccordService implements IAccordService, Shutdownable
TxnId txnId = ((Timeout) cause).txnId();
((AccordAgent) node.agent()).onFailedBarrier(txnId, keysOrRanges, cause);
metrics.timeouts.mark();
throw newBarrierTimeout(txnId, barrierType, isForWrite, keysOrRanges);
throw newBarrierTimeout(((CoordinationFailed)cause).txnId(), barrierType, isForWrite, keysOrRanges);
}
if (cause instanceof Preempted)
{
@ -667,7 +529,7 @@ public class AccordService implements IAccordService, Shutdownable
//TODO need to improve
// Coordinator "could" query the accord state to see whats going on but that doesn't exist yet.
// Protocol also doesn't have a way to denote "unknown" outcome, so using a timeout as the closest match
throw newBarrierPreempted(txnId, barrierType, isForWrite, keysOrRanges);
throw newBarrierPreempted(((CoordinationFailed)cause).txnId(), barrierType, isForWrite, keysOrRanges);
}
if (cause instanceof Exhausted)
{
@ -675,7 +537,7 @@ public class AccordService implements IAccordService, Shutdownable
((AccordAgent) node.agent()).onFailedBarrier(txnId, keysOrRanges, cause);
// this case happens when a non-timeout exception is seen, and we are unable to move forward
metrics.failures.mark();
throw newBarrierExhausted(txnId, barrierType, isForWrite, keysOrRanges);
throw newBarrierExhausted(((CoordinationFailed)cause).txnId(), barrierType, isForWrite, keysOrRanges);
}
// unknown error
metrics.failures.mark();
@ -769,19 +631,19 @@ public class AccordService implements IAccordService, Shutdownable
}
@VisibleForTesting
static ReadTimeoutException newBarrierTimeout(TxnId txnId, BarrierType barrierType, boolean isForWrite, Seekables<?, ?> keysOrRanges)
static ReadTimeoutException newBarrierTimeout(@Nonnull TxnId txnId, BarrierType barrierType, boolean isForWrite, Seekables<?, ?> keysOrRanges)
{
return new ReadTimeoutException(barrierType.global ? ConsistencyLevel.ANY : ConsistencyLevel.QUORUM, 0, 0, false, String.format("Timeout waiting on barrier %s / %s / %s; impacted ranges %s", txnId, barrierType, isForWrite ? "write" : "not write", keysOrRanges));
}
@VisibleForTesting
static ReadTimeoutException newBarrierPreempted(TxnId txnId, BarrierType barrierType, boolean isForWrite, Seekables<?, ?> keysOrRanges)
static ReadTimeoutException newBarrierPreempted(@Nullable TxnId txnId, BarrierType barrierType, boolean isForWrite, Seekables<?, ?> keysOrRanges)
{
return new ReadPreemptedException(barrierType.global ? ConsistencyLevel.ANY : ConsistencyLevel.QUORUM, 0, 0, false, String.format("Preempted waiting on barrier %s / %s / %s; impacted ranges %s", txnId, barrierType, isForWrite ? "write" : "not write", keysOrRanges));
}
@VisibleForTesting
static ReadExhaustedException newBarrierExhausted(TxnId txnId, BarrierType barrierType, boolean isForWrite, Seekables<?, ?> keysOrRanges)
static ReadExhaustedException newBarrierExhausted(@Nullable TxnId txnId, BarrierType barrierType, boolean isForWrite, Seekables<?, ?> keysOrRanges)
{
return new ReadExhaustedException(barrierType.global ? ConsistencyLevel.ANY : ConsistencyLevel.QUORUM, 0, 0, false, String.format("Exhausted (too many failures from peers) waiting on barrier %s / %s / %s; impacted ranges %s", txnId, barrierType, isForWrite ? "write" : "not write", keysOrRanges));
}

View File

@ -46,7 +46,7 @@ public class AccordVerbHandler<T extends Request> implements IVerbHandler<T>
@Override
public void doVerb(Message<T> message) throws IOException
{
if (!((AccordService)AccordService.instance()).shouldAcceptMessages())
if (!AccordService.instance().shouldAcceptMessages())
{
dropping.debug(message.verb(), message.from());
return;

View File

@ -19,6 +19,7 @@
package org.apache.cassandra.service.accord;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -27,6 +28,10 @@ import java.util.function.Supplier;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import accord.api.Agent;
import accord.api.BarrierType;
import accord.local.CommandStores.RangesForEpoch;
import accord.local.DurableBefore;
@ -42,6 +47,7 @@ import accord.primitives.TxnId;
import accord.topology.Topology;
import accord.topology.TopologyManager;
import org.agrona.collections.Int2ObjectHashMap;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.dht.Range;
@ -51,19 +57,26 @@ import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.IVerbHandler;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.accord.api.AccordAgent;
import org.apache.cassandra.service.accord.AccordSyncPropagator.Notification;
import org.apache.cassandra.service.accord.api.AccordScheduler;
import org.apache.cassandra.service.accord.api.AccordTopologySorter;
import org.apache.cassandra.service.accord.txn.TxnResult;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.transport.Dispatcher.RequestTime;
import org.apache.cassandra.utils.concurrent.AsyncPromise;
import org.apache.cassandra.utils.concurrent.Future;
import org.apache.cassandra.utils.concurrent.ImmediateFuture;
import static com.google.common.base.Preconditions.checkNotNull;
// Avoid default methods that aren't just providing wrappers around other methods
// so it will be a compile error if DelegatingAccordService doesn't implement them
public interface IAccordService
{
Logger logger = LoggerFactory.getLogger(IAccordService.class);
EnumSet<ConsistencyLevel> SUPPORTED_COMMIT_CONSISTENCY_LEVELS = EnumSet.of(ConsistencyLevel.ANY, ConsistencyLevel.ONE, ConsistencyLevel.QUORUM, ConsistencyLevel.SERIAL, ConsistencyLevel.ALL);
EnumSet<ConsistencyLevel> SUPPORTED_READ_CONSISTENCY_LEVELS = EnumSet.of(ConsistencyLevel.ONE, ConsistencyLevel.QUORUM, ConsistencyLevel.SERIAL, ConsistencyLevel.ALL);
@ -74,10 +87,7 @@ public interface IAccordService
Seekables<?, ?> barrier(@Nonnull Seekables<?, ?> keysOrRanges, long minEpoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite);
default Seekables<?, ?> repairWithRetries(Seekables<?, ?> keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints) throws InterruptedException
{
throw new UnsupportedOperationException();
}
Seekables<?, ?> repairWithRetries(Seekables<?, ?> keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints) throws InterruptedException;
Seekables<?, ?> repair(@Nonnull Seekables<?, ?> keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints);
@ -154,19 +164,378 @@ public interface IAccordService
*/
CompactionInfo getCompactionInfo();
AccordAgent agent();
Agent agent();
default Id nodeId() { throw new UnsupportedOperationException(); }
Id nodeId();
List<CommandStoreTxnBlockedGraph> debugTxnBlockedGraph(TxnId txnId);
@Nullable
Long minEpoch(Collection<TokenRange> ranges);
void tryMarkRemoved(Topology topology, Node.Id node);
default void awaitTableDrop(TableId id)
{
}
void awaitTableDrop(TableId id);
Params journalConfiguration();
boolean shouldAcceptMessages();
Node node();
// Implementation for the NO_OP service that also has what used to be the default implementations
// that had to be overridden by the real AccordService anyways
class NoOpAccordService implements IAccordService
{
private static final Future<Void> BOOTSTRAP_SUCCESS = ImmediateFuture.success(null);
@Override
public IVerbHandler<? extends Request> requestHandler()
{
return null;
}
@Override
public IVerbHandler<? extends Reply> responseHandler()
{
return null;
}
@Override
public Seekables<?, ?> barrierWithRetries(Seekables<?, ?> keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException
{
throw new UnsupportedOperationException();
}
@Override
public Seekables<?, ?> barrier(@Nonnull Seekables<?, ?> keysOrRanges, long minEpoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite)
{
throw new UnsupportedOperationException("No accord barriers should be executed when accord.enabled = false in cassandra.yaml");
}
@Override
public Seekables<?, ?> repairWithRetries(Seekables<?, ?> keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints) throws InterruptedException
{
return null;
}
@Override
public Seekables<?, ?> repair(@Nonnull Seekables<?, ?> keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints)
{
throw new UnsupportedOperationException("No accord repairs should be executed when accord.enabled = false in cassandra.yaml");
}
@Override
public @Nonnull TxnResult coordinate(@Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, @Nonnull Dispatcher.RequestTime requestTime)
{
throw new UnsupportedOperationException("No accord transaction should be executed when accord.enabled = false in cassandra.yaml");
}
@Override
public @Nonnull AsyncTxnResult coordinateAsync(@Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
{
throw new UnsupportedOperationException("No accord transaction should be executed when accord.enabled = false in cassandra.yaml");
}
@Override
public TxnResult getTxnResult(AsyncTxnResult asyncTxnResult, boolean isWrite, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime)
{
throw new UnsupportedOperationException("No accord transaction should be executed when accord.enabled = false in cassandra.yaml");
}
@Override
public long currentEpoch()
{
throw new UnsupportedOperationException("Cannot return epoch when accord.enabled = false in cassandra.yaml");
}
@Override
public void setCacheSize(long kb) { }
@Override
public void setWorkingSetSize(long kb) {}
@Override
public TopologyManager topology()
{
throw new UnsupportedOperationException("Cannot return topology when accord.enabled = false in cassandra.yaml");
}
@Override
public void startup()
{
try
{
AccordTopologySorter.checkSnitchSupported(DatabaseDescriptor.getNodeProximity());
}
catch (Throwable t)
{
logger.warn("Current snitch is not compatable with Accord, make sure to fix the snitch before enabling Accord; {}", t.toString());
}
}
@Override
public void shutdownAndWait(long timeout, TimeUnit unit) { }
@Override
public AccordScheduler scheduler()
{
return null;
}
@Override
public Future<Void> epochReady(Epoch epoch)
{
return BOOTSTRAP_SUCCESS;
}
@Override
public void receive(Message<List<AccordSyncPropagator.Notification>> message) {}
@Override
public CompactionInfo getCompactionInfo()
{
return new CompactionInfo(new Int2ObjectHashMap<>(), new Int2ObjectHashMap<>(), new Int2ObjectHashMap<>());
}
@Override
public Agent agent()
{
return null;
}
@Override
public Id nodeId()
{
throw new UnsupportedOperationException();
}
@Override
public List<CommandStoreTxnBlockedGraph> debugTxnBlockedGraph(TxnId txnId)
{
return Collections.emptyList();
}
@Nullable
@Override
public Long minEpoch(Collection<TokenRange> ranges)
{
return null;
}
@Override
public void tryMarkRemoved(Topology topology, Id node)
{
}
@Override
public void awaitTableDrop(TableId id)
{
}
@Override
public Params journalConfiguration()
{
throw new UnsupportedOperationException("Cannot return configuration when accord.enabled = false in cassandra.yaml");
}
@Override
public boolean shouldAcceptMessages()
{
return true;
}
@Override
public Node node()
{
return null;
}
}
class DelegatingAccordService implements IAccordService
{
protected final IAccordService delegate;
public DelegatingAccordService(IAccordService delegate)
{
this.delegate = delegate;
}
@Override
public IVerbHandler<? extends Request> requestHandler()
{
return delegate.requestHandler();
}
@Override
public IVerbHandler<? extends Reply> responseHandler()
{
return delegate.responseHandler();
}
@Override
public Seekables<?, ?> barrierWithRetries(Seekables<?, ?> keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException
{
return delegate.barrierWithRetries(keysOrRanges, minEpoch, barrierType, isForWrite);
}
@Override
public Seekables<?, ?> barrier(@Nonnull Seekables<?, ?> keysOrRanges, long minEpoch, RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite)
{
return delegate.barrier(keysOrRanges, minEpoch, requestTime, timeoutNanos, barrierType, isForWrite);
}
@Override
public Seekables<?, ?> repairWithRetries(Seekables<?, ?> keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints) throws InterruptedException
{
return delegate.repairWithRetries(keysOrRanges, minEpoch, barrierType, isForWrite, allEndpoints);
}
@Override
public Seekables<?, ?> repair(@Nonnull Seekables<?, ?> keysOrRanges, long epoch, RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints)
{
return delegate.repair(keysOrRanges, epoch, requestTime, timeoutNanos, barrierType, isForWrite, allEndpoints);
}
@Override
public void postStreamReceivingBarrier(ColumnFamilyStore cfs, List<Range<Token>> ranges)
{
IAccordService.super.postStreamReceivingBarrier(cfs, ranges);
}
@Nonnull
@Override
public TxnResult coordinate(@Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, RequestTime requestTime)
{
return delegate.coordinate(txn, consistencyLevel, requestTime);
}
@Nonnull
@Override
public AsyncTxnResult coordinateAsync(@Nonnull Txn txn, @Nonnull ConsistencyLevel consistencyLevel, RequestTime requestTime)
{
return delegate.coordinateAsync(txn, consistencyLevel, requestTime);
}
@Override
public TxnResult getTxnResult(AsyncTxnResult asyncTxnResult, boolean isWrite, @Nullable ConsistencyLevel consistencyLevel, RequestTime requestTime)
{
return delegate.getTxnResult(asyncTxnResult, isWrite, consistencyLevel, requestTime);
}
@Override
public long currentEpoch()
{
return delegate.currentEpoch();
}
@Override
public void setCacheSize(long kb)
{
delegate.setCacheSize(kb);
}
@Override
public void setWorkingSetSize(long kb)
{
delegate.setWorkingSetSize(kb);
}
@Override
public TopologyManager topology()
{
return delegate.topology();
}
@Override
public void startup()
{
delegate.startup();
}
@Override
public void shutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{
delegate.shutdownAndWait(timeout, unit);
}
@Override
public AccordScheduler scheduler()
{
return delegate.scheduler();
}
@Override
public Future<Void> epochReady(Epoch epoch)
{
return delegate.epochReady(epoch);
}
@Override
public void receive(Message<List<Notification>> message)
{
delegate.receive(message);
}
@Override
public CompactionInfo getCompactionInfo()
{
return delegate.getCompactionInfo();
}
@Override
public Agent agent()
{
return delegate.agent();
}
@Override
public Id nodeId()
{
return delegate.nodeId();
}
@Override
public List<CommandStoreTxnBlockedGraph> debugTxnBlockedGraph(TxnId txnId)
{
return delegate.debugTxnBlockedGraph(txnId);
}
@Nullable
@Override
public Long minEpoch(Collection<TokenRange> ranges)
{
return delegate.minEpoch(ranges);
}
@Override
public void tryMarkRemoved(Topology topology, Id node)
{
delegate.tryMarkRemoved(topology, node);
}
@Override
public void awaitTableDrop(TableId id)
{
delegate.awaitTableDrop(id);
}
@Override
public Params journalConfiguration()
{
return delegate.journalConfiguration();
}
@Override
public boolean shouldAcceptMessages()
{
return delegate.shouldAcceptMessages();
}
@Override
public Node node()
{
return delegate.node();
}
}
}

View File

@ -100,11 +100,6 @@ public class AccordAgent implements Agent
throw error;
}
public void onSuccessfulBarrier(TxnId id, Seekables<?, ?> keysOrRanges)
{
}
public void onFailedBarrier(TxnId id, Seekables<?, ?> keysOrRanges, Throwable cause)
{

View File

@ -217,18 +217,17 @@ public abstract class TxnQuery implements Query
private static boolean transactionIsInMigratingOrMigratedRange(Epoch epoch, Seekables<?, ?> keys)
{
// TODO (required): This is going to be problematic when we presumably support range reads and don't validate them
// Whatever this transaction might be it isn't one supported for migration anyways
if (!keys.domain().isKey())
return false;
if (keys.size() > 1)
// It has to be a transaction statement and we don't support migration with those
return false;
// Could be a transaction statement, but this check does no additional harm
// and transaction statement will generate an error when it sees
// the RetryOnNewProtocolResult
PartitionKey partitionKey = (PartitionKey)keys.get(0);
// TODO (required): This is looking at ClusterMetadata, but not the ClusterMetadata for the specified epoch, just that epoch or later. Need to store ConsensusMigrationState in the global Topologies Accord stores for itself.
return ConsensusRequestRouter.instance.isKeyInMigratingOrMigratedRangeFromAccord(epoch, partitionKey.table(), partitionKey.partitionKey());
for (PartitionKey partitionKey : (Seekables<PartitionKey, ?>)keys)
{
// TODO (required): This is looking at ClusterMetadata, but not the ClusterMetadata for the specified epoch, just that epoch or later. Need to store ConsensusMigrationState in the global Topologies Accord stores for itself.
if (ConsensusRequestRouter.instance.isKeyInMigratingOrMigratedRangeFromAccord(epoch, partitionKey.table(), partitionKey.partitionKey()))
return true;
}
return false;
}
}

View File

@ -19,7 +19,6 @@
package org.apache.cassandra.service.consensus;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.accord.IAccordService;
@ -69,8 +68,11 @@ public enum TransactionalMode
off(false, false, false, false, false),
/*
* Execute writes through Cassandra via StorageProxy's normal write path. This can lead Accord to compute
* multiple outcomes for a transaction that depends on data written by non-SERIAL writes.
* Execute non-SERIAL writes through Cassandra via StorageProxy's normal write path. This can lead Accord to compute
* multiple outcomes for a transaction that depend on data written by non-SERIAL writes.
*
* SERIAL reads and CAS will run on Accord. Accord will honor provided consistency levels and do synchronous commit
* so the results can be read with non-SERIAL CLs.
*/
unsafe(true, false, false, false, false),
@ -95,26 +97,38 @@ public enum TransactionalMode
full(true, true, true, true, true);
public final boolean accordIsEnabled;
public final boolean ignoresSuppleidCommitCL;
public final boolean ignoresSuppliedCommitCL;
public final boolean writesThroughAccord;
public final boolean readsThroughAccord;
public final boolean blockingReadRepairThroughAccord;
private final String cqlParam;
TransactionalMode(boolean accordIsEnabled, boolean ignoresSuppleidCommitCL, boolean writesThroughAccord, boolean readsThroughAccord, boolean blockingReadRepairThroughAccord)
TransactionalMode(boolean accordIsEnabled, boolean ignoresSuppliedCommitCL, boolean writesThroughAccord, boolean readsThroughAccord, boolean blockingReadRepairThroughAccord)
{
this.accordIsEnabled = accordIsEnabled;
this.ignoresSuppleidCommitCL = ignoresSuppleidCommitCL;
this.ignoresSuppliedCommitCL = ignoresSuppliedCommitCL;
this.writesThroughAccord = writesThroughAccord;
this.readsThroughAccord = readsThroughAccord;
this.blockingReadRepairThroughAccord = blockingReadRepairThroughAccord;
this.cqlParam = String.format("transactional_mode = '%s'", LocalizeString.toLowerCaseLocalized(this.name()));
}
public ConsistencyLevel commitCLForStrategy(ConsistencyLevel consistencyLevel)
public ConsistencyLevel commitCLForStrategy(ConsistencyLevel consistencyLevel, TableId tableId, Token token)
{
if (ignoresSuppleidCommitCL)
if (ignoresSuppliedCommitCL)
{
TableMigrationState tms = ClusterMetadata.current().consensusMigrationState.tableStates.get(tableId);
if (tms != null)
{
// Only ignore the supplied consistency level if the token is not migrating
// otherwise honor it since there could still be Paxos and non-SERIAL reads racing with migration.
// Migrating to Accord, Paxos continues reading during the first phase of migration
// Migrating to Paxos, this doesn't really matter since this transaction will get RetryOnDifferentSystemException
if (tms.migratingRanges.intersects(token))
return consistencyLevel;
}
return null;
}
if (!IAccordService.SUPPORTED_COMMIT_CONSISTENCY_LEVELS.contains(consistencyLevel))
throw new UnsupportedOperationException("Consistency level " + consistencyLevel + " is unsupported with Accord for write/commit, supported are ANY, ONE, QUORUM, and ALL");
@ -138,7 +152,7 @@ public enum TransactionalMode
// otherwise honor it because we might read through Accord for non-SERIAL reads before repair is run
// this is OK to do because BRR still works and Accord isn't computing a write so recovery
// determinism isn't an issue
if (tms == null || Range.isInNormalizedRanges(token, tms.migratedRanges))
if (tms == null || tms.migratedRanges.intersects(token))
return null;
}

View File

@ -44,7 +44,6 @@ import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.WriteType;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.exceptions.CasWriteTimeoutException;
import org.apache.cassandra.exceptions.RetryOnDifferentSystemException;
import org.apache.cassandra.io.IVersionedSerializer;
@ -245,7 +244,7 @@ public abstract class ConsensusKeyMigrationState
if (tms == null)
return KeyMigrationState.MIGRATION_NOT_NEEDED;
if (Range.isInNormalizedRanges(key.getToken(), tms.migratingRanges))
if (tms.migratingRanges.intersects(key.getToken()))
{
ConsensusMigratedAt consensusMigratedAt = getConsensusMigratedAt(tableId, key);
if (consensusMigratedAt == null)

View File

@ -65,7 +65,6 @@ import org.apache.cassandra.transport.Dispatcher;
import static com.google.common.base.Preconditions.checkState;
import static java.util.function.Predicate.not;
import static org.apache.cassandra.dht.Range.isInNormalizedRanges;
import static org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter.getTableMetadata;
/**
@ -115,7 +114,7 @@ public class ConsensusMigrationMutationHelper
// commitCLForStrategy should return either null or the supplied consistency level
// in which case we will commit everything at that CL since Accord doesn't support per table
// commit consistency
ConsistencyLevel commitCL = mode.commitCLForStrategy(consistencyLevel);
ConsistencyLevel commitCL = mode.commitCLForStrategy(consistencyLevel, tableId, mutation.key().getToken());
if (commitCL != null)
return commitCL;
}
@ -344,7 +343,7 @@ public class ConsensusMigrationMutationHelper
// Accord needs to do synchronous commit and respect the consistency level so that Accord will later be able to
// read its own writes
if (transactionalModeWritesThroughAccord)
return isInNormalizedRanges(token, tms.migratingAndMigratedRanges);
return tms.migratingAndMigratedRanges.intersects(token);
// If we are migrating from a mode that used to write to Accord then any range that isn't migrating/migrated
// should continue to write through Accord.
@ -355,7 +354,7 @@ public class ConsensusMigrationMutationHelper
// reading through Accord so that repair + Accord metadata is sufficient for Accord to be able to read
// safely and deterministically from any coordinator
if (migrationFromWritesThroughAccord)
return !isInNormalizedRanges(token, tms.migratingAndMigratedRanges);
return !tms.migratingAndMigratedRanges.intersects(token);
}
return false;
}

View File

@ -27,7 +27,7 @@ import static com.google.common.base.Preconditions.checkArgument;
public class ConsensusMigrationRepairResult
{
private static final ConsensusMigrationRepairResult INELIGIBLE = new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.ineligible, Epoch.EMPTY, null);
private static final ConsensusMigrationRepairResult INELIGIBLE = new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.INELIGIBLE, Epoch.EMPTY, null);
public final ConsensusMigrationRepairType type;
public final Epoch minEpoch;
@Nullable
@ -40,24 +40,10 @@ public class ConsensusMigrationRepairResult
this.barrieredRanges = barrieredRanges;
}
public static ConsensusMigrationRepairResult fromRepair(Epoch minEpoch, Ranges barrieredRanges, boolean paxosAndDataRepaired, boolean accordRepaired, boolean deadNodesExcluded)
public static ConsensusMigrationRepairResult fromRepair(Epoch minEpoch, Ranges barrieredRanges, boolean dataRepaired, boolean paxosRepaired, boolean accordRepaired, boolean deadNodesExcluded)
{
checkArgument((!paxosAndDataRepaired && !accordRepaired) || minEpoch.isAfter(Epoch.EMPTY), "Epoch should not be empty if Paxos and regular repairs were performed");
checkArgument(!accordRepaired || minEpoch.isAfter(Epoch.EMPTY), "Epoch should not be empty if Accord repairs was performed");
if (deadNodesExcluded) return INELIGIBLE;
if (paxosAndDataRepaired && accordRepaired) return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.either, minEpoch, barrieredRanges);
if (paxosAndDataRepaired) return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.paxos, minEpoch, barrieredRanges);
if (accordRepaired) return new ConsensusMigrationRepairResult(ConsensusMigrationRepairType.accord, minEpoch, barrieredRanges);
return INELIGIBLE;
}
public static ConsensusMigrationRepairResult fromPaxosOnlyRepair(Epoch minEpoch, boolean deadNodesExcluded)
{
return fromRepair(minEpoch, null, false, false, deadNodesExcluded);
}
public static ConsensusMigrationRepairResult fromAccordOnlyRepair(Epoch minEpoch, Ranges barrieredRanges, boolean deadNodesExcluded)
{
return fromRepair(minEpoch, barrieredRanges, false, true, deadNodesExcluded);
return new ConsensusMigrationRepairResult(new ConsensusMigrationRepairType(dataRepaired, paxosRepaired, accordRepaired), minEpoch, barrieredRanges);
}
}

View File

@ -18,49 +18,49 @@
package org.apache.cassandra.service.consensus.migration;
import com.google.common.primitives.SignedBytes;
import org.apache.cassandra.utils.LocalizeString;
public enum ConsensusMigrationRepairType
public class ConsensusMigrationRepairType
{
ineligible(0, false, false),
paxos(1, false, true),
accord(2, true, false),
either(3, true, true);
public static final ConsensusMigrationRepairType INELIGIBLE = new ConsensusMigrationRepairType(false, false ,false);
public final byte value;
public final boolean repairedData;
public final boolean repairedPaxos;
public final boolean repairedAccord;
public final boolean accordMigrationEligible;
public final boolean paxosMigrationEligible;
ConsensusMigrationRepairType(int value, boolean accordMigrationEligible, boolean paxosMigrationEligible)
public ConsensusMigrationRepairType(boolean repairedData, boolean repairedPaxos, boolean repairedAccord)
{
this.value = SignedBytes.checkedCast(value);
this.accordMigrationEligible = accordMigrationEligible;
this.paxosMigrationEligible = paxosMigrationEligible;
this.repairedData = repairedData;
this.repairedPaxos = repairedPaxos;
this.repairedAccord = repairedAccord;
}
public static ConsensusMigrationRepairType fromString(String repairType)
public boolean migrationToAccordEligible()
{
return ConsensusMigrationRepairType.valueOf(LocalizeString.toLowerCaseLocalized(repairType));
return repairedData;
}
public static ConsensusMigrationRepairType fromValue(byte value)
public boolean migrationToPaxosEligible()
{
switch (value)
{
default:
throw new IllegalArgumentException(value + " is not recognized");
case 0:
return ConsensusMigrationRepairType.ineligible;
case 1:
return ConsensusMigrationRepairType.paxos;
case 2:
return ConsensusMigrationRepairType.accord;
case 3:
return ConsensusMigrationRepairType.either;
}
return repairedAccord;
}
// Require both data and Paxos repair since Paxos only repairs to QUORUM and Accord needs ALL
public boolean repairsPaxos()
{
return repairedData && repairedPaxos;
}
public boolean ineligibleForMigration()
{
return !migrationToAccordEligible() && !migrationToPaxosEligible();
}
@Override
public String toString()
{
return "ConsensusMigrationRepairType{" +
"repairedData=" + repairedData +
", repairedPaxos=" + repairedPaxos +
", repairedAccord=" + repairedAccord +
'}';
}
}

View File

@ -51,6 +51,7 @@ import org.apache.cassandra.utils.PojoToString;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.apache.cassandra.service.consensus.migration.TableMigrationState.initialRepairPendingRanges;
import static org.apache.cassandra.utils.CollectionSerializers.deserializeMap;
import static org.apache.cassandra.utils.CollectionSerializers.newHashMap;
import static org.apache.cassandra.utils.CollectionSerializers.serializeMap;
@ -60,6 +61,7 @@ import static org.apache.cassandra.utils.CollectionSerializers.serializedMapSize
public class ConsensusMigrationState implements MetadataValue<ConsensusMigrationState>
{
public static ConsensusMigrationState EMPTY = new ConsensusMigrationState(Epoch.EMPTY, ImmutableMap.of());
@Nonnull
public final Map<TableId, TableMigrationState> tableStates;
@ -148,7 +150,7 @@ public class ConsensusMigrationState implements MetadataValue<ConsensusMigration
ImmutableMap.of(Epoch.EMPTY, ranges);
if (overwrite)
tableState = new TableMigrationState(metadata.keyspace, metadata.name, metadata.id, target, ImmutableSet.of(), migratingRangesByEpoch);
tableState = new TableMigrationState(metadata.keyspace, metadata.name, metadata.id, target, ImmutableSet.of(), initialRepairPendingRanges(target, ranges), migratingRangesByEpoch);
else
tableState = tableState.withRangesMigrating(ranges, target);
next.put(metadata.id, tableState);
@ -189,10 +191,10 @@ public class ConsensusMigrationState implements MetadataValue<ConsensusMigration
return new ConsensusMigrationState(lastModified, updated.build());
}
public ConsensusMigrationState withRangesRepairedAtEpoch(TableMetadata metadata, List<Range<Token>> ranges, Epoch minEpoch)
public ConsensusMigrationState withRangesRepairedAtEpoch(TableMetadata metadata, List<Range<Token>> ranges, Epoch minEpoch, ConsensusMigrationRepairType repairType)
{
TableMigrationState state = Preconditions.checkNotNull(tableStates.get(metadata.id));
state = state.withRangesRepairedAtEpoch(ranges, minEpoch);
state = state.withRangesRepairedAtEpoch(ranges, minEpoch, repairType);
if (state.hasMigratedFullTokenRange(metadata.partitioner))
{
@ -271,4 +273,13 @@ public class ConsensusMigrationState implements MetadataValue<ConsensusMigration
+ serializedMapSize(t.tableStates, version, TableId.metadataSerializer, TableMigrationState.serializer);
}
};
@Override
public String toString()
{
return "ConsensusMigrationState{" +
"tableStates=" + tableStates +
", lastModified=" + lastModified +
'}';
}
}

View File

@ -37,13 +37,7 @@ public enum ConsensusMigrationTarget
public boolean isMigratedBy(ConsensusMigrationRepairType repairType)
{
switch (repairType)
{
case either: return true;
case paxos: return this == accord;
case accord: return this == paxos;
default: return false;
}
return this == accord ? repairType.migrationToAccordEligible() : repairType.migrationToPaxosEligible();
}
public static ConsensusMigrationTarget fromString(String targetProtocol)

View File

@ -26,7 +26,7 @@ import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.ReplicaLayout;
@ -191,10 +191,11 @@ public class ConsensusRequestRouter
if (tms == null)
return decisionFor(tmd.params.transactionalMigrationFrom.from);
if (Range.isInNormalizedRanges(key.getToken(), tms.migratedRanges))
Token token = key.getToken();
if (tms.migratedRanges.intersects(token))
return pickMigrated(tms.targetProtocol);
if (Range.isInNormalizedRanges(key.getToken(), tms.migratingRanges))
if (tms.migratingRanges.intersects(token))
return pickBasedOnKeyMigrationStatus(cm, tmd, tms, key, consistencyLevel, requestTime, timeoutNanos, isForWrite);
// It's not migrated so infer the protocol from the target
@ -212,8 +213,16 @@ public class ConsensusRequestRouter
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(tmd.id);
if (cfs == null)
throw new InvalidRequestException("Can't route consensus request to nonexistent CFS %s.%s".format(tmd.keyspace, tmd.name));
// Migration to accord has two phases for each range, in the first phase we can't do key migration because Accord
// can't safely read until the range has had its data repaired so Paxos continues to be used for all reads
// and writes
Token token = key.getToken();
if (tms.targetProtocol == ConsensusMigrationTarget.accord && tms.repairPendingRanges.intersects(token))
return pickPaxos();
// If it is locally replicated we can check our local migration state to see if it was already migrated
EndpointsForToken naturalReplicas = ReplicaLayout.forNonLocalStrategyTokenRead(cm, cfs.keyspace.getMetadata(), key.getToken());
EndpointsForToken naturalReplicas = ReplicaLayout.forNonLocalStrategyTokenRead(cm, cfs.keyspace.getMetadata(), token);
boolean isLocallyReplicated = naturalReplicas.lookup(FBUtilities.getBroadcastAddressAndPort()) != null;
if (isLocallyReplicated)
{
@ -232,7 +241,7 @@ public class ConsensusRequestRouter
// barrier transactions to accomplish the migration
// They still might need to go through the fast local path for barrier txns
// at each replica, but they won't create their own txn since we created it here
ConsensusKeyMigrationState.repairKeyAccord(key, tms.tableId, tms.minMigrationEpoch(key.getToken()).getEpoch(), requestTime, true, isForWrite);
ConsensusKeyMigrationState.repairKeyAccord(key, tms.tableId, tms.minMigrationEpoch(token).getEpoch(), requestTime, true, isForWrite);
return paxosV2;
}
// Fall through for repairKeyPaxos
@ -264,7 +273,7 @@ public class ConsensusRequestRouter
/*
* A lightweight check against cluster metadata that doesn't check if the key has already been migrated
* using local system table state
* using local system table state.
*/
public boolean isKeyInMigratingOrMigratedRangeFromPaxos(TableId tableId, DecoratedKey key)
{
@ -273,12 +282,18 @@ public class ConsensusRequestRouter
if (tms == null)
return false;
// We assume that key migration was already performed and it's safe to execute this on Paxos
if (tms.targetProtocol == ConsensusMigrationTarget.paxos)
return false;
Token token = key.getToken();
// Migration from Paxos to Accord has two phases and in the first phase we continue to run Paxos
// until the data has been repaired for the range so that Accord can safely read it after Paxos key migration
if (tms.repairPendingRanges.intersects(token))
return false;
// The coordinator will need to retry either on Accord if they are trying
// to propose their own value, or by setting the consensus migration epoch to recover an incomplete transaction
if (Range.isInNormalizedRanges(key.getToken(), tms.migratingAndMigratedRanges))
if (tms.migratingAndMigratedRanges.intersects(token))
return true;
return false;
@ -307,7 +322,7 @@ public class ConsensusRequestRouter
if (tms.targetProtocol == ConsensusMigrationTarget.accord)
return false;
if (Range.isInNormalizedRanges(key.getToken(), tms.migratingAndMigratedRanges))
if (tms.migratingAndMigratedRanges.intersects(key.getToken()))
return true;
return false;

View File

@ -18,11 +18,13 @@
package org.apache.cassandra.service.consensus.migration;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -36,8 +38,11 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.NormalizedRanges;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.repair.RepairJobDesc;
import org.apache.cassandra.repair.RepairParallelism;
import org.apache.cassandra.repair.RepairResult;
@ -54,6 +59,7 @@ import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.serialization.MetadataSerializer;
import org.apache.cassandra.tcm.serialization.Version;
import org.apache.cassandra.tcm.transformations.BeginConsensusMigrationForTableAndRange;
import org.apache.cassandra.tcm.transformations.MaybeFinishConsensusMigrationForTableAndRange;
@ -63,8 +69,11 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static org.apache.cassandra.dht.NormalizedRanges.normalizedRanges;
import static org.apache.cassandra.dht.Range.normalize;
import static org.apache.cassandra.utils.CollectionSerializers.newListSerializer;
import static org.apache.cassandra.utils.CollectionSerializers.deserializeList;
import static org.apache.cassandra.utils.CollectionSerializers.serializeCollection;
import static org.apache.cassandra.utils.CollectionSerializers.serializedCollectionSize;
/**
* Track and update the migration state of individual table and ranges within those tables
@ -73,7 +82,27 @@ public abstract class ConsensusTableMigration
{
private static final Logger logger = LoggerFactory.getLogger(ConsensusTableMigration.class);
public static final MetadataSerializer<List<Range<Token>>> rangesSerializer = newListSerializer(Range.serializer);
public static final MetadataSerializer<NormalizedRanges<Token>> rangesSerializer = new MetadataSerializer<NormalizedRanges<Token>>()
{
@Override
public void serialize(NormalizedRanges<Token> t, DataOutputPlus out, Version version) throws IOException
{
serializeCollection(t, out, version, Range.serializer);
}
@Override
public NormalizedRanges<Token> deserialize(DataInputPlus in, Version version) throws IOException
{
return normalizedRanges(deserializeList(in, version, Range.serializer));
}
@Override
public long serializedSize(NormalizedRanges<Token> t, Version version)
{
return serializedCollectionSize(t, version, Range.serializer);
}
};
public static final FutureCallback<RepairResult> completedRepairJobHandler = new FutureCallback<RepairResult>()
{
@ -82,10 +111,11 @@ public abstract class ConsensusTableMigration
{
checkNotNull(repairResult, "repairResult should not be null");
ConsensusMigrationRepairResult migrationResult = repairResult.consensusMigrationRepairResult;
ConsensusMigrationRepairType repairType = migrationResult.type;
// Need to repair both Paxos and base table state
// Could track them separately, but doesn't seem worth the effort
if (migrationResult.type == ConsensusMigrationRepairType.ineligible)
if (repairType.ineligibleForMigration())
return;
RepairJobDesc desc = repairResult.desc;
@ -99,25 +129,25 @@ public abstract class ConsensusTableMigration
if (!tms.targetProtocol.isMigratedBy(repairResult.consensusMigrationRepairResult.type))
return;
List<Range<Token>> paxosRepairedRanges = ImmutableList.of();
if (migrationResult.type.paxosMigrationEligible)
NormalizedRanges<Token> paxosRepairedRanges = NormalizedRanges.empty();
if (repairType.migrationToAccordEligible())
// Paxos always repairs all ranges requested by the repair although there should be nothing
// repaired in the migrated and Accord managed ranges
paxosRepairedRanges = ImmutableList.copyOf(desc.ranges);
paxosRepairedRanges = normalizedRanges(desc.ranges);
List<Range<Token>> accordBarrieredRanges = ImmutableList.of();
if (migrationResult.type.accordMigrationEligible)
NormalizedRanges<Token> accordBarrieredRanges = NormalizedRanges.empty();
if (repairType.migrationToPaxosEligible())
// Accord only barriers ranges it thinks it manages and repair collects which it barriered
// precisely which doesn't have to match what the entire repair covers
accordBarrieredRanges = migrationResult.barrieredRanges.stream()
accordBarrieredRanges = normalizedRanges(migrationResult.barrieredRanges.stream()
.map(range -> ((TokenRange)range).toKeyspaceRange())
.collect(toImmutableList());
accordBarrieredRanges = Range.normalize(accordBarrieredRanges);
.collect(toImmutableList()));
accordBarrieredRanges = normalizedRanges(accordBarrieredRanges);
ClusterMetadataService.instance().commit(
new MaybeFinishConsensusMigrationForTableAndRange(
desc.keyspace, desc.columnFamily, paxosRepairedRanges, accordBarrieredRanges,
migrationResult.minEpoch, migrationResult.type));
migrationResult.minEpoch, repairType.repairedData, repairType.repairedPaxos, repairType.repairedAccord));
}
@Override
@ -175,7 +205,7 @@ public abstract class ConsensusTableMigration
IPartitioner partitioner = DatabaseDescriptor.getPartitioner();
Optional<List<Range<Token>>> maybeParsedRanges = maybeRangesStr.map(rangesStr -> ImmutableList.copyOf(RepairOption.parseRanges(rangesStr, partitioner)));
Token minToken = partitioner.getMinimumToken();
List<Range<Token>> ranges = maybeParsedRanges.orElse(ImmutableList.of(new Range(minToken, minToken)));
NormalizedRanges<Token> ranges = normalizedRanges(maybeParsedRanges.orElse(ImmutableList.of(new Range(minToken, minToken))));
ClusterMetadataService.instance().commit(new BeginConsensusMigrationForTableAndRange(targetProtocol, ranges, tableIds));
@ -221,7 +251,12 @@ public abstract class ConsensusTableMigration
{
case accord:
List<TableMigrationState> migratingToAccord = tableMigrationStates.stream().filter(tms -> tms.targetProtocol == ConsensusMigrationTarget.accord).collect(toImmutableList());
return finishMigrationToAccord(keyspace, migratingToAccord, ranges);
Integer accordDataRepairCmd = finishMigrationToAccordDataRepair(keyspace, migratingToAccord, ranges);
// All ranges are already repaired and ready for Paxos repair
// so kick that off instead
if (accordDataRepairCmd == null)
return finishMigrationToAccordPaxosRepair(keyspace, migratingToAccord, ranges);
return accordDataRepairCmd;
case paxos:
List<TableMigrationState> migratingToPaxos = tableMigrationStates.stream().filter(tms -> tms.targetProtocol == ConsensusMigrationTarget.paxos).collect(toImmutableList());;
return finishMigrationToPaxos(keyspace, migratingToPaxos, ranges);
@ -235,16 +270,16 @@ public abstract class ConsensusTableMigration
Integer finish(Collection<TableMigrationState> tables, List<Range<Token>> ranges);
}
private static Integer finishMigrationTo(String name, List<TableMigrationState> tableMigrationStates, List<Range<Token>> requestedRanges, MigrationFinisher migrationFinisher)
private static Integer finishMigrationTo(String name, List<TableMigrationState> tableMigrationStates, List<Range<Token>> requestedRanges, Function<TableMigrationState, List<Range<Token>>> migratingRanges, MigrationFinisher migrationFinisher)
{
logger.info("Begin finish migration to {} for ranges {} and tables {}", name, requestedRanges, tableMigrationStates);
List<Range<Token>> intersectingRanges = new ArrayList<>();
tableMigrationStates.stream().map(TableMigrationState::migratingRanges).forEach(intersectingRanges::addAll);
intersectingRanges = Range.normalize(intersectingRanges);
intersectingRanges = Range.intersectionOfNormalizedRanges(intersectingRanges, requestedRanges);
List<Range<Token>> intersectingRangesList = new ArrayList<>();
tableMigrationStates.stream().map(migratingRanges).forEach(intersectingRangesList::addAll);
NormalizedRanges<Token> intersectingRanges = normalizedRanges(intersectingRangesList);
intersectingRanges = intersectingRanges.intersection(normalizedRanges(requestedRanges));
if (intersectingRanges.isEmpty())
{
logger.warn("No requested ranges {} intersect any migrating ranges in any table in keyspace {}");
logger.warn("No requested ranges {} intersect any migrating ranges in any table for migration: {}", requestedRanges, name);
return null;
}
@ -268,19 +303,39 @@ public abstract class ConsensusTableMigration
*
* Still maybe more valuable to put this layer of abstraction in so we can change how it works later and it's less
* tightly coupled with the Repair interface which is pretty orthogonal to consensus migration.
*
* This first repair is necessary to allow Accord to read data that was written non-serially because we can't do key
* migration for those operations because there is no metadata like we have with Paxos.
*/
private static Integer finishMigrationToAccord(String keyspace, List<TableMigrationState> migratingToAccord, List<Range<Token>> requestedRanges)
private static Integer finishMigrationToAccordDataRepair(String keyspace, List<TableMigrationState> migratingToAccord, List<Range<Token>> requestedRanges)
{
return finishMigrationTo("Accord", migratingToAccord, requestedRanges, (tables, intersectingRanges) -> {
RepairOption repairOption = getRepairOption(tables, intersectingRanges, false);
return finishMigrationTo("Accord Data Repair", migratingToAccord, requestedRanges, TableMigrationState::repairPendingRanges, (tables, intersectingRanges) -> {
RepairOption repairOption = getRepairOption(tables, intersectingRanges, true, false, false);
return StorageService.instance.repair(keyspace, repairOption, emptyList()).left;
});
}
/*
* Need to perform a second repair that is Paxos and then data so that when migrating to FULL mode Accord can read
* the result of any Paxos operation from any replica. This should only be done on the migrating ranges that are no longer pending data repair
*/
private static Integer finishMigrationToAccordPaxosRepair(String keyspace, List<TableMigrationState> migratingToAccord, List<Range<Token>> requestedRanges)
{
return finishMigrationTo("Accord Paxos Repair", migratingToAccord, requestedRanges, tms -> tms.migratingRanges.subtract(tms.repairPendingRanges), (tables, intersectingRanges) -> {
RepairOption repairOption = getRepairOption(tables, intersectingRanges, true, true, false);
return StorageService.instance.repair(keyspace, repairOption, emptyList()).left;
});
}
/*
* Migration back to Paxos is pretty simple since Accord can bring all replicas up to date by running barriers and
* supports key migration immediately without any repair. Paxos doesn't have the same sensitivity to non-deterministic
* data reads.
*/
private static Integer finishMigrationToPaxos(String keyspace, List<TableMigrationState> migratingToPaxos, List<Range<Token>> requestedRanges)
{
return finishMigrationTo("Paxos", migratingToPaxos, requestedRanges, (tables, intersectingRanges) -> {
RepairOption repairOption = getRepairOption(tables, intersectingRanges, true);
return finishMigrationTo("Paxos", migratingToPaxos, requestedRanges, TableMigrationState::migratingRanges, (tables, intersectingRanges) -> {
RepairOption repairOption = getRepairOption(tables, intersectingRanges, false, false, true);
return StorageService.instance.repair(keyspace, repairOption, emptyList()).left;
});
}
@ -318,22 +373,19 @@ public abstract class ConsensusTableMigration
}
@Nonnull
private static RepairOption getRepairOption(Collection<TableMigrationState> tables, List<Range<Token>> intersectingRanges, boolean accordRepair)
private static RepairOption getRepairOption(Collection<TableMigrationState> tables, List<Range<Token>> intersectingRanges, boolean repairData, boolean repairPaxos, boolean repairAccord)
{
boolean primaryRange = false;
// TODO (review): Should disabling incremental repair be exposed for the Paxos repair in case someone explicitly does not do incremental repair?
boolean incremental = !accordRepair;
boolean incremental = repairData;
boolean trace = false;
int numJobThreads = 1;
boolean pullRepair = false;
boolean forceRepair = false;
boolean optimiseStreams = false;
boolean ignoreUnreplicatedKeyspaces = true;
boolean repairPaxos = !accordRepair;
boolean paxosOnly = false;
boolean dontPurgeTombstones = false;
boolean accordOnly = false;
RepairOption repairOption = new RepairOption(RepairParallelism.PARALLEL, primaryRange, incremental, trace, numJobThreads, intersectingRanges, pullRepair, forceRepair, PreviewKind.NONE, optimiseStreams, ignoreUnreplicatedKeyspaces, repairPaxos, paxosOnly, dontPurgeTombstones, accordOnly, true);
RepairOption repairOption = new RepairOption(RepairParallelism.PARALLEL, primaryRange, incremental, trace, numJobThreads, intersectingRanges, pullRepair, forceRepair, PreviewKind.NONE, optimiseStreams, ignoreUnreplicatedKeyspaces, repairData, repairPaxos, dontPurgeTombstones, repairAccord);
tables.forEach(table -> repairOption.getColumnFamilies().add(table.tableName));
return repairOption;
}

View File

@ -19,7 +19,17 @@
package org.apache.cassandra.service.consensus.migration;
import java.io.IOException;
import java.util.*;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -27,14 +37,13 @@ import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.dht.IPartitioner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.NormalizedRanges;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.util.DataInputPlus;
@ -48,10 +57,9 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static org.apache.cassandra.db.TypeSizes.sizeof;
import static org.apache.cassandra.dht.Range.intersectionOfNormalizedRanges;
import static org.apache.cassandra.dht.Range.normalize;
import static org.apache.cassandra.dht.NormalizedRanges.normalizedRanges;
import static org.apache.cassandra.dht.Range.subtract;
import static org.apache.cassandra.dht.Range.subtractNormalizedRanges;
import static org.apache.cassandra.utils.CollectionSerializers.deserializeMap;
import static org.apache.cassandra.utils.CollectionSerializers.deserializeSet;
import static org.apache.cassandra.utils.CollectionSerializers.newHashMap;
@ -77,8 +85,11 @@ public class TableMigrationState
@Nonnull
public final ConsensusMigrationTarget targetProtocol;
/**
* Migrated means that both phases are completed when migrating to Accord. Paxos only has one phase.
*/
@Nonnull
public final List<Range<Token>> migratedRanges;
public final NormalizedRanges<Token> migratedRanges;
/*
* Necessary to track which ranges started migrating at which epoch
@ -86,33 +97,59 @@ public class TableMigrationState
* migration of the range.
*/
@Nonnull
public final NavigableMap<Epoch, List<Range<Token>>> migratingRangesByEpoch;
public final NavigableMap<Epoch, NormalizedRanges<Token>> migratingRangesByEpoch;
/**
* Ranges that are migrating and could be in either phase when migrating to Accord. Paxos only has one phase.
*/
@Nonnull
public final List<Range<Token>> migratingRanges;
public final NormalizedRanges<Token> migratingRanges;
/**
* These are ranges that are migrating and have not been repaired yet when migrating to Accord so Accord can't read from them. These ranges
* should continue to be operated on by Paxos
*
* When migrating to Accord a repair can only move the range to migrated if it is already in repairCompletedRanges
*
* Additionally Paxos continues to operate on a migrating range and key migration is not performed
*
* This is skipped when migrating from Accord to Paxos.
*/
@Nonnull
public final List<Range<Token>> migratingAndMigratedRanges;
public final NormalizedRanges<Token> repairPendingRanges;
/**
* Ranges that are migrating could be in either phase when migrating to Accord. PAxos only has one phase.
*/
@Nonnull
public final NormalizedRanges<Token> migratingAndMigratedRanges;
public TableMigrationState(@Nonnull String keyspaceName,
@Nonnull String tableName,
@Nonnull TableId tableId,
@Nonnull ConsensusMigrationTarget targetProtocol,
@Nonnull Collection<Range<Token>> migratedRanges,
@Nonnull Map<Epoch, List<Range<Token>>> migratingRangesByEpoch)
@Nonnull Collection<Range<Token>> repairPendingRanges,
@Nonnull Map<Epoch, ? extends List<Range<Token>>> migratingRangesByEpoch)
{
this.keyspaceName = keyspaceName;
this.tableName = tableName;
this.tableId = tableId;
this.targetProtocol = targetProtocol;
this.migratedRanges = ImmutableList.copyOf(normalize(migratedRanges));
this.migratedRanges = normalizedRanges(migratedRanges);
this.repairPendingRanges = normalizedRanges(repairPendingRanges);
this.migratingRangesByEpoch = ImmutableSortedMap.copyOf(
migratingRangesByEpoch.entrySet()
.stream()
.map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), ImmutableList.copyOf(normalize(entry.getValue()))))
.map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), normalizedRanges(entry.getValue())))
.collect(Collectors.toList()));
this.migratingRanges = ImmutableList.copyOf(normalize(migratingRangesByEpoch.values().stream().flatMap(Collection::stream).collect(Collectors.toList())));
this.migratingAndMigratedRanges = ImmutableList.copyOf(normalize(ImmutableList.<Range<Token>>builder().addAll(migratedRanges).addAll(migratingRanges).build()));
this.migratingRanges = normalizedRanges(migratingRangesByEpoch.values().stream().flatMap(Collection::stream).collect(Collectors.toList()));
this.migratingAndMigratedRanges = normalizedRanges(ImmutableList.<Range<Token>>builder().addAll(migratedRanges).addAll(migratingRanges).build());
}
static List<Range<Token>> initialRepairPendingRanges(ConsensusMigrationTarget target, List<Range<Token>> initialMigratingRanges)
{
return target == ConsensusMigrationTarget.accord ? ImmutableList.copyOf(initialMigratingRanges) : ImmutableList.of();
}
public TableMigrationState reverseMigration(ConsensusMigrationTarget target, Epoch epoch)
@ -124,6 +161,7 @@ public class TableMigrationState
allTouched = Range.deoverlap(allTouched);
return new TableMigrationState(keyspaceName, tableName, tableId, target,
Range.normalize(fullRange.subtractAll(allTouched)),
initialRepairPendingRanges(target, migratingRanges),
Collections.singletonMap(epoch, migratingRanges));
}
@ -143,6 +181,12 @@ public class TableMigrationState
return migratingRanges;
}
@Nonnull
public List<Range<Token>> repairPendingRanges()
{
return repairPendingRanges;
}
public TableMigrationState withRangesMigrating(@Nonnull Collection<Range<Token>> ranges,
@Nonnull ConsensusMigrationTarget target)
{
@ -167,7 +211,11 @@ public class TableMigrationState
newMigratingRanges.putAll(migratingRangesByEpoch);
newMigratingRanges.put(Epoch.EMPTY, normalizedRanges);
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, migratedRanges, newMigratingRanges);
List<Range<Token>> newRepairPendingRanges = new ArrayList<>(repairPendingRanges);
if (target == ConsensusMigrationTarget.accord)
newRepairPendingRanges.addAll(withoutBoth);
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, migratedRanges, newRepairPendingRanges, newMigratingRanges);
}
public TableMigrationState withReplacementForEmptyEpoch(@Nonnull Epoch replacementEpoch)
@ -183,45 +231,69 @@ public class TableMigrationState
});
if (newMigratingRangesByEpoch != null)
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, migratedRanges, newMigratingRangesByEpoch);
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, migratedRanges, repairPendingRanges, newMigratingRangesByEpoch);
else
return this;
}
public TableMigrationState withRangesRepairedAtEpoch(@Nonnull Collection<Range<Token>> ranges,
@Nonnull Epoch epoch)
@Nonnull Epoch epoch,
@Nonnull ConsensusMigrationRepairType repairType)
{
checkState(!migratingRangesByEpoch.containsKey(Epoch.EMPTY), "Shouldn't have an entry for the empty epoch");
checkArgument(epoch.isAfter(Epoch.EMPTY), "Epoch shouldn't be empty");
List<Range<Token>> normalizedRepairedRanges = normalize(ranges);
NormalizedRanges<Token> normalizedRepairedRanges = normalizedRanges(ranges);
// This should be inclusive because the epoch we store in the map is the epoch in which the range has been marked migrating
// in startMigrationToConsensusProtocol
NavigableMap<Epoch, List<Range<Token>>> coveredEpochs = migratingRangesByEpoch.headMap(epoch, true);
List<Range<Token>> normalizedMigratingRanges = normalize(coveredEpochs.values().stream().flatMap(Collection::stream).collect(Collectors.toList()));
List<Range<Token>> normalizedRepairedIntersection = intersectionOfNormalizedRanges(normalizedRepairedRanges, normalizedMigratingRanges);
NavigableMap<Epoch, NormalizedRanges<Token>> coveredEpochs = migratingRangesByEpoch.headMap(epoch, true);
NormalizedRanges<Token> normalizedMigratingRanges = normalizedRanges(coveredEpochs.values().stream().flatMap(Collection::stream).collect(Collectors.toList()));
// These are the ranges that are impacted by this repair based on the epoch filtering needed to make sure this repair applies to this migration
NormalizedRanges<Token> normalizedRepairedIntersection = normalizedRepairedRanges.intersection(normalizedMigratingRanges);
checkState(!normalizedRepairedIntersection.isEmpty(), "None of Ranges " + ranges + " were being migrated");
Map<Epoch, List<Range<Token>>> newMigratingRangesByEpoch = new HashMap<>();
// Any ranges that were repair pending can't actually be fully migrated yet, they will be subtracted from repairPendingRanges after
// the new migratingRangesByEpoch and migratedRanges are constructed
NormalizedRanges<Token> actuallyMigratedRanges = normalizedRepairedIntersection.subtract(repairPendingRanges);
// Everything in this epoch or later can't have been migrated so re-add all of them
newMigratingRangesByEpoch.putAll(migratingRangesByEpoch.tailMap(epoch, false));
List<Range<Token>> newMigratedRanges = migratedRanges;
Map<Epoch, NormalizedRanges<Token>> newMigratingRangesByEpoch = migratingRangesByEpoch;
// Include anything still remaining to be migrated after subtracting what was repaired
for (Map.Entry<Epoch, List<Range<Token>>> e : coveredEpochs.entrySet())
// Not all repairs are capable of completing the migration to a given target
if ((targetProtocol == ConsensusMigrationTarget.accord && repairType.repairsPaxos())
|| (targetProtocol == ConsensusMigrationTarget.paxos && repairType.repairedAccord))
{
// Epoch when these ranges started migrating
Epoch rangesEpoch = e.getKey();
List<Range<Token>> epochMigratingRanges = e.getValue();
List<Range<Token>> remainingRanges = subtractNormalizedRanges(epochMigratingRanges, normalizedRepairedIntersection);
if (!remainingRanges.isEmpty())
newMigratingRangesByEpoch.put(rangesEpoch, remainingRanges);
newMigratingRangesByEpoch = new HashMap<>();
// Everything in this epoch or later can't have been migrated so re-add all of them
newMigratingRangesByEpoch.putAll(migratingRangesByEpoch.tailMap(epoch, false));
// Include anything still remaining to be migrated after subtracting what was repaired (and not excluded to due repairPendingRanges)
for (Map.Entry<Epoch, NormalizedRanges<Token>> e : coveredEpochs.entrySet())
{
// Epoch when these ranges started migrating
Epoch rangesEpoch = e.getKey();
NormalizedRanges<Token> epochMigratingRanges = e.getValue();
NormalizedRanges<Token> remainingRanges = epochMigratingRanges.subtract(actuallyMigratedRanges);
if (!remainingRanges.isEmpty())
newMigratingRangesByEpoch.put(rangesEpoch, remainingRanges);
}
newMigratedRanges = new ArrayList<>(normalizedMigratingRanges.size() + ranges.size());
newMigratedRanges.addAll(migratedRanges);
newMigratedRanges.addAll(actuallyMigratedRanges);
}
List<Range<Token>> newMigratedRanges = new ArrayList<>(normalizedMigratingRanges.size() + ranges.size());
newMigratedRanges.addAll(migratedRanges);
newMigratedRanges.addAll(normalizedRepairedIntersection);
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, newMigratedRanges, newMigratingRangesByEpoch);
// After this repair any ranges in normalizedRepairedIntersection is repaired and no longer repair pending
// Accord can safely read from them if this is a migration to Accord (after Paxos key migration)
List<Range<Token>> newRepairPendingRanges = repairPendingRanges;
if (repairType.repairedData)
{
List<Range<Token>> repairedRangesSatisfyingEpoch = new ArrayList<>();
for (Map.Entry<Epoch, NormalizedRanges<Token>> e : coveredEpochs.entrySet())
repairedRangesSatisfyingEpoch.addAll(normalizedRepairedRanges.intersection(e.getValue()));
newRepairPendingRanges = repairPendingRanges.subtract(normalizedRanges(repairedRangesSatisfyingEpoch));
}
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, newMigratedRanges, newRepairPendingRanges, newMigratingRangesByEpoch);
}
public boolean paxosReadSatisfiedByKeyMigrationAtEpoch(DecoratedKey key, ConsensusMigratedAt consensusMigratedAt)
@ -240,7 +312,7 @@ public class TableMigrationState
if (consensusMigratedAt == null)
{
// It hasn't been migrated and needs migration if it is in a migrating range
return Range.isInNormalizedRanges(key.getToken(), migratingRanges);
return migratingRanges.intersects(key.getToken());
}
else
{
@ -256,9 +328,9 @@ public class TableMigrationState
public Epoch minMigrationEpoch(Token token)
{
// TODO should there be an index to make this more efficient?
for (Map.Entry<Epoch, List<Range<Token>>> e : migratingRangesByEpoch.entrySet())
for (Map.Entry<Epoch, NormalizedRanges<Token>> e : migratingRangesByEpoch.entrySet())
{
if (Range.isInNormalizedRanges(token, e.getValue()))
if (e.getValue().intersects(token))
return e.getKey();
}
return Epoch.EMPTY;
@ -270,25 +342,6 @@ public class TableMigrationState
return tableId;
}
public TableMigrationState withMigrationTarget(ConsensusMigrationTarget newTargetProtocol)
{
checkState(!migratingRangesByEpoch.containsKey(Epoch.EMPTY), "Shouldn't have an entry for the empty epoch");
if (targetProtocol == newTargetProtocol)
return this;
// Migrating ranges remain migrating because individual keys may have already been migrated
// So for correctness we need to perform key migration
// We do need to update the epoch so that a new repair is required to drive the migration
Map<Epoch, List<Range<Token>>> migratingRangesByEpoch = ImmutableMap.of(Epoch.EMPTY, migratingRanges);
Token minToken = ColumnFamilyStore.getIfExists(tableId).getPartitioner().getMinimumToken();
Range<Token> fullRange = new Range(minToken, minToken);
// What is migrated already is anything that was never migrated/migrating before (untouched)
List<Range<Token>> migratedRanges = ImmutableList.copyOf(normalize(fullRange.subtractAll(migratingAndMigratedRanges)));
return new TableMigrationState(keyspaceName, tableName, tableId, newTargetProtocol, migratedRanges, migratingRangesByEpoch);
}
public Map<String, Object> toMap()
{
ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
@ -298,11 +351,12 @@ public class TableMigrationState
builder.put("targetProtocol", targetProtocol.toString());
builder.put("migratedRanges", migratedRanges.stream().map(Objects::toString).collect(toImmutableList()));
Map<Long, List<String>> rangesByEpoch = new LinkedHashMap<>();
for (Map.Entry<Epoch, List<Range<Token>>> entry : migratingRangesByEpoch.entrySet())
for (Map.Entry<Epoch, NormalizedRanges<Token>> entry : migratingRangesByEpoch.entrySet())
{
rangesByEpoch.put(entry.getKey().getEpoch(), entry.getValue().stream().map(Objects::toString).collect(toImmutableList()));
}
builder.put("migratingRangesByEpoch", rangesByEpoch);
builder.put("repairPendingRanges", repairPendingRanges.stream().map(Objects::toString).collect(toImmutableList()));
return builder.build();
}
@ -331,6 +385,7 @@ public class TableMigrationState
out.writeUTF(t.tableName);
t.tableId.serialize(out);
serializeCollection(t.migratedRanges, out, version, Range.serializer);
serializeCollection(t.repairPendingRanges, out, version, Range.serializer);
serializeMap(t.migratingRangesByEpoch, out, version, Epoch.serializer, ConsensusTableMigration.rangesSerializer);
}
@ -342,8 +397,9 @@ public class TableMigrationState
String tableName = in.readUTF();
TableId tableId = TableId.deserialize(in);
Set<Range<Token>> migratedRanges = deserializeSet(in, version, Range.serializer);
Map<Epoch, List<Range<Token>>> migratingRangesByEpoch = deserializeMap(in, version, Epoch.serializer, ConsensusTableMigration.rangesSerializer, newHashMap());
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, migratedRanges, migratingRangesByEpoch);
Set<Range<Token>> repairPendingRanges = deserializeSet(in, version, Range.serializer);
Map<Epoch, NormalizedRanges<Token>> migratingRangesByEpoch = deserializeMap(in, version, Epoch.serializer, ConsensusTableMigration.rangesSerializer, newHashMap());
return new TableMigrationState(keyspaceName, tableName, tableId, targetProtocol, migratedRanges, repairPendingRanges, migratingRangesByEpoch);
}
@Override
@ -354,7 +410,24 @@ public class TableMigrationState
+ sizeof(t.tableName)
+ t.tableId.serializedSize()
+ serializedCollectionSize(t.migratedRanges, version, Range.serializer)
+ serializedCollectionSize(t.repairPendingRanges, version, Range.serializer)
+ serializedMapSize(t.migratingRangesByEpoch, version, Epoch.serializer, ConsensusTableMigration.rangesSerializer);
}
};
@Override
public String toString()
{
return "TableMigrationState{" +
"keyspaceName='" + keyspaceName + '\'' +
", tableName='" + tableName + '\'' +
", tableId=" + tableId +
", targetProtocol=" + targetProtocol +
", migratedRanges=" + migratedRanges +
", migratingRangesByEpoch=" + migratingRangesByEpoch +
", migratingRanges=" + migratingRanges +
", repairPendingRanges=" + repairPendingRanges +
", migratingAndMigratedRanges=" + migratingAndMigratedRanges +
'}';
}
}

View File

@ -37,21 +37,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.codahale.metrics.Meter;
import org.apache.cassandra.exceptions.CasWriteTimeoutException;
import org.apache.cassandra.exceptions.ExceptionCode;
import org.apache.cassandra.gms.FailureDetector;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.InOurDc;
import org.apache.cassandra.locator.MetaStrategy;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaLayout;
import org.apache.cassandra.locator.ReplicaLayout.ForTokenWrite;
import org.apache.cassandra.locator.ReplicaPlan.ForRead;
import org.apache.cassandra.metrics.ClientRequestSizeMetrics;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ConsistencyLevel;
@ -65,6 +50,8 @@ import org.apache.cassandra.db.partitions.PartitionIterators;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.rows.RowIterator;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.CasWriteTimeoutException;
import org.apache.cassandra.exceptions.ExceptionCode;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.IsBootstrappingException;
import org.apache.cassandra.exceptions.ReadFailureException;
@ -78,25 +65,38 @@ import org.apache.cassandra.exceptions.UnavailableException;
import org.apache.cassandra.exceptions.WriteFailureException;
import org.apache.cassandra.exceptions.WriteTimeoutException;
import org.apache.cassandra.gms.EndpointState;
import org.apache.cassandra.gms.FailureDetector;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.InOurDc;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.MetaStrategy;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaLayout;
import org.apache.cassandra.locator.ReplicaLayout.ForTokenWrite;
import org.apache.cassandra.locator.ReplicaPlan.ForRead;
import org.apache.cassandra.metrics.ClientRequestMetrics;
import org.apache.cassandra.metrics.ClientRequestSizeMetrics;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.CASRequest;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.FailureRecordingCallback.AsMap;
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter;
import org.apache.cassandra.service.paxos.Commit.Proposal;
import org.apache.cassandra.service.paxos.cleanup.PaxosRepairState;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.service.paxos.PaxosPrepare.FoundIncompleteAccepted;
import org.apache.cassandra.service.paxos.PaxosPrepare.FoundIncompleteCommitted;
import org.apache.cassandra.service.paxos.PaxosPropose.Superseded;
import org.apache.cassandra.service.paxos.cleanup.PaxosRepairState;
import org.apache.cassandra.service.reads.DataResolver;
import org.apache.cassandra.service.reads.ReadCoordinator;
import org.apache.cassandra.service.reads.repair.NoopReadRepair;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.membership.NodeId;
import org.apache.cassandra.tcm.ownership.DataPlacement;
@ -142,6 +142,7 @@ import static org.apache.cassandra.service.paxos.ContentionStrategy.Type.WRITE;
import static org.apache.cassandra.service.paxos.ContentionStrategy.waitForContention;
import static org.apache.cassandra.service.paxos.PaxosCommit.commit;
import static org.apache.cassandra.service.paxos.PaxosCommitAndPrepare.commitAndPrepare;
import static org.apache.cassandra.service.paxos.PaxosPrepare.Status.Outcome.MAYBE_FAILURE;
import static org.apache.cassandra.service.paxos.PaxosPrepare.prepare;
import static org.apache.cassandra.service.paxos.PaxosPropose.propose;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
@ -1069,7 +1070,7 @@ public class Paxos
// After performing the prepare phase we may discover that we can't propose
// our own transaction on this protocol by discovering a new CM Epoch
if (ConsensusRequestRouter.instance.isKeyInMigratingOrMigratedRangeDuringPaxosBegin(query.metadata().id, query.partitionKey()))
if (ConsensusRequestRouter.instance.isKeyInMigratingOrMigratedRangeDuringPaxosBegin(query.metadata().id, query.partitionKey()) && prepare.outcome != MAYBE_FAILURE)
{
return BeginResult.retryOnNewProtocol();
}

View File

@ -25,7 +25,7 @@ import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.NormalizedRanges;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
@ -55,13 +55,13 @@ public class BeginConsensusMigrationForTableAndRange implements Transformation
public final ConsensusMigrationTarget targetProtocol;
@Nonnull
public final List<Range<Token>> ranges;
public final NormalizedRanges<Token> ranges;
@Nonnull
public final List<TableId> tables;
public BeginConsensusMigrationForTableAndRange(@Nonnull ConsensusMigrationTarget targetProtocol,
@Nonnull List<Range<Token>> ranges,
@Nonnull NormalizedRanges<Token> ranges,
@Nonnull List<TableId> tables)
{
checkNotNull(targetProtocol, "targetProtocol should not be null");
@ -101,7 +101,7 @@ public class BeginConsensusMigrationForTableAndRange implements Transformation
public BeginConsensusMigrationForTableAndRange deserialize(DataInputPlus in, Version version) throws IOException
{
ConsensusMigrationTarget targetProtocol = ConsensusMigrationTarget.fromString(in.readUTF());
List<Range<Token>> ranges = ConsensusTableMigration.rangesSerializer.deserialize(in, version);
NormalizedRanges<Token> ranges = ConsensusTableMigration.rangesSerializer.deserialize(in, version);
List<TableId> tables = deserializeList(in, version, TableId.metadataSerializer);
return new BeginConsensusMigrationForTableAndRange(targetProtocol, ranges, tables);
}

View File

@ -26,6 +26,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.dht.NormalizedRanges;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.util.DataInputPlus;
@ -71,10 +72,10 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma
public final String cf;
@Nonnull
public final List<Range<Token>> paxosRepairedRanges;
public final NormalizedRanges<Token> paxosRepairedRanges;
@Nonnull
public final List<Range<Token>> accordBarrieredRanges;
public final NormalizedRanges<Token> accordBarrieredRanges;
@Nonnull
public final Epoch minEpoch;
@ -84,10 +85,12 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma
public MaybeFinishConsensusMigrationForTableAndRange(@Nonnull String keyspace,
@Nonnull String cf,
@Nonnull List<Range<Token>> paxosRepairedRanges,
@Nonnull List<Range<Token>> accordBarrieredRanges,
@Nonnull NormalizedRanges<Token> paxosRepairedRanges,
@Nonnull NormalizedRanges<Token> accordBarrieredRanges,
@Nonnull Epoch minEpoch,
@Nonnull ConsensusMigrationRepairType repairType)
boolean repairedData,
boolean repairedPaxos,
boolean repairedAccord)
{
checkNotNull(keyspace, "keyspace should not be null");
checkNotNull(cf, "cf should not be null");
@ -95,8 +98,9 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma
checkNotNull(accordBarrieredRanges, "accordBarrierRanges should not be null");
checkNotNull(minEpoch, "minEpoch should not be null");
checkArgument(minEpoch.isAfter(Epoch.EMPTY), "minEpoch should not be empty");
ConsensusMigrationRepairType repairType = new ConsensusMigrationRepairType(repairedData, repairedPaxos, repairedAccord);
checkNotNull(repairType, "repairType is null");
checkArgument(repairType != ConsensusMigrationRepairType.ineligible, "Shouldn't attempt to finish migration with ineligible repair");
checkArgument(!repairType.ineligibleForMigration(), "Shouldn't attempt to finish migration with ineligible repair");
this.keyspace = keyspace;
this.cf = cf;
this.paxosRepairedRanges = paxosRepairedRanges;
@ -130,7 +134,7 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma
public Result execute(@Nonnull ClusterMetadata metadata)
{
logger.info("Completed repair eligibiliy '{}' paxos repaired ranges {}, accord repaired ranges {}", repairType, paxosRepairedRanges, accordBarrieredRanges);
logger.info("Completed repair eligibility '{}' paxos repaired ranges {}, accord repaired ranges {}", repairType, paxosRepairedRanges, accordBarrieredRanges);
checkNotNull(metadata, "clusterMetadata should not be null");
String ksAndCF = keyspace + "." + cf;
TableMetadata tbm = metadata.schema.getTableMetadata(keyspace, cf);
@ -146,9 +150,9 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma
return new Rejected(INVALID, format("Table %s has a target protocol of %s and is the repair type %s is not eligible/needed to progress the migration", ksAndCF, tms.targetProtocol, repairType));
List<Range<Token>> repairedRanges;
if (tms.targetProtocol == accord)
if (tms.targetProtocol == accord && (repairType.repairedPaxos || repairType.repairedData))
repairedRanges = paxosRepairedRanges;
else if (tms.targetProtocol == paxos)
else if (tms.targetProtocol == paxos && repairType.repairedAccord)
repairedRanges = accordBarrieredRanges;
else
throw new IllegalStateException("Unhandled migration target " + tms.targetProtocol);
@ -159,9 +163,7 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma
return new Rejected(INVALID, format("Table %s is migrating ranges %s, which doesn't include repaired ranges %s", ksAndCF, tms.migratingRanges, normalizedRepairedRanges));
Transformer next = metadata.transformer();
ConsensusMigrationState migrationState = metadata.consensusMigrationState.withRangesRepairedAtEpoch(tbm, normalizedRepairedRanges, minEpoch);
logger.debug("Original migration state {}");
logger.debug("New migration state {}");
ConsensusMigrationState migrationState = metadata.consensusMigrationState.withRangesRepairedAtEpoch(tbm, normalizedRepairedRanges, minEpoch, repairType);
next = next.with(migrationState);
// reset the migration value on the table if the migration has completed
@ -182,18 +184,22 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma
ConsensusTableMigration.rangesSerializer.serialize(v.paxosRepairedRanges, out, version);
ConsensusTableMigration.rangesSerializer.serialize(v.accordBarrieredRanges, out, version);
Epoch.serializer.serialize(v.minEpoch, out, version);
out.write(v.repairType.value);
out.writeBoolean(v.repairType.repairedData);
out.writeBoolean(v.repairType.repairedPaxos);
out.writeBoolean(v.repairType.repairedAccord);
}
public MaybeFinishConsensusMigrationForTableAndRange deserialize(DataInputPlus in, Version version) throws IOException
{
String keyspace = in.readUTF();
String cf = in.readUTF();
List<Range<Token>> paxosRepairedRanges = ConsensusTableMigration.rangesSerializer.deserialize(in, version);
List<Range<Token>> accordBarrieredRanges = ConsensusTableMigration.rangesSerializer.deserialize(in, version);
NormalizedRanges<Token> paxosRepairedRanges = ConsensusTableMigration.rangesSerializer.deserialize(in, version);
NormalizedRanges<Token> accordBarrieredRanges = ConsensusTableMigration.rangesSerializer.deserialize(in, version);
Epoch minEpoch = Epoch.serializer.deserialize(in, version);
ConsensusMigrationRepairType repairType = ConsensusMigrationRepairType.fromValue(in.readByte());
return new MaybeFinishConsensusMigrationForTableAndRange(keyspace, cf, paxosRepairedRanges, accordBarrieredRanges, minEpoch, repairType);
boolean repairedData = in.readBoolean();
boolean repairedPaxos = in.readBoolean();
boolean repairedAccord = in.readBoolean();
return new MaybeFinishConsensusMigrationForTableAndRange(keyspace, cf, paxosRepairedRanges, accordBarrieredRanges, minEpoch, repairedData, repairedPaxos, repairedAccord);
}
public long serializedSize(Transformation t, Version version)
@ -204,7 +210,9 @@ public class MaybeFinishConsensusMigrationForTableAndRange implements Transforma
+ ConsensusTableMigration.rangesSerializer.serializedSize(v.paxosRepairedRanges, version)
+ ConsensusTableMigration.rangesSerializer.serializedSize(v.accordBarrieredRanges, version)
+ Epoch.serializer.serializedSize(v.minEpoch)
+ TypeSizes.sizeof(v.repairType.value);
+ TypeSizes.sizeof(v.repairType.repairedData)
+ TypeSizes.sizeof(v.repairType.repairedPaxos)
+ TypeSizes.sizeof(v.repairType.repairedAccord);
}
}
}

View File

@ -130,6 +130,12 @@ public abstract class ConsensusMigrationAdmin extends NodeTool.NodeToolCmd
List<String> keyspaceNames = parseOptionalKeyspace(schemaArgs, probe, KeyspaceSet.ACCORD_MANAGED);
List<String> maybeTableNames = schemaArgs.size() > 1 ? schemaArgs.subList(1, schemaArgs.size()) : null;
List<RepairCmd> repairCmds = new ArrayList<>(keyspaceNames.size() * 2);
// Finish can't actually finish with one set of repairs when migrating from Paxos -> Accord
// and it's async when the next invocation will see TCM updates from the repair that will correctly determine
// the next set of repairs needed. If we spin we will issue redundant repairs.
// It's also pretty involved not to return handles on the repairs since there is already a lot of plumbing
// leveraging monitoring in progress repairs.
output.out.println("Starting first round of repairs");
for (String keyspace : keyspaceNames)
{
repairCmds.add(new FinishMigrationRepairCommand(probe, keyspace, maybeTableNames, maybeRangesStr, ConsensusMigrationTarget.paxos));
@ -143,6 +149,23 @@ public abstract class ConsensusMigrationAdmin extends NodeTool.NodeToolCmd
{
throw new RuntimeException("Error occurred attempting to finish migration for keyspace(s) " + keyspaceNames + " tables " + maybeTableNames + " and ranges " + maybeRangesStr, e);
}
// The repair should have at least committed the TCM change to the node we asked to coordinate the repair
// so calling finishedConsensusMigration a second time should trigger any needed 2nd phase repairs
// or does nothing if none are needed
output.out.println("Starting second round of repairs (may do nothing if migrating from Accord to Paxos)");
repairCmds.clear();
for (String keyspace : keyspaceNames)
{
repairCmds.add(new FinishMigrationRepairCommand(probe, keyspace, maybeTableNames, maybeRangesStr, ConsensusMigrationTarget.accord));
}
try
{
probe.startAndBlockOnAsyncRepairs(probe.output().out, repairCmds);
}
catch (IOException e)
{
throw new RuntimeException("Error occurred attempting to finish migration for keyspace(s) " + keyspaceNames + " tables " + maybeTableNames + " and ranges " + maybeRangesStr, e);
}
probe.output().out.printf("Finished consensus migration range (%s) of keyspaces %s and tables %s%n", maybeRangesStr, keyspaceNames, maybeTableNames);
}
}

View File

@ -17,13 +17,6 @@
*/
package org.apache.cassandra.tools.nodetool;
import static com.google.common.collect.Lists.newArrayList;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import io.airlift.airline.Arguments;
import io.airlift.airline.Cli;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -34,6 +27,10 @@ import java.util.function.Supplier;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;
import io.airlift.airline.Arguments;
import io.airlift.airline.Cli;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import org.apache.cassandra.repair.RepairParallelism;
import org.apache.cassandra.repair.messages.RepairOption;
import org.apache.cassandra.schema.SchemaConstants;
@ -41,6 +38,10 @@ import org.apache.cassandra.streaming.PreviewKind;
import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Lists.newArrayList;
import static org.apache.commons.lang3.StringUtils.EMPTY;
@Command(name = "repair", description = "Repair one or more tables")
public class Repair extends NodeToolCmd
{
@ -108,6 +109,10 @@ public class Repair extends NodeToolCmd
@Option(title = "accord-only", name = {"-accord-only", "--accord-only"}, description = "If the --accord-only flag is included, no table data is repaired, only accord operations..")
private boolean accordOnly = false;
@Option(title = "skip-accord", name = {"-skip-accord", "--skip-accord"}, description = "If the --skip-accord flag is included, the Accord repair step is skipped. Accord repair is also skipped for preview repairs.")
private boolean skipAccord = false;
@Option(title = "ignore_unreplicated_keyspaces", name = {"-iuk","--ignore-unreplicated-keyspaces"}, description = "Use --ignore-unreplicated-keyspaces to ignore keyspaces which are not replicated, otherwise the repair will fail")
private boolean ignoreUnreplicatedKeyspaces = false;
@ -191,10 +196,33 @@ public class Repair extends NodeToolCmd
options.put(RepairOption.PREVIEW, getPreviewKind().toString());
options.put(RepairOption.OPTIMISE_STREAMS_KEY, Boolean.toString(optimiseStreams));
options.put(RepairOption.IGNORE_UNREPLICATED_KS, Boolean.toString(ignoreUnreplicatedKeyspaces));
options.put(RepairOption.REPAIR_PAXOS_KEY, Boolean.toString(!skipPaxos && getPreviewKind() == PreviewKind.NONE));
options.put(RepairOption.PAXOS_ONLY_KEY, Boolean.toString(paxosOnly && getPreviewKind() == PreviewKind.NONE));
options.put(RepairOption.NO_TOMBSTONE_PURGING, Boolean.toString(dontPurgeTombstones));
options.put(RepairOption.ACCORD_ONLY_KEY, Boolean.toString(accordOnly && getPreviewKind() == PreviewKind.NONE));
checkArgument(!(paxosOnly && accordOnly), "Can't specify both paxos-only and accord-only");
checkArgument(!(skipPaxos && paxosOnly), "Can't specify both skip-paxos and paxos-only");
boolean repairPaxos = !skipPaxos && !accordOnly && getPreviewKind() == PreviewKind.NONE;
options.put(RepairOption.REPAIR_PAXOS_KEY, Boolean.toString(repairPaxos));
checkArgument(!(skipAccord && accordOnly), "Can't specify both skip-accord and accord-only");
boolean repairAccord = !skipAccord && !paxosOnly && getPreviewKind() == PreviewKind.NONE;
options.put(RepairOption.REPAIR_ACCORD_KEY, Boolean.toString(repairAccord));
boolean repairData = false;
if (getPreviewKind() == PreviewKind.NONE)
{
// Paxos only historically doesn't do a repair, but Accord sticks to repairing at ALL
// unless --force is specified.
// If repair is incremental we need to do the repair to get the sstables created in the repaired set
if (accordOnly)
repairData = !fullRepair;
// Default if not Paxos/Accord only is to repair data
else if (!paxosOnly)
repairData = true;
}
else
{
// Preview also "repairs" data
repairData = true;
}
// Incremental repair always needs a data repair to actually do the incremental repair and move the sstables
options.put(RepairOption.REPAIR_DATA_KEY, Boolean.toString(repairData));
if (!startToken.isEmpty() || !endToken.isEmpty())
{

View File

@ -28,12 +28,6 @@ import java.util.concurrent.TimeUnit;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.repair.SharedContext;
import org.apache.cassandra.service.paxos.cleanup.PaxosCleanupLocalCoordinator;
import org.apache.cassandra.service.paxos.cleanup.PaxosCleanupResponse;
import org.apache.cassandra.utils.Shared;
import org.awaitility.Awaitility;
import org.junit.Assert;
import org.junit.Test;
@ -71,11 +65,13 @@ import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.api.IInvokableInstance;
import org.apache.cassandra.distributed.shared.ClusterUtils;
import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.exceptions.CasWriteTimeoutException;
import org.apache.cassandra.gms.FailureDetector;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.Verb;
import org.apache.cassandra.repair.RepairParallelism;
import org.apache.cassandra.repair.SharedContext;
import org.apache.cassandra.repair.messages.RepairOption;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.Schema;
@ -88,6 +84,8 @@ import org.apache.cassandra.service.paxos.Ballot;
import org.apache.cassandra.service.paxos.Commit;
import org.apache.cassandra.service.paxos.Paxos;
import org.apache.cassandra.service.paxos.PaxosState;
import org.apache.cassandra.service.paxos.cleanup.PaxosCleanupLocalCoordinator;
import org.apache.cassandra.service.paxos.cleanup.PaxosCleanupResponse;
import org.apache.cassandra.service.paxos.uncommitted.PaxosKeyState;
import org.apache.cassandra.service.paxos.uncommitted.PaxosRows;
import org.apache.cassandra.service.paxos.uncommitted.PaxosUncommittedTracker;
@ -98,6 +96,7 @@ import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.CloseableIterator;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.Shared;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.cassandra.config.CassandraRelevantProperties.AUTO_REPAIR_FREQUENCY_SECONDS;
@ -150,8 +149,9 @@ public class PaxosRepair2Test extends TestBaseImpl
options.put(RepairOption.FORCE_REPAIR_KEY, Boolean.toString(force));
options.put(RepairOption.PREVIEW, PreviewKind.NONE.toString());
options.put(RepairOption.IGNORE_UNREPLICATED_KS, Boolean.toString(false));
options.put(RepairOption.REPAIR_DATA_KEY, Boolean.toString(false));
options.put(RepairOption.REPAIR_PAXOS_KEY, Boolean.toString(true));
options.put(RepairOption.PAXOS_ONLY_KEY, Boolean.toString(true));
options.put(RepairOption.REPAIR_ACCORD_KEY, Boolean.toString(false));
cluster.get(1).runOnInstance(() -> {
int cmd = StorageService.instance.repairAsync(keyspace, options);

View File

@ -19,7 +19,12 @@
package org.apache.cassandra.distributed.test;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -38,15 +43,23 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.*;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.cql3.statements.SelectStatement;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.ReadExecutionController;
import org.apache.cassandra.db.ReadQuery;
import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.RowIterator;
import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.Constants;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
@ -68,7 +81,9 @@ import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.paxos.*;
import org.apache.cassandra.service.paxos.Ballot;
import org.apache.cassandra.service.paxos.Commit;
import org.apache.cassandra.service.paxos.PaxosState;
import org.apache.cassandra.service.paxos.cleanup.PaxosCleanup;
import org.apache.cassandra.service.paxos.uncommitted.PaxosRows;
import org.apache.cassandra.streaming.PreviewKind;
@ -78,7 +93,12 @@ import org.apache.cassandra.tcm.membership.Directory;
import org.apache.cassandra.tcm.membership.NodeVersion;
import org.apache.cassandra.tcm.transformations.CustomTransformation;
import org.apache.cassandra.tcm.transformations.ForceSnapshot;
import org.apache.cassandra.utils.*;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.CassandraVersion;
import org.apache.cassandra.utils.ExecutorUtils;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.TimeUUID;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.cassandra.distributed.shared.AssertUtils.assertRows;
@ -167,8 +187,9 @@ public class PaxosRepairTest extends TestBaseImpl
options.put(RepairOption.FORCE_REPAIR_KEY, Boolean.toString(force));
options.put(RepairOption.PREVIEW, PreviewKind.NONE.toString());
options.put(RepairOption.IGNORE_UNREPLICATED_KS, Boolean.toString(false));
options.put(RepairOption.REPAIR_DATA_KEY, Boolean.toString(false));
options.put(RepairOption.REPAIR_PAXOS_KEY, Boolean.toString(true));
options.put(RepairOption.PAXOS_ONLY_KEY, Boolean.toString(true));
options.put(RepairOption.REPAIR_ACCORD_KEY, Boolean.toString(false));
cluster.get(1).runOnInstance(() -> {
int cmd = StorageService.instance.repairAsync(keyspace, options);

View File

@ -574,7 +574,7 @@ public abstract class AccordCQLTestBase extends AccordTestBase
{
accordRead = wrapInTxn(accordRead);
Object[][] simpleReadResult;
if (transactionalMode.ignoresSuppleidCommitCL)
if (transactionalMode.ignoresSuppliedCommitCL)
// With accord non-SERIAL write strategy the commit CL is effectively ANY so we need to read at SERIAL
simpleReadResult = cluster.coordinator(1).execute(simpleRead, ConsistencyLevel.SERIAL, key);
else

View File

@ -18,7 +18,6 @@
package org.apache.cassandra.distributed.test.accord;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@ -27,15 +26,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nonnull;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import org.junit.After;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import accord.api.BarrierType;
import accord.impl.progresslog.DefaultProgressLogs;
import accord.local.Node;
import accord.local.PreLoadContext;
@ -45,10 +45,8 @@ import accord.local.cfk.CommandsForKey;
import accord.local.cfk.SafeCommandsForKey;
import accord.primitives.Seekables;
import accord.primitives.Status;
import accord.primitives.Timestamp;
import accord.primitives.TxnId;
import accord.utils.async.AsyncChains;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.db.ColumnFamilyStore;
@ -62,9 +60,11 @@ import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.accord.AccordSafeCommandStore;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.api.AccordAgent;
import org.apache.cassandra.service.accord.IAccordService;
import org.apache.cassandra.service.accord.IAccordService.DelegatingAccordService;
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
import org.apache.cassandra.service.consensus.TransactionalMode;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.concurrent.Future;
@ -77,68 +77,61 @@ public class AccordIncrementalRepairTest extends AccordTestBase
{
private static final Logger logger = LoggerFactory.getLogger(AccordIncrementalRepairTest.class);
public static class BarrierRecordingAgent extends AccordAgent
public static class BarrierRecordingService extends DelegatingAccordService
{
static class ExecutedBarrier
private volatile boolean executedBarriers = false;
public BarrierRecordingService(IAccordService delegate)
{
final Seekables<?, ?> keysOrRanges;
final @Nonnull Timestamp executeAt;
public ExecutedBarrier(Seekables<?, ?> keysOrRanges, @Nonnull Timestamp executeAt)
{
this.keysOrRanges = keysOrRanges;
this.executeAt = executeAt;
}
@Override
public String toString()
{
return "ExecutedBarrier{" +
"keysOrRanges=" + keysOrRanges +
", executeAt=" + executeAt +
'}';
}
super(delegate);
}
private final List<ExecutedBarrier> barriers = new ArrayList<>();
@Override
public void onSuccessfulBarrier(@Nonnull TxnId txnId, @Nonnull Seekables<?, ?> keysOrRanges)
public Seekables<?, ?> barrierWithRetries(Seekables<?, ?> keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite) throws InterruptedException
{
super.onSuccessfulBarrier(txnId, keysOrRanges);
synchronized (barriers)
{
barriers.add(new ExecutedBarrier(keysOrRanges, txnId));
}
Seekables<?, ?> retval = delegate.barrierWithRetries(keysOrRanges, minEpoch, barrierType, isForWrite);
executedBarriers = true;
return retval;
}
public List<ExecutedBarrier> executedBarriers()
@Override
public Seekables<?, ?> barrier(@Nonnull Seekables<?, ?> keysOrRanges, long minEpoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite)
{
synchronized (barriers)
{
return ImmutableList.copyOf(barriers);
}
Seekables<?, ?> retval = delegate.barrier(keysOrRanges, minEpoch, requestTime, timeoutNanos, barrierType, isForWrite);
executedBarriers = true;
return retval;
}
@Override
public Seekables<?, ?> repairWithRetries(Seekables<?, ?> keysOrRanges, long minEpoch, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints) throws InterruptedException
{
Seekables<?, ?> retval = delegate.repairWithRetries(keysOrRanges, minEpoch, barrierType, isForWrite, allEndpoints);
executedBarriers = true;
return retval;
}
@Override
public Seekables<?, ?> repair(@Nonnull Seekables<?, ?> keysOrRanges, long epoch, Dispatcher.RequestTime requestTime, long timeoutNanos, BarrierType barrierType, boolean isForWrite, List<InetAddressAndPort> allEndpoints)
{
Seekables<?, ?> retval = delegate.repair(keysOrRanges, epoch, requestTime, timeoutNanos, barrierType, isForWrite, allEndpoints);
executedBarriers = true;
return retval;
}
public void reset()
{
synchronized (barriers)
{
barriers.clear();
}
executedBarriers = false;
}
}
static BarrierRecordingAgent agent()
static BarrierRecordingService barrierRecordingService()
{
AccordService service = (AccordService) AccordService.instance();
return (BarrierRecordingAgent) service.node().agent();
return (BarrierRecordingService) AccordService.instance();
}
static AccordService accordService()
static IAccordService accordService()
{
return (AccordService) AccordService.instance();
return AccordService.instance();
}
@Override
@ -150,8 +143,9 @@ public class AccordIncrementalRepairTest extends AccordTestBase
@BeforeClass
public static void setupClass() throws Throwable
{
CassandraRelevantProperties.ACCORD_AGENT_CLASS.setString(BarrierRecordingAgent.class.getName());
setupCluster(opt -> opt.withConfig(conf -> conf.with(Feature.NETWORK, Feature.GOSSIP).set("accord.recover_delay", "1s")), 3);
for (IInvokableInstance instance : SHARED_CLUSTER)
instance.runOnInstance(() -> AccordService.unsafeSetNewAccordService(new BarrierRecordingService(AccordService.instance())));
// setupCluster(opt -> opt, 3);
}
@ -262,6 +256,8 @@ public class AccordIncrementalRepairTest extends AccordTestBase
// TODO (required): After conversation with Ariel: it's a known issue that I am not sure we need to fix now.
// The problem is that we don't flush after Accord repair, but before data repair when running incremental
// repair so it doesn't see the repaired sstables it is checking for.
// This hard fails now that incremental repair Accord barriers are at all to account for the missing flushes
@Ignore
@Test
public void txnRepairTest() throws Throwable
{
@ -281,7 +277,7 @@ public class AccordIncrementalRepairTest extends AccordTestBase
awaitLocalApplyOnKey(metadata, 1);
}));
SHARED_CLUSTER.forEach(instance -> instance.runOnInstance(() -> agent().reset()));
SHARED_CLUSTER.forEach(instance -> instance.runOnInstance(() -> barrierRecordingService().reset()));
SHARED_CLUSTER.get(1, 2).forEach(instance -> {
instance.runOnInstance(() -> {
@ -304,14 +300,14 @@ public class AccordIncrementalRepairTest extends AccordTestBase
for (IInvokableInstance instance : SHARED_CLUSTER)
instance.runOnInstance(() -> {
DefaultProgressLogs.unsafePauseForTesting(true);
Assert.assertTrue(agent().executedBarriers().isEmpty());
Assert.assertFalse(barrierRecordingService().executedBarriers);
});
SHARED_CLUSTER.filters().reset();
awaitEndpointUp(SHARED_CLUSTER.get(1), SHARED_CLUSTER.get(3));
nodetool(SHARED_CLUSTER.get(1), "repair", KEYSPACE);
SHARED_CLUSTER.get(1).runOnInstance(() -> {
Assert.assertFalse( agent().executedBarriers().isEmpty());
Assert.assertTrue(barrierRecordingService().executedBarriers);
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
Assert.assertFalse(cfs.getLiveSSTables().isEmpty());
cfs.getLiveSSTables().forEach(sstable -> {
@ -356,12 +352,12 @@ public class AccordIncrementalRepairTest extends AccordTestBase
Assert.assertTrue(cfs.getLiveSSTables().isEmpty());
}));
SHARED_CLUSTER.forEach(instance -> instance.runOnInstance(() -> {
agent().reset();
barrierRecordingService().reset();
}));
nodetool(SHARED_CLUSTER.get(1), "repair", KEYSPACE);
SHARED_CLUSTER.get(1).runOnInstance(() -> {
Assert.assertFalse( agent().executedBarriers().isEmpty());
Assert.assertTrue(barrierRecordingService().executedBarriers);
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
Assert.assertFalse(cfs.getLiveSSTables().isEmpty());
cfs.getLiveSSTables().forEach(sstable -> {
@ -401,6 +397,33 @@ public class AccordIncrementalRepairTest extends AccordTestBase
final String keyspace = KEYSPACE;
final String table = accordTableName;
executeWithRetry(SHARED_CLUSTER, format("BEGIN TRANSACTION\n" +
"INSERT INTO %s (k, v) VALUES (1, 1);\n" +
"COMMIT TRANSACTION", qualifiedAccordTableName));
SHARED_CLUSTER.get(1, 2).forEach(instance -> instance.runOnInstance(() -> {
TableMetadata metadata = Schema.instance.getTableMetadata(keyspace, table);
awaitLocalApplyOnKey(metadata, 1);
}));
SHARED_CLUSTER.forEach(instance -> instance.runOnInstance(() -> barrierRecordingService().reset()));
SHARED_CLUSTER.filters().reset();
awaitEndpointUp(SHARED_CLUSTER.get(1), SHARED_CLUSTER.get(3));
nodetool(SHARED_CLUSTER.get(1), "repair", "--accord-only", KEYSPACE);
SHARED_CLUSTER.get(1).runOnInstance(() -> {
Assert.assertTrue(barrierRecordingService().executedBarriers);
});
}
@Test
public void onlyAccordWithForceTest()
{
SHARED_CLUSTER.schemaChange(format("CREATE TABLE %s.%s (k int primary key, v int) WITH transactional_mode='full' AND fast_path={'size':2};", KEYSPACE, accordTableName));
final String keyspace = KEYSPACE;
final String table = accordTableName;
SHARED_CLUSTER.filters().allVerbs().to(3).drop();
awaitEndpointDown(SHARED_CLUSTER.get(1), SHARED_CLUSTER.get(3));
awaitEndpointDown(SHARED_CLUSTER.get(2), SHARED_CLUSTER.get(3));
@ -414,14 +437,14 @@ public class AccordIncrementalRepairTest extends AccordTestBase
awaitLocalApplyOnKey(metadata, 1);
}));
SHARED_CLUSTER.forEach(instance -> instance.runOnInstance(() -> agent().reset()));
SHARED_CLUSTER.forEach(instance -> instance.runOnInstance(() -> barrierRecordingService().reset()));
SHARED_CLUSTER.filters().reset();
awaitEndpointUp(SHARED_CLUSTER.get(1), SHARED_CLUSTER.get(3));
nodetool(SHARED_CLUSTER.get(1), "repair", "--accord-only", KEYSPACE);
nodetool(SHARED_CLUSTER.get(1), "repair", "--force", "--accord-only", KEYSPACE);
SHARED_CLUSTER.get(1).runOnInstance(() -> {
Assert.assertFalse( agent().executedBarriers().isEmpty());
Assert.assertTrue(barrierRecordingService().executedBarriers);
});
}
}

View File

@ -661,16 +661,20 @@ public abstract class AccordMigrationRaceTestBase extends AccordTestBase
{
// Expect two retry on different system responses when migrating from Paxos to Accord, one from each
// node that knows it is on the wrong system
Util.spinUntilTrue(() -> messageSink.messages.stream().filter(p -> {
if (p.right.verb() != Verb.FAILURE_RSP.id)
return false;
if (!p.left.equals(outOfSyncInstance.broadcastAddress()))
return false;
RequestFailureReason reason = ((RequestFailure) Instance.deserializeMessage(p.right).payload).reason;
if (reason == RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM)
return true;
return false;
}).count() == 2, 20);
Util.spinUntilTrue(() ->
{
outOfSyncInstance.runOnInstance(() -> HintsService.instance.flushAndFsyncBlockingly());
return messageSink.messages.stream().filter(p -> {
if (p.right.verb() != Verb.FAILURE_RSP.id)
return false;
if (!p.left.equals(outOfSyncInstance.broadcastAddress()))
return false;
RequestFailureReason reason = ((RequestFailure) Instance.deserializeMessage(p.right).payload).reason;
if (reason == RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM)
return true;
return false;
}).count() == 2;
}, 20);
}
// After this hints should deliver and the final validation should succeed
// if we don't unpause enactment

View File

@ -56,6 +56,7 @@ import org.apache.cassandra.db.Mutation.SimpleBuilder;
import org.apache.cassandra.db.SimpleBuilders.PartitionUpdateBuilder;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.Murmur3Partitioner.LongToken;
import org.apache.cassandra.dht.NormalizedRanges;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.distributed.api.Feature;
@ -103,6 +104,7 @@ import static org.apache.cassandra.Util.spinUntilSuccess;
import static org.apache.cassandra.db.SystemKeyspace.CONSENSUS_MIGRATION_STATE;
import static org.apache.cassandra.db.SystemKeyspace.PAXOS;
import static org.apache.cassandra.dht.Range.normalize;
import static org.apache.cassandra.dht.NormalizedRanges.normalizedRanges;
import static org.apache.cassandra.distributed.api.ConsistencyLevel.ALL;
import static org.apache.cassandra.distributed.api.ConsistencyLevel.ANY;
import static org.apache.cassandra.distributed.api.ConsistencyLevel.SERIAL;
@ -408,20 +410,24 @@ public class AccordMigrationTest extends AccordTestBase
Consumer<Integer> runCasApplies = key -> assertRowEquals(cluster, new Object[]{true}, casCQL, key);
Consumer<Integer> runCasOnSecondNode = key -> assertEquals( "[applied]", cluster.coordinator(2).executeWithResult(casCQL, ANY, key).names().get(0));
String tableName = qualifiedAccordTableName.split("\\.")[1];
int migratingKey = getKeyBetweenTokens(midToken, maxToken);
int migratingKey = getKeyBetweenTokens(upperMidToken, maxToken);
int notMigratingKey = getKeyBetweenTokens(minToken, midToken);
Range<Token> migratingRange = new Range(midToken, maxToken);
List<Range<Token>> migratingRanges = ImmutableList.of(migratingRange);
NormalizedRanges<Token> migratingRanges = normalizedRanges(ImmutableList.of(migratingRange));
// Not actually migrating yet so should do nothing special
assertTargetAccordWrite(runCasNoApply, 1, migratingKey, expectedKeyMigrations, 0, 1, 0, 0, 0);
// Mark ranges migrating and check migration state is correct
nodetool(coordinator, "consensus_admin", "begin-migration", "-st", midToken.toString(), "-et", maxToken.toString(), "-tp", "accord", KEYSPACE, tableName);
assertMigrationState(tableName, ConsensusMigrationTarget.accord, emptyList(), migratingRanges, 1);
assertMigrationState(tableName, ConsensusMigrationTarget.accord, emptyList(), migratingRanges, migratingRanges, 1);
// Should be routed directly to Accord, and perform key migration, as well as key migration read in Accord
addExpectedMigratedKey(expectedKeyMigrations, migratingKey, tableUUID);
// Without data repaired Paxos should continue to run
assertTargetPaxosWrite(runCasNoApply, 1, migratingKey, emptyList(), 0, 1, 0, 0, 0);
nodetool(coordinator, "repair", "-st", upperMidToken.toString(), "-et", maxAlignedWithLocalRanges.toString(), "-skip-accord", "-skip-paxos");
// With data repaired the write should now key migrated
assertTargetAccordWrite(runCasNoApply, 1, migratingKey, expectedKeyMigrations, 1, 0, 1, 0, 0);
// Should not repeat key migration, and should still do a migration read in Accord
@ -442,15 +448,23 @@ public class AccordMigrationTest extends AccordTestBase
Gossiper.runInGossipStageBlocking(() -> Gossiper.instance.markDead(secondNodeBroadcastAddress, endpointState));
});
nodetool(coordinator, "repair", "--force");
assertMigrationState(tableName, ConsensusMigrationTarget.accord, emptyList(), migratingRanges, 1);
// Data repair was already done for one node's local range
NormalizedRanges<Token> alreadyDataRepaired = normalizedRanges(ImmutableList.of(new Range<>(upperMidToken, maxAlignedWithLocalRanges)));
NormalizedRanges<Token> remainingPendingDataRepair = migratingRanges.subtract(alreadyDataRepaired);
assertMigrationState(tableName, ConsensusMigrationTarget.accord, emptyList(), remainingPendingDataRepair, migratingRanges, 1);
cluster.get(1).runOnInstance(() -> {
EndpointState endpointState = Gossiper.instance.getEndpointStateForEndpoint(secondNodeBroadcastAddress);
Gossiper.runInGossipStageBlocking(() -> Gossiper.instance.realMarkAlive(secondNodeBroadcastAddress, endpointState));
});
// Full repair should complete the migration and update the metadata, adding --force when nodes are up should be fine
nodetool(coordinator, "repair", "--force" );
// Some ranges will be migrated because they were already data repaired
NormalizedRanges<Token> alreadyMigrated = alreadyDataRepaired;
assertMigrationState(tableName, ConsensusMigrationTarget.accord, alreadyMigrated, emptyList(), migratingRanges.subtract(alreadyMigrated), 1);
// Need to repair a second time to complete the migration to Accord because we are invoking repair directly, finish would do both for us normally
nodetool(coordinator, "repair", "--force");
assertMigrationState(tableName, ConsensusMigrationTarget.accord, migratingRanges, emptyList(), 0);
assertMigrationState(tableName, ConsensusMigrationTarget.accord, migratingRanges, emptyList(), emptyList(), 0);
// Should run on Accord, and not perform key migration nor should it need to perform a migration read in Accord now that it is repaired
assertTargetAccordWrite(runCasNoApply, 1, migratingKey, expectedKeyMigrations, 1, 0, 0, 0, 0);
@ -472,6 +486,9 @@ public class AccordMigrationTest extends AccordTestBase
// PaxosRepair will have inserted a condition matching row, so it can apply, demonstrating repair and
// key migration occurred
addExpectedMigratedKey(expectedKeyMigrations, migratingKey, tableUUID);
// Need to data repair for key migration to be possible since otherwise it will just run on Paxos
nodetool(coordinator, "repair", "-st", lowerMidToken.toString(), "-et", "-3074457345618258603", "-skip-accord", "-skip-paxos");
nodetool(cluster.coordinator(2), "repair", "-st", "-3074457345618258603", "-et", midToken.toString(), "-skip-accord", "-skip-paxos");
assertTargetAccordWrite(runCasApplies, 1, migratingKey, expectedKeyMigrations, 1, 0, 1, 0, 0);
// This will force the write to use the normal write patch
@ -530,10 +547,10 @@ public class AccordMigrationTest extends AccordTestBase
List<Range<Token>> migratedRanges = ImmutableList.of(new Range<>(startTokenForRepair, endTokenForRepair), migratingRange);
List<Range<Token>> midMigratingRanges = ImmutableList.of(new Range<>(lowerMidToken, startTokenForRepair), new Range<>(endTokenForRepair, midToken));
List<Range<Token>> migratingAndMigratedRanges = ImmutableList.of(new Range<>(lowerMidToken, maxToken));
assertMigrationState(tableName, ConsensusMigrationTarget.accord, migratedRanges, midMigratingRanges, 1);
assertMigrationState(tableName, ConsensusMigrationTarget.accord, migratedRanges, emptyList(), midMigratingRanges, 1);
nodetool(coordinator, "consensus_admin", "finish-migration");
assertMigrationState(tableName, ConsensusMigrationTarget.accord, migratingAndMigratedRanges, emptyList(), 0);
assertMigrationState(tableName, ConsensusMigrationTarget.accord, migratingAndMigratedRanges, emptyList(), emptyList(), 0);
});
}
@ -560,8 +577,11 @@ public class AccordMigrationTest extends AccordTestBase
assertTargetAccordRead(runRead, 1, key, expectedKeyMigrations, 0, 1, 0, 0, 0);
// Mark wrap around range as migrating
nodetool(coordinator, "consensus_admin", "begin-migration", "-st", String.valueOf(Long.MIN_VALUE + 1), "-et", String.valueOf(Long.MIN_VALUE), "-tp", "accord", KEYSPACE, accordTableName);
assertMigrationState(accordTableName, ConsensusMigrationTarget.accord, emptyList(), migratingRanges, 1);
// Should run directly on accord, migrate the key, and perform a quorum read from Accord, Paxos repair will run prepare once
assertMigrationState(accordTableName, ConsensusMigrationTarget.accord, emptyList(), migratingRanges, migratingRanges, 1);
// Need to repair so key migration can occur
for (int i = 1; i <= 3; i++)
nodetool(cluster.coordinator(i), "repair", "-skip-paxos", "-skip-accord");
// Should run directly on accord, migrate the key, and perform a quorum read fro Accord, Paxos repair will run prepare once
addExpectedMigratedKey(expectedKeyMigrations, key, tableUUID);
assertTargetAccordRead(runRead, 1, key, expectedKeyMigrations, 1, 1, 1, 0, 0);
key++;
@ -608,12 +628,12 @@ public class AccordMigrationTest extends AccordTestBase
nodetool(coordinator, "consensus_admin", "finish-migration", "-st", "3074457345618258601", "-et", upperMidToken.toString());
Range<Token> accordMigratedRange = new Range(midToken, upperMidToken);
Range<Token> accordMigratingRange = new Range(upperMidToken, maxToken);
assertMigrationState(tableName, ConsensusMigrationTarget.accord, ImmutableList.of(accordMigratedRange), ImmutableList.of(accordMigratingRange), 1);
assertMigrationState(tableName, ConsensusMigrationTarget.accord, ImmutableList.of(accordMigratedRange), ImmutableList.of(accordMigratingRange), ImmutableList.of(accordMigratingRange), 1);
// Test that we can reverse the migration and go back to Paxos
alterTableTransactionalMode(TransactionalMode.off);
assertTransactionalModes(TransactionalMode.off, TransactionalMigrationFromMode.mixed_reads);
assertMigrationState(tableName, ConsensusMigrationTarget.paxos, ImmutableList.of(new Range(minToken, midToken), new Range(maxToken, minToken)), ImmutableList.of(accordMigratingRange), 1);
assertMigrationState(tableName, ConsensusMigrationTarget.paxos, ImmutableList.of(new Range(minToken, midToken), new Range(maxToken, minToken)), emptyList(), ImmutableList.of(accordMigratingRange), 1);
Iterator<Integer> paxosNonMigratingKeys = getKeysBetweenTokens(minToken, midToken);
Iterator<Integer> paxosMigratingKeys = getKeysBetweenTokens(upperMidToken, maxToken);
Iterator<Integer> accordKeys = getKeysBetweenTokens(midToken, upperMidToken);
@ -638,8 +658,8 @@ public class AccordMigrationTest extends AccordTestBase
assertTargetPaxosWrite(runCasNoApply, 1, nextMigratingKey, expectedKeyMigrations, 2, 1, 1, 1, 1);
// Repair the currently migrating range from when targets were switched, but it's not an Accord repair, this is to make sure the wrong repair type doesn't trigger progress
nodetool(coordinator, "repair", "-st", upperMidToken.toString(), "-et", maxAlignedWithLocalRanges.toString(), "--paxos-only");
assertMigrationState(tableName, ConsensusMigrationTarget.paxos, ImmutableList.of(new Range(minToken, midToken), new Range(maxToken, minToken)), ImmutableList.of(accordMigratingRange), 1);
nodetool(coordinator, "repair", "-st", upperMidToken.toString(), "-et", maxAlignedWithLocalRanges.toString(), "--skip-accord");
assertMigrationState(tableName, ConsensusMigrationTarget.paxos, ImmutableList.of(new Range(minToken, midToken), new Range(maxToken, minToken)), emptyList(), ImmutableList.of(accordMigratingRange), 1);
// Paxos migrating keys should still need key migration after non-Accord repair
nextMigratingKey = paxosMigratingKeys.next();
@ -652,7 +672,7 @@ public class AccordMigrationTest extends AccordTestBase
// Sliver remaining because of precise repairs
// TODO This precision isn't needed for Accord repair? Worth lifting that restriction or keep it consistent?
Range<Token> remainingRange = new Range(maxAlignedWithLocalRanges, maxToken);
assertMigrationState(tableName, ConsensusMigrationTarget.paxos, ImmutableList.of(new Range(minToken, midToken), repairedRange, new Range(maxToken, minToken)), ImmutableList.of(remainingRange), 1);
assertMigrationState(tableName, ConsensusMigrationTarget.paxos, ImmutableList.of(new Range(minToken, midToken), repairedRange, new Range(maxToken, minToken)), emptyList(), ImmutableList.of(remainingRange), 1);
// Paxos migrating keys shouldn't need key migration after Accord repair
assertTargetPaxosWrite(runCasNoApply, 1, paxosMigratingKeys.next(), expectedKeyMigrations, 0, 1, 0, 0, 0);
@ -667,7 +687,7 @@ public class AccordMigrationTest extends AccordTestBase
expectedKeyMigrations.add(Pair.create(key, tableUUID));
}
private static void assertMigrationState(String tableName, ConsensusMigrationTarget target, List<Range<Token>> migratedRanges, List<Range<Token>> migratingRanges, int numMigratingEpochs) throws Throwable
private static void assertMigrationState(String tableName, ConsensusMigrationTarget target, List<Range<Token>> migratedRanges, List<Range<Token>> repairPendingRanges, List<Range<Token>> migratingRanges, int numMigratingEpochs) throws Throwable
{
// Validate nodetool consensus admin list output
String yamlResultString = nodetool(SHARED_CLUSTER.coordinator(1), "consensus_admin", "list");
@ -703,6 +723,8 @@ public class AccordMigrationTest extends AccordTestBase
tableIds.add((String) tableStateMap.get("tableId"));
List<Range<Token>> migratedRangesFromStateMap = ((List<String>) tableStateMap.get("migratedRanges")).stream().map(Range::fromString).collect(toImmutableList());
assertEquals(migratedRanges, migratedRangesFromStateMap);
List<Range<Token>> repairPendingRangesFromStateMap = ((List<String>) tableStateMap.get("repairPendingRanges")).stream().map(Range::fromString).collect(toImmutableList());
assertEquals(repairPendingRanges, repairPendingRangesFromStateMap);
Map<Long, List<Range<Token>>> migratingRangesByEpochFromStateMap = new LinkedHashMap<>();
for (Map.Entry<Object, List<String>> entry : ((Map<Object, List<String>>) tableStateMap.get("migratingRangesByEpoch")).entrySet())
{
@ -738,6 +760,7 @@ public class AccordMigrationTest extends AccordTestBase
assertEquals(target, state.targetProtocol);
assertEquals("Migrated ranges:", migratedRanges, state.migratedRanges);
assertEquals("Migrating ranges:", migratingRanges, state.migratingRanges);
assertEquals("Repair pending ranges:", repairPendingRanges, state.repairPendingRanges);
assertEquals("Migrating and migrated ranges:", migratingAndMigratedRanges, state.migratingAndMigratedRanges);
assertEquals(numMigratingEpochs, state.migratingRangesByEpoch.size());
if (migratingRanges.isEmpty())

View File

@ -18,16 +18,7 @@
package org.apache.cassandra.distributed.test.accord;
import java.io.IOException;
import java.nio.ByteBuffer;
import com.google.common.collect.ImmutableList;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.ServerTestUtils;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor;
@ -46,6 +37,14 @@ import org.apache.cassandra.locator.ReplicaPlans;
import org.apache.cassandra.service.StorageProxy;
import org.apache.cassandra.service.consensus.migration.ConsensusKeyMigrationState;
import org.apache.cassandra.service.consensus.migration.ConsensusRequestRouter;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;
import static java.lang.String.format;
import static org.apache.cassandra.Util.spinAssertEquals;

View File

@ -97,7 +97,7 @@ class OnInstanceRepair extends ClusterAction
{
Collection<Range<Token>> ranges = rangesSupplier.call();
// no need to wait for completion, as we track all task submissions and message exchanges, and ensure they finish before continuing to next action
StorageService.instance.repair(keyspaceName, new RepairOption(RepairParallelism.SEQUENTIAL, isPrimaryRangeOnly, false, false, 1, ranges, false, force, PreviewKind.NONE, false, true, repairPaxos, repairOnlyPaxos, false, false, false), singletonList((tag, event) -> {
StorageService.instance.repair(keyspaceName, new RepairOption(RepairParallelism.SEQUENTIAL, isPrimaryRangeOnly, false, false, 1, ranges, false, force, PreviewKind.NONE, false, true, !repairOnlyPaxos, repairPaxos, false, false), singletonList((tag, event) -> {
if (event.getType() == ProgressEventType.COMPLETE)
listener.run();
}));

View File

@ -25,8 +25,6 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.slf4j.Logger;
@ -54,7 +52,6 @@ import org.apache.cassandra.locator.BaseProximity;
import org.apache.cassandra.security.ThreadAwareSecurityManager;
import org.apache.cassandra.service.DiskErrorsHandlerService;
import org.apache.cassandra.service.EmbeddedCassandraService;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.tcm.AtomicLongBackedProcessor;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadataService;
@ -76,10 +73,8 @@ import org.apache.cassandra.tcm.transformations.UnsafeJoin;
import org.apache.cassandra.tcm.transformations.cms.Initialize;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Sortable;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import static org.apache.cassandra.config.CassandraRelevantProperties.ORG_APACHE_CASSANDRA_DISABLE_MBEAN_REGISTRATION;
import static org.apache.cassandra.schema.SchemaConstants.ACCORD_KEYSPACE_NAME;
/**
* Utility methodes used by SchemaLoader and CQLTester to manage the server and its state.
@ -347,36 +342,6 @@ public final class ServerTestUtils
cms.mark();
}
public static void recreateAccord(NodeId tcmid)
{
if (!DatabaseDescriptor.getAccordTransactionsEnabled())
return;
if (AccordService.isSetup())
{
try
{
AccordService.instance().shutdownAndWait(1, TimeUnit.MINUTES);
}
catch (InterruptedException e)
{
throw new UncheckedInterruptedException(e);
}
catch (TimeoutException e)
{
throw new RuntimeException(e);
}
Keyspace ks = Keyspace.open(ACCORD_KEYSPACE_NAME);
FBUtilities.waitOnFutures(ks.flush(ColumnFamilyStore.FlushReason.UNIT_TESTS));
cleanupDirectory(DatabaseDescriptor.getAccordJournalDirectory());
for (ColumnFamilyStore t : ks.getColumnFamilyStores())
t.truncateBlockingWithoutSnapshot();
AccordService.unsafeSetNewAccordService();
}
AccordService.startup(tcmid);
}
public static void markCMS()
{
ClusterMetadataService cms = ClusterMetadataService.instance();

View File

@ -33,13 +33,6 @@ import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import accord.local.CommandStores;
import accord.local.StoreParticipants;
import accord.primitives.Route;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.service.accord.*;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@ -48,28 +41,32 @@ import org.slf4j.LoggerFactory;
import accord.api.Key;
import accord.api.Result;
import accord.local.cfk.CommandsForKey;
import accord.local.CheckedCommands;
import accord.local.Command;
import accord.local.CommandStore;
import accord.local.CommandStores;
import accord.local.DurableBefore;
import accord.local.RedundantBefore;
import accord.primitives.SaveStatus;
import accord.primitives.Status;
import accord.primitives.Status.Durability;
import accord.local.StoreParticipants;
import accord.local.cfk.CommandsForKey;
import accord.primitives.Ballot;
import accord.primitives.Deps;
import accord.primitives.FullRoute;
import accord.primitives.PartialDeps;
import accord.primitives.PartialTxn;
import accord.primitives.Ranges;
import accord.primitives.Route;
import accord.primitives.SaveStatus;
import accord.primitives.Seekable;
import accord.primitives.Status;
import accord.primitives.Status.Durability;
import accord.primitives.Txn;
import accord.primitives.Txn.Kind;
import accord.primitives.TxnId;
import accord.primitives.Writes;
import org.agrona.collections.Int2ObjectHashMap;
import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.db.Clustering;
@ -81,6 +78,7 @@ import org.apache.cassandra.db.partitions.ImmutableBTreePartition;
import org.apache.cassandra.db.partitions.Partition;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.io.sstable.ISSTableScanner;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.schema.ColumnMetadata;
@ -88,6 +86,12 @@ import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.accord.AccordCommandStore;
import org.apache.cassandra.service.accord.AccordExecutor;
import org.apache.cassandra.service.accord.AccordKeyspace;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.AccordTestUtils;
import org.apache.cassandra.service.accord.IAccordService;
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
import org.apache.cassandra.service.accord.serializers.CommandsForKeySerializer;
import org.apache.cassandra.utils.FBUtilities;
@ -101,10 +105,19 @@ import static accord.primitives.Routable.Domain.Range;
import static accord.utils.async.AsyncChains.getUninterruptibly;
import static org.apache.cassandra.Util.spinAssertEquals;
import static org.apache.cassandra.cql3.statements.schema.CreateTableStatement.parse;
import static org.apache.cassandra.db.compaction.CompactionAccordIteratorsTest.DurableBeforeType.*;
import static org.apache.cassandra.db.compaction.CompactionAccordIteratorsTest.DurableBeforeType.MAJORITY;
import static org.apache.cassandra.db.compaction.CompactionAccordIteratorsTest.DurableBeforeType.NOT_DURABLE;
import static org.apache.cassandra.db.compaction.CompactionAccordIteratorsTest.DurableBeforeType.UNIVERSAL;
import static org.apache.cassandra.schema.SchemaConstants.ACCORD_KEYSPACE_NAME;
import static org.apache.cassandra.service.accord.AccordKeyspace.*;
import static org.junit.Assert.*;
import static org.apache.cassandra.service.accord.AccordKeyspace.COMMANDS_FOR_KEY;
import static org.apache.cassandra.service.accord.AccordKeyspace.CommandRows;
import static org.apache.cassandra.service.accord.AccordKeyspace.CommandsColumns;
import static org.apache.cassandra.service.accord.AccordKeyspace.CommandsForKeysAccessor;
import static org.apache.cassandra.service.accord.AccordKeyspace.TIMESTAMPS_FOR_KEY;
import static org.apache.cassandra.service.accord.AccordKeyspace.TimestampsForKeyRows;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

View File

@ -48,11 +48,8 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.cassandra.Util.range;
import static org.apache.cassandra.config.CassandraRelevantProperties.TEST_RANGE_EXPENSIVE_CHECKS;
import static org.apache.cassandra.dht.Range.fromString;
import static org.apache.cassandra.dht.Range.intersectionOfNormalizedRanges;
import static org.apache.cassandra.dht.Range.invertNormalizedRanges;
import static org.apache.cassandra.dht.Range.isInNormalizedRanges;
import static org.apache.cassandra.dht.Range.normalize;
import static org.apache.cassandra.dht.Range.subtractNormalizedRanges;
import static org.apache.cassandra.dht.NormalizedRanges.normalizedRanges;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
@ -782,10 +779,10 @@ public class RangeTest extends CassandraTestBase
@UseMurmur3Partitioner
public void testIsInNormalizedRanges()
{
List<Range<Token>> ranges = ImmutableList.of(fromString("(1,10]"), fromString("(10,20]"), fromString("(30,40]"), fromString("(50,60]"), fromString("(60,70]"), fromString("(80,90]"), fromString("(" + Long.MAX_VALUE + ",-9223372036854775808]"));
NormalizedRanges<Token> ranges = normalizedRanges(ImmutableList.of(fromString("(1,10]"), fromString("(10,20]"), fromString("(30,40]"), fromString("(50,60]"), fromString("(60,70]"), fromString("(80,90]"), fromString("(" + Long.MAX_VALUE + ",-9223372036854775808]")));
for (int ii = 0; ii < 100; ii++)
{
boolean isIn = isInNormalizedRanges(new LongToken(ii), ranges);
boolean isIn = ranges.intersects(new LongToken(ii));
if (ii > 1 && ii <= 20)
assertTrue("Index " + ii, isIn);
else if (ii > 30 && ii <= 40)
@ -797,28 +794,28 @@ public class RangeTest extends CassandraTestBase
else
assertFalse("Index " + ii, isIn);
}
assertFalse(isInNormalizedRanges(new LongToken(Long.MAX_VALUE), ranges));
assertTrue(isInNormalizedRanges(new LongToken(Long.MIN_VALUE), ranges));
ranges = ImmutableList.of(fromString("(-9223372036854775808,-9223372036854775807]"));
assertFalse(isInNormalizedRanges(new LongToken(Long.MIN_VALUE), ranges));
assertTrue(isInNormalizedRanges(new LongToken(Long.MIN_VALUE + 1), ranges));
ranges = ImmutableList.of(fromString("(" + (Long.MAX_VALUE - 1) + ",-9223372036854775808]"));
assertFalse(isInNormalizedRanges(new LongToken(Long.MAX_VALUE - 1), ranges));
assertTrue(isInNormalizedRanges(new LongToken(Long.MAX_VALUE), ranges));
assertTrue(isInNormalizedRanges(new LongToken(Long.MIN_VALUE), ranges));
assertFalse(isInNormalizedRanges(new LongToken(Long.MAX_VALUE - 1), normalize(ranges)));
assertTrue(isInNormalizedRanges(new LongToken(Long.MAX_VALUE), normalize(ranges)));
assertTrue(isInNormalizedRanges(new LongToken(Long.MIN_VALUE), normalize(ranges)));
assertFalse(ranges.intersects(new LongToken(Long.MAX_VALUE)));
assertTrue(ranges.intersects(new LongToken(Long.MIN_VALUE)));
ranges = normalizedRanges(ImmutableList.of(fromString("(-9223372036854775808,-9223372036854775807]")));
assertFalse(ranges.intersects(new LongToken(Long.MIN_VALUE)));
assertTrue(ranges.intersects(new LongToken(Long.MIN_VALUE + 1)));
ranges = normalizedRanges(ImmutableList.of(fromString("(" + (Long.MAX_VALUE - 1) + ",-9223372036854775808]")));
assertFalse(ranges.intersects(new LongToken(Long.MAX_VALUE - 1)));
assertTrue(ranges.intersects(new LongToken(Long.MAX_VALUE)));
assertTrue(ranges.intersects(new LongToken(Long.MIN_VALUE)));
assertFalse(ranges.intersects(new LongToken(Long.MAX_VALUE - 1)));
assertTrue(ranges.intersects(new LongToken(Long.MAX_VALUE)));
assertTrue(ranges.intersects(new LongToken(Long.MIN_VALUE)));
}
@Test
@UseMurmur3Partitioner
public void testSubtractNormalizedRanges()
{
List<Range<Token>> ranges = ImmutableList.of(fromString("(1,10]"), fromString("(10,20]"), fromString("(30,40]"), fromString("(50,60]"), fromString("(60,70]"), fromString("(80,90]"), fromString("(" + Long.MAX_VALUE + ",-9223372036854775808]"));
NormalizedRanges<Token> ranges = normalizedRanges(ImmutableList.of(fromString("(1,10]"), fromString("(10,20]"), fromString("(30,40]"), fromString("(50,60]"), fromString("(60,70]"), fromString("(80,90]"), fromString("(" + Long.MAX_VALUE + ",-9223372036854775808]")));
for (int ii = 0; ii < 100; ii++)
{
boolean isIn = isInNormalizedRanges(new LongToken(ii), ranges);
boolean isIn = ranges.intersects(new LongToken(ii));
if (ii > 1 && ii <= 20)
assertTrue("Index " + ii, isIn);
else if (ii > 30 && ii <= 40)
@ -830,20 +827,20 @@ public class RangeTest extends CassandraTestBase
else
assertFalse("Index " + ii, isIn);
}
List<Range<Token>> rightMostRange = ImmutableList.of(r(Long.MAX_VALUE, Long.MIN_VALUE));
List<Range<Token>> maxLongRange = ImmutableList.of(r(Long.MAX_VALUE - 1, Long.MAX_VALUE));
NormalizedRanges<Token> rightMostRange = normalizedRanges(ImmutableList.of(r(Long.MAX_VALUE, Long.MIN_VALUE)));
NormalizedRanges<Token> maxLongRange = normalizedRanges(ImmutableList.of(r(Long.MAX_VALUE - 1, Long.MAX_VALUE)));
assertEquals(emptyList(), subtractNormalizedRanges(ranges, ranges));
assertEquals(emptyList(), subtractNormalizedRanges(rightMostRange, ranges));
assertEquals(maxLongRange, subtractNormalizedRanges(maxLongRange, ranges));
assertEquals(emptyList(), ranges.subtract(ranges));
assertEquals(emptyList(), rightMostRange.subtract(ranges));
assertEquals(maxLongRange, maxLongRange.subtract(ranges));
ranges = maxLongRange;
assertEquals(emptyList(), subtractNormalizedRanges(ranges, ranges));
assertEquals(rightMostRange, subtractNormalizedRanges(rightMostRange, ranges));
assertEquals(emptyList(), subtractNormalizedRanges(maxLongRange, ranges));
ranges = ImmutableList.of(fromString("(" + (Long.MAX_VALUE - 1) + ",-9223372036854775808]"));
assertEquals(emptyList(), subtractNormalizedRanges(ranges, ranges));
assertEquals(emptyList(), subtractNormalizedRanges(rightMostRange, ranges));
assertEquals(emptyList(), subtractNormalizedRanges(maxLongRange, ranges));
assertEquals(emptyList(), ranges.subtract(ranges));
assertEquals(rightMostRange, rightMostRange.subtract(ranges));
assertEquals(emptyList(), maxLongRange.subtract(ranges));
ranges = normalizedRanges(ImmutableList.of(fromString("(" + (Long.MAX_VALUE - 1) + ",-9223372036854775808]")));
assertEquals(emptyList(), ranges.subtract(ranges));
assertEquals(emptyList(), rightMostRange.subtract(ranges));
assertEquals(emptyList(), maxLongRange.subtract(ranges));
}
@Test
@ -857,32 +854,38 @@ public class RangeTest extends CassandraTestBase
while (elapsed.elapsed(SECONDS) != 10)
{
int numRanges = 3;
List<Range<Token>> a = new ArrayList();
List<Range<Token>> aList = new ArrayList();
for (int ii = 0; ii < numRanges; ii++)
{
a.add(new Range<>(new LongToken(r.nextLong()), new LongToken(r.nextLong())));
aList.add(new Range<>(new LongToken(r.nextLong()), new LongToken(r.nextLong())));
}
a = ImmutableList.copyOf(normalize(a));
List<Range<Token>> b = new ArrayList();
NormalizedRanges<Token> a = normalizedRanges(aList);
List<Range<Token>> bList = new ArrayList();
for (int ii = 0; ii < numRanges; ii++)
{
b.add(new Range<>(new LongToken(r.nextLong()), new LongToken(r.nextLong())));
bList.add(new Range<>(new LongToken(r.nextLong()), new LongToken(r.nextLong())));
}
b = ImmutableList.copyOf(normalize(b));
NormalizedRanges<Token> b = normalizedRanges(bList);
for (int ii = 0; ii < 1000; ii++)
{
Token t = new LongToken(r.nextLong());
isInNormalizedRanges(t, a);
isInNormalizedRanges(t, b);
a.intersects(t);
b.intersects(t);
}
intersectionOfNormalizedRanges(a, b);
intersectionOfNormalizedRanges(b, a);
subtractNormalizedRanges(a, b);
subtractNormalizedRanges(b, a);
invertNormalizedRanges(a);
invertNormalizedRanges(b);
a.intersection(b);
a.intersection(a);
b.intersection(a);
b.intersection(b);
a.subtract(b);
a.subtract(a);
b.subtract(a);
b.subtract(b);
a.invert();
b.invert();
}
}
}

View File

@ -124,12 +124,12 @@ public class RepairJobTest
public MeasureableRepairSession(TimeUUID parentRepairSession, CommonRange commonRange, boolean excludedDeadNodes, String keyspace,
RepairParallelism parallelismDegree, boolean isIncremental, boolean pullRepair,
PreviewKind previewKind, boolean optimiseStreams, boolean repairPaxos, boolean paxosOnly,
boolean dontPurgeTombstones, String... cfnames)
PreviewKind previewKind, boolean optimiseStreams, boolean repairData, boolean repairPaxos,
boolean dontPurgeTombstones, boolean repairAccord, String... cfnames)
{
super(SharedContext.Global.instance, new Scheduler.NoopScheduler(),
parentRepairSession, commonRange, excludedDeadNodes, keyspace, parallelismDegree, isIncremental, pullRepair,
previewKind, optimiseStreams, repairPaxos, paxosOnly, dontPurgeTombstones, false, false, cfnames);
previewKind, optimiseStreams, repairData, repairPaxos, dontPurgeTombstones, repairAccord, cfnames);
}
@Override
@ -195,7 +195,7 @@ public class RepairJobTest
this.session = new MeasureableRepairSession(parentRepairSession,
new CommonRange(neighbors, emptySet(), FULL_RANGE), false,
KEYSPACE, SEQUENTIAL, false, false,
NONE, false, true, false, false, CF);
NONE, false, true, true, false, true, CF);
this.job = new RepairJob(session, CF);
this.sessionJobDesc = new RepairJobDesc(session.state.parentRepairSession, session.getId(),

View File

@ -66,8 +66,8 @@ public class RepairSessionTest
RepairSession session = new RepairSession(SharedContext.Global.instance, new Scheduler.NoopScheduler(), parentSessionId,
new CommonRange(endpoints, Collections.emptySet(), Arrays.asList(repairRange)),
false, "Keyspace1", RepairParallelism.SEQUENTIAL,
false, false,
PreviewKind.NONE, false, false, false, false, false, false, "Standard1");
false, false, PreviewKind.NONE, false,
false, false, false, false, "Standard1");
// perform convict
session.convict(remote, Double.MAX_VALUE);

View File

@ -78,7 +78,9 @@ public class NodeToolCommandTest
public void repairCommandTest() throws IOException
{
Map<String, String> options = testRepairCommand(0, "--paxos-only", "ks");
Assert.assertEquals(options.get(RepairOption.PAXOS_ONLY_KEY), Boolean.toString(true));
Assert.assertEquals(options.get(RepairOption.REPAIR_DATA_KEY), Boolean.toString(false));
Assert.assertEquals(options.get(RepairOption.REPAIR_PAXOS_KEY), Boolean.toString(true));
Assert.assertEquals(options.get(RepairOption.REPAIR_ACCORD_KEY), Boolean.toString(false));
Assert.assertEquals(options.get(RepairOption.INCREMENTAL_KEY), Boolean.toString(false));
}