Skip to content

Commit d3dd87d

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

2 files changed

Lines changed: 145 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: 143 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,11 +66,14 @@
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;
7174
import org.apache.bookkeeper.test.TestStatsProvider;
7275
import org.apache.bookkeeper.util.DiskChecker;
76+
import org.apache.bookkeeper.util.HardLink;
7377
import org.apache.bookkeeper.util.IOUtils;
7478
import org.apache.bookkeeper.util.collections.ConcurrentLongLongHashMap;
7579
import org.apache.commons.io.FileUtils;
@@ -87,6 +91,13 @@
8791
@TestMethodOrder(MethodOrderer.MethodName.class)
8892
public class DefaultEntryLogTest {
8993
private static final Logger LOG = LoggerFactory.getLogger(DefaultEntryLogTest.class);
94+
private static final long COMPACTION_SOURCE_LOG_ID = 1L;
95+
private static final long COMPACTION_PREVIOUS_LOG_ID = 499L;
96+
private static final long COMPACTION_LOG_ID = COMPACTION_PREVIOUS_LOG_ID + 1;
97+
private static final long COMPACTION_LEDGER_ID = 1234L;
98+
private static final long COMPACTION_ENTRY_ID = 5L;
99+
private static final String HEX_FINAL_LOG_FILE_NAME = "1f4.log";
100+
private static final String DECIMAL_FINAL_LOG_FILE_NAME = "500.log";
90101

91102
final List<File> tempDirs = new ArrayList<File>();
92103
final Random rand = new Random();
@@ -983,6 +994,138 @@ public void testReadEntryWithoutLedgerID() throws Exception {
983994
}
984995
}
985996

