Skip to content

Commit 2fad33b

Browse files
authored
Add small files check in garbage collection (apache#3631)
### Motivation When we use `TransactionalEntryLogCompactor` to compact the entry log files, it will generate a lot of small entry log files, and for those files, the file usage is usually greater than 90%, which can not be compacted unless the file usage decreased. ![image](https://user-images.githubusercontent.com/5436568/201135615-4d6072f5-e353-483d-9afb-48fad8134044.png) ### Changes We introduce the entry log file size check during compaction, and the checker is controlled by `gcEntryLogSizeRatio`. If the total entry log file size is less than `gcEntryLogSizeRatio * logSizeLimit`, the entry log file will be compacted even though the file usage is greater than 90%. This feature is disabled by default and the `gcEntryLogSizeRatio` default value is `0.0`
1 parent 8aee66a commit 2fad33b

File tree

4 files changed

+162
-3
lines changed

4 files changed

+162
-3
lines changed

bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/GarbageCollectorThread.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -561,15 +561,20 @@ void doCompactEntryLogs(double threshold, long maxTimeMillis) throws EntryLogMet
561561
MutableLong timeDiff = new MutableLong(0);
562562

563563
entryLogMetaMap.forEach((entryLogId, meta) -> {
564-
int bucketIndex = calculateUsageIndex(numBuckets, meta.getUsage());
564+
double usage = meta.getUsage();
565+
if (conf.isUseTargetEntryLogSizeForGc() && usage < 1.0d) {
566+
usage = (double) meta.getRemainingSize() / Math.max(meta.getTotalSize(), conf.getEntryLogSizeLimit());
567+
}
568+
int bucketIndex = calculateUsageIndex(numBuckets, usage);
565569
entryLogUsageBuckets[bucketIndex]++;
566570

567571
if (timeDiff.getValue() < maxTimeMillis) {
568572
end.setValue(System.currentTimeMillis());
569573
timeDiff.setValue(end.getValue() - start);
570574
}
571-
if (meta.getUsage() >= threshold || (maxTimeMillis > 0 && timeDiff.getValue() >= maxTimeMillis)
572-
|| !running) {
575+
if ((usage >= threshold
576+
|| (maxTimeMillis > 0 && timeDiff.getValue() >= maxTimeMillis)
577+
|| !running)) {
573578
// We allow the usage limit calculation to continue so that we get an accurate
574579
// report of where the usage was prior to running compaction.
575580
return;

bookkeeper-server/src/main/java/org/apache/bookkeeper/conf/ServerConfiguration.java

+10
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ public class ServerConfiguration extends AbstractConfiguration<ServerConfigurati
116116
protected static final String VERIFY_METADATA_ON_GC = "verifyMetadataOnGC";
117117
protected static final String GC_ENTRYLOGMETADATA_CACHE_ENABLED = "gcEntryLogMetadataCacheEnabled";
118118
protected static final String GC_ENTRYLOG_METADATA_CACHE_PATH = "gcEntryLogMetadataCachePath";
119+
protected static final String USE_TARGET_ENTRYLOG_SIZE_FOR_GC = "useTargetEntryLogSizeForGc";
119120
// Scrub Parameters
120121
protected static final String LOCAL_SCRUB_PERIOD = "localScrubInterval";
121122
protected static final String LOCAL_SCRUB_RATE_LIMIT = "localScrubRateLimit";
@@ -554,6 +555,15 @@ public ServerConfiguration setGcEntryLogMetadataCachePath(String gcEntrylogMetad
554555
return this;
555556
}
556557

558+
public boolean isUseTargetEntryLogSizeForGc() {
559+
return getBoolean(USE_TARGET_ENTRYLOG_SIZE_FOR_GC, false);
560+
}
561+
562+
public ServerConfiguration setUseTargetEntryLogSizeForGc(boolean useTargetEntryLogSizeForGc) {
563+
this.setProperty(USE_TARGET_ENTRYLOG_SIZE_FOR_GC, useTargetEntryLogSizeForGc);
564+
return this;
565+
}
566+
557567
/**
558568
* Get whether local scrub is enabled.
559569
*

bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/GarbageCollectorThreadTest.java

+135
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import static org.hamcrest.Matchers.empty;
3131
import static org.hamcrest.Matchers.equalTo;
3232
import static org.hamcrest.Matchers.greaterThan;
33+
import static org.junit.Assert.assertEquals;
3334
import static org.junit.Assert.assertFalse;
3435
import static org.junit.Assert.assertTrue;
3536
import static org.mockito.Mockito.any;
@@ -238,4 +239,138 @@ private void testExtractMetaFromEntryLogs(EntryLogger entryLogger, File ledgerDi
238239
assertTrue(entryLogMetaMap.isEmpty());
239240
assertFalse(entryLogger.logExists(logId3));
240241
}
242+
243+
@Test
244+
public void testCompactionWithFileSizeCheck() throws Exception {
245+
File ledgerDir = tmpDirs.createNew("testFileSize", "ledgers");
246+
EntryLogger entryLogger = newLegacyEntryLogger(20000, ledgerDir);
247+
248+
MockLedgerStorage storage = new MockLedgerStorage();
249+
MockLedgerManager lm = new MockLedgerManager();
250+
251+
GarbageCollectorThread gcThread = new GarbageCollectorThread(
252+
TestBKConfiguration.newServerConfiguration().setUseTargetEntryLogSizeForGc(true), lm,
253+
newDirsManager(ledgerDir),
254+
storage, entryLogger, NullStatsLogger.INSTANCE);
255+
256+
// Add entries.
257+
// Ledger 1 is on first entry log
258+
// Ledger 2 spans first, second and third entry log
259+
// Ledger 3 is on the third entry log (which is still active when extract meta)
260+
long loc1 = entryLogger.addEntry(1L, makeEntry(1L, 1L, 5000));
261+
long loc2 = entryLogger.addEntry(2L, makeEntry(2L, 1L, 5000));
262+
assertThat(logIdFromLocation(loc2), equalTo(logIdFromLocation(loc1)));
263+
long loc3 = entryLogger.addEntry(2L, makeEntry(2L, 2L, 15000));
264+
assertThat(logIdFromLocation(loc3), greaterThan(logIdFromLocation(loc2)));
265+
long loc4 = entryLogger.addEntry(2L, makeEntry(2L, 3L, 15000));
266+
assertThat(logIdFromLocation(loc4), greaterThan(logIdFromLocation(loc3)));
267+
long loc5 = entryLogger.addEntry(3L, makeEntry(3L, 1L, 1000));
268+
assertThat(logIdFromLocation(loc5), equalTo(logIdFromLocation(loc4)));
269+
long loc6 = entryLogger.addEntry(3L, makeEntry(3L, 2L, 5000));
270+
271+
long logId1 = logIdFromLocation(loc2);
272+
long logId2 = logIdFromLocation(loc3);
273+
long logId3 = logIdFromLocation(loc5);
274+
long logId4 = logIdFromLocation(loc6);
275+
entryLogger.flush();
276+
277+
storage.setMasterKey(1L, new byte[0]);
278+
storage.setMasterKey(2L, new byte[0]);
279+
storage.setMasterKey(3L, new byte[0]);
280+
281+
assertThat(entryLogger.getFlushedLogIds(), containsInAnyOrder(logId1, logId2, logId3));
282+
assertTrue(entryLogger.logExists(logId1));
283+
assertTrue(entryLogger.logExists(logId2));
284+
assertTrue(entryLogger.logExists(logId3));
285+
assertTrue(entryLogger.logExists(logId4));
286+
287+
// all ledgers exist, nothing should disappear
288+
final EntryLogMetadataMap entryLogMetaMap = gcThread.getEntryLogMetaMap();
289+
gcThread.extractMetaFromEntryLogs();
290+
291+
assertThat(entryLogger.getFlushedLogIds(), containsInAnyOrder(logId1, logId2, logId3));
292+
assertTrue(entryLogMetaMap.containsKey(logId1));
293+
assertTrue(entryLogMetaMap.containsKey(logId2));
294+
assertTrue(entryLogger.logExists(logId3));
295+
296+
storage.deleteLedger(1);
297+
// only logId 1 will be compacted.
298+
gcThread.runWithFlags(true, true, false);
299+
300+
// logId1 and logId2 should be compacted
301+
assertFalse(entryLogger.logExists(logId1));
302+
assertTrue(entryLogger.logExists(logId2));
303+
assertTrue(entryLogger.logExists(logId3));
304+
assertFalse(entryLogMetaMap.containsKey(logId1));
305+
assertTrue(entryLogMetaMap.containsKey(logId2));
306+
307+
assertEquals(1, storage.getUpdatedLocations().size());
308+
309+
EntryLocation location2 = storage.getUpdatedLocations().get(0);
310+
assertEquals(2, location2.getLedger());
311+
assertEquals(1, location2.getEntry());
312+
assertEquals(logIdFromLocation(location2.getLocation()), logId4);
313+
}
314+
315+
@Test
316+
public void testCompactionWithoutFileSizeCheck() throws Exception {
317+
File ledgerDir = tmpDirs.createNew("testFileSize", "ledgers");
318+
EntryLogger entryLogger = newLegacyEntryLogger(20000, ledgerDir);
319+
320+
MockLedgerStorage storage = new MockLedgerStorage();
321+
MockLedgerManager lm = new MockLedgerManager();
322+
323+
GarbageCollectorThread gcThread = new GarbageCollectorThread(
324+
TestBKConfiguration.newServerConfiguration(), lm,
325+
newDirsManager(ledgerDir),
326+
storage, entryLogger, NullStatsLogger.INSTANCE);
327+
328+
// Add entries.
329+
// Ledger 1 is on first entry log
330+
// Ledger 2 spans first, second and third entry log
331+
// Ledger 3 is on the third entry log (which is still active when extract meta)
332+
long loc1 = entryLogger.addEntry(1L, makeEntry(1L, 1L, 5000));
333+
long loc2 = entryLogger.addEntry(2L, makeEntry(2L, 1L, 5000));
334+
assertThat(logIdFromLocation(loc2), equalTo(logIdFromLocation(loc1)));
335+
long loc3 = entryLogger.addEntry(2L, makeEntry(2L, 2L, 15000));
336+
assertThat(logIdFromLocation(loc3), greaterThan(logIdFromLocation(loc2)));
337+
long loc4 = entryLogger.addEntry(2L, makeEntry(2L, 3L, 15000));
338+
assertThat(logIdFromLocation(loc4), greaterThan(logIdFromLocation(loc3)));
339+
long loc5 = entryLogger.addEntry(3L, makeEntry(3L, 1L, 1000));
340+
assertThat(logIdFromLocation(loc5), equalTo(logIdFromLocation(loc4)));
341+
342+
long logId1 = logIdFromLocation(loc2);
343+
long logId2 = logIdFromLocation(loc3);
344+
long logId3 = logIdFromLocation(loc5);
345+
entryLogger.flush();
346+
347+
storage.setMasterKey(1L, new byte[0]);
348+
storage.setMasterKey(2L, new byte[0]);
349+
storage.setMasterKey(3L, new byte[0]);
350+
351+
assertThat(entryLogger.getFlushedLogIds(), containsInAnyOrder(logId1, logId2));
352+
assertTrue(entryLogger.logExists(logId1));
353+
assertTrue(entryLogger.logExists(logId2));
354+
assertTrue(entryLogger.logExists(logId3));
355+
356+
// all ledgers exist, nothing should disappear
357+
final EntryLogMetadataMap entryLogMetaMap = gcThread.getEntryLogMetaMap();
358+
gcThread.extractMetaFromEntryLogs();
359+
360+
assertThat(entryLogger.getFlushedLogIds(), containsInAnyOrder(logId1, logId2));
361+
assertTrue(entryLogMetaMap.containsKey(logId1));
362+
assertTrue(entryLogMetaMap.containsKey(logId2));
363+
assertTrue(entryLogger.logExists(logId3));
364+
365+
gcThread.runWithFlags(true, true, false);
366+
367+
assertTrue(entryLogger.logExists(logId1));
368+
assertTrue(entryLogger.logExists(logId2));
369+
assertTrue(entryLogger.logExists(logId3));
370+
assertTrue(entryLogMetaMap.containsKey(logId1));
371+
assertTrue(entryLogMetaMap.containsKey(logId2));
372+
373+
assertEquals(0, storage.getUpdatedLocations().size());
374+
}
375+
241376
}

conf/bk_server.conf

+9
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,15 @@ gcEntryLogMetadataCacheEnabled=false
621621
# name "entrylogIndexCache"]
622622
# gcEntryLogMetadataCachePath=
623623

624+
# When judging whether an entry log file need to be compacted, we calculate the usage rate of the entry log file based
625+
# on the actual size of the entry log file. However, if an entry log file is 1MB in size and 0.9MB of data is
626+
# being used, this entry log file won't be compacted by garbage collector due to the high usage ratio,
627+
# which will result in many small entry log files.
628+
# We introduced the parameter `useTargetEntryLogSizeForGc` to determine whether to calculate entry log file usage
629+
# based on the configured target entry log file size, which is configured by `logSizeLimit`.
630+
# Default: useTargetEntryLogSizeForGc is false.
631+
# useTargetEntryLogSizeForGc=false
632+
624633
#############################################################################
625634
## Disk utilization
626635
#############################################################################

0 commit comments

Comments
 (0)