DUE TO SPAM, SIGN-UP IS DISABLED. Goto Selfserve wiki signup and request an account.
...
If for any reason we succeed in flushing the compaction log but then fail to update the index or flush the ledger cache, we would get into the situation that the index is partially updated. In this case, we can't simply roll back by deleting the old entry log or compaction log, because the partially updated index file might already pointing some ledgers to the new compaction log. So we need a way to recover the partially updated index file for the compaction log file.
Design
...
Create a separate file for compaction but share the same allocation logic
| Code Block |
|---|
EntryLogWriteChannel createNewCompactionLog() throws IOException {
synchronized (createCompactionLogLock) {
List<File> writableDirs;
try {
writableDirs = new ArrayList<>(ledgerDirsManager.getWritableLedgerDirs());
} catch (LedgerDirsManager.NoWritableLedgerDirException nlde) {
// if all ledgers dirs are full, try to pick a ledger dir that has enough space for compaction log.
writableDirs = ledgerDirsManager.getAllLedgerDirs();
for (File dir : writableDirs) {
if (dir.getUsableSpace() <= logSizeLimit) {
writableDirs.remove(dir);
}
}
}
EntryLogWriteChannel bc = allocateNewLogFile(COMPACTING_SUFFIX, writableDirs);
LOG.info("Created new compaction logger {}.", bc.getLogId());
return bc;
}
} |
| Code Block |
|---|
/**
* Allocate a new entry log file
*/
EntryLogWriteChannel allocateNewLogFile(String suffix, List<File> writableDirs)
throws IOException {
if (writableDirs.isEmpty()) {
throw new LedgerDirsManager.NoWritableLedgerDirException(
"No writable ledger directories to allocate new entry log.");
}
Collections.shuffle(writableDirs);
synchronized (this) {
// It would better not to overwrite existing entry log files
File newLogFile = null;
do {
if (nextLogId >= Integer.MAX_VALUE) {
nextLogId = 0;
} else {
++nextLogId;
}
String logFileName = Long.toHexString(nextLogId) + suffix;
for (File dir : writableDirs) {
newLogFile = new File(dir, logFileName);
if (newLogFile.exists()) {
LOG.warn("Found existed entry log " + newLogFile
+ " when trying to create it as a new log.");
newLogFile = null;
break;
}
}
} while (newLogFile == null);
FileChannel channel = new RandomAccessFile(newLogFile, "rw").getChannel();
EntryLogWriteChannel logChannel = new EntryLogWriteChannel(nextLogId, channel,
newLogFile, serverCfg.getWriteBufferBytes(), serverCfg.getReadBufferBytes());
logChannel.writeHeader((ByteBuffer) LOGFILE_HEADER.clear());
for (File dir : writableDirs) {
try {
setLastLogId(dir, nextLogId);
} catch (IOException ioe) {
LOG.warn("Failed to write lastId {} to directory {} : ",
new Object[]{nextLogId, dir, ioe});
}
}
LOG.info("Preallocated log file {} for logId {}.", newLogFile, nextLogId);
return logChannel;
}
} |
Introduce Compaction Transactional Phases
| Code Block |
|---|
/** * An abstract class that would be extended to be the actual transactional phases for compaction */ abstract static class CompactionPhase { private String phaseName = ""; CompactionPhase() { } CompactionPhase(String phaseName) { this.phaseName = phaseName; } boolean run() { try { start(); return complete(); } catch (IOException e) { LOG.error("Encounter exception in compaction phase {}. Abort current compaction.", phaseName, e); abort(); } return false; } abstract void start() throws IOException; abstract boolean complete() throws IOException; abstract void abort(); } |
| Code Block |
|---|
/** * This phase is to scan entry log and copy to compaction log file "x.log.compacting" * If after scanning, there's no data written, it means there's no valid entries to be compacted, * so we can remove the log directly and end the compaction. * Otherwise, we should move to the next phase. */ class ScanEntryLogPhase extends CompactionPhase { ... } |
| Code Block |
|---|
/** * This phase is to flush the compaction log and create a new log for next compaction. * When this phase starts, there should be a compaction log file like "x.log.compacting" * When compaction log is flushed, a hardlink file "x.log.y.compacted" should be created, * and "x.log.compacting" should be deleted */ class FlushCompactionLogPhase extends CompactionPhase { ... } |
| Code Block |
|---|
/**
* This phase is to update the entry locations and flush the index.
* When the phase start, there should be a compacted file like "x.log.y.compacted",
* where x is the new entry logId being compacted to and y is the old logId.
* When the index the flushed, "x.log" file should be created and x.log.y.compacted file should be deleted
*
* This phase is also used to recover partially flushed index when we pass isInRecovery=true
*/
class UpdateIndexPhase extends CompactionPhase {
...
} |