997+
@Test
998+
public void testRecoverCompactedLogUsesHexFinalLogFileName() throws Exception {
999+
ByteBuf entry = generateEntry(COMPACTION_LEDGER_ID, COMPACTION_ENTRY_ID);
1000+
try {
1001+
prepareCompactedLog(entry);
1002+
1003+
CompactionEntryLog recoveredLog = recoverSingleCompactionLog();
1004+
1005+
// Recovery creates the final entry log in makeAvailable(). It must create
1006+
// the normal hex entry log name, not the decimal name from the parsed id.
1007+
recoveredLog.makeAvailable();
1008+
assertTrue(hexFinalLogFile().exists(), "Recovery should create the hex-named final log");
1009+
assertFalse(decimalFinalLogFile().exists(), "Recovery should not create a decimal-named final log");
1010+
assertCompactionLogContainsEntry(recoveredLog);
1011+
} finally {
1012+
ReferenceCountUtil.release(entry);
1013+
}
1014+
}
1015+
1016+
@Test
1017+
public void testRecoverCompactedLogWithStaleDecimalLogFileFromOldRecovery() throws Exception {
1018+
ByteBuf entry = generateEntry(COMPACTION_LEDGER_ID, COMPACTION_ENTRY_ID);
1019+
try {
1020+
prepareCompactedLog(entry);
1021+
1022+
// Simulate an upgrade after the old buggy recovery already created
1023+
// "500.log", while the ".compacted" marker is still present.
1024+
HardLink.createHardLink(compactedFile(), decimalFinalLogFile());
1025+
assertTrue(decimalFinalLogFile().exists(), "Stale decimal log should exist before fixed recovery");
1026+
1027+
CompactionEntryLog recoveredLog = recoverSingleCompactionLog();
1028+
recoveredLog.makeAvailable();
1029+
1030+
assertTrue(hexFinalLogFile().exists(), "Fixed recovery should create the hex-named final log");
1031+
assertTrue(decimalFinalLogFile().exists(), "Fixed recovery should leave the stale file untouched");
1032+
assertCompactionLogContainsEntry(recoveredLog);
1033+
1034+
recoveredLog.finalizeAndCleanup();
1035+
assertFalse(compactedFile().exists(), "Successful recovery cleanup should remove the marker");
1036+
assertTrue(decimalFinalLogFile().exists(), "Cleanup should not remove unrelated stale decimal logs");
1037+
} finally {
1038+
ReferenceCountUtil.release(entry);
1039+
}
1040+
}
1041+
1042+
@Test
1043+
public void testNewCompactionLogKeepsHexFinalLogFileName() throws Exception {
1044+
ByteBuf entry = generateEntry(COMPACTION_LEDGER_ID, COMPACTION_ENTRY_ID);
1045+
try {
1046+
CompactionEntryLog compactionLog = prepareCompactedLog(entry);
1047+
1048+
// Normal compaction derives finalLogFile from the compacting file name,
1049+
// so makeAvailable() should create "1f4.log", not "500.log".
1050+
compactionLog.makeAvailable();
1051+
assertTrue(hexFinalLogFile().exists(), "Normal compaction should create the hex-named final log");
1052+
assertFalse(decimalFinalLogFile().exists(), "Normal compaction should not create a decimal-named log");
1053+
assertCompactionLogContainsEntry(compactionLog);
1054+
} finally {
1055+
ReferenceCountUtil.release(entry);
1056+
}
1057+
}
1058+
1059+
private CompactionEntryLog prepareCompactedLog(ByteBuf entry) throws Exception {
1060+
// Force the next compaction log id to 500, whose entry log file name is "1f4.log".
1061+
resetEntryLoggerWithLastLogId(COMPACTION_PREVIOUS_LOG_ID);
1062+
1063+
CompactionEntryLog compactionLog = entryLogger.newCompactionLog(COMPACTION_SOURCE_LOG_ID);
1064+
assertEquals(COMPACTION_LOG_ID, compactionLog.getDstLogId());
1065+
compactionLog.addEntry(COMPACTION_LEDGER_ID, entry);
1066+
compactionLog.flush();
1067+
1068+
// Leave "<dst-hex>.log.<src-hex>.compacted", as if the bookie restarted
1069+
// after markCompacted() but before makeAvailable()/index recovery completed.
1070+
compactionLog.markCompacted();
1071+
1072+
assertTrue(compactedFile().exists(), "Compacted recovery marker should exist");
1073+
assertFalse(hexFinalLogFile().exists(), "Final log file should not exist before makeAvailable");
1074+
1075+
return compactionLog;
1076+
}
1077+
1078+
private CompactionEntryLog recoverSingleCompactionLog() {
1079+
Collection<CompactionEntryLog> incompleteLogs = entryLogger.incompleteCompactionLogs();
1080+
assertEquals(1, incompleteLogs.size());
1081+
1082+
CompactionEntryLog recoveredLog = incompleteLogs.iterator().next();
1083+
assertEquals(COMPACTION_LOG_ID, recoveredLog.getDstLogId());
1084+
assertEquals(COMPACTION_SOURCE_LOG_ID, recoveredLog.getSrcLogId());
1085+
return recoveredLog;
1086+
}
1087+
1088+
private File compactedFile() {
1089+
return new File(curDir, HEX_FINAL_LOG_FILE_NAME + "."
1090+
+ DefaultEntryLogger.logId2HexString(COMPACTION_SOURCE_LOG_ID)
1091+
+ TransactionalEntryLogCompactor.COMPACTED_SUFFIX);
1092+
}
1093+
1094+
private File hexFinalLogFile() {
1095+
return new File(curDir, HEX_FINAL_LOG_FILE_NAME);
1096+
}
1097+
1098+
private File decimalFinalLogFile() {
1099+
return new File(curDir, DECIMAL_FINAL_LOG_FILE_NAME);
1100+
}
1101+
1102+
private void assertCompactionLogContainsEntry(CompactionEntryLog compactionLog) throws Exception {
1103+
AtomicBoolean foundEntry = new AtomicBoolean(false);
1104+
// Scanning uses the compaction log id to resolve the final log file. This
1105+
// catches cases where makeAvailable() created a file under the wrong name.
1106+
compactionLog.scan(new EntryLogScanner() {
1107+
@Override
1108+
public boolean accept(long ledgerId) {
1109+
return true;
1110+
}
1111+
1112+
@Override
1113+
public void process(long ledgerIdFromScanner, long offset, ByteBuf recoveredEntry) {
1114+
assertEquals(COMPACTION_LEDGER_ID, ledgerIdFromScanner);
1115+
assertEquals(COMPACTION_LEDGER_ID, recoveredEntry.getLong(recoveredEntry.readerIndex()));
1116+
assertEquals(COMPACTION_ENTRY_ID, recoveredEntry.getLong(recoveredEntry.readerIndex() + Long.BYTES));
1117+
foundEntry.set(true);
1118+
}
1119+
});
1120+
assertTrue(foundEntry.get(), "Compacted log should be readable through the compaction log id");
1121+
}
1122+
1123+
private void resetEntryLoggerWithLastLogId(long lastLogId) throws Exception {
1124+
entryLogger.getEntryLoggerAllocator().setLastLogId(curDir, lastLogId);
1125+
entryLogger.close();
1126+
entryLogger = new DefaultEntryLogger(conf, dirsMgr);
1127+
}
1128+
9861129

9871130
/*
9881131
* tests basic logic of EntryLogManager interface for

0 commit comments

Comments
 (0)