Add tunable initial size and growth factor to RangeTombstoneList

Added `initial_range_tombstone_list_allocation_size` and
`range_tombstone_list_growth_factor` in config, which can be altered via JMX.

Patch By Michael Kjellman and Yifan Cai; Reviewed by Dinesh Joshi for CASSANDRA-15763

Co-Authored-By: Yifan Cai <yifan_cai@apple.com>
Co-Authored-By: Michael Kjellman <kjellman@apple.com>
This commit is contained in:
Yifan Cai 2020-04-28 13:51:15 -07:00 committed by Dinesh A. Joshi
parent 6cf352ea9c
commit da95e4b3fb
8 changed files with 172 additions and 7 deletions

View File

@ -1,4 +1,5 @@
4.0-alpha5
* Add tunable initial size and growth factor to RangeTombstoneList (CASSANDRA-15763)
* Improve debug logging in SSTableReader for index summary (CASSANDRA-15755)
* bin/sstableverify should support user provided token ranges (CASSANDRA-15753)
* Improve logging when mutation passed to commit log is too large (CASSANDRA-14781)

View File

@ -485,6 +485,15 @@ public class Config
*/
public volatile int validation_preview_purge_head_start_in_sec = 60 * 60;
/**
* The intial capacity for creating RangeTombstoneList.
*/
public volatile int initial_range_tombstone_list_allocation_size = 1;
/**
* The growth factor to enlarge a RangeTombstoneList.
*/
public volatile double range_tombstone_list_growth_factor = 1.5;
/**
* @deprecated migrate to {@link DatabaseDescriptor#isClientInitialized()}
*/

View File

@ -3090,4 +3090,23 @@ public class DatabaseDescriptor
return Math.max(seconds, 0);
}
public static int getInitialRangeTombstoneListAllocationSize()
{
return conf.initial_range_tombstone_list_allocation_size;
}
public static void setInitialRangeTombstoneListAllocationSize(int size)
{
conf.initial_range_tombstone_list_allocation_size = size;
}
public static double getRangeTombstoneListGrowthFactor()
{
return conf.range_tombstone_list_growth_factor;
}
public static void setRangeTombstoneListGrowthFactor(double resizeFactor)
{
conf.range_tombstone_list_growth_factor = resizeFactor;
}
}

View File

@ -22,6 +22,7 @@ import java.util.Iterator;
import com.google.common.base.Objects;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.rows.EncodingStats;
import org.apache.cassandra.utils.ObjectSizes;
@ -113,8 +114,8 @@ public class MutableDeletionInfo implements DeletionInfo
public void add(RangeTombstone tombstone, ClusteringComparator comparator)
{
if (ranges == null)
ranges = new RangeTombstoneList(comparator, 1);
if (ranges == null) // Introduce getInitialRangeTombstoneAllocationSize
ranges = new RangeTombstoneList(comparator, DatabaseDescriptor.getInitialRangeTombstoneListAllocationSize());
ranges.add(tombstone);
}

View File

@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.utils.AbstractIterator;
import com.google.common.collect.Iterators;
@ -667,7 +668,12 @@ public class RangeTombstoneList implements Iterable<RangeTombstone>, IMeasurable
*/
private void growToFree(int i)
{
int newLength = (capacity() * 3) / 2 + 1;
// Introduce getRangeTombstoneResizeFactor
int newLength = (int) Math.ceil(capacity() * DatabaseDescriptor.getRangeTombstoneListGrowthFactor());
// Fallback to the original calculation if the newLength calculated from the resize factor is not valid.
if (newLength <= capacity())
newLength = ((capacity() * 3) / 2) + 1;
grow(i, newLength);
}

View File

@ -5415,6 +5415,41 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
logger.info("Updated batch_size_warn_threshold_in_kb to {}", threshold);
}
public int getInitialRangeTombstoneListAllocationSize()
{
return DatabaseDescriptor.getInitialRangeTombstoneListAllocationSize();
}
public void setInitialRangeTombstoneListAllocationSize(int size)
{
if (size < 0 || size > 1024)
{
throw new IllegalStateException("Not updating initial_range_tombstone_allocation_size as it must be in the range [0, 1024] inclusive");
}
int originalSize = DatabaseDescriptor.getInitialRangeTombstoneListAllocationSize();
DatabaseDescriptor.setInitialRangeTombstoneListAllocationSize(size);
logger.info("Updated initial_range_tombstone_allocation_size from {} to {}", originalSize, size);
}
public double getRangeTombstoneResizeListGrowthFactor()
{
return DatabaseDescriptor.getRangeTombstoneListGrowthFactor();
}
public void setRangeTombstoneListResizeGrowthFactor(double growthFactor) throws IllegalStateException
{
if (growthFactor < 1.2 || growthFactor > 5)
{
throw new IllegalStateException("Not updating range_tombstone_resize_factor as growth factor must be in the range [1.2, 5.0] inclusive");
}
else
{
double originalGrowthFactor = DatabaseDescriptor.getRangeTombstoneListGrowthFactor();
DatabaseDescriptor.setRangeTombstoneListGrowthFactor(growthFactor);
logger.info("Updated range_tombstone_resize_factor from {} to {}", originalGrowthFactor, growthFactor);
}
}
public void setHintedHandoffThrottleInKB(int throttleInKB)
{
DatabaseDescriptor.setHintedHandoffThrottleInKB(throttleInKB);

View File

@ -771,4 +771,15 @@ public interface StorageServiceMBean extends NotificationEmitter
*/
public void stopFullQueryLogger();
/** Sets the initial allocation size of backing arrays for new RangeTombstoneList objects */
public void setInitialRangeTombstoneListAllocationSize(int size);
/** Returns the initial allocation size of backing arrays for new RangeTombstoneList objects */
public int getInitialRangeTombstoneListAllocationSize();
/** Sets the resize factor to use when growing/resizing a RangeTombstoneList */
public void setRangeTombstoneListResizeGrowthFactor(double growthFactor);
/** Returns the resize factor to use when growing/resizing a RangeTombstoneList */
public double getRangeTombstoneResizeListGrowthFactor();
}

