strangedb/src/main/java/org/warp/jcwdb/FileIndexManager.java

493 lines
15 KiB
Java
Raw Normal View History

2018-11-19 15:16:12 +01:00
package org.warp.jcwdb;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
2018-12-05 02:39:41 +01:00
import it.unimi.dsi.fastutil.longs.*;
2018-12-21 10:03:30 +01:00
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import org.warp.jcwdb.ann.DatabaseManager;
2018-11-27 10:26:01 +01:00
2018-11-19 15:16:12 +01:00
import java.io.IOException;
2018-11-20 18:39:48 +01:00
import java.nio.ByteBuffer;
2018-11-19 15:16:12 +01:00
import java.nio.channels.Channels;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class FileIndexManager implements IndexManager {
public static final boolean ALWAYS_ALLOCATE_NEW = false;
2018-11-19 15:16:12 +01:00
private final SeekableByteChannel dataFileChannel, metadataFileChannel;
2019-01-06 00:31:52 +01:00
private volatile long metadataFileChannelSize;
2018-11-19 15:16:12 +01:00
private final FileAllocator fileAllocator;
2018-11-26 14:50:44 +01:00
private final ByteBuffer metadataByteBuffer = ByteBuffer.allocateDirect(IndexDetails.TOTAL_BYTES);
private final ByteBuffer maskByteBuffer = ByteBuffer.allocateDirect(Long.BYTES);
2018-11-20 18:39:48 +01:00
private volatile boolean closed;
private final Object closeLock = new Object();
2018-11-26 14:50:44 +01:00
private final Object metadataByteBufferLock = new Object();
2018-11-27 10:26:01 +01:00
private final Object maskByteBufferLock = new Object();
2018-11-27 17:47:19 +01:00
private final Object indicesMapsAccessLock = new Object();
2018-11-19 15:16:12 +01:00
2018-11-20 18:39:48 +01:00
/**
* Edit this using editIndex()
* Get using getIndexMetadata()
* This hashmap must contain all indices.
*/
2018-11-27 10:26:01 +01:00
private final Long2ObjectMap<IndexDetails> loadedIndices;
2018-11-20 18:39:48 +01:00
/**
* Edit this using editIndex()
*/
private final LongSet dirtyLoadedIndices, removedIndices;
2018-11-21 01:02:25 +01:00
private long firstAllocableIndex;
2018-11-20 18:39:48 +01:00
2018-11-27 10:26:01 +01:00
public FileIndexManager(Path dataFile, Path metadataFile) throws IOException {
2019-01-06 00:31:52 +01:00
if (Cleaner.DISABLE_CLEANER) {
loadedIndices = new Long2ObjectOpenHashMap<>();
dirtyLoadedIndices = new LongOpenHashSet();
removedIndices = new LongOpenHashSet();
} else {
loadedIndices = new Long2ObjectLinkedOpenHashMap<>();
dirtyLoadedIndices = new LongLinkedOpenHashSet();
removedIndices = new LongLinkedOpenHashSet();
}
2018-11-22 23:31:41 +01:00
if (Files.notExists(dataFile)) {
Files.createFile(dataFile);
}
if (Files.notExists(metadataFile)) {
Files.createFile(metadataFile);
}
dataFileChannel = Files.newByteChannel(dataFile, StandardOpenOption.READ, StandardOpenOption.WRITE);
metadataFileChannel = Files.newByteChannel(metadataFile, StandardOpenOption.READ, StandardOpenOption.WRITE);
2019-01-06 00:31:52 +01:00
metadataFileChannelSize = metadataFileChannel.size();
fileAllocator = createFileAllocator(dataFileChannel, metadataFileChannel.position(0));
2019-01-06 00:31:52 +01:00
firstAllocableIndex = getMetadataFileChannelSize() / (long) IndexDetails.TOTAL_BYTES;
2018-11-19 15:16:12 +01:00
}
private long getMetadataFileChannelSize() {
2019-01-06 00:31:52 +01:00
return metadataFileChannelSize;
}
2018-12-21 10:03:30 +01:00
private FileAllocator createFileAllocator(final SeekableByteChannel dataFileChannel, final SeekableByteChannel metadataFileChannel) throws IOException {
if (ALWAYS_ALLOCATE_NEW) {
return new FileAllocator(dataFileChannel);
} else {
Long2IntMap freeBytes = new Long2IntRBTreeMap();
Long2IntMap usedBytes = new Long2IntRBTreeMap();
long firstOffset = 0;
metadataFileChannel.position(0);
while (metadataFileChannel.position() + IndexDetails.TOTAL_BYTES <= getMetadataFileChannelSize()) {
IndexDetails indexDetails = readIndexDetailsAt(metadataFileChannel);
if (indexDetails != null) {
long offset = indexDetails.getOffset();
if (!usedBytes.containsKey(offset) || indexDetails.getSize() > usedBytes.get(offset)) {
usedBytes.put(offset, indexDetails.getSize());
}
if (offset < firstOffset) {
firstOffset = offset;
}
2018-12-21 10:03:30 +01:00
}
}
long previousEntryOffset = 0;
long previousEntrySize = 0;
ObjectIterator<Long2IntMap.Entry> it = usedBytes.long2IntEntrySet().iterator();
while (it.hasNext()) {
final Long2IntMap.Entry entry = it.next();
final long entryOffset = entry.getLongKey();
final long entrySize = entry.getIntValue();
it.remove();
if (previousEntryOffset + previousEntrySize < entryOffset) {
freeBytes.put(previousEntryOffset + previousEntrySize, (int) (entryOffset - (previousEntryOffset + previousEntrySize)));
}
2018-12-21 10:03:30 +01:00
previousEntryOffset = entryOffset;
previousEntrySize = entrySize;
2018-12-21 10:03:30 +01:00
}
final long fileSize = previousEntryOffset + previousEntrySize;
2018-12-21 10:03:30 +01:00
return new FileAllocator(dataFileChannel, fileSize, freeBytes);
}
2018-12-21 10:03:30 +01:00
}
2018-11-19 15:16:12 +01:00
@Override
2018-11-20 18:39:48 +01:00
public <T> T get(long index, DBReader<T> reader) throws IOException {
checkClosed();
IndexDetails details = getIndexMetadata(index);
Input i = new Input(Channels.newInputStream(dataFileChannel.position(details.getOffset())));
2018-11-22 23:31:41 +01:00
T result = reader.read(i, details.getSize());
2018-11-19 15:16:12 +01:00
return result;
}
@Override
public IndexDetails set(long index, int size, DBWriter data) throws IOException {
2018-11-20 18:39:48 +01:00
checkClosed();
2019-01-06 00:31:52 +01:00
IndexDetails indexDetails = getIndexMetadataUnsafe(index);
if (ALWAYS_ALLOCATE_NEW || indexDetails == null || indexDetails.getSize() < size) {
2018-12-07 00:26:38 +01:00
// Allocate new space
IndexDetails newDetails = allocateAndWrite(index, size, data);
2018-12-20 01:12:46 +01:00
if (indexDetails != null) {
// Mark free the old bytes
fileAllocator.markFree(indexDetails.getOffset(), indexDetails.getSize());
}
return newDetails;
2018-11-20 18:39:48 +01:00
} else {
2018-12-07 00:26:38 +01:00
// Check if size changed
if (size < indexDetails.getSize()) {
2018-12-07 00:26:38 +01:00
// Mark free the unused bytes
fileAllocator.markFree(indexDetails.getOffset() + size, size);
2018-11-20 18:39:48 +01:00
}
2018-12-07 00:26:38 +01:00
// Update index details
indexDetails = editIndex(index, indexDetails, indexDetails.getOffset(), size);
2018-12-07 00:26:38 +01:00
// Write data
writeExact(indexDetails, size, data);
2018-12-11 23:00:51 +01:00
// Before returning, return IndexDetails
return indexDetails;
2018-11-20 18:39:48 +01:00
}
}
2018-11-21 01:02:25 +01:00
@Override
public long add(int size) {
checkClosed();
final long offset = fileAllocator.allocate(size);
final IndexDetails indexDetails = new IndexDetails(offset, size);
final long index = createIndexMetadata(indexDetails);
return index;
}
@Override
public long add(int size, DBWriter data) throws IOException {
2018-11-21 01:02:25 +01:00
checkClosed();
final long offset = fileAllocator.allocate(size);
final IndexDetails indexDetails = new IndexDetails(offset, size);
2018-11-21 01:02:25 +01:00
final long index = createIndexMetadata(indexDetails);
writeExact(indexDetails, size, data);
2018-11-21 01:02:25 +01:00
return index;
}
2018-12-21 10:03:30 +01:00
@Override
public FullIndexDetails addAndGetDetails(int size, DBWriter data) throws IOException {
2018-12-21 10:03:30 +01:00
checkClosed();
final long offset = fileAllocator.allocate(size);
final IndexDetails indexDetails = new IndexDetails(offset, size);
2018-12-21 10:03:30 +01:00
final long index = createIndexMetadata(indexDetails);
writeExact(indexDetails, size, data);
2018-12-21 10:03:30 +01:00
return new FullIndexDetails(index, indexDetails);
}
2018-11-21 01:02:25 +01:00
/**
* Write the data at index.
* The input size must be equal to the index size!
*/
private void writeExact(final IndexDetails indexDetails, int size, DBWriter data) throws IOException {
if (indexDetails.getSize() != size) {
throw new IOException("Unable to write " + size + " in a space of " + indexDetails.getSize());
2018-11-21 01:02:25 +01:00
}
2018-11-20 18:39:48 +01:00
final long offset = indexDetails.getOffset();
final Output o = new Output(Channels.newOutputStream(dataFileChannel.position(offset)), size);
data.write(o);
2018-11-22 23:31:41 +01:00
o.flush();
2018-11-20 18:39:48 +01:00
}
private IndexDetails allocateAndWrite(final long index, int size, DBWriter w) throws IOException {
2018-11-20 18:39:48 +01:00
final long offset = fileAllocator.allocate(size);
IndexDetails details = editIndex(index, offset, size);
writeExact(details, size, w);
2018-12-11 23:00:51 +01:00
return details;
2018-11-19 15:16:12 +01:00
}
@Override
2018-11-21 01:02:25 +01:00
public void delete(long index) throws IOException {
2018-11-20 18:39:48 +01:00
checkClosed();
2018-11-21 01:02:25 +01:00
IndexDetails indexDetails = getIndexMetadataUnsafe(index);
if (indexDetails != null) {
fileAllocator.markFree(indexDetails.getOffset(), indexDetails.getSize());
}
2018-12-06 12:30:04 +01:00
synchronized (indicesMapsAccessLock) {
2018-11-27 17:47:19 +01:00
dirtyLoadedIndices.remove(index);
loadedIndices.remove(index);
removedIndices.add(index);
}
}
2018-12-06 12:30:04 +01:00
2018-11-27 17:47:19 +01:00
public void flushAndUnload(long index) throws IOException {
if (removedIndices.contains(index)) {
2018-12-06 12:30:04 +01:00
synchronized (indicesMapsAccessLock) {
2018-11-27 17:47:19 +01:00
removedIndices.remove(index);
dirtyLoadedIndices.remove(index);
loadedIndices.remove(index);
}
// Update indices metadata
SeekableByteChannel metadata = metadataFileChannel.position(index * IndexDetails.TOTAL_BYTES);
eraseIndexDetails(metadata);
}
2018-12-11 23:00:51 +01:00
boolean isDirty = false;
IndexDetails indexDetails = null;
synchronized (indicesMapsAccessLock) {
if (dirtyLoadedIndices.contains(index)) {
indexDetails = loadedIndices.get(index);
2018-11-27 17:47:19 +01:00
dirtyLoadedIndices.remove(index);
}
2018-12-11 23:00:51 +01:00
}
if (isDirty) {
2018-11-27 17:47:19 +01:00
// Update indices metadata
2019-01-06 00:31:52 +01:00
long position = index * IndexDetails.TOTAL_BYTES;
resizeMetadataFileChannel(position);
SeekableByteChannel metadata = metadataFileChannel.position(position);
2018-11-27 17:47:19 +01:00
writeIndexDetails(metadata, indexDetails);
}
2018-12-06 12:30:04 +01:00
synchronized (indicesMapsAccessLock) {
2018-11-27 17:47:19 +01:00
loadedIndices.remove(index);
}
2018-11-19 15:16:12 +01:00
}
@Override
public boolean has(long index) {
2018-11-20 18:39:48 +01:00
checkClosed();
2018-11-21 01:02:25 +01:00
try {
return getIndexMetadataUnsafe(index) != null;
} catch (IOException ex) {
ex.printStackTrace();
return false;
}
2018-11-19 15:16:12 +01:00
}
2018-11-20 18:39:48 +01:00
2018-12-07 00:26:38 +01:00
/**
* Edit index data if a change is detected
* @param index
* @param oldData Old index data to check
* @param offset offset
* @param size size
* @return
*/
private IndexDetails editIndex(long index, IndexDetails oldData, long offset, int size) {
if (oldData.getOffset() != offset || oldData.getSize() != size) {
return editIndex(index, offset, size);
2018-12-07 00:26:38 +01:00
} else {
return oldData;
}
}
/**
* Edit index data
* @param index
* @param offset
* @param size
* @return
*/
private IndexDetails editIndex(long index, long offset, int size) {
IndexDetails indexDetails = new IndexDetails(offset, size);
2018-11-20 18:39:48 +01:00
editIndex(index, indexDetails);
return indexDetails;
}
2018-12-07 00:26:38 +01:00
/**
* Edit index data
* @param index
* @param details
*/
2018-11-22 23:31:41 +01:00
private void editIndex(long index, IndexDetails details) {
2018-12-21 10:03:30 +01:00
synchronized (indicesMapsAccessLock) {
2018-11-27 17:47:19 +01:00
loadedIndices.put(index, details);
dirtyLoadedIndices.add(index);
}
2018-11-22 23:31:41 +01:00
}
private long createIndexMetadata(IndexDetails indexDetails) {
2018-12-06 12:30:04 +01:00
synchronized (indicesMapsAccessLock) {
2018-11-27 17:47:19 +01:00
long newIndex = firstAllocableIndex++;
loadedIndices.put(newIndex, indexDetails);
dirtyLoadedIndices.add(newIndex);
removedIndices.remove(newIndex);
return newIndex;
}
2018-11-22 23:31:41 +01:00
}
2018-11-21 01:02:25 +01:00
private IndexDetails getIndexMetadataUnsafe(long index) throws IOException {
// Return index details if loaded
2018-11-27 17:47:19 +01:00
IndexDetails details;
2018-12-06 12:30:04 +01:00
synchronized (indicesMapsAccessLock) {
2018-11-27 17:47:19 +01:00
details = loadedIndices.getOrDefault(index, null);
}
2018-11-20 18:39:48 +01:00
if (details != null) return details;
2018-11-21 01:02:25 +01:00
// Try to load the details from file
2018-11-22 23:31:41 +01:00
final long metadataPosition = index * IndexDetails.TOTAL_BYTES;
2019-01-06 00:31:52 +01:00
if (metadataPosition + IndexDetails.TOTAL_BYTES > getMetadataFileChannelSize()) {
2018-11-22 23:31:41 +01:00
// Avoid underflow exception
return null;
}
SeekableByteChannel currentMetadataFileChannel = metadataFileChannel.position(metadataPosition);
2018-12-21 10:03:30 +01:00
IndexDetails indexDetails = readIndexDetailsAt(currentMetadataFileChannel);
if (indexDetails != null) {
editIndex(index, indexDetails);
return indexDetails;
}
// No results found. Returning null
return null;
}
private IndexDetails readIndexDetailsAt(SeekableByteChannel currentMetadataFileChannel) throws IOException {
2018-12-19 12:19:03 +01:00
IndexDetails indexDetails = null;
2018-12-21 10:03:30 +01:00
synchronized (metadataByteBufferLock) {
2018-11-26 14:50:44 +01:00
metadataByteBuffer.rewind();
currentMetadataFileChannel.read(metadataByteBuffer);
metadataByteBuffer.rewind();
// If it's not deleted continue
final long offset = metadataByteBuffer.getLong();
if (offset >= 0) { // If it's < 0 it means that the index has been deleted
2018-11-26 14:50:44 +01:00
final int size = metadataByteBuffer.getInt();
indexDetails = new IndexDetails(offset, size);
2018-11-26 14:50:44 +01:00
}
2018-11-21 01:02:25 +01:00
}
2018-12-21 10:03:30 +01:00
return indexDetails;
2018-11-20 18:39:48 +01:00
}
2018-11-21 01:02:25 +01:00
2018-11-20 18:39:48 +01:00
private IndexDetails getIndexMetadata(long index) throws IOException {
IndexDetails details = getIndexMetadataUnsafe(index);
if (details == null)
throw new IOException("Index " + index + " not found");
else
return details;
}
@Override
public void close() throws IOException {
if (closed) {
return;
}
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
2018-11-21 01:02:25 +01:00
// Update indices metadata
2019-01-06 00:31:52 +01:00
flushAllFlushableIndices();
2018-11-21 01:02:25 +01:00
// Remove removed indices
2018-12-19 12:19:03 +01:00
removeRemovedIndices();
2018-11-20 18:39:48 +01:00
fileAllocator.close();
}
2018-12-06 12:30:04 +01:00
2018-11-27 17:47:19 +01:00
private void writeIndexDetails(SeekableByteChannel position, IndexDetails indexDetails) throws IOException {
synchronized (metadataByteBufferLock) {
2018-12-04 23:57:49 +01:00
final int size = indexDetails.getSize();
final long offset = indexDetails.getOffset();
2018-11-27 17:47:19 +01:00
metadataByteBuffer.rewind();
2018-12-04 23:57:49 +01:00
metadataByteBuffer.putLong(offset);
metadataByteBuffer.putInt(size);
2018-11-27 17:47:19 +01:00
metadataByteBuffer.rewind();
position.write(metadataByteBuffer);
}
}
2018-12-06 12:30:04 +01:00
2018-11-27 17:47:19 +01:00
private void eraseIndexDetails(SeekableByteChannel position) throws IOException {
synchronized (maskByteBufferLock) {
maskByteBuffer.rewind();
maskByteBuffer.putLong(-1); // -1 = deleted
2018-11-27 17:47:19 +01:00
maskByteBuffer.rewind();
position.write(maskByteBuffer);
}
}
2018-11-20 18:39:48 +01:00
private void checkClosed() {
if (closed) {
throw new RuntimeException("Index Manager is closed.");
}
}
2018-11-27 17:47:19 +01:00
@Override
public long clean() {
2018-12-19 12:19:03 +01:00
long cleaned = 0;
2019-01-06 00:31:52 +01:00
long tim1 = System.currentTimeMillis();
2018-12-19 12:19:03 +01:00
try {
2019-01-06 00:31:52 +01:00
cleaned += flushAllFlushableIndices();
2018-12-19 12:19:03 +01:00
} catch (IOException ex) {
ex.printStackTrace();
}
2019-01-06 00:31:52 +01:00
long tim2 = System.currentTimeMillis();
2018-12-19 12:19:03 +01:00
try {
cleaned += removeRemovedIndices();
} catch (IOException ex) {
ex.printStackTrace();
}
2019-01-06 00:31:52 +01:00
long tim3 = System.currentTimeMillis();
2018-12-19 12:19:03 +01:00
cleaned += cleanExtraIndices();
2019-01-06 00:31:52 +01:00
long tim4 = System.currentTimeMillis();
if (Cleaner.ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] FileIndexManager CLEAN_TIME: " + (tim2-tim1) + "," + (tim3-tim2) + "," + (tim4-tim3));
2018-12-19 12:19:03 +01:00
return cleaned;
}
2019-01-06 00:31:52 +01:00
private long flushAllFlushableIndices() throws IOException {
2018-12-19 12:19:03 +01:00
long flushedIndices = 0;
SeekableByteChannel metadata = metadataFileChannel;
long lastIndex = -2;
synchronized (indicesMapsAccessLock) {
for (long index : dirtyLoadedIndices) {
IndexDetails indexDetails = loadedIndices.get(index);
long position = index * IndexDetails.TOTAL_BYTES;
resizeMetadataFileChannel(position);
if (index - lastIndex != 1) {
metadata = metadata.position(position);
2018-12-19 12:19:03 +01:00
}
writeIndexDetails(metadata, indexDetails);
lastIndex = index;
flushedIndices++;
2018-12-19 12:19:03 +01:00
}
dirtyLoadedIndices.clear();
}
return flushedIndices;
}
2019-01-06 00:31:52 +01:00
private void resizeMetadataFileChannel(long position) {
if (position + IndexDetails.TOTAL_BYTES > metadataFileChannelSize) {
metadataFileChannelSize = position + IndexDetails.TOTAL_BYTES;
}
}
2018-12-19 12:19:03 +01:00
private long removeRemovedIndices() throws IOException {
SeekableByteChannel metadata = metadataFileChannel;
synchronized (indicesMapsAccessLock) {
long removed = this.removedIndices.size();
for (long index : this.removedIndices) {
metadata = metadata.position(index * IndexDetails.TOTAL_BYTES);
eraseIndexDetails(metadata);
}
this.removedIndices.clear();
return removed;
}
2018-11-27 17:47:19 +01:00
}
2018-12-06 12:30:04 +01:00
2018-11-27 17:47:19 +01:00
private long cleanExtraIndices() {
long removedIndices = 0;
2018-12-06 12:30:04 +01:00
LongArrayList toUnload = new LongArrayList();
synchronized (indicesMapsAccessLock) {
if (loadedIndices.size() > DatabaseManager.MAX_LOADED_INDICES) {
2018-11-27 17:47:19 +01:00
long count = loadedIndices.size();
2018-12-05 02:39:41 +01:00
LongIterator it = loadedIndices.keySet().iterator();
2018-12-06 12:30:04 +01:00
while (it.hasNext()) {
2018-12-05 02:39:41 +01:00
long loadedIndex = it.nextLong();
if (count < DatabaseManager.MAX_LOADED_INDICES * 3l / 2l) {
2018-11-27 17:47:19 +01:00
break;
}
2018-12-06 12:30:04 +01:00
toUnload.add(loadedIndex);
2018-11-27 17:47:19 +01:00
removedIndices++;
count--;
}
}
}
2018-12-06 12:30:04 +01:00
for (long index : toUnload.elements()) {
try {
flushAndUnload(index);
} catch (IOException e) {
e.printStackTrace();
}
}
2018-11-27 17:47:19 +01:00
return removedIndices;
}
2018-11-19 15:16:12 +01:00
}