diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/Main.java b/src/main/java/ru/vk/itmo/test/kislovdanil/Main.java new file mode 100644 index 000000000..883408ee6 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/Main.java @@ -0,0 +1,25 @@ +package ru.vk.itmo.test.kislovdanil; + +import ru.vk.itmo.Service; +import ru.vk.itmo.ServiceConfig; +import ru.vk.itmo.test.kislovdanil.service.DatabaseServiceFactory; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.ExecutionException; + +public final class Main { + private Main() { + + } + + public static void main(String[] args) throws IOException, ExecutionException, InterruptedException { + DatabaseServiceFactory factory = new DatabaseServiceFactory(); + ServiceConfig config = new ServiceConfig(8080, "localhost", List.of(), + Path.of("/home/burprop/Study/2024-highload-dht")); + Service service = factory.create(config); + service.start().get(); + + } +} diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/dao/MemTable.java b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/MemTable.java new file mode 100644 index 000000000..7f3dbf197 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/MemTable.java @@ -0,0 +1,39 @@ +package ru.vk.itmo.test.kislovdanil.dao; + +import ru.vk.itmo.dao.Entry; + +import java.lang.foreign.MemorySegment; +import java.util.Comparator; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.atomic.AtomicLong; + +/* Basically, ConcurrentSkipList with bytes threshold. + */ +public class MemTable { + private final ConcurrentSkipListMap> storage; + private final long threshold; + private final AtomicLong size = new AtomicLong(0); + + private static long getEntrySize(Entry entry) { + long valueSize = entry.value() == null ? 0 : entry.value().byteSize(); + return valueSize + entry.key().byteSize(); + } + + public MemTable(Comparator comparator, long threshold) { + this.storage = new ConcurrentSkipListMap<>(comparator); + this.threshold = threshold; + } + + public boolean put(Entry entry) { + long entrySize = getEntrySize(entry); + if (size.addAndGet(entrySize) - entrySize > threshold) { + return false; + } + storage.put(entry.key(), entry); + return true; + } + + public ConcurrentSkipListMap> getStorage() { + return storage; + } +} diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/dao/PersistentDao.java b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/PersistentDao.java new file mode 100644 index 000000000..615eb5623 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/PersistentDao.java @@ -0,0 +1,257 @@ +package ru.vk.itmo.test.kislovdanil.dao; + +import ru.vk.itmo.dao.Config; +import ru.vk.itmo.dao.Dao; +import ru.vk.itmo.dao.Entry; +import ru.vk.itmo.test.kislovdanil.dao.exceptions.DBException; +import ru.vk.itmo.test.kislovdanil.dao.exceptions.OverloadException; +import ru.vk.itmo.test.kislovdanil.dao.iterators.DatabaseIterator; +import ru.vk.itmo.test.kislovdanil.dao.iterators.MemTableIterator; +import ru.vk.itmo.test.kislovdanil.dao.iterators.MergeIterator; +import ru.vk.itmo.test.kislovdanil.dao.sstable.SSTable; + +import java.io.File; +import java.io.IOException; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class PersistentDao implements Dao>, Iterable> { + + public static final MemorySegment DELETED_VALUE = null; + private final Config config; + private volatile List tables = new ArrayList<>(); + private final Comparator comparator = new MemSegComparator(); + private volatile MemTable memTable; + // Temporary storage in case of main storage flushing (Read only) + private volatile MemTable additionalStorage; + // In case of additional table overload while main table is flushing + private final AtomicLong nextId = new AtomicLong(); + private final ExecutorService commonExecutorService = Executors.newFixedThreadPool(2); + // To prevent parallel flushing + private volatile Future compcatFuture; + // To make sure that flushing in close() will be started + private volatile Future flushFuture; + // Have to take before any tables modification + private final Lock compactionLock = new ReentrantLock(); + // Have to take read while upsert and write while flushing (to prevent data loss) + private final ReadWriteLock upsertLock = new ReentrantReadWriteLock(); + private final Arena filesArena = Arena.ofShared(); + + private long getMaxTablesId(Iterable tableIterable) { + long curMaxId = -1; + for (SSTable table : tableIterable) { + curMaxId = Math.max(curMaxId, table.getTableId()); + } + return curMaxId; + } + + public PersistentDao(Config config) throws IOException { + this.config = config; + this.memTable = new MemTable(comparator, config.flushThresholdBytes()); + File basePathDirectory = new File(config.basePath().toString()); + String[] ssTablesIds = basePathDirectory.list(); + if (ssTablesIds == null) return; + for (String tableID : ssTablesIds) { + // SSTable constructor without entries iterator reads table data from disk if it exists + tables.add(new SSTable(config.basePath(), comparator, Long.parseLong(tableID), filesArena)); + } + nextId.set(getMaxTablesId(tables) + 1); + tables.sort(SSTable::compareTo); + } + + @Override + public Iterator> get(MemorySegment from, MemorySegment to) { + List iterators = new ArrayList<>(tables.size() + 2); + for (SSTable table : tables) { + iterators.add(table.getRange(from, to)); + } + iterators.add(new MemTableIterator(from, to, memTable, Long.MAX_VALUE)); + if (additionalStorage != null) { + iterators.add(new MemTableIterator(from, to, additionalStorage, Long.MAX_VALUE - 1)); + } + return new MergeIterator(iterators, comparator); + } + + private static Entry wrapEntryIfDeleted(Entry entry) { + if (entry.value() == DELETED_VALUE) return null; + return entry; + } + + private long getNextId() { + return nextId.getAndIncrement(); + } + + // Return null if it doesn't find + @Override + public Entry get(MemorySegment key) { + Entry ans = memTable.getStorage().get(key); + if (ans != null) return wrapEntryIfDeleted(ans); + if (additionalStorage != null) { + ans = additionalStorage.getStorage().get(key); + if (ans != null) return wrapEntryIfDeleted(ans); + } + try { + for (SSTable table : tables.reversed()) { + ans = table.find(key); + if (ans != null) { + return wrapEntryIfDeleted(ans); + } + } + } catch (IOException e) { + throw new DBException(e); + } + return null; + } + + @Override + public void upsert(Entry entry) { + upsertLock.readLock().lock(); + try { + if (memTable.put(entry)) { + return; + } + } finally { + upsertLock.readLock().unlock(); + } + flush(); + upsertLock.readLock().lock(); + try { + if (!memTable.put(entry)) { + throw new OverloadException(entry); + } + } finally { + upsertLock.readLock().unlock(); + } + } + + private void makeFlush() throws IOException { + compactionLock.lock(); + try { + if (additionalStorage == null) return; + // SSTable constructor with entries iterator writes MemTable data on disk deleting old data if it exists + tables.add(new SSTable(config.basePath(), comparator, + getNextId(), additionalStorage.getStorage().values().iterator(), filesArena)); + additionalStorage = null; + } finally { + compactionLock.unlock(); + } + } + + @Override + public void flush() { + upsertLock.writeLock().lock(); + try { + if (additionalStorage != null || memTable.getStorage().isEmpty()) { + return; + } + additionalStorage = memTable; + memTable = new MemTable(comparator, config.flushThresholdBytes()); + flushFuture = commonExecutorService.submit( + () -> { + try { + makeFlush(); + } catch (IOException e) { + throw new DBException(e); + } + }); + } finally { + upsertLock.writeLock().unlock(); + } + } + + private void closeExecutorService(ExecutorService executorService) { + executorService.shutdown(); + try { + if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) { + executorService.shutdownNow(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + @Override + public void close() { + if (!filesArena.scope().isAlive()) { + return; + } + if (flushFuture != null) { + try { + flushFuture.get(); + } catch (InterruptedException | ExecutionException e) { + Thread.currentThread().interrupt(); + } + } + flush(); + closeExecutorService(commonExecutorService); + filesArena.close(); + } + + private void makeCompaction() throws IOException { + compactionLock.lock(); + try { + if (tables.size() <= 1) return; + long compactedTableId = getNextId(); + SSTable compactedTable = new SSTable(config.basePath(), comparator, compactedTableId, + new MergeIterator(tables, comparator), filesArena); + List oldTables = tables; + List newTables = new ArrayList<>(); + newTables.add(compactedTable); + tables = newTables; + for (SSTable table : oldTables) { + table.deleteFromDisk(); + } + } finally { + compactionLock.unlock(); + } + } + + @Override + public void compact() { + if (compcatFuture != null && !compcatFuture.isDone()) { + compcatFuture.cancel(false); + } + compcatFuture = commonExecutorService.submit( + () -> { + try { + makeCompaction(); + } catch (IOException e) { + throw new DBException(e); + } + }); + } + + @Override + public Iterator> iterator() { + return get(null, null); + } + + private static class MemSegComparator implements Comparator { + @Override + public int compare(MemorySegment o1, MemorySegment o2) { + long mismatch = o1.mismatch(o2); + if (mismatch == -1) { + return 0; + } + if (mismatch == Math.min(o1.byteSize(), o2.byteSize())) { + return Long.compare(o1.byteSize(), o2.byteSize()); + } + return Byte.compare(o1.get(ValueLayout.JAVA_BYTE, mismatch), o2.get(ValueLayout.JAVA_BYTE, mismatch)); + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/dao/exceptions/DBException.java b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/exceptions/DBException.java new file mode 100644 index 000000000..ec9183682 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/exceptions/DBException.java @@ -0,0 +1,11 @@ +package ru.vk.itmo.test.kislovdanil.dao.exceptions; + +public class DBException extends RuntimeException { + public DBException(Exception e) { + super(e); + } + + public DBException() { + super(); + } +} diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/dao/exceptions/OverloadException.java b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/exceptions/OverloadException.java new file mode 100644 index 000000000..2fcceedf7 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/exceptions/OverloadException.java @@ -0,0 +1,14 @@ +package ru.vk.itmo.test.kislovdanil.dao.exceptions; + +import ru.vk.itmo.dao.Entry; + +import java.lang.foreign.MemorySegment; + +public class OverloadException extends DBException { + public final Entry entry; + + public OverloadException(Entry entry) { + super(); + this.entry = entry; + } +} diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/dao/iterators/DatabaseIterator.java b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/iterators/DatabaseIterator.java new file mode 100644 index 000000000..0572782a4 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/iterators/DatabaseIterator.java @@ -0,0 +1,10 @@ +package ru.vk.itmo.test.kislovdanil.dao.iterators; + +import ru.vk.itmo.dao.Entry; + +import java.lang.foreign.MemorySegment; +import java.util.Iterator; + +public interface DatabaseIterator extends Iterator> { + long getPriority(); +} diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/dao/iterators/MemTableIterator.java b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/iterators/MemTableIterator.java new file mode 100644 index 000000000..3560b420f --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/iterators/MemTableIterator.java @@ -0,0 +1,42 @@ +package ru.vk.itmo.test.kislovdanil.dao.iterators; + +import ru.vk.itmo.dao.Entry; +import ru.vk.itmo.test.kislovdanil.dao.MemTable; + +import java.lang.foreign.MemorySegment; +import java.util.Iterator; + +public class MemTableIterator implements DatabaseIterator { + private final Iterator> innerIter; + private final long priority; + + public MemTableIterator(MemorySegment from, MemorySegment to, + MemTable memTable, + long priority) { + this.priority = priority; + if (from == null && to == null) { + innerIter = memTable.getStorage().values().iterator(); + } else if (from != null && to == null) { + innerIter = memTable.getStorage().tailMap(from).values().iterator(); + } else if (from == null) { + innerIter = memTable.getStorage().headMap(to).values().iterator(); + } else { + innerIter = memTable.getStorage().subMap(from, to).values().iterator(); + } + } + + @Override + public long getPriority() { + return priority; + } + + @Override + public boolean hasNext() { + return innerIter.hasNext(); + } + + @Override + public Entry next() { + return innerIter.next(); + } +} diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/dao/iterators/MergeIterator.java b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/iterators/MergeIterator.java new file mode 100644 index 000000000..3b167d2a0 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/iterators/MergeIterator.java @@ -0,0 +1,97 @@ +package ru.vk.itmo.test.kislovdanil.dao.iterators; + +import ru.vk.itmo.dao.BaseEntry; +import ru.vk.itmo.dao.Entry; +import ru.vk.itmo.test.kislovdanil.dao.sstable.SSTable; + +import java.lang.foreign.MemorySegment; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.NavigableMap; +import java.util.TreeMap; + +// Iterates through SSTables and MemTable using N pointers algorithm. Conflicts being solved by iterator priority. +public class MergeIterator implements Iterator> { + private final NavigableMap itemsPool; + private Entry currentEntry; + + private static Iterable getDBIterators(Collection tables) { + List it = new ArrayList<>(tables.size()); + for (SSTable table: tables) { + it.add(table.getRange()); + } + return it; + } + + public MergeIterator(Iterable iterators, Comparator comp) { + this.itemsPool = new TreeMap<>(comp); + for (DatabaseIterator iter : iterators) { + moveIterator(iter); + } + updateCurrentEntry(); + } + + public MergeIterator(Collection tables, Comparator comp) { + this(getDBIterators(tables), comp); + } + + // Get next entry (skip all entries with null value) + private void updateCurrentEntry() { + MemorySegment value = null; + MemorySegment key = null; + while (value == null && !itemsPool.isEmpty()) { + key = itemsPool.firstKey(); + IteratorAndValue iteratorAndValue = itemsPool.get(key); + itemsPool.remove(key); + moveIterator(iteratorAndValue.iterator); + value = iteratorAndValue.value; + } + currentEntry = (value == null) ? null : new BaseEntry<>(key, value); + } + + // Move iterator to next value keeping invariant (several iterators mustn't point to equal keys at the same time) + private void moveIterator(DatabaseIterator iter) { + while (iter.hasNext()) { + Entry entry = iter.next(); + boolean hasConcurrentKey = itemsPool.containsKey(entry.key()); + boolean winPriorityConflict = false; + DatabaseIterator concurrentIterator = null; + if (itemsPool.containsKey(entry.key())) { + concurrentIterator = itemsPool.get(entry.key()).iterator; + winPriorityConflict = iter.getPriority() > concurrentIterator.getPriority(); + } + if (winPriorityConflict) { + moveIterator(concurrentIterator); + } + if (!hasConcurrentKey || winPriorityConflict) { + itemsPool.put(entry.key(), new IteratorAndValue(iter, entry.value())); + break; + } + } + } + + @Override + public boolean hasNext() { + return currentEntry != null; + } + + @Override + public Entry next() { + Entry result = currentEntry; + updateCurrentEntry(); + return result; + } + + private static final class IteratorAndValue { + private final DatabaseIterator iterator; + private final MemorySegment value; + + public IteratorAndValue(DatabaseIterator iterator, MemorySegment value) { + this.iterator = iterator; + this.value = value; + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/dao/sstable/Metadata.java b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/sstable/Metadata.java new file mode 100644 index 000000000..a91ea51ac --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/sstable/Metadata.java @@ -0,0 +1,45 @@ +package ru.vk.itmo.test.kislovdanil.dao.sstable; + +import ru.vk.itmo.dao.Entry; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + +class Metadata { + private final SSTable table; + private final SSTable.Range keyRange; + private final SSTable.Range valueRange; + private final Boolean isDeletion; + public static final long SIZE = Long.BYTES * 4 + 1; + + public Metadata(long index, SSTable table) { + this.table = table; + long base = index * Metadata.SIZE; + keyRange = table.readRange(table.summaryFile, base); + valueRange = table.readRange(table.summaryFile, base + 2 * Long.BYTES); + isDeletion = table.summaryFile.get(ValueLayout.JAVA_BOOLEAN, base + 4 * Long.BYTES); + } + + public MemorySegment readKey() { + return table.indexFile.asSlice(keyRange.offset, keyRange.length); + } + + public MemorySegment readValue() { + return isDeletion ? null : table.dataFile.asSlice(valueRange.offset, valueRange.length); + } + + public static void writeEntryMetadata(Entry entry, MemorySegment summaryFile, + long sumOffset, long indexOffset, long dataOffset) { + summaryFile.set(ValueLayout.JAVA_LONG_UNALIGNED, + sumOffset, indexOffset); + summaryFile.set(ValueLayout.JAVA_LONG_UNALIGNED, + sumOffset + Long.BYTES, entry.key().byteSize()); + summaryFile.set(ValueLayout.JAVA_LONG_UNALIGNED, + sumOffset + 2 * Long.BYTES, dataOffset); + summaryFile.set(ValueLayout.JAVA_BOOLEAN, + sumOffset + 4 * Long.BYTES, entry.value() == null); + summaryFile.set(ValueLayout.JAVA_LONG_UNALIGNED, + sumOffset + 3 * Long.BYTES, entry.value() == null ? 0 : entry.value().byteSize()); + } + +} diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/dao/sstable/SSTable.java b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/sstable/SSTable.java new file mode 100644 index 000000000..4abd7692c --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/sstable/SSTable.java @@ -0,0 +1,242 @@ +package ru.vk.itmo.test.kislovdanil.dao.sstable; + +import ru.vk.itmo.dao.BaseEntry; +import ru.vk.itmo.dao.Entry; +import ru.vk.itmo.test.kislovdanil.dao.iterators.DatabaseIterator; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +public class SSTable implements Comparable, Iterable> { + // Contains offset and size for every key and every value in index file + MemorySegment summaryFile; + private static final String SUMMARY_FILENAME = "summary"; + // Contains keys + MemorySegment indexFile; + private static final String INDEX_FILENAME = "index"; + // Contains values + MemorySegment dataFile; + private static final String DATA_FILENAME = "data"; + final Comparator memSegComp; + private final Arena filesArena; + private final long tableId; + private final Path ssTablePath; + + final long size; + + /* In case deletion while compaction of this table field would link to table with compacted data. + Necessary for iterators created before compaction. */ + // Gives a guarantee that SSTable files wouldn't be deleted while reading + + public long getTableId() { + return tableId; + } + + public SSTable(Path basePath, Comparator memSegComp, long tableId, Arena filesArena) + throws IOException { + this(basePath, memSegComp, tableId, null, false, filesArena); + } + + public SSTable(Path basePath, Comparator memSegComp, long tableId, + Iterator> entriesContainer, Arena filesArena) throws IOException { + this(basePath, memSegComp, tableId, entriesContainer, true, filesArena); + } + + private SSTable(Path basePath, Comparator memSegComp, long tableId, + Iterator> entriesContainer, + boolean rewrite, Arena filesArena) throws IOException { + this.tableId = tableId; + this.filesArena = filesArena; + this.ssTablePath = basePath.resolve(Long.toString(tableId)); + this.memSegComp = memSegComp; + Path summaryFilePath = this.ssTablePath.resolve(SUMMARY_FILENAME); + Path indexFilePath = this.ssTablePath.resolve(INDEX_FILENAME); + Path dataFilePath = this.ssTablePath.resolve(DATA_FILENAME); + if (rewrite) { + write(entriesContainer, summaryFilePath, indexFilePath, dataFilePath); + } else { + readOld(summaryFilePath, indexFilePath, dataFilePath); + } + + this.summaryFile = this.summaryFile.asReadOnly(); + this.indexFile = this.indexFile.asReadOnly(); + this.dataFile = this.dataFile.asReadOnly(); + + this.size = (this.summaryFile.byteSize() / Metadata.SIZE); + } + + private void readOld(Path summaryFilePath, Path indexFilePath, Path dataFilePath) throws IOException { + createIfNotExist(summaryFilePath); + createIfNotExist(indexFilePath); + createIfNotExist(dataFilePath); + summaryFile = mapFile(Files.size(summaryFilePath), summaryFilePath); + indexFile = mapFile(Files.size(indexFilePath), indexFilePath); + dataFile = mapFile(Files.size(dataFilePath), dataFilePath); + } + + private void createIfNotExist(Path file) throws IOException { + if (Files.notExists(file)) { + Files.createDirectories(file.getParent()); + Files.createFile(file); + } + } + + private void prepareForWriting(Path file) throws IOException { + if (Files.exists(file)) { + Files.delete(file); + } + Files.createDirectories(file.getParent()); + Files.createFile(file); + + } + + private MemorySegment mapFile(long size, Path filePath) throws IOException { + try (RandomAccessFile raFile = new RandomAccessFile(filePath.toString(), "rw")) { + return raFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, size, filesArena); + } + } + + private void writeEntry(Entry entry, + long summaryOffset, long indexOffset, long dataOffset) { + MemorySegment.copy(entry.key(), 0, indexFile, indexOffset, entry.key().byteSize()); + if (entry.value() != null) { + MemorySegment.copy(entry.value(), 0, dataFile, dataOffset, entry.value().byteSize()); + } + Metadata.writeEntryMetadata(entry, summaryFile, summaryOffset, indexOffset, dataOffset); + } + + private long[] getFilesSize(Iterable> entriesContainer) { + long indexSize = 0; + long dataSize = 0; + long summarySize = 0; + for (Entry entry : entriesContainer) { + indexSize += entry.key().byteSize(); + dataSize += entry.value() == null ? 0 : entry.value().byteSize(); + summarySize += Metadata.SIZE; + } + return new long[]{summarySize, indexSize, dataSize}; + } + + // Sequentially writes every entity data in SStable keeping files data consistent + private void write(Iterator> entryIterator, + Path summaryFilePath, Path indexFilePath, Path dataFilePath) throws IOException { + prepareForWriting(summaryFilePath); + prepareForWriting(indexFilePath); + prepareForWriting(dataFilePath); + + List> entries = new ArrayList<>(); + while (entryIterator.hasNext()) { + entries.add(entryIterator.next()); + } + + long[] filesSize = getFilesSize(entries); + + summaryFile = mapFile(filesSize[0], summaryFilePath); + indexFile = mapFile(filesSize[1], indexFilePath); + dataFile = mapFile(filesSize[2], dataFilePath); + + long currentSummaryOffset = 0; + long currentIndexOffset = 0; + long currentDataOffset = 0; + for (Entry entry : entries) { + MemorySegment value = entry.value(); + value = value == null ? filesArena.allocate(0) : value; + MemorySegment key = entry.key(); + writeEntry(entry, currentSummaryOffset, currentIndexOffset, currentDataOffset); + currentDataOffset += value.byteSize(); + currentIndexOffset += key.byteSize(); + currentSummaryOffset += Metadata.SIZE; + } + } + + // Deletes all SSTable files from disk. Don't use object after invocation of this method! + public void deleteFromDisk() throws IOException { + Files.delete(ssTablePath.resolve(SUMMARY_FILENAME)); + Files.delete(ssTablePath.resolve(INDEX_FILENAME)); + Files.delete(ssTablePath.resolve(DATA_FILENAME)); + Files.delete(ssTablePath); + } + + Range readRange(MemorySegment segment, long offset) { + return new Range(segment.get(ValueLayout.JAVA_LONG_UNALIGNED, offset), + segment.get(ValueLayout.JAVA_LONG_UNALIGNED, offset + Long.BYTES)); + } + + /* Binary search in summary and index files. Returns index of least record greater than key or equal. + Returns -1 if no such key */ + long findByKey(MemorySegment key) { + long left = -1; // Always less than key + long right = size; // Always greater or equal than key + while (right - left > 1) { + long middle = (right + left) / 2; + Metadata currentEntryMetadata = new Metadata(middle, this); + MemorySegment curKey = currentEntryMetadata.readKey(); + int compRes = memSegComp.compare(key, curKey); + if (compRes <= 0) { + right = middle; + } else { + left = middle; + } + } + return right == size ? -1 : right; // If right == size, then key is bigger than any SSTable key + } + + private long findByKeyExact(MemorySegment key) { + long goe = findByKey(key); + if (goe == -1 || memSegComp.compare(readEntry(goe).key(), key) != 0) return -1; + return goe; + } + + Entry readEntry(long index) { + Metadata metadata = new Metadata(index, this); + MemorySegment key = metadata.readKey(); + MemorySegment value = metadata.readValue(); + return new BaseEntry<>(key, value); + } + + public Entry find(MemorySegment key) throws IOException { + long entryId = findByKeyExact(key); + if (entryId == -1) return null; + return readEntry(entryId); + } + + public DatabaseIterator getRange(MemorySegment from, MemorySegment to) { + return new SSTableIterator(from, to, this); + } + + public DatabaseIterator getRange() { + return getRange(null, null); + } + + @Override + public Iterator> iterator() { + return getRange(); + } + + // The less the ID, the less the table + @Override + public int compareTo(SSTable o) { + return Long.compare(this.tableId, o.tableId); + } + + // Describes offset and size of any data segment + static class Range { + public long offset; + public long length; + + public Range(long offset, long length) { + this.offset = offset; + this.length = length; + } + } +} diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/dao/sstable/SSTableIterator.java b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/sstable/SSTableIterator.java new file mode 100644 index 000000000..4f3dae416 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/dao/sstable/SSTableIterator.java @@ -0,0 +1,51 @@ +package ru.vk.itmo.test.kislovdanil.dao.sstable; + +import ru.vk.itmo.dao.Entry; +import ru.vk.itmo.test.kislovdanil.dao.iterators.DatabaseIterator; + +import java.lang.foreign.MemorySegment; + +class SSTableIterator implements DatabaseIterator { + private long curItemIndex; + private final MemorySegment maxKey; + + private Entry curEntry; + private final SSTable table; + + public SSTableIterator(MemorySegment minKey, MemorySegment maxKey, SSTable table) { + this.table = table; + this.maxKey = maxKey; + if (table.size == 0) return; + if (minKey == null) { + this.curItemIndex = 0; + } else { + this.curItemIndex = this.table.findByKey(minKey); + } + if (curItemIndex == -1) { + curItemIndex = Long.MAX_VALUE; + } else { + this.curEntry = this.table.readEntry(curItemIndex); + } + } + + @Override + public boolean hasNext() { + if (curItemIndex >= this.table.size) return false; + return maxKey == null || table.memSegComp.compare(curEntry.key(), maxKey) < 0; + } + + @Override + public Entry next() { + Entry result = curEntry; + curItemIndex++; + if (curItemIndex < table.size) { + curEntry = table.readEntry(curItemIndex); + } + return result; + } + + @Override + public long getPriority() { + return table.getTableId(); + } +} diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/report/scripts/get.lua b/src/main/java/ru/vk/itmo/test/kislovdanil/report/scripts/get.lua new file mode 100644 index 000000000..424902f91 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/report/scripts/get.lua @@ -0,0 +1,9 @@ +wrk.method = "GET" + +math.randomseed(os.time()) + +request = function() + id = math.random(100000) + path = "/v0/entity?id=" .. id + return wrk.format(nil, path) +end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/report/scripts/put.lua b/src/main/java/ru/vk/itmo/test/kislovdanil/report/scripts/put.lua new file mode 100644 index 000000000..d504bcabf --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/report/scripts/put.lua @@ -0,0 +1,10 @@ +wrk.method = "PUT" + +math.randomseed(os.time()) + +request = function() + id = math.random(100000) + wrk.body = "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" + path = "/v0/entity?id=" .. id + return wrk.format(nil, path) +end \ No newline at end of file diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/Histogram.png b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/Histogram.png new file mode 100644 index 000000000..dd712637c Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/Histogram.png differ diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/get4000.txt b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/get4000.txt new file mode 100644 index 000000000..573a96511 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/get4000.txt @@ -0,0 +1,104 @@ +Running 1m test @ http://localhost:8080 + 1 threads and 1 connections + Thread calibration: mean lat.: 53.550ms, rate sampling interval: 445ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 3.17ms 6.56ms 78.53ms 94.71% + Req/Sec 4.00k 68.86 4.47k 89.29% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.46ms + 75.000% 2.69ms + 90.000% 5.99ms + 99.000% 36.61ms + 99.900% 75.78ms + 99.990% 78.21ms + 99.999% 78.59ms +100.000% 78.59ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.036 0.000000 1 1.00 + 0.501 0.100000 20030 1.11 + 0.782 0.200000 40067 1.25 + 1.019 0.300000 60078 1.43 + 1.218 0.400000 80063 1.67 + 1.456 0.500000 99996 2.00 + 1.611 0.550000 110025 2.22 + 1.783 0.600000 120015 2.50 + 1.991 0.650000 130024 2.86 + 2.279 0.700000 140037 3.33 + 2.687 0.750000 150018 4.00 + 2.971 0.775000 155006 4.44 + 3.319 0.800000 160002 5.00 + 3.779 0.825000 165006 5.71 + 4.311 0.850000 170026 6.67 + 4.967 0.875000 174991 8.00 + 5.431 0.887500 177487 8.89 + 5.995 0.900000 179997 10.00 + 6.603 0.912500 182501 11.43 + 7.215 0.925000 184991 13.33 + 8.375 0.937500 187492 16.00 + 9.327 0.943750 188742 17.78 + 10.143 0.950000 189988 20.00 + 11.423 0.956250 191241 22.86 + 12.879 0.962500 192489 26.67 + 15.383 0.968750 193733 32.00 + 18.127 0.971875 194362 35.56 + 20.991 0.975000 194983 40.00 + 23.455 0.978125 195609 45.71 + 25.679 0.981250 196235 53.33 + 28.783 0.984375 196864 64.00 + 30.655 0.985938 197171 71.11 + 33.311 0.987500 197484 80.00 + 34.911 0.989062 197796 91.43 + 37.759 0.990625 198108 106.67 + 41.887 0.992188 198423 128.00 + 43.679 0.992969 198580 142.22 + 45.919 0.993750 198733 160.00 + 47.487 0.994531 198891 182.86 + 51.071 0.995313 199045 213.33 + 55.679 0.996094 199201 256.00 + 57.727 0.996484 199283 284.44 + 58.847 0.996875 199358 320.00 + 60.991 0.997266 199436 365.71 + 62.431 0.997656 199517 426.67 + 65.663 0.998047 199594 512.00 + 67.071 0.998242 199631 568.89 + 67.775 0.998437 199671 640.00 + 70.335 0.998633 199709 731.43 + 74.751 0.998828 199749 853.33 + 75.839 0.999023 199788 1024.00 + 76.223 0.999121 199807 1137.78 + 76.607 0.999219 199828 1280.00 + 76.799 0.999316 199847 1462.86 + 76.927 0.999414 199869 1706.67 + 77.055 0.999512 199885 2048.00 + 77.247 0.999561 199900 2275.56 + 77.311 0.999609 199906 2560.00 + 77.439 0.999658 199921 2925.71 + 77.503 0.999707 199924 3413.33 + 77.695 0.999756 199935 4096.00 + 77.759 0.999780 199939 4551.11 + 77.823 0.999805 199945 5120.00 + 77.887 0.999829 199950 5851.43 + 77.951 0.999854 199954 6826.67 + 78.143 0.999878 199960 8192.00 + 78.207 0.999890 199963 9102.22 + 78.207 0.999902 199963 10240.00 + 78.271 0.999915 199968 11702.86 + 78.271 0.999927 199968 13653.33 + 78.335 0.999939 199970 16384.00 + 78.399 0.999945 199973 18204.44 + 78.399 0.999951 199973 20480.00 + 78.463 0.999957 199974 23405.71 + 78.527 0.999963 199976 27306.67 + 78.527 0.999969 199976 32768.00 + 78.591 0.999973 199982 36408.89 + 78.591 1.000000 199982 inf +#[Mean = 3.172, StdDeviation = 6.564] +#[Max = 78.528, Total count = 199982] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 239997 requests in 1.00m, 26.82MB read +Requests/sec: 3999.97 +Transfer/sec: 457.72KB diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/getCpu.png b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/getCpu.png new file mode 100644 index 000000000..49488b3e7 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/getCpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/getMemory.png b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/getMemory.png new file mode 100644 index 000000000..21259726c Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/getMemory.png differ diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/put4000.txt b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/put4000.txt new file mode 100644 index 000000000..a62cdbea3 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/put4000.txt @@ -0,0 +1,109 @@ +Running 1m test @ http://localhost:8080 + 1 threads and 1 connections + Thread calibration: mean lat.: 4.727ms, rate sampling interval: 10ms + Thread Stats Avg Stdev Max +/- Stdev + Latency 1.52ms 3.56ms 59.74ms 98.29% + Req/Sec 4.23k 466.71 11.22k 81.12% + Latency Distribution (HdrHistogram - Recorded Latency) + 50.000% 1.08ms + 75.000% 1.47ms + 90.000% 2.04ms + 99.000% 16.23ms + 99.900% 50.72ms + 99.990% 59.49ms + 99.999% 59.78ms +100.000% 59.78ms + + Detailed Percentile spectrum: + Value Percentile TotalCount 1/(1-Percentile) + + 0.031 0.000000 1 1.00 + 0.391 0.100000 20012 1.11 + 0.585 0.200000 39995 1.25 + 0.763 0.300000 60119 1.43 + 0.925 0.400000 80073 1.67 + 1.076 0.500000 100038 2.00 + 1.144 0.550000 110089 2.22 + 1.208 0.600000 120072 2.50 + 1.273 0.650000 130051 2.86 + 1.358 0.700000 140088 3.33 + 1.465 0.750000 150021 4.00 + 1.526 0.775000 155033 4.44 + 1.596 0.800000 159997 5.00 + 1.677 0.825000 165009 5.71 + 1.770 0.850000 169997 6.67 + 1.885 0.875000 175012 8.00 + 1.956 0.887500 177494 8.89 + 2.040 0.900000 179995 10.00 + 2.141 0.912500 182510 11.43 + 2.263 0.925000 184986 13.33 + 2.431 0.937500 187488 16.00 + 2.535 0.943750 188736 17.78 + 2.657 0.950000 189992 20.00 + 2.821 0.956250 191230 22.86 + 3.037 0.962500 192478 26.67 + 3.327 0.968750 193727 32.00 + 3.545 0.971875 194356 35.56 + 3.835 0.975000 194976 40.00 + 4.231 0.978125 195607 45.71 + 4.683 0.981250 196233 53.33 + 5.631 0.984375 196853 64.00 + 6.991 0.985938 197163 71.11 + 11.615 0.987500 197476 80.00 + 15.183 0.989062 197788 91.43 + 17.359 0.990625 198101 106.67 + 20.895 0.992188 198415 128.00 + 21.743 0.992969 198570 142.22 + 23.791 0.993750 198726 160.00 + 28.687 0.994531 198882 182.86 + 33.887 0.995313 199038 213.33 + 36.223 0.996094 199194 256.00 + 37.663 0.996484 199272 284.44 + 41.823 0.996875 199352 320.00 + 44.479 0.997266 199432 365.71 + 45.663 0.997656 199510 426.67 + 46.495 0.998047 199589 512.00 + 46.847 0.998242 199626 568.89 + 48.031 0.998437 199664 640.00 + 49.311 0.998633 199703 731.43 + 50.335 0.998828 199741 853.33 + 50.783 0.999023 199782 1024.00 + 51.071 0.999121 199801 1137.78 + 51.327 0.999219 199820 1280.00 + 51.775 0.999316 199839 1462.86 + 51.999 0.999414 199858 1706.67 + 52.575 0.999512 199878 2048.00 + 53.471 0.999561 199888 2275.56 + 54.239 0.999609 199898 2560.00 + 55.199 0.999658 199907 2925.71 + 56.415 0.999707 199917 3413.33 + 57.599 0.999756 199927 4096.00 + 58.079 0.999780 199932 4551.11 + 58.591 0.999805 199936 5120.00 + 59.231 0.999829 199941 5851.43 + 59.327 0.999854 199946 6826.67 + 59.423 0.999878 199951 8192.00 + 59.455 0.999890 199954 9102.22 + 59.487 0.999902 199957 10240.00 + 59.519 0.999915 199958 11702.86 + 59.583 0.999927 199964 13653.33 + 59.583 0.999939 199964 16384.00 + 59.615 0.999945 199966 18204.44 + 59.615 0.999951 199966 20480.00 + 59.647 0.999957 199968 23405.71 + 59.647 0.999963 199968 27306.67 + 59.711 0.999969 199970 32768.00 + 59.711 0.999973 199970 36408.89 + 59.743 0.999976 199972 40960.00 + 59.743 0.999979 199972 46811.43 + 59.743 0.999982 199972 54613.33 + 59.743 0.999985 199972 65536.00 + 59.775 0.999986 199975 72817.78 + 59.775 1.000000 199975 inf +#[Mean = 1.521, StdDeviation = 3.559] +#[Max = 59.744, Total count = 199975] +#[Buckets = 27, SubBuckets = 2048] +---------------------------------------------------------- + 239991 requests in 1.00m, 15.33MB read +Requests/sec: 3999.87 +Transfer/sec: 261.71KB diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/putCpu.png b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/putCpu.png new file mode 100644 index 000000000..2c0d34f57 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/putCpu.png differ diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/putMemory.png b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/putMemory.png new file mode 100644 index 000000000..1d6282f71 Binary files /dev/null and b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/putMemory.png differ diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/report.md b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/report.md new file mode 100644 index 000000000..31118e200 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/report/stage1/report.md @@ -0,0 +1,60 @@ +# Отчёт о нагрузочном тестировании +## Этап 1 + +Опытным путём установлена *точка разладки* ~5.000 RPS. +* Тестирование производилось при 4.000 RPS на одном потоке с одним соединением. +* База заполнена на 65 Mb всеми ключами от 0 до 100000. +* Для тестирования была использована утилита wrk2. +* Для профилирования был использован async-profiler внутри IntelliJ IDEA + +### Запросы + +PUT запросы +> ./wrk -t1 -c1 -d1m -R4000 -s put.lua -L http://localhost:8080 + +GET запросы +> ./wrk -t1 -c1 -d1m -R4000 -s get.lua -L http://localhost:8080 + +### Скрипты +* [get.lua](../scripts/get.lua) +* [put.lua](../scripts/put.lua) + +### Результаты +[Вывод wrk2 для GET](get4000.txt) + +[Вывод wrk2 для PUT](put4000.txt) +![](Histogram.png) + +#### Флеймграфы для GET запросов +##### CPU +![](getCpu.png) + +##### Allocations +![](getMemory.png) + + +#### Флеймграфы для PUT запросов +##### CPU +![](putCpu.png) + +##### Allocations +![](putMemory.png) + +### Вывод +Большую часть времени занимает обработка HTTP запросов, +а не работа самой базы данных. +Конкретно чтение запроса из сессии (NativeSocket.read) +и ожидание селектора (NativeSelector.epollWait). + +Значительную часть времени занимают чтения с диска (get, mismatch). + +Много памяти уходит на операции с диском, а также на хранение +метаданных при чтении (Metadata и Range). + +Повысить скорость можно: +* Более тонкой настройкой HTTP сервера +* Выделением большего количества ресурсов +* Уменьшением количества чтений с диска за счёт более +оптимальной организации данных на диске + + diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/service/DatabaseHttpServer.java b/src/main/java/ru/vk/itmo/test/kislovdanil/service/DatabaseHttpServer.java new file mode 100644 index 000000000..c2c6c73fc --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/service/DatabaseHttpServer.java @@ -0,0 +1,84 @@ +package ru.vk.itmo.test.kislovdanil.service; + +import one.nio.http.HttpServer; +import one.nio.http.HttpServerConfig; +import one.nio.http.HttpSession; +import one.nio.http.Param; +import one.nio.http.Path; +import one.nio.http.Request; +import one.nio.http.Response; +import one.nio.server.AcceptorConfig; +import ru.vk.itmo.ServiceConfig; +import ru.vk.itmo.dao.BaseEntry; +import ru.vk.itmo.dao.Dao; +import ru.vk.itmo.dao.Entry; + +import java.io.IOException; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.charset.StandardCharsets; + +public class DatabaseHttpServer extends HttpServer { + private final Dao> dao; + private static final String ENTITY_ACCESS_URL = "/v0/entity"; + + public DatabaseHttpServer(ServiceConfig config, Dao> dao) throws IOException { + super(transformConfig(config)); + this.dao = dao; + } + + @Override + public void handleDefault(Request request, HttpSession session) throws IOException { + Response response = new Response(Response.BAD_REQUEST, Response.EMPTY); + session.sendResponse(response); + } + + @Path(ENTITY_ACCESS_URL) + public Response handleEntityRequest(Request request, + @Param(value = "id", required = true) String entityKey) { + if (entityKey.isEmpty()) { + return new Response(Response.BAD_REQUEST, Response.EMPTY); + } + MemorySegment key = fromString(entityKey); + return switch (request.getMethod()) { + case Request.METHOD_GET -> getEntity(key); + case Request.METHOD_PUT -> putEntity(key, request.getBody()); + case Request.METHOD_DELETE -> deleteEntity(key); + default -> new Response(Response.METHOD_NOT_ALLOWED, Response.EMPTY); + }; + } + + private Response putEntity(MemorySegment entityKey, byte[] entityValue) { + dao.upsert(new BaseEntry<>(entityKey, MemorySegment.ofArray(entityValue))); + return new Response(Response.CREATED, Response.EMPTY); + } + + private Response getEntity(MemorySegment entityKey) { + Entry data = dao.get(entityKey); + if (data == null) { + return new Response(Response.NOT_FOUND, Response.EMPTY); + } else { + return Response.ok(data.value().toArray(ValueLayout.OfByte.JAVA_BYTE)); + } + } + + private Response deleteEntity(MemorySegment entityKey) { + dao.upsert(new BaseEntry<>(entityKey, null)); + return new Response(Response.ACCEPTED, Response.EMPTY); + } + + private static HttpServerConfig transformConfig(ServiceConfig serviceConfig) { + AcceptorConfig acceptorConfig = new AcceptorConfig(); + acceptorConfig.port = serviceConfig.selfPort(); + acceptorConfig.reusePort = true; + + HttpServerConfig httpServerConfig = new HttpServerConfig(); + httpServerConfig.acceptors = new AcceptorConfig[]{acceptorConfig}; + httpServerConfig.closeSessions = true; + return httpServerConfig; + } + + private static MemorySegment fromString(String data) { + return (data == null) ? null : MemorySegment.ofArray(data.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/service/DatabaseService.java b/src/main/java/ru/vk/itmo/test/kislovdanil/service/DatabaseService.java new file mode 100644 index 000000000..d0411d602 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/service/DatabaseService.java @@ -0,0 +1,40 @@ +package ru.vk.itmo.test.kislovdanil.service; + +import ru.vk.itmo.Service; +import ru.vk.itmo.ServiceConfig; +import ru.vk.itmo.dao.Config; +import ru.vk.itmo.dao.Dao; +import ru.vk.itmo.dao.Entry; +import ru.vk.itmo.test.kislovdanil.dao.PersistentDao; + +import java.io.IOException; +import java.lang.foreign.MemorySegment; +import java.util.concurrent.CompletableFuture; + +public class DatabaseService implements Service { + private DatabaseHttpServer httpServer; + private final ServiceConfig serverConfig; + private Dao> dao; + private final Config daoConfig; + + public DatabaseService(ServiceConfig serverConfig, Config daoConfig) throws IOException { + this.serverConfig = serverConfig; + this.daoConfig = daoConfig; + } + + @Override + public CompletableFuture start() throws IOException { + dao = new PersistentDao(daoConfig); + httpServer = new DatabaseHttpServer(serverConfig, dao); + httpServer.start(); + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture stop() throws IOException { + dao.close(); + httpServer.stop(); + return CompletableFuture.completedFuture(null); + } + +} diff --git a/src/main/java/ru/vk/itmo/test/kislovdanil/service/DatabaseServiceFactory.java b/src/main/java/ru/vk/itmo/test/kislovdanil/service/DatabaseServiceFactory.java new file mode 100644 index 000000000..3024673d8 --- /dev/null +++ b/src/main/java/ru/vk/itmo/test/kislovdanil/service/DatabaseServiceFactory.java @@ -0,0 +1,25 @@ +package ru.vk.itmo.test.kislovdanil.service; + +import ru.vk.itmo.Service; +import ru.vk.itmo.ServiceConfig; +import ru.vk.itmo.dao.Config; +import ru.vk.itmo.test.ServiceFactory; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Paths; + +@ServiceFactory(stage = 1) +public class DatabaseServiceFactory implements ServiceFactory.Factory { + @Override + public Service create(ServiceConfig serverConfig) { + Config daoConfig = new Config( + serverConfig.workingDir().resolve(Paths.get("dao", "memtables")), + 1024 * 1024); + try { + return new DatabaseService(serverConfig, daoConfig); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +}