提交 4f5fcd83 authored 作者: Thomas Mueller's avatar Thomas Mueller

A persistent multi-version map: online backup

上级 c7065ac0
...@@ -5,12 +5,19 @@ ...@@ -5,12 +5,19 @@
*/ */
package org.h2.test.store; package org.h2.test.store;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import org.h2.dev.store.btree.MVMap; import org.h2.dev.store.btree.MVMap;
import org.h2.dev.store.btree.MVStore; import org.h2.dev.store.btree.MVStore;
import org.h2.dev.store.btree.MVStoreBuilder; import org.h2.dev.store.btree.MVStoreBuilder;
import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.Task; import org.h2.util.Task;
...@@ -28,12 +35,98 @@ public class TestConcurrent extends TestMVStore { ...@@ -28,12 +35,98 @@ public class TestConcurrent extends TestMVStore {
TestBase.createCaller().init().test(); TestBase.createCaller().init().test();
} }
public void test() throws InterruptedException { public void test() throws Exception {
FileUtils.deleteRecursive(getBaseDir(), true);
testConcurrentOnlineBackup();
testConcurrentIterate(); testConcurrentIterate();
testConcurrentWrite(); testConcurrentWrite();
testConcurrentRead(); testConcurrentRead();
} }
private void testConcurrentOnlineBackup() throws Exception {
String fileName = getBaseDir() + "/onlineBackup.h3";
String fileNameRestore = getBaseDir() + "/onlineRestore.h3";
final MVStore s = openStore(fileName);
final MVMap<Integer, byte[]> map = s.openMap("test");
final Random r = new Random();
Task t = new Task() {
@Override
public void call() throws Exception {
while (!stop) {
for (int i = 0; i < 100; i++) {
map.put(i, new byte[100 * r.nextInt(100)]);
}
s.store();
map.clear();
s.store();
long len = new File(s.getFileName()).length();
if (len > 1024 * 100) {
// slow down writing
Thread.sleep(10);
}
}
}
};
t.execute();
// the wrong way to back up
try {
for (int i = 0; i < 10; i++) {
byte[] buff = readFileSlowly(fileName);
FileOutputStream out = new FileOutputStream(fileNameRestore);
out.write(buff);
MVStore s2 = openStore(fileNameRestore);
try {
MVMap<Integer, byte[]> test = s2.openMap("test");
for (Integer k : test.keySet()) {
test.get(k);
}
} finally {
s2.close();
}
}
fail();
} catch (Exception e) {
// expected
}
// the right way to back up
for (int i = 0; i < 10; i++) {
// System.out.println("test " + i);
s.setReuseSpace(false);
byte[] buff = readFileSlowly(fileName);
s.setReuseSpace(true);
FileOutputStream out = new FileOutputStream(fileNameRestore);
out.write(buff);
MVStore s2 = openStore(fileNameRestore);
MVMap<Integer, byte[]> test = s2.openMap("test");
for (Integer k : test.keySet()) {
test.get(k);
}
s2.close();
// let it compact
Thread.sleep(10);
}
t.get();
s.close();
}
private static byte[] readFileSlowly(String fileName) throws Exception {
InputStream in = new BufferedInputStream(new FileInputStream(
fileName));
ByteArrayOutputStream buff = new ByteArrayOutputStream();
for (int j = 0;; j++) {
int x = in.read();
if (x < 0) {
break;
}
buff.write(x);
if (j % 4096 == 0) {
Thread.sleep(1);
}
}
in.close();
return buff.toByteArray();
}
private void testConcurrentIterate() { private void testConcurrentIterate() {
MVStore s = MVStoreBuilder.inMemory().with(new TestMapFactory()).open(); MVStore s = MVStoreBuilder.inMemory().with(new TestMapFactory()).open();
s.setMaxPageSize(3); s.setMaxPageSize(3);
......
...@@ -1037,7 +1037,8 @@ public class TestMVStore extends TestBase { ...@@ -1037,7 +1037,8 @@ public class TestMVStore extends TestBase {
* @return the store * @return the store
*/ */
protected static MVStore openStore(String fileName) { protected static MVStore openStore(String fileName) {
MVStore store = MVStoreBuilder.fileBased(fileName).with(new TestMapFactory()).open(); MVStore store = MVStoreBuilder.fileBased(fileName).
with(new TestMapFactory()).open();
store.setMaxPageSize(1000); store.setMaxPageSize(1000);
return store; return store;
} }
......
...@@ -91,6 +91,8 @@ public class MVStore { ...@@ -91,6 +91,8 @@ public class MVStore {
static final int BLOCK_SIZE = 4 * 1024; static final int BLOCK_SIZE = 4 * 1024;
private final HashMap<String, Object> config;
private final String fileName; private final String fileName;
private final MapFactory mapFactory; private final MapFactory mapFactory;
...@@ -130,7 +132,7 @@ public class MVStore { ...@@ -130,7 +132,7 @@ public class MVStore {
private int lastMapId; private int lastMapId;
private boolean reuseSpace = true; private volatile boolean reuseSpace = true;
private long retainVersion = -1; private long retainVersion = -1;
private int retainChunk = -1; private int retainChunk = -1;
...@@ -142,6 +144,7 @@ public class MVStore { ...@@ -142,6 +144,7 @@ public class MVStore {
private int unsavedPageCount; private int unsavedPageCount;
MVStore(HashMap<String, Object> config) { MVStore(HashMap<String, Object> config) {
this.config = config;
this.fileName = (String) config.get("fileName"); this.fileName = (String) config.get("fileName");
this.mapFactory = (MapFactory) config.get("mapFactory"); this.mapFactory = (MapFactory) config.get("mapFactory");
this.readOnly = "r".equals(config.get("openMode")); this.readOnly = "r".equals(config.get("openMode"));
...@@ -395,6 +398,7 @@ public class MVStore { ...@@ -395,6 +398,7 @@ public class MVStore {
} }
} }
} catch (Exception e) { } catch (Exception e) {
close();
throw convert(e); throw convert(e);
} }
} }
...@@ -985,6 +989,19 @@ public class MVStore { ...@@ -985,6 +989,19 @@ public class MVStore {
return reuseSpace; return reuseSpace;
} }
/**
* Whether empty space in the file should be re-used. If enabled, old data
* is overwritten (default). If disabled, writes are appended at the end of
* the file.
* <p>
* This setting is specially useful for online backup. To create an online
* backup, disable this setting, then copy the file (starting at the
* beginning of the file). In this case, concurrent backup and write
* operations are possible (obviously the backup process needs to be faster
* than the write operations).
*
* @param reuseSpace the new value
*/
public void setReuseSpace(boolean reuseSpace) { public void setReuseSpace(boolean reuseSpace) {
this.reuseSpace = reuseSpace; this.reuseSpace = reuseSpace;
} }
...@@ -1210,4 +1227,8 @@ public class MVStore { ...@@ -1210,4 +1227,8 @@ public class MVStore {
return fileHeader; return fileHeader;
} }
public String toString() {
return DataUtils.appendMap(new StringBuilder(), config).toString();
}
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论