提交 bcf35f36 authored 作者: Andrei Tokar's avatar Andrei Tokar

Streamline last chunk verification on startup

上级 7a86246f
...@@ -706,15 +706,31 @@ public class MVStore { ...@@ -706,15 +706,31 @@ public class MVStore {
} }
} }
} }
if (newest == null) {
// no chunk long blocksInStore = fileStore.size() / BLOCK_SIZE;
return; // this queue will hold potential candidates for lastChunk to fall back to
Queue<Chunk> lastChunkCandidates = new PriorityQueue<>(Math.max(32, (int)(blocksInStore / 4)), new Comparator<Chunk>() {
@Override
public int compare(Chunk one, Chunk two) {
int result = Integer.compare(two.id, one.id);
if (result == 0) {
// out of two versions of the same chunk we prefer the one
// close to the begining of file (presumably later version)
result = Long.compare(one.block, two.block);
} }
return result;
}
});
Map<Long, Chunk> validChunkCacheByLocation = new HashMap<>();
if (newest != null) {
// read the chunk header and footer, // read the chunk header and footer,
// and follow the chain of next chunks // and follow the chain of next chunks
while (true) { while (true) {
validChunkCacheByLocation.put(newest.block, newest);
lastChunkCandidates.add(newest);
if (newest.next == 0 || if (newest.next == 0 ||
newest.next >= fileStore.size() / BLOCK_SIZE) { newest.next >= blocksInStore) {
// no (valid) next // no (valid) next
break; break;
} }
...@@ -724,9 +740,45 @@ public class MVStore { ...@@ -724,9 +740,45 @@ public class MVStore {
} }
newest = test; newest = test;
} }
do { }
setLastChunk(newest);
loadChunkMeta(); // Try candidates for "last chunk" in order from newest to oldest
// until suitable is found. Suitable one should have meta map
// where all chunk references point to valid locations.
boolean verified = false;
while(!verified && setLastChunk(lastChunkCandidates.poll()) != null) {
verified = true;
// load the chunk metadata: although meta's root page resides in the lastChunk,
// traversing meta map might recursively load another chunk(s)
Cursor<String, String> cursor = meta.cursor("chunk.");
while (cursor.hasNext() && cursor.next().startsWith("chunk.")) {
Chunk c = Chunk.fromString(cursor.getValue());
assert c.version <= currentVersion;
// might be there already, due to meta traversal
// see readPage() ... getChunkIfFound()
chunks.putIfAbsent(c.id, c);
long block = c.block;
test = validChunkCacheByLocation.get(block);
if (test == null) {
test = readChunkHeaderAndFooter(block);
if (test != null && test.id == c.id) { // chunk is valid
validChunkCacheByLocation.put(block, test);
lastChunkCandidates.offer(test);
continue;
}
} else if (test.id == c.id) { // chunk is valid
// nothing to do, since chunk was already verified
// and registered as potential "last chunk" candidate
continue;
}
// chunk reference is invalid
// this "last chunk" cadidate is not suitable
// but we continue to process all references
// to find other potential candidates
verified = false;
}
}
fileStore.clear(); fileStore.clear();
// build the free space list // build the free space list
for (Chunk c : chunks.values()) { for (Chunk c : chunks.values()) {
...@@ -736,37 +788,13 @@ public class MVStore { ...@@ -736,37 +788,13 @@ public class MVStore {
} }
assert fileStore.getFileLengthInUse() == measureFileLengthInUse() : assert fileStore.getFileLengthInUse() == measureFileLengthInUse() :
fileStore.getFileLengthInUse() + " != " + measureFileLengthInUse(); fileStore.getFileLengthInUse() + " != " + measureFileLengthInUse();
// read all chunk headers and footers within the retention time,
// to detect unwritten data after a power failure
} while((newest = verifyLastChunks()) != null);
setWriteVersion(currentVersion); setWriteVersion(currentVersion);
if (lastStoredVersion == INITIAL_VERSION) { if (lastStoredVersion == INITIAL_VERSION) {
lastStoredVersion = currentVersion - 1; lastStoredVersion = currentVersion - 1;
} }
assert fileStore.getFileLengthInUse() == measureFileLengthInUse() :
fileStore.getFileLengthInUse() + " != " + measureFileLengthInUse();
} }
private void loadChunkMeta() { private Chunk setLastChunk(Chunk last) {
// load the chunk metadata: we can load in any order,
// because loading chunk metadata might recursively load another chunk
Cursor<String, String> cursor = meta.cursor("chunk.");
while (cursor.hasNext() && cursor.next().startsWith("chunk.")) {
Chunk c = Chunk.fromString(cursor.getValue());
if (c.version < lastChunk.version) {
if (chunks.putIfAbsent(c.id, c) == null) {
if (c.block == Long.MAX_VALUE) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT,
"Chunk {0} is invalid", c.id);
}
}
}
}
}
private void setLastChunk(Chunk last) {
chunks.clear(); chunks.clear();
lastChunk = last; lastChunk = last;
if (last == null) { if (last == null) {
...@@ -782,55 +810,9 @@ public class MVStore { ...@@ -782,55 +810,9 @@ public class MVStore {
lastStoredVersion = currentVersion - 1; lastStoredVersion = currentVersion - 1;
meta.setRootPos(last.metaRootPos, lastStoredVersion); meta.setRootPos(last.metaRootPos, lastStoredVersion);
} }
return last;
} }
private Chunk verifyLastChunks() {
assert lastChunk == null || chunks.containsKey(lastChunk.id) : lastChunk;
BitSet validIds = new BitSet();
Queue<Chunk> queue = new PriorityQueue<>(chunks.size(), new Comparator<Chunk>() {
@Override
public int compare(Chunk one, Chunk two) {
return Integer.compare(one.id, two.id);
}
});
queue.addAll(chunks.values());
int newestValidChunk = -1;
Chunk c;
while((c = queue.poll()) != null) {
Chunk test = readChunkHeaderAndFooter(c.block);
if (test == null || test.id != c.id) {
continue;
}
validIds.set(c.id);
try {
Cursor<String, Object> cursor = new Cursor<>(readPage(meta, c.metaRootPos), "chunk.");
boolean valid = true;
String key;
while (valid && cursor.hasNext() && (key = cursor.next()).startsWith("chunk.")) {
valid = validIds.get((int) Long.parseLong(key.substring("chunk.".length()), 16));
}
if (valid) {
newestValidChunk = c.id;
}
} catch (Exception ignore) {/**/}
}
Chunk newest = chunks.get(newestValidChunk);
if (newest != lastChunk) {
if (newest == null) {
rollbackTo(0);
} else {
// to avoid re-using newer chunks later on, we could clear
// the headers and footers of those, but we might not know about all
// of them, so that could be incomplete - but we check that newer
// chunks are written after older chunks, so we are safe
rollbackTo(newest.version);
return newest;
}
}
return null;
}
/** /**
* Read a chunk header and footer, and verify the stored data is consistent. * Read a chunk header and footer, and verify the stored data is consistent.
......
...@@ -1450,7 +1450,7 @@ public class TestMVStore extends TestBase { ...@@ -1450,7 +1450,7 @@ public class TestMVStore extends TestBase {
assertEquals(0, m.size()); assertEquals(0, m.size());
s.commit(); s.commit();
// ensure only nodes are read, but not leaves // ensure only nodes are read, but not leaves
assertEquals(10, s.getFileStore().getReadCount()); assertEquals(8, s.getFileStore().getReadCount());
assertTrue(s.getFileStore().getWriteCount() < 5); assertTrue(s.getFileStore().getWriteCount() < 5);
s.close(); s.close();
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论