提交 060abfca authored 作者: Thomas Mueller's avatar Thomas Mueller

MVStore: improved compact operation

上级 dee33ae1
...@@ -105,7 +105,7 @@ public class Constants { ...@@ -105,7 +105,7 @@ public class Constants {
/** /**
* The minor version of this database. * The minor version of this database.
*/ */
; public static final int VERSION_MINOR = 3; public static final int VERSION_MINOR = 4;
/** /**
* The lock mode that means no locking is used at all. * The lock mode that means no locking is used at all.
......
...@@ -14,6 +14,8 @@ import org.h2.util.MathUtils; ...@@ -14,6 +14,8 @@ import org.h2.util.MathUtils;
* A free space bit set. * A free space bit set.
*/ */
public class FreeSpaceBitSet { public class FreeSpaceBitSet {
private static final boolean DETAILED_INFO = false;
/** /**
* The first usable block. * The first usable block.
...@@ -171,7 +173,27 @@ public class FreeSpaceBitSet { ...@@ -171,7 +173,27 @@ public class FreeSpaceBitSet {
@Override @Override
public String toString() { public String toString() {
StringBuilder buff = new StringBuilder("["); StringBuilder buff = new StringBuilder();
if (DETAILED_INFO) {
int onCount = 0, offCount = 0;
int on = 0;
for (int i = 0; i < set.length(); i++) {
if (set.get(i)) {
onCount++;
on++;
} else {
offCount++;
}
if ((i & 1023) == 1023) {
buff.append(String.format("%3x", on)).append(' ');
on = 0;
}
}
buff.append("\n");
buff.append(" on " + onCount + " off " + offCount);
buff.append(" " + 100 * onCount / (onCount+offCount) + "% used ");
}
buff.append('[');
for (int i = 0;;) { for (int i = 0;;) {
if (i > 0) { if (i > 0) {
buff.append(", "); buff.append(", ");
...@@ -185,7 +207,8 @@ public class FreeSpaceBitSet { ...@@ -185,7 +207,8 @@ public class FreeSpaceBitSet {
buff.append(Integer.toHexString(end - 1)); buff.append(Integer.toHexString(end - 1));
i = end + 1; i = end + 1;
} }
return buff.append(']').toString(); buff.append(']');
return buff.toString();
} }
} }
\ No newline at end of file
...@@ -783,7 +783,6 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -783,7 +783,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
} }
private int rewrite(Page p, Set<Integer> set) { private int rewrite(Page p, Set<Integer> set) {
; // TODO write more tests
if (p.isLeaf()) { if (p.isLeaf()) {
long pos = p.getPos(); long pos = p.getPos();
if (pos == 0) { if (pos == 0) {
......
...@@ -1320,7 +1320,6 @@ public class MVStore { ...@@ -1320,7 +1320,6 @@ public class MVStore {
// nothing to do // nothing to do
return false; return false;
} }
; // TODO write tests
for (MVMap<?, ?> m : maps.values()) { for (MVMap<?, ?> m : maps.values()) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
MVMap<Object, Object> map = (MVMap<Object, Object>) m; MVMap<Object, Object> map = (MVMap<Object, Object>) m;
...@@ -1342,22 +1341,79 @@ public class MVStore { ...@@ -1342,22 +1341,79 @@ public class MVStore {
return true; return true;
} }
public synchronized boolean compactMoveChunks() {
return compactMoveChunks(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 double 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 moveSize the number of bytes to move
* @return if anything was written * @return if anything was written
*/ */
public synchronized boolean compactMoveChunks() { public synchronized boolean compactMoveChunks(long moveSize) {
checkOpen(); checkOpen();
if (lastChunk == null) { if (lastChunk == null) {
// nothing to do // nothing to do
return false; return false;
} }
int oldRetentionTime = retentionTime; int oldRetentionTime = retentionTime;
retentionTime = 0; boolean oldReuse = reuseSpace;
try {
retentionTime = 0;
compactFreeUnusedChunks();
if (fileStore.getFillRate() == 100) {
return false;
}
ArrayList<Chunk> move;
long start = fileStore.getFirstFree() / BLOCK_SIZE;
move = compactGetMoveBlocks(start, moveSize);
compactMoveChunks(move);
} finally {
reuseSpace = oldReuse;
retentionTime = oldRetentionTime;
}
return true;
}
private ArrayList<Chunk> compactGetMoveBlocks(long startBlock, long moveSize) {
ArrayList<Chunk> move = New.arrayList();
for (Chunk c : chunks.values()) {
if (c.block > startBlock) {
move.add(c);
}
}
// sort by block
Collections.sort(move, new Comparator<Chunk>() {
@Override
public int compare(Chunk o1, Chunk o2) {
return Long.signum(o1.block - o2.block);
}
});
// find which is the last block to keep
int count = 0;
long size = 0;
for (Chunk c : move) {
long chunkSize = c.len * (long) BLOCK_SIZE;
if (size + chunkSize > moveSize) {
break;
}
size += chunkSize;
count++;
}
// move the first block (so the first gap is moved),
// and the one at the end (so the file shrinks)
while (move.size() > count && move.size() > 1) {
move.remove(1);
}
return move;
}
private void compactFreeUnusedChunks() {
long time = getTime(); long time = getTime();
ArrayList<Chunk> free = New.arrayList(); ArrayList<Chunk> free = New.arrayList();
for (Chunk c : chunks.values()) { for (Chunk c : chunks.values()) {
...@@ -1375,16 +1431,9 @@ public class MVStore { ...@@ -1375,16 +1431,9 @@ public class MVStore {
int length = c.len * BLOCK_SIZE; int length = c.len * BLOCK_SIZE;
fileStore.free(start, length); fileStore.free(start, length);
} }
if (fileStore.getFillRate() == 100) { }
return false;
} private void compactMoveChunks(ArrayList<Chunk> move) {
long firstFree = fileStore.getFirstFree() / BLOCK_SIZE;
ArrayList<Chunk> move = New.arrayList();
for (Chunk c : chunks.values()) {
if (c.block > firstFree) {
move.add(c);
}
}
for (Chunk c : move) { for (Chunk c : move) {
WriteBuffer buff = getWriteBuffer(); WriteBuffer buff = getWriteBuffer();
long start = c.block * BLOCK_SIZE; long start = c.block * BLOCK_SIZE;
...@@ -1410,7 +1459,6 @@ public class MVStore { ...@@ -1410,7 +1459,6 @@ public class MVStore {
markMetaChanged(); markMetaChanged();
meta.put(Chunk.getMetaKey(c.id), c.asString()); meta.put(Chunk.getMetaKey(c.id), c.asString());
} }
boolean oldReuse = reuseSpace;
// update the metadata (store at the end of the file) // update the metadata (store at the end of the file)
reuseSpace = false; reuseSpace = false;
...@@ -1453,11 +1501,6 @@ public class MVStore { ...@@ -1453,11 +1501,6 @@ public class MVStore {
commitAndSave(); commitAndSave();
sync(); sync();
shrinkFileIfPossible(0); shrinkFileIfPossible(0);
reuseSpace = oldReuse;
retentionTime = oldRetentionTime;
return true;
} }
/** /**
...@@ -1519,7 +1562,7 @@ public class MVStore { ...@@ -1519,7 +1562,7 @@ public class MVStore {
for (Chunk c : chunks.values()) { for (Chunk c : chunks.values()) {
if (canOverwriteChunk(c, time)) { if (canOverwriteChunk(c, time)) {
long age = last.version - c.version + 1; long age = last.version - c.version + 1;
c.collectPriority = (int) (c.getFillRate() / age); c.collectPriority = (int) (c.getFillRate() * 1000 / age);
old.add(c); old.add(c);
} }
} }
...@@ -1547,7 +1590,7 @@ public class MVStore { ...@@ -1547,7 +1590,7 @@ public class MVStore {
for (Chunk c : old) { for (Chunk c : old) {
long size = c.maxLen - c.maxLenLive; long size = c.maxLen - c.maxLenLive;
if (move != null) { if (move != null) {
if (saved > saving) { if (c.collectPriority > 0 && saved > saving) {
break; break;
} }
} }
...@@ -1575,7 +1618,6 @@ public class MVStore { ...@@ -1575,7 +1618,6 @@ public class MVStore {
for (MVMap<?, ?> m : maps.values()) { for (MVMap<?, ?> m : maps.values()) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
MVMap<Object, Object> map = (MVMap<Object, Object>) m; MVMap<Object, Object> map = (MVMap<Object, Object>) m;
; // TODO write more tests
map.rewrite(set); map.rewrite(set);
} }
commitAndSave(); commitAndSave();
...@@ -1750,7 +1792,9 @@ public class MVStore { ...@@ -1750,7 +1792,9 @@ public class MVStore {
} }
buff.position(offset); buff.position(offset);
Page page = new Page(map, 0); Page page = new Page(map, 0);
int limit = buff.limit();
page.read(buff, chunk.id, buff.position(), length); page.read(buff, chunk.id, buff.position(), length);
buff.limit(limit);
int type = page.isLeaf() ? 0 : 1; int type = page.isLeaf() ? 0 : 1;
long pos = DataUtils.getPagePos(chunk.id, offset, pageLength, type); long pos = DataUtils.getPagePos(chunk.id, offset, pageLength, type);
page.setPos(pos); page.setPos(pos);
......
...@@ -16,6 +16,7 @@ import java.util.Map; ...@@ -16,6 +16,7 @@ import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.TreeMap; import java.util.TreeMap;
import org.h2.mvstore.type.DataType;
import org.h2.mvstore.type.StringDataType; import org.h2.mvstore.type.StringDataType;
import org.h2.store.fs.FilePath; import org.h2.store.fs.FilePath;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
...@@ -291,5 +292,55 @@ public class MVStoreTool { ...@@ -291,5 +292,55 @@ public class MVStoreTool {
String x = new Timestamp(t).toString(); String x = new Timestamp(t).toString();
return x.substring(0, 19); return x.substring(0, 19);
} }
/**
* A data type that can read any data that is persisted, and converts it to
* a byte array.
*/
static class GenericDataType implements DataType {
@Override
public int compare(Object a, Object b) {
throw DataUtils.newUnsupportedOperationException("Can not compare");
}
@Override
public int getMemory(Object obj) {
return obj == null ? 0 : ((byte[]) obj).length;
}
@Override
public void write(WriteBuffer buff, Object obj) {
if (obj != null) {
buff.put((byte[]) obj);
}
}
@Override
public void write(WriteBuffer buff, Object[] obj, int len, boolean key) {
for (Object o : obj) {
write(buff, o);
}
}
@Override
public Object read(ByteBuffer buff) {
int len = buff.remaining();
if (len == 0) {
return null;
}
byte[] data = new byte[len];
buff.get(data);
return data;
}
@Override
public void read(ByteBuffer buff, Object[] obj, int len, boolean key) {
for (int i = 0; i < obj.length; i++) {
obj[i] = read(buff);
}
}
}
} }
...@@ -763,6 +763,7 @@ public class Page { ...@@ -763,6 +763,7 @@ public class Page {
"File corrupted in chunk {0}, expected page length =< {1}, got {2}", "File corrupted in chunk {0}, expected page length =< {1}, got {2}",
chunkId, maxLength, pageLength); chunkId, maxLength, pageLength);
} }
buff.limit(start + pageLength);
short check = buff.getShort(); short check = buff.getShort();
int mapId = DataUtils.readVarInt(buff); int mapId = DataUtils.readVarInt(buff);
if (mapId != map.getId()) { if (mapId != map.getId()) {
......
...@@ -310,18 +310,21 @@ public class MVTableEngine implements TableEngine { ...@@ -310,18 +310,21 @@ public class MVTableEngine implements TableEngine {
public void compactFile(long maxCompactTime) { public void compactFile(long maxCompactTime) {
store.setRetentionTime(0); store.setRetentionTime(0);
if (maxCompactTime == Long.MAX_VALUE) { if (maxCompactTime == Long.MAX_VALUE) {
store.setReuseSpace(false);
store.compactRewriteFully(); store.compactRewriteFully();
store.setReuseSpace(true);
store.compactMoveChunks();
} else { } else {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
while (store.compact(99, 4 * 1024 * 1024)) { while (store.compact(99, 16 * 1024 * 1024)) {
store.sync(); store.sync();
store.compactMoveChunks(16 * 1024 * 1024);
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
if (time > maxCompactTime) { if (time > maxCompactTime) {
break; break;
} }
} }
} }
store.compactMoveChunks();
} }
/** /**
...@@ -336,12 +339,9 @@ public class MVTableEngine implements TableEngine { ...@@ -336,12 +339,9 @@ public class MVTableEngine implements TableEngine {
if (!store.isClosed() && store.getFileStore() != null) { if (!store.isClosed() && store.getFileStore() != null) {
if (!store.getFileStore().isReadOnly()) { if (!store.getFileStore().isReadOnly()) {
transactionStore.close(); transactionStore.close();
long start = System.currentTimeMillis(); if (maxCompactTime > 0) {
while (store.compact(90, 4 * 1024 * 1024)) { store.compact(99, 1 * 1024 * 1024);
long time = System.currentTimeMillis() - start; store.compactMoveChunks(1 * 1024 * 1024);
if (time > maxCompactTime) {
break;
}
} }
} }
store.close(); store.close();
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论