View File

@ -19,22 +19,39 @@
package org.apache.cassandra.db;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.regex.Pattern;
import java.util.Iterator;
import java.util.Random;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.base.Joiner;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.distributed.impl.IsolatedExecutor;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class RangeTombstoneListTest
{
private static final ClusteringComparator cmp = new ClusteringComparator(Int32Type.instance);
@BeforeClass
public static void beforeClass()
{
// Needed to initialize initial_range_tombstone_allocation_size and range_tombstone_resize_factor
DatabaseDescriptor.daemonInitialization();
}
@Test
public void sortedAdditionTest()
{
@ -539,6 +556,72 @@ public class RangeTombstoneListTest
assertEquals(6, l.searchDeletionTime(clustering(1000)).markedForDeleteAt());
}
@Test
public void testSetResizeFactor()
{
double original = DatabaseDescriptor.getRangeTombstoneListGrowthFactor();
final StorageService storageService = StorageService.instance;
final Consumer<Throwable> expectIllegalStateExceptio = exception -> {
assertSame(IllegalStateException.class, exception.getClass());
assertEquals("Not updating range_tombstone_resize_factor as growth factor must be in the range [1.2, 5.0] inclusive" , exception.getMessage());
};
try
{
// prevent bad ones
assertHasException(() -> storageService.setRangeTombstoneListResizeGrowthFactor(-1), expectIllegalStateExceptio);
assertHasException(() -> storageService.setRangeTombstoneListResizeGrowthFactor(0), expectIllegalStateExceptio);
assertHasException(() -> storageService.setRangeTombstoneListResizeGrowthFactor(1.1), expectIllegalStateExceptio);
assertHasException(() -> storageService.setRangeTombstoneListResizeGrowthFactor(5.1), expectIllegalStateExceptio);
// accept good ones
storageService.setRangeTombstoneListResizeGrowthFactor(1.2);
storageService.setRangeTombstoneListResizeGrowthFactor(2.0);
storageService.setRangeTombstoneListResizeGrowthFactor(5.0);
}
finally
{
storageService.setRangeTombstoneListResizeGrowthFactor(original);
}
}
@Test
public void testSetInitialAllocationSize()
{
int original = DatabaseDescriptor.getInitialRangeTombstoneListAllocationSize();
final StorageService storageService = StorageService.instance;
final Consumer<Throwable> expectIllegalStateExceptio = exception -> {
assertSame(String.format("The actual exception message:<%s>", exception.getMessage()), IllegalStateException.class, exception.getClass());
assertEquals("Not updating initial_range_tombstone_allocation_size as it must be in the range [0, 1024] inclusive" , exception.getMessage());
};
try
{
// prevent bad ones
assertHasException(() -> storageService.setInitialRangeTombstoneListAllocationSize(-1), expectIllegalStateExceptio);
assertHasException(() -> storageService.setInitialRangeTombstoneListAllocationSize(1025), expectIllegalStateExceptio);
// accept good ones
storageService.setInitialRangeTombstoneListAllocationSize(1);
storageService.setInitialRangeTombstoneListAllocationSize(1024);
}
finally
{
storageService.setInitialRangeTombstoneListAllocationSize(original);
}
}
private void assertHasException(IsolatedExecutor.ThrowingRunnable block, Consumer<Throwable> verifier)
{
try
{
block.run();
fail("Expect the code block to throw but not");
}
catch (Throwable throwable)
{
verifier.accept(throwable);
}
}
private static void assertRT(RangeTombstone expected, RangeTombstone actual)
{
assertTrue(String.format("%s != %s", toString(expected), toString(actual)), cmp.compare(expected.deletedSlice().start(), actual.deletedSlice().start()) == 0);