Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IntervalTree should support copyAndReplace when ranges are unchanged (CASSANDRA-20158) #3754

Open
wants to merge 1 commit into
base: cassandra-4.1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
4.1.8
* IntervalTree should support updateIntervals for checkpoint when ranges are unchanged (CASSANDRA-20158)
* Add nodetool checktokenmetadata command that checks TokenMetadata is insync with Gossip endpointState (CASSANDRA-18758)
* Backport Java 11 support for Simulator (CASSANDRA-17178/CASSANDRA-19935)
* Equality check for Paxos.Electorate should not depend on collection types (CASSANDRA-19935)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,26 @@ public class SSTableIntervalTree extends IntervalTree<PartitionPosition, SSTable
super(intervals);
}

SSTableIntervalTree(int count, IntervalNode head)
{
super(count, head);
}

public static SSTableIntervalTree empty()
{
return EMPTY;
}

public static SSTableIntervalTree build(Iterable<SSTableReader> sstables)
{
return new SSTableIntervalTree(buildIntervals(sstables));
List<Interval<PartitionPosition, SSTableReader>> intervals = buildIntervals(sstables);
SSTableIntervalTree tree = new SSTableIntervalTree(intervals);
return tree;
}

public static SSTableIntervalTree replaceSSTables(SSTableIntervalTree tree, Iterable<SSTableReader> sstables)
{
return new SSTableIntervalTree(tree.count, tree.head.copyAndReplace(buildIntervals(sstables)));
}

public static List<Interval<PartitionPosition, SSTableReader>> buildIntervals(Iterable<SSTableReader> sstables)
Expand Down
23 changes: 23 additions & 0 deletions src/java/org/apache/cassandra/db/lifecycle/View.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.*;

Expand Down Expand Up @@ -283,6 +284,13 @@ static Function<View, View> updateLiveSet(final Set<SSTableReader> remove, final
public View apply(View view)
{
Map<SSTableReader, SSTableReader> sstableMap = replace(view.sstablesMap, remove, add);
if (isRangesUnchanged(remove, add))
{
// If the SSTable before and after has the same key range, then there is no need to rebuild the interval tree.
// Instead, we find and update the node
return new View(view.liveMemtables, view.flushingMemtables, sstableMap, view.compactingMap,
SSTableIntervalTree.replaceSSTables(view.intervalTree, add));
}
return new View(view.liveMemtables, view.flushingMemtables, sstableMap, view.compactingMap,
SSTableIntervalTree.build(sstableMap.keySet()));
}
Expand Down Expand Up @@ -353,4 +361,19 @@ public boolean apply(T t)
}
};
}

private static int getSSTablesHash(Iterable<SSTableReader> readers) {
int hashSum = 0;
for (SSTableReader reader : readers) {
if (reader != null) {
hashSum += Objects.hashCode(reader.descriptor.hashCode(), reader.first, reader.last);
}
}
return hashSum;
}

private static boolean isRangesUnchanged(final Set<SSTableReader> remove, final Iterable<SSTableReader> add)
{
return getSSTablesHash(remove) == getSSTablesHash(add);
}
}
95 changes: 92 additions & 3 deletions src/java/org/apache/cassandra/utils/IntervalTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,14 @@ public class IntervalTree<C extends Comparable<? super C>, D, I extends Interval
@SuppressWarnings("unchecked")
private static final IntervalTree EMPTY_TREE = new IntervalTree(null);

private final IntervalNode head;
private final int count;
protected final IntervalNode head;
protected final int count;

protected IntervalTree(int count, IntervalNode head)
{
this.head = head;
this.count = count;
}

protected IntervalTree(Collection<I> intervals)
{
Expand Down Expand Up @@ -142,7 +148,7 @@ public final int hashCode()
return result;
}

private class IntervalNode
protected class IntervalNode
{
final C center;
final C low;
Expand Down Expand Up @@ -217,6 +223,17 @@ else if (candidate.min.compareTo(center) > 0)
}
}

public IntervalNode(C center, C low, C high, List<I> intersectsLeft, List<I> intersectsRight, IntervalNode left, IntervalNode right)
{
this.center = center;
this.low = low;
this.high = high;
this.intersectsLeft = intersectsLeft;
this.intersectsRight = intersectsRight;
this.left = left;
this.right = right;
}

void searchInternal(Interval<C, D> searchInterval, List<D> results)
{
if (center.compareTo(searchInterval.min) < 0)
Expand Down Expand Up @@ -256,6 +273,78 @@ else if (center.compareTo(searchInterval.max) > 0)
right.searchInternal(searchInterval, results);
}
}

public IntervalNode copyAndReplace(List<I> intervals)
{
return copyAndReplaceHelper(this, intervals);
}

