提交 cbab3234 authored 作者: Thomas Mueller's avatar Thomas Mueller

MVStore: tool to quickly compact a store.

上级 5da4e45c
...@@ -13,7 +13,7 @@ public class CursorPos { ...@@ -13,7 +13,7 @@ public class CursorPos {
/** /**
* The current page. * The current page.
*/ */
public final Page page; public Page page;
/** /**
* The current index. * The current index.
......
...@@ -995,7 +995,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -995,7 +995,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
return; return;
} }
Page last = oldRoots.peekLast(); Page last = oldRoots.peekLast();
// TODO why is this? // TODO why is this? (maybe not needed)
; ;
oldest--; oldest--;
while (true) { while (true) {
...@@ -1257,23 +1257,32 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1257,23 +1257,32 @@ public class MVMap<K, V> extends AbstractMap<K, V>
* @param sourceMap the source map * @param sourceMap the source map
*/ */
void copyFrom(MVMap<K, V> sourceMap) { void copyFrom(MVMap<K, V> sourceMap) {
; // TODO work in progress beforeWrite();
root = copy(sourceMap.root, null); newRoot(copy(sourceMap.root, null));
afterWrite();
} }
private Page copy(Page source, CursorPos parent) { private Page copy(Page source, CursorPos parent) {
Page target = Page.create(this, writeVersion, source); Page target = Page.create(this, writeVersion, source);
if (target.isLeaf()) {
Page child = target;
for (CursorPos p = parent; p != null; p = p.parent) { for (CursorPos p = parent; p != null; p = p.parent) {
p.page.setChild(p.index, target); p.page.setChild(p.index, child);
p.page = copyOnWrite(p.page, writeVersion);
child = p.page;
if (p.parent == null) {
newRoot(p.page);
afterWrite();
beforeWrite();
}
} }
if (!target.isLeaf()) { } else {
CursorPos pos = new CursorPos(target, 0, parent); CursorPos pos = new CursorPos(target, 0, parent);
target = copyOnWrite(target, writeVersion);
for (int i = 0; i < target.getChildPageCount(); i++) { for (int i = 0; i < target.getChildPageCount(); i++) {
Page sourceChild = source.getChildPage(i);
pos.index = i; pos.index = i;
copy(sourceChild, pos); copy(source.getChildPage(i), pos);
} }
target = pos.page;
} }
return target; return target;
} }
......
...@@ -238,8 +238,8 @@ public class MVStore { ...@@ -238,8 +238,8 @@ public class MVStore {
*/ */
private int autoCommitDelay; private int autoCommitDelay;
private int autoCompactFillRate = 10; private int autoCompactFillRate;
private int autoCompactSize = 2 * 1024 * 1024; private long autoCompactLastFileOpCount;
/** /**
* Create and open the store. * Create and open the store.
...@@ -291,6 +291,10 @@ public class MVStore { ...@@ -291,6 +291,10 @@ public class MVStore {
int kb = o == null ? 512 : (Integer) o; int kb = o == null ? 512 : (Integer) o;
// 19 KB memory is about 1 KB storage // 19 KB memory is about 1 KB storage
autoCommitMemory = kb * 1024 * 19; autoCommitMemory = kb * 1024 * 19;
o = config.get("autoCompactFillRate");
autoCompactFillRate = o == null ? 80 : (Integer) o;
char[] encryptionKey = (char[]) config.get("encryptionKey"); char[] encryptionKey = (char[]) config.get("encryptionKey");
try { try {
fileStore.open(fileName, readOnly, encryptionKey); fileStore.open(fileName, readOnly, encryptionKey);
...@@ -1349,19 +1353,20 @@ public class MVStore { ...@@ -1349,19 +1353,20 @@ public class MVStore {
* @return if anything was written * @return if anything was written
*/ */
public synchronized boolean compactMoveChunks() { public synchronized boolean compactMoveChunks() {
return compactMoveChunks(Long.MAX_VALUE); return compactMoveChunks(100, Long.MAX_VALUE);
} }
/** /**
* Compact the store by moving all chunks next to each other, if there is * Compact the store by moving all chunks next to each other, if there is
* free space between chunks. This might temporarily double the file size. * free space between chunks. This might temporarily increase the file size.
* Chunks are overwritten irrespective of the current retention time. Before * Chunks are overwritten irrespective of the current retention time. Before
* overwriting chunks and before resizing the file, syncFile() is called. * overwriting chunks and before resizing the file, syncFile() is called.
* *
* @param targetFillRate do nothing if the file store fill rate is higher than this
* @param moveSize the number of bytes to move * @param moveSize the number of bytes to move
* @return if anything was written * @return if anything was written
*/ */
public synchronized boolean compactMoveChunks(long moveSize) { public synchronized boolean compactMoveChunks(int targetFillRate, long moveSize) {
checkOpen(); checkOpen();
if (lastChunk == null) { if (lastChunk == null) {
// nothing to do // nothing to do
...@@ -1372,12 +1377,11 @@ public class MVStore { ...@@ -1372,12 +1377,11 @@ public class MVStore {
try { try {
retentionTime = 0; retentionTime = 0;
compactFreeUnusedChunks(); compactFreeUnusedChunks();
if (fileStore.getFillRate() == 100) { if (fileStore.getFillRate() > targetFillRate) {
return false; return false;
} }
ArrayList<Chunk> move;
long start = fileStore.getFirstFree() / BLOCK_SIZE; long start = fileStore.getFirstFree() / BLOCK_SIZE;
move = compactGetMoveBlocks(start, moveSize); ArrayList<Chunk> move = compactGetMoveBlocks(start, moveSize);
compactMoveChunks(move); compactMoveChunks(move);
} finally { } finally {
reuseSpace = oldReuse; reuseSpace = oldReuse;
...@@ -1523,7 +1527,6 @@ public class MVStore { ...@@ -1523,7 +1527,6 @@ public class MVStore {
* with a low number of live items are re-written. * with a low number of live items are re-written.
* <p> * <p>
* If the current fill rate is higher than the target fill rate, nothing is * If the current fill rate is higher than the target fill rate, nothing is
* done. If not at least a minimum amount of space can be saved, nothing is
* done. * done.
* <p> * <p>
* Please note this method will not necessarily reduce the file size, as * Please note this method will not necessarily reduce the file size, as
...@@ -1534,7 +1537,7 @@ public class MVStore { ...@@ -1534,7 +1537,7 @@ public class MVStore {
* before calling this method. * before calling this method.
* *
* @param targetFillRate the minimum percentage of live entries * @param targetFillRate the minimum percentage of live entries
* @param write the number of bytes to write * @param write the minimum number of bytes to write
* @return if a chunk was re-written * @return if a chunk was re-written
*/ */
public synchronized boolean compact(int targetFillRate, int write) { public synchronized boolean compact(int targetFillRate, int write) {
...@@ -1642,118 +1645,6 @@ public class MVStore { ...@@ -1642,118 +1645,6 @@ public class MVStore {
return true; return true;
} }
/**
* Try to increase the fill rate by re-writing partially full chunks. Chunks
* with a low number of live items are re-written.
* <p>
* If the current fill rate is higher than the target fill rate, nothing is
* done. If not at least a minimum amount of space can be saved, nothing is
* done.
* <p>
* Please note this method will not necessarily reduce the file size, as
* empty chunks are not overwritten.
* <p>
* Only data of open maps can be moved. For maps that are not open, the old
* chunk is still referenced. Therefore, it is recommended to open all maps
* before calling this method.
*
* @param targetFillRate the minimum percentage of live entries
* @param minSaving the amount of saved space,
* which is also the size of the new chunk
* @return if a chunk was re-written
*/
public synchronized boolean compactOld(int targetFillRate, int minSaving) {
checkOpen();
if (lastChunk == null) {
// nothing to do
return false;
}
// calculate the fill rate
long maxLengthSum = 0;
long maxLengthLiveSum = 0;
for (Chunk c : chunks.values()) {
maxLengthSum += c.maxLen;
maxLengthLiveSum += c.maxLenLive;
}
// the fill rate of all chunks combined
if (maxLengthSum <= 0) {
// avoid division by 0
maxLengthSum = 1;
}
int fillRate = (int) (100 * maxLengthLiveSum / maxLengthSum);
if (fillRate >= targetFillRate) {
return false;
}
long time = getTime();
// the 'old' list contains the chunks we want to free up
ArrayList<Chunk> old = New.arrayList();
Chunk last = chunks.get(lastChunk.id);
for (Chunk c : chunks.values()) {
if (canOverwriteChunk(c, time)) {
long age = last.version - c.version + 1;
c.collectPriority = (int) (c.getFillRate() / age);
old.add(c);
}
}
if (old.size() == 0) {
return false;
}
// sort the list, so the first entry should be collected first
Collections.sort(old, new Comparator<Chunk>() {
@Override
public int compare(Chunk o1, Chunk o2) {
int comp = new Integer(o1.collectPriority).
compareTo(o2.collectPriority);
if (comp == 0) {
comp = new Long(o1.maxLenLive).
compareTo(o2.maxLenLive);
}
return comp;
}
});
// find out up to were in the old list we need to move
long saved = 0;
long totalSize = 0;
Chunk move = null;
for (Chunk c : old) {
long size = c.maxLen - c.maxLenLive;
totalSize += c.maxLenLive;
if (move != null) {
if (saved > minSaving && totalSize > minSaving) {
break;
}
}
saved += size;
move = c;
}
if (saved < minSaving) {
return false;
}
// remove the chunks we want to keep from this list
boolean remove = false;
for (Iterator<Chunk> it = old.iterator(); it.hasNext();) {
Chunk c = it.next();
if (move == c) {
remove = true;
} else if (remove) {
it.remove();
}
}
// iterate over all the pages in the old pages
for (Chunk c : old) {
copyLive(c);
}
commitAndSave();
return true;
}
private void copyLive(Chunk chunk) { private void copyLive(Chunk chunk) {
if (chunk.pageCountLive == 0) { if (chunk.pageCountLive == 0) {
// remove this chunk in the next save operation // remove this chunk in the next save operation
...@@ -2355,10 +2246,11 @@ public class MVStore { ...@@ -2355,10 +2246,11 @@ public class MVStore {
} }
/** /**
* Commit and save all changes, if there are any. * Commit and save all changes, if there are any, and compact the store if
* needed.
*/ */
void commitInBackground() { void writeInBackground() {
if (unsavedMemory == 0 || closed) { if (closed) {
return; return;
} }
...@@ -2379,9 +2271,26 @@ public class MVStore { ...@@ -2379,9 +2271,26 @@ public class MVStore {
} }
} }
} }
if (autoCompactSize > 0) { if (autoCompactFillRate > 0) {
try { try {
compact(autoCompactFillRate, autoCompactSize); // whether there were file read or write operations since
// the last time
boolean fileOps;
long fileOpCount = fileStore.getWriteCount() + fileStore.getReadCount();
if (autoCompactLastFileOpCount != fileOpCount) {
fileOps = true;
} else {
fileOps = false;
}
// use a lower fill rate if there were any file operations
int fillRate = fileOps ? autoCompactFillRate / 4 : autoCompactFillRate;
compact(fillRate, autoCommitMemory);
if (!fileOps) {
// if there were no file operations at all,
// compact the file by moving chunks
compactMoveChunks(autoCompactFillRate, autoCommitMemory);
}
autoCompactLastFileOpCount = fileStore.getWriteCount() + fileStore.getReadCount();
} catch (Exception e) { } catch (Exception e) {
if (backgroundExceptionHandler != null) { if (backgroundExceptionHandler != null) {
backgroundExceptionHandler.uncaughtException(null, e); backgroundExceptionHandler.uncaughtException(null, e);
...@@ -2570,7 +2479,7 @@ public class MVStore { ...@@ -2570,7 +2479,7 @@ public class MVStore {
continue; continue;
} }
} }
store.commitInBackground(); store.writeInBackground();
} }
} }
...@@ -2620,6 +2529,24 @@ public class MVStore { ...@@ -2620,6 +2529,24 @@ public class MVStore {
return set("autoCommitBufferSize", kb); return set("autoCommitBufferSize", kb);
} }
/**
* Set the auto-compact target fill rate. If the average fill rate (the
* percentage of the storage space that contains active data) of the
* chunks is lower, then the chunks with a low fill rate are re-written.
* Also, if the percentage of empty space between chunks is higher than
* this value, then chunks at the end of the file are moved. Compaction
* stops if the target fill rate is reached.
* <p>
* The default value is 80 (80%). The value 0 disables auto-compacting.
* <p>
*
* @param percent the target fill rate
* @return this
*/
public Builder autoCompactFillRate(int percent) {
return set("autoCompactFillRate", percent);
}
/** /**
* Use the following file name. If the file does not exist, it is * Use the following file name. If the file does not exist, it is
* automatically created. The parent directory already must exist. * automatically created. The parent directory already must exist.
......
...@@ -45,6 +45,12 @@ public class MVStoreTool { ...@@ -45,6 +45,12 @@ public class MVStoreTool {
} else if ("-info".equals(args[i])) { } else if ("-info".equals(args[i])) {
String fileName = args[++i]; String fileName = args[++i];
info(fileName, new PrintWriter(System.out)); info(fileName, new PrintWriter(System.out));
} else if ("-compact".equals(args[i])) {
String fileName = args[++i];
compact(fileName, false);
} else if ("-compress".equals(args[i])) {
String fileName = args[++i];
compact(fileName, true);
} }
} }
} }
...@@ -301,9 +307,10 @@ public class MVStoreTool { ...@@ -301,9 +307,10 @@ public class MVStoreTool {
* there. * there.
* *
* @param fileName the file name * @param fileName the file name
* @param compress whether to compress the data
*/ */
public static void compress(String fileName) { public static void compact(String fileName, boolean compress) {
compress(fileName, fileName + ".new"); compact(fileName, fileName + ".new", compress);
FileUtils.moveTo(fileName, fileName + ".old"); FileUtils.moveTo(fileName, fileName + ".old");
FileUtils.moveTo(fileName, fileName); FileUtils.moveTo(fileName, fileName);
FileUtils.delete(fileName + ".old"); FileUtils.delete(fileName + ".old");
...@@ -315,12 +322,18 @@ public class MVStoreTool { ...@@ -315,12 +322,18 @@ public class MVStoreTool {
* @param sourceFileName the name of the source store * @param sourceFileName the name of the source store
* @param targetFileName the name of the target store * @param targetFileName the name of the target store
*/ */
public static void compress(String sourceFileName, String targetFileName) { public static void compact(String sourceFileName, String targetFileName, boolean compress) {
MVStore source = new MVStore.Builder(). MVStore source = new MVStore.Builder().
fileName(sourceFileName).readOnly().open(); fileName(sourceFileName).
readOnly().
open();
FileUtils.delete(targetFileName); FileUtils.delete(targetFileName);
MVStore target = new MVStore.Builder(). MVStore.Builder b = new MVStore.Builder().
fileName(targetFileName).open(); fileName(targetFileName);
if (compress) {
b.compress();
}
MVStore target = b.open();
MVMap<String, String> sourceMeta = source.getMetaMap(); MVMap<String, String> sourceMeta = source.getMetaMap();
MVMap<String, String> targetMeta = target.getMetaMap(); MVMap<String, String> targetMeta = target.getMetaMap();
for (Entry<String, String> m : sourceMeta.entrySet()) { for (Entry<String, String> m : sourceMeta.entrySet()) {
...@@ -345,7 +358,6 @@ public class MVStoreTool { ...@@ -345,7 +358,6 @@ public class MVStoreTool {
MVMap<Object, Object> sourceMap = source.openMap(mapName, mp); MVMap<Object, Object> sourceMap = source.openMap(mapName, mp);
MVMap<Object, Object> targetMap = target.openMap(mapName, mp); MVMap<Object, Object> targetMap = target.openMap(mapName, mp);
targetMap.copyFrom(sourceMap); targetMap.copyFrom(sourceMap);
target.commit();
} }
target.close(); target.close();
source.close(); source.close();
...@@ -364,7 +376,7 @@ public class MVStoreTool { ...@@ -364,7 +376,7 @@ public class MVStoreTool {
@Override @Override
public int getMemory(Object obj) { public int getMemory(Object obj) {
return obj == null ? 0 : ((byte[]) obj).length; return obj == null ? 0 : ((byte[]) obj).length * 8;
} }
@Override @Override
......
...@@ -304,7 +304,11 @@ public class WriteBuffer { ...@@ -304,7 +304,11 @@ public class WriteBuffer {
// grow at least 50% of the current size // grow at least 50% of the current size
grow = Math.max(temp.capacity() / 2, grow); grow = Math.max(temp.capacity() / 2, grow);
int newCapacity = temp.capacity() + grow; int newCapacity = temp.capacity() + grow;
try {
buff = ByteBuffer.allocate(newCapacity); buff = ByteBuffer.allocate(newCapacity);
} catch (OutOfMemoryError e) {
throw new OutOfMemoryError("Capacity: " + newCapacity);
}
temp.flip(); temp.flip();
buff.put(temp); buff.put(temp);
if (newCapacity <= MAX_REUSE_CAPACITY) { if (newCapacity <= MAX_REUSE_CAPACITY) {
......
...@@ -317,9 +317,9 @@ public class MVTableEngine implements TableEngine { ...@@ -317,9 +317,9 @@ public class MVTableEngine implements TableEngine {
store.compactMoveChunks(); store.compactMoveChunks();
} else { } else {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
while (store.compact(99, 16 * 1024 * 1024)) { while (store.compact(95, 16 * 1024 * 1024)) {
store.sync(); store.sync();
store.compactMoveChunks(16 * 1024 * 1024); store.compactMoveChunks(95, 16 * 1024 * 1024);
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
if (time > maxCompactTime) { if (time > maxCompactTime) {
break; break;
...@@ -341,8 +341,8 @@ public class MVTableEngine implements TableEngine { ...@@ -341,8 +341,8 @@ public class MVTableEngine implements TableEngine {
if (!store.getFileStore().isReadOnly()) { if (!store.getFileStore().isReadOnly()) {
transactionStore.close(); transactionStore.close();
if (maxCompactTime > 0) { if (maxCompactTime > 0) {
store.compact(99, 1 * 1024 * 1024); store.compact(95, 1024 * 1024);
store.compactMoveChunks(1 * 1024 * 1024); store.compactMoveChunks(95, 1024 * 1024);
} }
} }
store.close(); store.close();
......
...@@ -32,12 +32,12 @@ public class TestMVStoreTool extends TestBase { ...@@ -32,12 +32,12 @@ public class TestMVStoreTool extends TestBase {
@Override @Override
public void test() throws Exception { public void test() throws Exception {
; // TODO work in progress testCompress();
// testCompress();
} }
private void testCompress() { private void testCompress() {
String fileName = getBaseDir() + "/testCompress.h3"; String fileName = getBaseDir() + "/testCompress.h3";
FileUtils.createDirectory(getBaseDir());
FileUtils.delete(fileName); FileUtils.delete(fileName);
// store with a very small page size, to make sure // store with a very small page size, to make sure
// there are many leaf pages // there are many leaf pages
...@@ -51,15 +51,26 @@ public class TestMVStoreTool extends TestBase { ...@@ -51,15 +51,26 @@ public class TestMVStoreTool extends TestBase {
s.commit(); s.commit();
} }
} }
for (int i = 0; i < 10; i++) {
map = s.openMap("data" + i);
for (int j = 0; j < i * i; j++) {
map.put(j, j * 10);
}
s.commit();
}
s.close(); s.close();
// MVStoreTool.dump(fileName); MVStoreTool.compact(fileName, fileName + ".new", false);
// MVStoreTool.dump(fileName + ".new"); MVStoreTool.compact(fileName, fileName + ".new.compress", true);
MVStoreTool.compress(fileName, fileName + ".new");
MVStore s1 = new MVStore.Builder(). MVStore s1 = new MVStore.Builder().
fileName(fileName).readOnly().open(); fileName(fileName).readOnly().open();
MVStore s2 = new MVStore.Builder(). MVStore s2 = new MVStore.Builder().
fileName(fileName + ".new").readOnly().open(); fileName(fileName + ".new").readOnly().open();
MVStore s3 = new MVStore.Builder().
fileName(fileName + ".new.compress").readOnly().open();
assertEquals(s1, s2); assertEquals(s1, s2);
assertEquals(s1, s3);
assertTrue(FileUtils.size(fileName + ".new") < FileUtils.size(fileName));
assertTrue(FileUtils.size(fileName + ".new.compress") < FileUtils.size(fileName + ".new"));
} }
private void assertEquals(MVStore a, MVStore b) { private void assertEquals(MVStore a, MVStore b) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论