Skip to content

Commit

Permalink
제대로 컴팩션이 되지 않는 문제 해결
Browse files Browse the repository at this point in the history
  • Loading branch information
gunkim committed Apr 22, 2024
1 parent a860840 commit aba2376
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.github.gunkim.engine.storage.lsm;

import io.github.gunkim.engine.storage.exception.CompactionFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileWriter;
Expand All @@ -15,6 +17,8 @@
import java.util.TreeMap;

public class Compation {
private static final Logger LOGGER = LoggerFactory.getLogger(Compation.class);

private final String basePath;
private final String ssTableFileBaseName;

Expand All @@ -28,7 +32,9 @@ public void start() {
if (!isCompactionRequired(level)) {
return;
}
LOGGER.info("Level-%d의 컴팩션 시작".formatted(level.value()));
compation(level);
LOGGER.info("Level-%d의 컴팩션 완료".formatted(level.value()));
}
}

Expand All @@ -38,7 +44,10 @@ private void compation(CompationLevel level) {
SortedMap<String, String> newSSTableCompatiningMap = new TreeMap<>();
ssTables.forEach(ssTable -> compation(ssTable, newSSTableCompatiningMap));

var newSSTablePath = String.format(basePath.formatted(level.value()) + ssTableFileBaseName, level.nextLevel(), generateIdentifier());
var newSSTablePath = String.format(
basePath + ssTableFileBaseName,
level.nextLevel(), generateIdentifier()
);

existsDirectory(new File(newSSTablePath));
try (var fileWriter = new FileWriter(newSSTablePath)) {
Expand All @@ -50,6 +59,8 @@ private void compation(CompationLevel level) {
throw new CompactionFailedException("level-%d 컴팩션에 실패했습니다.".formatted(level.value()), e);
}

//TODO: 병합이 완료된 SS-Table을 바로 삭제할 경우 이를 참조하는 다른 스레드에서 해당 파일에 접근할 경우 NoSuchFileException 예외를 발생시킬 수 있다.
//TODO: 우선 삭제가 아니라 마킹을 해둔 후 별도의 스레드를 스케줄링하여 삭제하는게 더 안전할 수 있다.
ssTables.forEach(File::delete);
}

Expand All @@ -74,7 +85,6 @@ private void compation(File ssTable, SortedMap<String, String> newSSTableCompati
}
}


private boolean isCompactionRequired(CompationLevel level) {
var ssTableCount = ssTables(level).size();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.gunkim.engine.storage.lsm;

import java.util.Arrays;

enum CompationLevel {
LEVEL_1(1, 2),
LEVEL_2(2, 4),
Expand All @@ -16,6 +18,13 @@ enum CompationLevel {
this.threshold = threshold;
}

public static CompationLevel valueOf(int levelValue) {
return Arrays.stream(values())
.filter(level -> level.value() == levelValue)
.findAny()
.orElseThrow();
}

public static CompationLevel maxLevel() {
return LEVEL_6;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.github.gunkim.engine.storage.lsm;

import io.github.gunkim.engine.storage.Storage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.nio.file.Path;
Expand All @@ -17,18 +19,20 @@
* <p>LSM-Tree 개념 학습을 위해 구현하는 객체이기 때문에 동시성을 위한 동기화 로직은 고려하지 않는다.</p>
*/
public class LsmTreeStorage<T> implements Storage<T> {
private static final int THRESHOLD = 5;
private static final Logger LOGGER = LoggerFactory.getLogger(LsmTreeStorage.class);

private static final String ROOT_DIRECTORY_NAME = "/lsm-tree";
private static final int MEMTABLE_THRESHOLD = 5;

private static final String ROOT_DIRECTORY_NAME = "lsm-tree";
private static final String SS_TABLE_DIRECTORY_RELATIVE_PATH = "/sstable/data/level-%d";
private static final String SS_TABLE_FILE_BASE_NAME = "/sstable-%s";

private static final ExecutorService executors = Executors.newFixedThreadPool(5);
private static final ExecutorService executors = Executors.newSingleThreadExecutor();

private final MemTable<T> memTable = new MemTable<>(THRESHOLD);
private final Compation compation;
private final String storagePath;

private final MemTable<T> memTable = new MemTable<>(MEMTABLE_THRESHOLD);
private final Compation compation;
private final FileSystemAccess<T> fileSystemAccess = new FileSystemAccess<>();

public LsmTreeStorage(String path) {
Expand All @@ -39,6 +43,7 @@ public LsmTreeStorage(String path) {
@Override
public void save(String key, T value) {
runIfMemtableFull(() -> {
LOGGER.info("Memtable 가득참");
flush();
compation();
});
Expand All @@ -49,12 +54,15 @@ public void save(String key, T value) {
@Override
public Optional<T> find(String key) {
return Optional.ofNullable(memTable.get(key))
.or(() -> searchInSSTable(key));
.or(() -> {
LOGGER.info("Memtable에 해당 key(%S)가 존재하지 않아 SS-Table 탐색".formatted(key));
return searchInSSTable(key);
});
}

private Optional<T> searchInSSTable(String key) {
for (int level = 1; level <= CompationLevel.maxLevel().value(); level++) {
var results = searchKeyInLevelSSTables(level, key);
var results = searchKeyInLevelSSTables(CompationLevel.valueOf(level), key);

if (results.isPresent()) {
return results;
Expand All @@ -63,7 +71,7 @@ private Optional<T> searchInSSTable(String key) {
return Optional.empty();
}

private Optional<T> searchKeyInLevelSSTables(int level, String key) {
private Optional<T> searchKeyInLevelSSTables(CompationLevel level, String key) {
var levelPath = Path.of(createDirectoryPath(level));
List<File> sstables = getSSTables(levelPath);

Expand All @@ -87,11 +95,13 @@ private List<File> getSSTables(Path path) {
}

private void flush() {
LOGGER.info("Memtable 디스크 플러쉬 시작");
var ssTableFileName = SS_TABLE_FILE_BASE_NAME.formatted(generateIdentifier());
var newSSTableSavePath = createFilePath(CompationLevel.LEVEL_1.value(), ssTableFileName);
var newSSTableSavePath = createLevel1SSTableFilePath(ssTableFileName);

fileSystemAccess.flush(newSSTableSavePath, memTable);
memTable.clear();
LOGGER.info("Memtable 디스크 플러쉬 완료");
}

private String generateIdentifier() {
Expand All @@ -108,11 +118,11 @@ private void compation() {
executors.execute(compation::start);
}

private String createFilePath(int level, String fileName) {
return createDirectoryPath(level) + SS_TABLE_FILE_BASE_NAME.formatted(fileName);
private String createLevel1SSTableFilePath(String fileName) {
return createDirectoryPath(CompationLevel.LEVEL_1) + fileName;
}

private String createDirectoryPath(int level) {
return this.storagePath + SS_TABLE_DIRECTORY_RELATIVE_PATH.formatted(level);
private String createDirectoryPath(CompationLevel level) {
return this.storagePath + SS_TABLE_DIRECTORY_RELATIVE_PATH.formatted(level.value());
}
}

0 comments on commit aba2376

Please sign in to comment.