public IntervalNode copyAndReplaceHelper(IntervalNode node, List<I> intervals)
{
if (node == null || intervals.isEmpty())
return node;

List<I> leftSegment = new ArrayList<>();
List<I> rightSegment = new ArrayList<>();
List<I> newIntersectsLeft = new ArrayList<>(node.intersectsLeft);
List<I> newIntersectsRight = new ArrayList<>(node.intersectsRight);
int updated = 0;

for (I interval : intervals)
{
if (node.center.compareTo(interval.min) < 0)
{
rightSegment.add(interval);
}
else if (node.center.compareTo(interval.max) > 0)
{
leftSegment.add(interval);
}
else
{
// intersects in current node
boolean leftUpdated = false;
boolean rightUpdated = false;

int i = Interval.<C, D>minOrdering().binarySearchAsymmetric(node.intersectsLeft, interval.min, Op.CEIL);
while (i < node.intersectsLeft.size())
{
if (node.intersectsLeft.get(i).equals(interval))
{
newIntersectsLeft.set(i, interval);
leftUpdated = true;
break;
}
i++;
}

int j = Interval.<C, D>maxOrdering().binarySearchAsymmetric(node.intersectsRight, interval.max, Op.CEIL);
while (j < node.intersectsRight.size())
{
if (node.intersectsRight.get(j).equals(interval))
{
newIntersectsRight.set(j, interval);
rightUpdated = true;
break;
}
j++;
}
assert leftUpdated && rightUpdated : "leftupdated = " + leftUpdated + ", rightupdated = " + rightUpdated;
updated++;
}
}

assert leftSegment.size() + rightSegment.size() + updated == intervals.size() :
"leftSegment size (" + leftSegment.size() + ") + rightSegment size (" + rightSegment.size() +
") + updated (" + updated + ") != intervals size (" + intervals.size() + ')';
return new IntervalNode(node.center,
node.low,
node.high,
updated > 0 ? newIntersectsLeft : node.intersectsLeft,
updated > 0 ? newIntersectsRight : node.intersectsRight,
copyAndReplaceHelper(node.left, leftSegment),
copyAndReplaceHelper(node.right, rightSegment));
}
}

private class TreeIterator extends AbstractIterator<I>
Expand Down
82 changes: 82 additions & 0 deletions test/unit/org/apache/cassandra/utils/IntervalTreeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.Collections;
import java.util.List;

import com.google.common.base.Objects;
import org.junit.Test;
import org.apache.cassandra.io.ISerializer;
import org.apache.cassandra.io.IVersionedSerializer;
Expand All @@ -36,6 +37,8 @@
import org.apache.cassandra.io.util.DataOutputPlus;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;

public class IntervalTreeTest
{
Expand Down Expand Up @@ -195,4 +198,83 @@ public long serializedSize(String v)

assertEquals(intervals, intervals2);
}

@Test
public void testCopyAndReplace()
{
class DummyObj
{
final String data;
public DummyObj(String d)
{
data = d;
}
@Override
public final boolean equals(Object o)
{
if(!(o instanceof DummyObj))
return false;

DummyObj that = (DummyObj)o;
return Objects.equal(data, that.data);
}
}
List<Interval<Integer, DummyObj>> intervals = new ArrayList<>();

DummyObj data1 = new DummyObj("b");
DummyObj data2 = new DummyObj("i");
DummyObj newData1 = new DummyObj("b");
DummyObj newData2 = new DummyObj("i");

Interval<Integer, DummyObj> target1 = Interval.create(-3, -2, data1);
Interval<Integer, DummyObj> target2 = Interval.create(8, 9, data2);

// the two new intervals should replace the old ones in tree
Interval<Integer, DummyObj> newTarget1 = Interval.create(-3, -2, newData1);
Interval<Integer, DummyObj> newTarget2 = Interval.create(8, 9, newData2);

intervals.add(Interval.create(-300, -200, new DummyObj("a")));
intervals.add(target1);
intervals.add(Interval.create(1, 2, new DummyObj("c")));
intervals.add(Interval.create(1, 3, new DummyObj("d")));
intervals.add(Interval.create(2, 4, new DummyObj("e")));
intervals.add(Interval.create(3, 6, new DummyObj("f")));
intervals.add(Interval.create(4, 6, new DummyObj("g")));
intervals.add(Interval.create(5, 7, new DummyObj("h")));
intervals.add(target2);
intervals.add(Interval.create(15, 20, new DummyObj("j")));
intervals.add(Interval.create(40, 50, new DummyObj("k")));
intervals.add(Interval.create(49, 60, new DummyObj("l")));

IntervalTree<Integer, DummyObj, Interval<Integer, DummyObj>> it = IntervalTree.build(intervals);
assertEquals(3, it.search(Interval.create(4, 4)).size());
assertEquals(4, it.search(Interval.create(4, 5)).size());
assertEquals(7, it.search(Interval.create(-1, 10)).size());
assertEquals(0, it.search(Interval.create(-1, -1)).size());
assertEquals(5, it.search(Interval.create(1, 4)).size());
assertEquals(2, it.search(Interval.create(0, 1)).size());
assertEquals(0, it.search(Interval.create(10, 12)).size());
List<DummyObj> intersection1 = it.search(Interval.create(-3, -2));
assertSame(intersection1.get(0), data1);
List<DummyObj> intersection2 = it.search(Interval.create(8, 9));
assertSame(intersection2.get(0), data2);

List<Interval<Integer, DummyObj>> toUpdate = new ArrayList<>();
toUpdate.add(newTarget1);
toUpdate.add(newTarget2);
it = new IntervalTree<>(it.count, it.head.copyAndReplace(toUpdate));
assertEquals(3, it.search(Interval.create(4, 4)).size());
assertEquals(4, it.search(Interval.create(4, 5)).size());
assertEquals(7, it.search(Interval.create(-1, 10)).size());
assertEquals(0, it.search(Interval.create(-1, -1)).size());
assertEquals(5, it.search(Interval.create(1, 4)).size());
assertEquals(2, it.search(Interval.create(0, 1)).size());
assertEquals(0, it.search(Interval.create(10, 12)).size());
intersection1 = it.search(Interval.create(-3, -2));
assertNotSame(intersection1.get(0), data1);
assertSame(intersection1.get(0), newData1);
intersection2 = it.search(Interval.create(8, 9));
assertNotSame(intersection1.get(0), data2);
assertSame(intersection2.get(0), newData2);
}
}