Skip to content

Commit

Permalink
Issue 6699: LTS - Prevent relocation of huge data with truncate when …
Browse files Browse the repository at this point in the history
…cluster is configured with very large max chunk size.

Signed-off-by: Sachin Joshi <sachin.joshi@emc.com>
  • Loading branch information
sachin-j-joshi committed Apr 15, 2022
1 parent 213576b commit 18192d2
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 1 deletion.
Expand Up @@ -72,6 +72,8 @@ public class ChunkedSegmentStorageConfig {

public static final Property<Boolean> RELOCATE_ON_TRUNCATE_ENABLED = Property.named("truncate.relocate.enable", true);
public static final Property<Long> MIN_TRUNCATE_RELOCATION_SIZE_BYTES = Property.named("truncate.relocate.size.bytes.min", 64 * 1024 * 1024L);
public static final Property<Long> MAX_TRUNCATE_RELOCATION_SIZE_BYTES = Property.named("truncate.relocate.size.bytes.max", 1 * 1024 * 1024 * 1024L);

public static final Property<Integer> MIN_TRUNCATE_RELOCATION_PERCENT = Property.named("truncate.relocate.percent.min", 80);

/**
Expand Down Expand Up @@ -108,6 +110,7 @@ public class ChunkedSegmentStorageConfig {
.safeStorageSizeCheckFrequencyInSeconds(60)
.relocateOnTruncateEnabled(true)
.minSizeForTruncateRelocationInbytes(64 * 1024 * 1024L)
.maxSizeForTruncateRelocationInbytes(1 * 1024 * 1024 * 1024L)
.minPercentForTruncateRelocation(80)
.build();

Expand Down Expand Up @@ -196,6 +199,12 @@ public class ChunkedSegmentStorageConfig {
@Getter
final private long minSizeForTruncateRelocationInbytes;

/**
* Maximum size of chunk after which it is not eligible for relocation.
*/
@Getter
final private long maxSizeForTruncateRelocationInbytes;

/**
* Minimum percentage of wasted space required for it to be eligible for relocation.
*/
Expand Down Expand Up @@ -341,6 +350,7 @@ public class ChunkedSegmentStorageConfig {
this.safeStorageSizeCheckFrequencyInSeconds = properties.getPositiveInt(SAFE_SIZE_CHECK_FREQUENCY);
this.relocateOnTruncateEnabled = properties.getBoolean(RELOCATE_ON_TRUNCATE_ENABLED);
this.minSizeForTruncateRelocationInbytes = properties.getPositiveLong(MIN_TRUNCATE_RELOCATION_SIZE_BYTES);
this.maxSizeForTruncateRelocationInbytes = properties.getPositiveLong(MAX_TRUNCATE_RELOCATION_SIZE_BYTES);
this.minPercentForTruncateRelocation = properties.getPositiveInt(MIN_TRUNCATE_RELOCATION_PERCENT);
}

Expand Down
Expand Up @@ -141,6 +141,7 @@ public CompletableFuture<Void> call() {

private CompletableFuture<Void> relocateFirstChunkIfRequired(MetadataTransaction txn) {
if (shouldRelocate()) {
val timer = new Timer();
String oldChunkName = segmentMetadata.getFirstChunk();
String newChunkName = chunkedSegmentStorage.getNewChunkName(handle.getSegmentName(), segmentMetadata.getStartOffset());
val startOffsetInChunk = segmentMetadata.getStartOffset() - segmentMetadata.getFirstChunkStartOffset();
Expand Down Expand Up @@ -187,12 +188,20 @@ private CompletableFuture<Void> relocateFirstChunkIfRequired(MetadataTransaction
isFirstChunkRelocated = true;
currentMetadata = newFirstChunkMetadata;
currentChunkName = newChunkName;

log.debug("{} truncate - relocated first chunk op={}, segment={}, offset={} old={} new={} relocatedBytes={} time={}.",
chunkedSegmentStorage.getLogPrefix(), System.identityHashCode(this), handle.getSegmentName(),
offset, oldChunkName, newChunkName, newLength, timer.getElapsedMillis());

}, chunkedSegmentStorage.getExecutor());
}
return CompletableFuture.completedFuture(null);
}

private CompletableFuture<Void> copyBytes(ChunkHandle writeHandle, ChunkHandle readHandle, long startOffset, long length) {
Preconditions.checkArgument(length <= chunkedSegmentStorage.getConfig().getMaxSizeForTruncateRelocationInbytes(),
"size of data exceeds max size allowed for relocation. length={}, max={} ",
length, chunkedSegmentStorage.getConfig().getMaxSizeForTruncateRelocationInbytes());
val bytesToRead = new AtomicLong(length);
val readAtOffset = new AtomicLong(startOffset);
val writeAtOffset = new AtomicLong(0);
Expand All @@ -217,6 +226,7 @@ private boolean shouldRelocate() {
&& chunkedSegmentStorage.shouldAppend()
&& !chunkedSegmentStorage.isSegmentInSystemScope(handle)
&& currentMetadata.getLength() > chunkedSegmentStorage.getConfig().getMinSizeForTruncateRelocationInbytes()
&& currentMetadata.getLength() <= chunkedSegmentStorage.getConfig().getMaxSizeForTruncateRelocationInbytes()
&& getWastedSpacePercentage() >= chunkedSegmentStorage.getConfig().getMinPercentForTruncateRelocation();
}

Expand Down
Expand Up @@ -57,6 +57,7 @@ public void testProvidedValues() {
props.setProperty(ChunkedSegmentStorageConfig.RELOCATE_ON_TRUNCATE_ENABLED.getFullName(ChunkedSegmentStorageConfig.COMPONENT_CODE), "false");
props.setProperty(ChunkedSegmentStorageConfig.MIN_TRUNCATE_RELOCATION_SIZE_BYTES.getFullName(ChunkedSegmentStorageConfig.COMPONENT_CODE), "20");
props.setProperty(ChunkedSegmentStorageConfig.MIN_TRUNCATE_RELOCATION_PERCENT.getFullName(ChunkedSegmentStorageConfig.COMPONENT_CODE), "21");
props.setProperty(ChunkedSegmentStorageConfig.MAX_TRUNCATE_RELOCATION_SIZE_BYTES.getFullName(ChunkedSegmentStorageConfig.COMPONENT_CODE), "22");

TypedProperties typedProperties = new TypedProperties(props, "storage");
ChunkedSegmentStorageConfig config = new ChunkedSegmentStorageConfig(typedProperties);
Expand Down Expand Up @@ -86,6 +87,7 @@ public void testProvidedValues() {
Assert.assertFalse(config.isRelocateOnTruncateEnabled());
Assert.assertEquals(config.getMinSizeForTruncateRelocationInbytes(), 20);
Assert.assertEquals(config.getMinPercentForTruncateRelocation(), 21);
Assert.assertEquals(config.getMaxSizeForTruncateRelocationInbytes(), 22);
}

@Test
Expand Down Expand Up @@ -123,6 +125,7 @@ private void testDefaultValues(ChunkedSegmentStorageConfig config) {
Assert.assertEquals(config.getSafeStorageSizeCheckFrequencyInSeconds(), ChunkedSegmentStorageConfig.DEFAULT_CONFIG.getSafeStorageSizeCheckFrequencyInSeconds());
Assert.assertEquals(config.isRelocateOnTruncateEnabled(), ChunkedSegmentStorageConfig.DEFAULT_CONFIG.isRelocateOnTruncateEnabled());
Assert.assertEquals(config.getMinSizeForTruncateRelocationInbytes(), ChunkedSegmentStorageConfig.DEFAULT_CONFIG.getMinSizeForTruncateRelocationInbytes());
Assert.assertEquals(config.getMaxSizeForTruncateRelocationInbytes(), ChunkedSegmentStorageConfig.DEFAULT_CONFIG.getMaxSizeForTruncateRelocationInbytes());
Assert.assertEquals(config.getMinPercentForTruncateRelocation(), ChunkedSegmentStorageConfig.DEFAULT_CONFIG.getMinPercentForTruncateRelocation());
}

Expand Down
Expand Up @@ -2272,6 +2272,24 @@ public void testMultipleRelocatingTruncate() throws Exception {
}
}

@Test
public void testRelocatingTruncateWithMaxSize() throws Exception {
val config = ChunkedSegmentStorageConfig.DEFAULT_CONFIG.toBuilder()
.indexBlockSize(3)
.relocateOnTruncateEnabled(true)
.minSizeForTruncateRelocationInbytes(1)
.maxSizeForTruncateRelocationInbytes(10)
.minPercentForTruncateRelocation(1)
.build();

@Cleanup
TestContext testContext = getTestContext(config);
val h1 = populateSegment(testContext, "test1", 11, 1);
val h2 = populateSegment(testContext, "test2", 10, 1);
testTruncate(testContext, "test1", 11, 10, 1, 11, 11);
testTruncate(testContext, "test2", 10, 9, 1, 10, 1);
}

private void testRelocatingTruncate(ChunkedSegmentStorageConfig config, int numberOfChunks, long maxChunkSize, int threshold) throws Exception {
for (int i = 0; i < numberOfChunks; i++) {
testTruncate(config, maxChunkSize, i * maxChunkSize, numberOfChunks, numberOfChunks - i, maxChunkSize * numberOfChunks, maxChunkSize);
Expand Down Expand Up @@ -2337,7 +2355,9 @@ private long[] calculateExpectedChunkLengths(ChunkedSegmentStorageConfig config,
val chunkCount = Math.toIntExact(length / maxChunkLength - startOffset / maxChunkLength); // Note two independent int divisions.
long[] expectedLengths = new long[chunkCount];
Arrays.fill(expectedLengths, maxChunkLength);
if (config.isRelocateOnTruncateEnabled() && maxChunkLength > config.getMinSizeForTruncateRelocationInbytes()) {
if (config.isRelocateOnTruncateEnabled()
&& maxChunkLength > config.getMinSizeForTruncateRelocationInbytes()
&& maxChunkLength <= config.getMaxSizeForTruncateRelocationInbytes()) {
val threshold = config.getMinPercentForTruncateRelocation() * maxChunkLength / 100;
val offset = startOffset % maxChunkLength;
expectedLengths[0] = offset >= threshold ? maxChunkLength - threshold : maxChunkLength;
Expand Down Expand Up @@ -3007,6 +3027,7 @@ public void testRelocateHugeChunks() throws Exception {
val config = ChunkedSegmentStorageConfig.DEFAULT_CONFIG.toBuilder()
.relocateOnTruncateEnabled(true)
.maxBufferSizeForChunkDataTransfer(128 * 1024 * 128)
.maxSizeForTruncateRelocationInbytes(10L * Integer.MAX_VALUE)
.build();
@Cleanup
TestContext testContext = getTestContext(config);
Expand Down

0 comments on commit 18192d2

Please sign in to comment.