adding the blocksize to the metadata section
This commit is contained in:
@@ -2,6 +2,7 @@ package org.lucares.pdb.map;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -31,6 +32,8 @@ public class PersistentMap<K, V> implements AutoCloseable {
|
|||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(PersistentMap.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(PersistentMap.class);
|
||||||
|
|
||||||
|
private static final long CURRENT_VERSION = 1;
|
||||||
|
|
||||||
// the maximum key
|
// the maximum key
|
||||||
static final byte[] MAX_KEY;
|
static final byte[] MAX_KEY;
|
||||||
static {
|
static {
|
||||||
@@ -151,7 +154,7 @@ public class PersistentMap<K, V> implements AutoCloseable {
|
|||||||
public static final EncoderDecoder<Empty> EMPTY_ENCODER = new EmptyCoder();
|
public static final EncoderDecoder<Empty> EMPTY_ENCODER = new EmptyCoder();
|
||||||
|
|
||||||
static final int BLOCK_SIZE = 4096;
|
static final int BLOCK_SIZE = 4096;
|
||||||
static final long NODE_OFFSET_TO_ROOT_NODE = 8;
|
static final long OFFSET_META_DATA = 8;
|
||||||
|
|
||||||
private DiskStorage diskStore;
|
private DiskStorage diskStore;
|
||||||
|
|
||||||
@@ -170,6 +173,8 @@ public class PersistentMap<K, V> implements AutoCloseable {
|
|||||||
|
|
||||||
private final Path path;
|
private final Path path;
|
||||||
|
|
||||||
|
private long version;
|
||||||
|
|
||||||
public PersistentMap(final Path path, final Path storageBasePath, final EncoderDecoder<K> keyEncoder,
|
public PersistentMap(final Path path, final Path storageBasePath, final EncoderDecoder<K> keyEncoder,
|
||||||
final EncoderDecoder<V> valueEncoder) {
|
final EncoderDecoder<V> valueEncoder) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
@@ -178,12 +183,8 @@ public class PersistentMap<K, V> implements AutoCloseable {
|
|||||||
this.valueEncoder = valueEncoder;
|
this.valueEncoder = valueEncoder;
|
||||||
initIfNew();
|
initIfNew();
|
||||||
|
|
||||||
readOffsetOfRootNode();
|
readMetaData();
|
||||||
}
|
updateIfNecessary();
|
||||||
|
|
||||||
private void readOffsetOfRootNode() {
|
|
||||||
final DiskBlock diskBlock = diskStore.getDiskBlock(NODE_OFFSET_TO_ROOT_NODE, diskStore.minAllocationSize());
|
|
||||||
nodeOffsetOfRootNode = diskBlock.getByteBuffer().getLong(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -198,7 +199,7 @@ public class PersistentMap<K, V> implements AutoCloseable {
|
|||||||
private void initIfNew() {
|
private void initIfNew() {
|
||||||
if (diskStore.size() < BLOCK_SIZE) {
|
if (diskStore.size() < BLOCK_SIZE) {
|
||||||
final long nodeOffsetToRootNode = diskStore.allocateBlock(diskStore.minAllocationSize());
|
final long nodeOffsetToRootNode = diskStore.allocateBlock(diskStore.minAllocationSize());
|
||||||
Preconditions.checkEqual(nodeOffsetToRootNode, NODE_OFFSET_TO_ROOT_NODE,
|
Preconditions.checkEqual(nodeOffsetToRootNode, OFFSET_META_DATA,
|
||||||
"The offset of the pointer to the root node must be at a well known location. "
|
"The offset of the pointer to the root node must be at a well known location. "
|
||||||
+ "Otherwise we would not be able to find it in an already existing file.");
|
+ "Otherwise we would not be able to find it in an already existing file.");
|
||||||
|
|
||||||
@@ -210,14 +211,40 @@ public class PersistentMap<K, V> implements AutoCloseable {
|
|||||||
final var rootNode = PersistentMapDiskNode.emptyRootNode(blockOffset);
|
final var rootNode = PersistentMapDiskNode.emptyRootNode(blockOffset);
|
||||||
writeNode(rootNode);
|
writeNode(rootNode);
|
||||||
|
|
||||||
// 4. update pointer to root node
|
// 4. meta data section with pointer to root node and version
|
||||||
writeNodeOffsetOfRootNode(blockOffset);
|
initMetaDataSection(blockOffset);
|
||||||
|
|
||||||
// 5. insert a dummy entry with a 'maximum' key
|
// 5. insert a dummy entry with a 'maximum' key
|
||||||
putValue(MAX_KEY, valueEncoder.getEmptyValue());
|
putValue(MAX_KEY, valueEncoder.getEmptyValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initMetaDataSection(final long newNodeOffsetToRootNode) {
|
||||||
|
|
||||||
|
final DiskBlock diskBlock = diskStore.getDiskBlock(OFFSET_META_DATA, diskStore.minAllocationSize());
|
||||||
|
final ByteBuffer byteBuffer = diskBlock.getByteBuffer();
|
||||||
|
byteBuffer.putLong(newNodeOffsetToRootNode);
|
||||||
|
byteBuffer.putLong(CURRENT_VERSION);
|
||||||
|
byteBuffer.putLong(BLOCK_SIZE);
|
||||||
|
diskBlock.force();
|
||||||
|
nodeOffsetOfRootNode = newNodeOffsetToRootNode;
|
||||||
|
version = CURRENT_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readMetaData() {
|
||||||
|
final DiskBlock diskBlock = diskStore.getDiskBlock(OFFSET_META_DATA, diskStore.minAllocationSize());
|
||||||
|
final ByteBuffer byteBuffer = diskBlock.getByteBuffer();
|
||||||
|
nodeOffsetOfRootNode = byteBuffer.getLong();
|
||||||
|
version = byteBuffer.getLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeNodeOffsetOfRootNode(final long newNodeOffsetToRootNode) {
|
||||||
|
final DiskBlock diskBlock = diskStore.getDiskBlock(OFFSET_META_DATA, diskStore.minAllocationSize());
|
||||||
|
diskBlock.getByteBuffer().putLong(0, newNodeOffsetToRootNode);
|
||||||
|
diskBlock.force();
|
||||||
|
nodeOffsetOfRootNode = newNodeOffsetToRootNode;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized void putAllValues(final Map<K, V> map) {
|
public synchronized void putAllValues(final Map<K, V> map) {
|
||||||
for (final Entry<K, V> e : map.entrySet()) {
|
for (final Entry<K, V> e : map.entrySet()) {
|
||||||
putValue(e.getKey(), e.getValue());
|
putValue(e.getKey(), e.getValue());
|
||||||
@@ -434,7 +461,9 @@ public class PersistentMap<K, V> implements AutoCloseable {
|
|||||||
// diskBlock.force(); // makes writing nodes slower by factor 800 (sic!)
|
// diskBlock.force(); // makes writing nodes slower by factor 800 (sic!)
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void print() {
|
public synchronized void print(final boolean printValues) {
|
||||||
|
|
||||||
|
System.out.println("printing nodes:");
|
||||||
|
|
||||||
visitNodeEntriesPreOrder((node, parentNode, nodeEntry, depth) -> {
|
visitNodeEntriesPreOrder((node, parentNode, nodeEntry, depth) -> {
|
||||||
|
|
||||||
@@ -442,8 +471,11 @@ public class PersistentMap<K, V> implements AutoCloseable {
|
|||||||
|
|
||||||
final String children = "#" + node.getEntries().size();
|
final String children = "#" + node.getEntries().size();
|
||||||
|
|
||||||
writer.println(" ".repeat(depth) + "@" + node.getNodeOffset() + " " + children + " " + nodeEntry
|
if (printValues || nodeEntry.isInnerNode()) {
|
||||||
.toString(b -> String.valueOf(keyEncoder.decode(b)), b -> String.valueOf(valueEncoder.decode(b))));
|
writer.println(" ".repeat(depth) + "@" + node.getNodeOffset() + " " + children + " "
|
||||||
|
+ nodeEntry.toString(b -> String.valueOf(keyEncoder.decode(b)),
|
||||||
|
b -> String.valueOf(valueEncoder.decode(b))));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,7 +539,7 @@ public class PersistentMap<K, V> implements AutoCloseable {
|
|||||||
public synchronized void reindex() throws IOException {
|
public synchronized void reindex() throws IOException {
|
||||||
final long start = System.nanoTime();
|
final long start = System.nanoTime();
|
||||||
final AtomicLong countValues = new AtomicLong();
|
final AtomicLong countValues = new AtomicLong();
|
||||||
LOGGER.info("start reindexing file: {}", path);
|
LOGGER.info("start reindexing file: {}, version: {}, stats before:\n{}", path, version, stats());
|
||||||
final Path newFile = path.getParent().resolve(path.getFileName() + ".tmp");
|
final Path newFile = path.getParent().resolve(path.getFileName() + ".tmp");
|
||||||
|
|
||||||
try (PersistentMap<K, V> newMap = new PersistentMap<>(newFile, null, keyEncoder, valueEncoder)) {
|
try (PersistentMap<K, V> newMap = new PersistentMap<>(newFile, null, keyEncoder, valueEncoder)) {
|
||||||
@@ -527,11 +559,12 @@ public class PersistentMap<K, V> implements AutoCloseable {
|
|||||||
swapFiles(newFile);
|
swapFiles(newFile);
|
||||||
|
|
||||||
diskStore = new DiskStorage(path, null);
|
diskStore = new DiskStorage(path, null);
|
||||||
readOffsetOfRootNode();
|
readMetaData();
|
||||||
|
version = CURRENT_VERSION;
|
||||||
final double durationInMs = (System.nanoTime() - start) / 1_000_000.0;
|
final double durationInMs = (System.nanoTime() - start) / 1_000_000.0;
|
||||||
final double valuesPerSecond = countValues.get() / (durationInMs / 1000);
|
final double valuesPerSecond = countValues.get() / (durationInMs / 1000);
|
||||||
LOGGER.info("done reindexing, took {} ms, {} values, {} values/s", (int) Math.ceil(durationInMs),
|
LOGGER.info("done reindexing, took {} ms, {} values, {} values/s, stats after:\n{}",
|
||||||
countValues.get(), valuesPerSecond);
|
(int) Math.ceil(durationInMs), countValues.get(), valuesPerSecond, stats());
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized PersistentMapStats stats() {
|
public synchronized PersistentMapStats stats() {
|
||||||
@@ -596,11 +629,15 @@ public class PersistentMap<K, V> implements AutoCloseable {
|
|||||||
return nodeOffsetOfRootNode;
|
return nodeOffsetOfRootNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeNodeOffsetOfRootNode(final long newNodeOffsetToRootNode) {
|
private void updateIfNecessary() {
|
||||||
final DiskBlock diskBlock = diskStore.getDiskBlock(NODE_OFFSET_TO_ROOT_NODE, diskStore.minAllocationSize());
|
try {
|
||||||
diskBlock.getByteBuffer().putLong(0, newNodeOffsetToRootNode);
|
if (version < 1) {
|
||||||
diskBlock.force();
|
reindex();
|
||||||
nodeOffsetOfRootNode = newNodeOffsetToRootNode;
|
}
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"failed to update " + path + " from version " + version + " to current version", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public class PersistentMapTest {
|
|||||||
final String actualValue = map.getValue(entry.getKey());
|
final String actualValue = map.getValue(entry.getKey());
|
||||||
|
|
||||||
if (!Objects.equals(actualValue, entry.getValue())) {
|
if (!Objects.equals(actualValue, entry.getValue())) {
|
||||||
map.print();
|
map.print(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assertions.assertEquals(entry.getValue(), actualValue,
|
Assertions.assertEquals(entry.getValue(), actualValue,
|
||||||
@@ -149,7 +149,7 @@ public class PersistentMapTest {
|
|||||||
final Long actualValue = map.getValue(entry.getKey());
|
final Long actualValue = map.getValue(entry.getKey());
|
||||||
|
|
||||||
if (!Objects.equals(actualValue, entry.getValue())) {
|
if (!Objects.equals(actualValue, entry.getValue())) {
|
||||||
map.print();
|
map.print(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assertions.assertEquals(entry.getValue(), actualValue,
|
Assertions.assertEquals(entry.getValue(), actualValue,
|
||||||
@@ -166,8 +166,8 @@ public class PersistentMapTest {
|
|||||||
map.visitNodeEntriesPreOrder(
|
map.visitNodeEntriesPreOrder(
|
||||||
(node, parentNode, nodeEntry, depth) -> counter.addAndGet(nodeEntry.isInnerNode() ? 1 : 0));
|
(node, parentNode, nodeEntry, depth) -> counter.addAndGet(nodeEntry.isInnerNode() ? 1 : 0));
|
||||||
|
|
||||||
Assertions.assertEquals(4, counter.get(),
|
Assertions.assertEquals(3, counter.get(),
|
||||||
"number of nodes should be small. Any number larger than 4 indicates, "
|
"number of nodes should be small. Any number larger than 3 indicates, "
|
||||||
+ "that new inner nodes are created even though the existing inner "
|
+ "that new inner nodes are created even though the existing inner "
|
||||||
+ "nodes could hold the values");
|
+ "nodes could hold the values");
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ public class PersistentMapTest {
|
|||||||
|
|
||||||
insertedValues.put(key, value);
|
insertedValues.put(key, value);
|
||||||
|
|
||||||
// map.print();
|
// map.print(false);
|
||||||
|
|
||||||
final boolean failEarly = false;
|
final boolean failEarly = false;
|
||||||
if (failEarly) {
|
if (failEarly) {
|
||||||
@@ -210,7 +210,7 @@ public class PersistentMapTest {
|
|||||||
final Empty actualValue = map.getValue(entry.getKey());
|
final Empty actualValue = map.getValue(entry.getKey());
|
||||||
|
|
||||||
if (!Objects.equals(actualValue, entry.getValue())) {
|
if (!Objects.equals(actualValue, entry.getValue())) {
|
||||||
map.print();
|
map.print(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assertions.assertEquals(entry.getValue(), actualValue,
|
Assertions.assertEquals(entry.getValue(), actualValue,
|
||||||
@@ -222,13 +222,13 @@ public class PersistentMapTest {
|
|||||||
|
|
||||||
try (final PersistentMap<Long, Empty> map = new PersistentMap<>(file, dataDirectory, PersistentMap.LONG_CODER,
|
try (final PersistentMap<Long, Empty> map = new PersistentMap<>(file, dataDirectory, PersistentMap.LONG_CODER,
|
||||||
PersistentMap.EMPTY_ENCODER)) {
|
PersistentMap.EMPTY_ENCODER)) {
|
||||||
map.print();
|
// map.print(false);
|
||||||
final AtomicInteger counter = new AtomicInteger();
|
final AtomicInteger counter = new AtomicInteger();
|
||||||
map.visitNodeEntriesPreOrder(
|
map.visitNodeEntriesPreOrder(
|
||||||
(node, parentNode, nodeEntry, depth) -> counter.addAndGet(nodeEntry.isInnerNode() ? 1 : 0));
|
(node, parentNode, nodeEntry, depth) -> counter.addAndGet(nodeEntry.isInnerNode() ? 1 : 0));
|
||||||
|
|
||||||
Assertions.assertEquals(4, counter.get(),
|
Assertions.assertEquals(3, counter.get(),
|
||||||
"number of nodes should be small. Any number larger than 4 indicates, "
|
"number of nodes should be small. Any number larger than 3 indicates, "
|
||||||
+ "that new inner nodes are created even though the existing inner "
|
+ "that new inner nodes are created even though the existing inner "
|
||||||
+ "nodes could hold the values");
|
+ "nodes could hold the values");
|
||||||
|
|
||||||
@@ -410,7 +410,7 @@ public class PersistentMapTest {
|
|||||||
final Long actualValue = map.getValue(entry.getKey());
|
final Long actualValue = map.getValue(entry.getKey());
|
||||||
|
|
||||||
if (!Objects.equals(actualValue, entry.getValue())) {
|
if (!Objects.equals(actualValue, entry.getValue())) {
|
||||||
map.print();
|
map.print(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assertions.assertEquals(entry.getValue(), actualValue,
|
Assertions.assertEquals(entry.getValue(), actualValue,
|
||||||
|
|||||||
Reference in New Issue
Block a user