Skip to content

Commit

Permalink
IntervalTree should support copyAndReplace when ranges are unchanged (C…
Browse files Browse the repository at this point in the history
  • Loading branch information
Yukei7 committed Dec 24, 2024
1 parent c44008e commit 67355f3
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 4 deletions.
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);
}
}

0 comments on commit 67355f3

Please sign in to comment.