Skip to content

Commit 68bf7a2

Browse files
committed
Fix compacted entry log recovery filename
1 parent cbb3367 commit 68bf7a2

2 files changed

Lines changed: 164 additions & 1 deletion

File tree

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1470,7 +1470,8 @@ public Collection<CompactionEntryLog> incompleteCompactionLogs() {
14701470
}
14711471
continue;
14721472
}
1473-
File finalLogFile = new File(compactedFile.getParentFile(), compactionLogId + ".log");
1473+
File finalLogFile = new File(compactedFile.getParentFile(),
1474+
logId2HexString(compactionLogId) + ".log");
14741475

14751476
compactionLogs.add(
14761477
new EntryLoggerCompactionEntryLog(compactionLogId, compactedLogId,

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

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import java.nio.channels.FileChannel;
4949
import java.nio.charset.Charset;
5050
import java.util.ArrayList;
51+
import java.util.Collection;
5152
import java.util.HashSet;
5253
import java.util.LinkedList;
5354
import java.util.List;
@@ -65,6 +66,8 @@
6566
import java.util.concurrent.locks.Lock;
6667
import org.apache.bookkeeper.bookie.DefaultEntryLogger.BufferedLogChannel;
6768
import org.apache.bookkeeper.bookie.LedgerDirsManager.NoWritableLedgerDirException;
69+
import org.apache.bookkeeper.bookie.storage.CompactionEntryLog;
70+
import org.apache.bookkeeper.bookie.storage.EntryLogScanner;
6871
import org.apache.bookkeeper.common.testing.annotations.FlakyTest;
6972
import org.apache.bookkeeper.conf.ServerConfiguration;
7073
import org.apache.bookkeeper.conf.TestBKConfiguration;
@@ -983,6 +986,165 @@ public void testReadEntryWithoutLedgerID() throws Exception {
983986
}
984987
}
985988

989+
@Test
990+
public void testRecoverCompactedLogUsesHexFinalLogFileNameForLogIdTen() throws Exception {
991+
// The next compaction log id is previousLogId + 1. Log id 10 is named "a.log",
992+
// so recovery must not rebuild the final log file as the decimal name "10.log".
993+
verifyRecoveredCompactionLogUsesExpectedFinalLogFileName(9, "a.log", "10.log");
994+
}
995+
996+
@Test
997+
public void testRecoverCompactedLogUsesHexFinalLogFileNameForLogIdSixteen() throws Exception {
998+
// Log id 16 is named "10.log". This covers a numeric-looking hex name that
999+
// was previously rebuilt as the decimal name "16.log" during recovery.
1000+
verifyRecoveredCompactionLogUsesExpectedFinalLogFileName(15, "10.log", "16.log");
1001+
}
1002+
1003+
@Test
1004+
public void testRecoverCompactedLogUsesHexFinalLogFileNameForLargerLogId() throws Exception {
1005+
// Use a larger id to make the decimal/hex mismatch obvious: 500 decimal is
1006+
// "1f4" in the entry log file name.
1007+
verifyRecoveredCompactionLogUsesExpectedFinalLogFileName(499, "1f4.log", "500.log");
1008+
}
1009+
1010+
@Test
1011+
public void testRecoverCompactedLogKeepsSingleDigitFinalLogFileName() throws Exception {
1012+
// Single-digit ids have the same decimal and hex file name. Keep this case to
1013+
// show the recovery path remains compatible when the old and new names match.
1014+
verifyRecoveredCompactionLogUsesExpectedFinalLogFileName(8, "9.log", null);
1015+
}
1016+
1017+
@Test
1018+
public void testNewCompactionLogKeepsHexFinalLogFileName() throws Exception {
1019+
long sourceLogId = 1L;
1020+
long previousLogId = 499L;
1021+
long expectedCompactionLogId = previousLogId + 1;
1022+
String expectedFinalLogFileName = "1f4.log";
1023+
long ledgerId = 1234L;
1024+
long entryId = 5L;
1025+
ByteBuf entry = generateEntry(ledgerId, entryId);
1026+
1027+
resetEntryLoggerWithLastLogId(previousLogId);
1028+
1029+
// This is the normal, non-recovery compaction path. finalLogFile is derived
1030+
// from the current compaction log file name, so it should already be hex.
1031+
CompactionEntryLog compactionLog = entryLogger.newCompactionLog(sourceLogId);
1032+
assertEquals(expectedCompactionLogId, compactionLog.getDstLogId());
1033+
1034+
try {
1035+
compactionLog.addEntry(ledgerId, entry);
1036+
compactionLog.flush();
1037+
// markCompacted() leaves a recovery marker such as
1038+
// "1f4.log.1.compacted" and removes the temporary compacting file.
1039+
compactionLog.markCompacted();
1040+
1041+
File compactedFile = new File(curDir, expectedFinalLogFileName + "."
1042+
+ DefaultEntryLogger.logId2HexString(sourceLogId)
1043+
+ TransactionalEntryLogCompactor.COMPACTED_SUFFIX);
1044+
File finalLogFile = new File(curDir, expectedFinalLogFileName);
1045+
assertTrue(compactedFile.exists(), "Compacted recovery marker should exist");
1046+
assertFalse(finalLogFile.exists(), "Final log file should not exist before makeAvailable");
1047+
1048+
// makeAvailable() is the point where the final entry log file is created
1049+
// on disk. Normal compaction must create "1f4.log", not "500.log".
1050+
compactionLog.makeAvailable();
1051+
assertTrue(finalLogFile.exists(), "Normal compaction path should keep the hex-named final log file");
1052+
assertFalse(new File(curDir, "500.log").exists(),
1053+
"Normal compaction path should not create a decimal-named log file");
1054+
1055+
assertCompactionLogContainsEntry(compactionLog, ledgerId, entryId);
1056+
} finally {
1057+
ReferenceCountUtil.release(entry);
1058+
}
1059+
}
1060+
1061+
private void verifyRecoveredCompactionLogUsesExpectedFinalLogFileName(long previousLogId,
1062+
String expectedFinalLogFileName,
1063+
String wrongDecimalLogFileName)
1064+
throws Exception {
1065+
long sourceLogId = 1L;
1066+
long expectedCompactionLogId = previousLogId + 1;
1067+
long ledgerId = 1234L;
1068+
long entryId = 5L;
1069+
ByteBuf entry = generateEntry(ledgerId, entryId);
1070+
1071+
// Force the next compaction log id to a known value. This lets the test
1072+
// control both the compacted marker name and the expected final log name.
1073+
resetEntryLoggerWithLastLogId(previousLogId);
1074+
1075+
CompactionEntryLog compactionLog = entryLogger.newCompactionLog(sourceLogId);
1076+
assertEquals(expectedCompactionLogId, compactionLog.getDstLogId());
1077+
1078+
try {
1079+
compactionLog.addEntry(ledgerId, entry);
1080+
compactionLog.flush();
1081+
// Simulate a restart after the compaction log was marked compacted but
1082+
// before makeAvailable()/index recovery finished. The remaining file is
1083+
// named "<dst-hex>.log.<src-hex>.compacted".
1084+
compactionLog.markCompacted();
1085+
1086+
File compactedFile = new File(curDir, expectedFinalLogFileName + "."
1087+
+ DefaultEntryLogger.logId2HexString(sourceLogId)
1088+
+ TransactionalEntryLogCompactor.COMPACTED_SUFFIX);
1089+
assertTrue(compactedFile.exists(), "Compacted recovery marker should exist");
1090+
assertFalse(new File(curDir, expectedFinalLogFileName).exists(),
1091+
"Final log file should not exist before recovery");
1092+
1093+
// incompleteCompactionLogs() rebuilds EntryLoggerCompactionEntryLog from
1094+
// the marker. The bug was here: the rebuilt finalLogFile used the decimal
1095+
// compactionLogId instead of the original hex entry log file name.
1096+
Collection<CompactionEntryLog> incompleteLogs = entryLogger.incompleteCompactionLogs();
1097+
assertEquals(1, incompleteLogs.size());
1098+
1099+
CompactionEntryLog recoveredLog = incompleteLogs.iterator().next();
1100+
assertEquals(expectedCompactionLogId, recoveredLog.getDstLogId());
1101+
assertEquals(sourceLogId, recoveredLog.getSrcLogId());
1102+
1103+
// Recovery creates the final entry log when makeAvailable() is called.
1104+
// It must create the hex name and must not leave a decimal-named log.
1105+
recoveredLog.makeAvailable();
1106+
assertTrue(new File(curDir, expectedFinalLogFileName).exists(),
1107+
"Recovery should make the hex-named log file available");
1108+
if (wrongDecimalLogFileName != null) {
1109+
assertFalse(new File(curDir, wrongDecimalLogFileName).exists(),
1110+
"Recovery should not create a decimal-named log file");
1111+
}
1112+
1113+
assertCompactionLogContainsEntry(recoveredLog, ledgerId, entryId);
1114+
} finally {
1115+
ReferenceCountUtil.release(entry);
1116+
}
1117+
}
1118+
1119+
private void assertCompactionLogContainsEntry(CompactionEntryLog compactionLog,
1120+
long ledgerId,
1121+
long entryId) throws Exception {
1122+
AtomicBoolean foundEntry = new AtomicBoolean(false);
1123+
// Scanning uses the compaction log id to resolve the final log file. This
1124+
// catches cases where makeAvailable() created a file under the wrong name.
1125+
compactionLog.scan(new EntryLogScanner() {
1126+
@Override
1127+
public boolean accept(long ledgerId) {
1128+
return true;
1129+
}
1130+
1131+
@Override
1132+
public void process(long ledgerIdFromScanner, long offset, ByteBuf recoveredEntry) {
1133+
assertEquals(ledgerId, ledgerIdFromScanner);
1134+
assertEquals(ledgerId, recoveredEntry.getLong(recoveredEntry.readerIndex()));
1135+
assertEquals(entryId, recoveredEntry.getLong(recoveredEntry.readerIndex() + Long.BYTES));
1136+
foundEntry.set(true);
1137+
}
1138+
});
1139+
assertTrue(foundEntry.get(), "Compacted log should be readable through the compaction log id");
1140+
}
1141+
1142+
private void resetEntryLoggerWithLastLogId(long lastLogId) throws Exception {
1143+
entryLogger.getEntryLoggerAllocator().setLastLogId(curDir, lastLogId);
1144+
entryLogger.close();
1145+
entryLogger = new DefaultEntryLogger(conf, dirsMgr);
1146+
}
1147+
9861148

9871149
/*
9881150
* tests basic logic of EntryLogManager interface for

0 commit comments

Comments
 (0)