mirror of https://github.com/apache/cassandra
Split accord migration into two phases
Patch by Ariel Weisberg; Reviewed by Benedict Elliott Smith for CASSANDRA-19436
This commit is contained in:
parent
513509ee2c
commit
b29eac16fe
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue