2020-12-07 22:15:18 +01:00
|
|
|
package it.cavallium.dbengine.database.disk;
|
|
|
|
|
2021-10-30 12:39:56 +02:00
|
|
|
import static io.net5.buffer.api.StandardAllocationTypes.OFF_HEAP;
|
2021-09-10 12:13:52 +02:00
|
|
|
import static it.cavallium.dbengine.database.LLUtils.MARKER_ROCKSDB;
|
|
|
|
|
2021-10-30 11:13:46 +02:00
|
|
|
import io.micrometer.core.instrument.MeterRegistry;
|
2022-01-15 20:00:10 +01:00
|
|
|
import io.micrometer.core.instrument.Tag;
|
2021-12-30 18:20:56 +01:00
|
|
|
import io.micrometer.core.instrument.Timer;
|
2021-09-17 16:56:28 +02:00
|
|
|
import io.net5.buffer.api.BufferAllocator;
|
|
|
|
import io.net5.util.internal.PlatformDependent;
|
2022-01-15 20:00:10 +01:00
|
|
|
import it.cavallium.dbengine.client.MemoryStats;
|
2022-03-02 12:34:30 +01:00
|
|
|
import it.cavallium.dbengine.database.ColumnUtils;
|
2021-01-30 00:24:55 +01:00
|
|
|
import it.cavallium.dbengine.database.LLKeyValueDatabase;
|
|
|
|
import it.cavallium.dbengine.database.LLSnapshot;
|
2022-01-26 14:22:54 +01:00
|
|
|
import it.cavallium.dbengine.database.LLUtils;
|
2021-02-13 01:31:24 +01:00
|
|
|
import it.cavallium.dbengine.database.UpdateMode;
|
2022-03-02 12:34:30 +01:00
|
|
|
import it.cavallium.dbengine.rpc.current.data.Column;
|
|
|
|
import it.cavallium.dbengine.rpc.current.data.DatabaseOptions;
|
|
|
|
import it.cavallium.dbengine.rpc.current.data.DatabaseVolume;
|
2020-12-07 22:15:18 +01:00
|
|
|
import java.io.File;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
import java.nio.file.Files;
|
|
|
|
import java.nio.file.Path;
|
|
|
|
import java.nio.file.Paths;
|
|
|
|
import java.nio.file.attribute.BasicFileAttributes;
|
|
|
|
import java.time.Duration;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.LinkedList;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
2021-09-04 16:42:47 +02:00
|
|
|
import java.util.Objects;
|
2020-12-07 22:15:18 +01:00
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
2021-03-21 13:06:54 +01:00
|
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
2020-12-07 22:15:18 +01:00
|
|
|
import java.util.concurrent.atomic.AtomicLong;
|
2021-03-21 13:06:54 +01:00
|
|
|
import org.apache.commons.lang3.time.StopWatch;
|
2021-12-17 01:48:49 +01:00
|
|
|
import org.apache.logging.log4j.LogManager;
|
|
|
|
import org.apache.logging.log4j.Logger;
|
2021-07-10 20:52:01 +02:00
|
|
|
import org.jetbrains.annotations.Nullable;
|
2020-12-07 22:15:18 +01:00
|
|
|
import org.rocksdb.BlockBasedTableConfig;
|
|
|
|
import org.rocksdb.BloomFilter;
|
2022-01-12 16:18:31 +01:00
|
|
|
import org.rocksdb.Cache;
|
2021-07-17 11:52:08 +02:00
|
|
|
import org.rocksdb.ClockCache;
|
2020-12-07 22:15:18 +01:00
|
|
|
import org.rocksdb.ColumnFamilyDescriptor;
|
|
|
|
import org.rocksdb.ColumnFamilyHandle;
|
2022-03-10 02:38:57 +01:00
|
|
|
import org.rocksdb.ColumnFamilyOptions;
|
2021-03-19 20:55:38 +01:00
|
|
|
import org.rocksdb.CompactRangeOptions;
|
2021-05-04 01:21:29 +02:00
|
|
|
import org.rocksdb.CompactionPriority;
|
2022-01-12 16:18:31 +01:00
|
|
|
import org.rocksdb.CompressionOptions;
|
2020-12-07 22:15:18 +01:00
|
|
|
import org.rocksdb.CompressionType;
|
|
|
|
import org.rocksdb.DBOptions;
|
|
|
|
import org.rocksdb.DbPath;
|
|
|
|
import org.rocksdb.FlushOptions;
|
2021-07-17 11:52:08 +02:00
|
|
|
import org.rocksdb.IndexType;
|
2022-03-10 02:38:57 +01:00
|
|
|
import org.rocksdb.InfoLogLevel;
|
2021-10-17 19:52:43 +02:00
|
|
|
import org.rocksdb.OptimisticTransactionDB;
|
2020-12-07 22:15:18 +01:00
|
|
|
import org.rocksdb.RocksDB;
|
|
|
|
import org.rocksdb.RocksDBException;
|
|
|
|
import org.rocksdb.Snapshot;
|
2021-10-17 19:52:43 +02:00
|
|
|
import org.rocksdb.TransactionDB;
|
2021-12-27 18:44:54 +01:00
|
|
|
import org.rocksdb.TransactionDBOptions;
|
|
|
|
import org.rocksdb.TxnDBWritePolicy;
|
2020-12-07 22:15:18 +01:00
|
|
|
import org.rocksdb.WALRecoveryMode;
|
2021-03-20 12:41:11 +01:00
|
|
|
import org.rocksdb.WriteBufferManager;
|
2022-01-12 16:18:31 +01:00
|
|
|
import org.rocksdb.util.SizeUnit;
|
2021-01-30 01:42:37 +01:00
|
|
|
import reactor.core.publisher.Mono;
|
2021-02-01 02:21:53 +01:00
|
|
|
import reactor.core.scheduler.Scheduler;
|
2021-01-30 01:42:37 +01:00
|
|
|
import reactor.core.scheduler.Schedulers;
|
2020-12-07 22:15:18 +01:00
|
|
|
|
|
|
|
public class LLLocalKeyValueDatabase implements LLKeyValueDatabase {
|
|
|
|
|
|
|
|
static {
|
|
|
|
RocksDB.loadLibrary();
|
2022-01-26 14:22:54 +01:00
|
|
|
LLUtils.initHooks();
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
2021-12-17 01:48:49 +01:00
|
|
|
protected static final Logger logger = LogManager.getLogger(LLLocalKeyValueDatabase.class);
|
2022-01-15 20:00:10 +01:00
|
|
|
private static final ColumnFamilyDescriptor DEFAULT_COLUMN_FAMILY
|
|
|
|
= new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY);
|
2020-12-07 22:15:18 +01:00
|
|
|
|
2021-08-29 23:18:03 +02:00
|
|
|
private final BufferAllocator allocator;
|
2021-10-30 11:13:46 +02:00
|
|
|
private final MeterRegistry meterRegistry;
|
2021-09-07 11:26:10 +02:00
|
|
|
private final Scheduler dbScheduler;
|
2021-06-27 15:40:56 +02:00
|
|
|
|
2021-12-30 18:20:56 +01:00
|
|
|
private final Timer snapshotTime;
|
|
|
|
|
2021-06-27 15:40:56 +02:00
|
|
|
// Configurations
|
|
|
|
|
2020-12-07 22:15:18 +01:00
|
|
|
private final Path dbPath;
|
|
|
|
private final String name;
|
2021-06-27 15:40:56 +02:00
|
|
|
private final DatabaseOptions databaseOptions;
|
2021-10-30 12:39:56 +02:00
|
|
|
private final boolean nettyDirect;
|
2021-06-27 15:40:56 +02:00
|
|
|
|
2021-06-19 16:26:54 +02:00
|
|
|
private final boolean enableColumnsBug;
|
2021-10-20 01:51:34 +02:00
|
|
|
private RocksDB db;
|
2020-12-07 22:15:18 +01:00
|
|
|
private final Map<Column, ColumnFamilyHandle> handles;
|
|
|
|
private final ConcurrentHashMap<Long, Snapshot> snapshotsHandles = new ConcurrentHashMap<>();
|
|
|
|
private final AtomicLong nextSnapshotNumbers = new AtomicLong(1);
|
2022-01-15 20:00:10 +01:00
|
|
|
private volatile boolean closed = false;
|
2020-12-07 22:15:18 +01:00
|
|
|
|
2021-06-27 15:40:56 +02:00
|
|
|
@SuppressWarnings("SwitchStatementWithTooFewBranches")
|
2021-08-29 23:18:03 +02:00
|
|
|
public LLLocalKeyValueDatabase(BufferAllocator allocator,
|
2021-10-30 11:13:46 +02:00
|
|
|
MeterRegistry meterRegistry,
|
2021-05-03 21:41:51 +02:00
|
|
|
String name,
|
2022-03-02 12:34:30 +01:00
|
|
|
boolean inMemory,
|
2021-07-10 20:52:01 +02:00
|
|
|
@Nullable Path path,
|
2021-05-03 21:41:51 +02:00
|
|
|
List<Column> columns,
|
|
|
|
List<ColumnFamilyHandle> handles,
|
2021-06-27 15:40:56 +02:00
|
|
|
DatabaseOptions databaseOptions) throws IOException {
|
|
|
|
this.name = name;
|
2021-05-03 21:41:51 +02:00
|
|
|
this.allocator = allocator;
|
2021-10-30 12:39:56 +02:00
|
|
|
this.nettyDirect = databaseOptions.allowNettyDirect() && allocator.getAllocationType() == OFF_HEAP;
|
2021-10-30 11:13:46 +02:00
|
|
|
this.meterRegistry = meterRegistry;
|
2021-09-02 17:15:40 +02:00
|
|
|
|
2021-12-30 18:20:56 +01:00
|
|
|
this.snapshotTime = Timer
|
|
|
|
.builder("db.snapshot.timer")
|
|
|
|
.publishPercentiles(0.2, 0.5, 0.95)
|
|
|
|
.publishPercentileHistogram()
|
|
|
|
.tags("db.name", name)
|
|
|
|
.register(meterRegistry);
|
|
|
|
|
2021-10-30 12:39:56 +02:00
|
|
|
if (nettyDirect) {
|
2021-09-02 17:15:40 +02:00
|
|
|
if (!PlatformDependent.hasUnsafe()) {
|
|
|
|
throw new UnsupportedOperationException("Please enable unsafe support or disable netty direct buffers",
|
|
|
|
PlatformDependent.getUnsafeUnavailabilityCause()
|
|
|
|
);
|
|
|
|
}
|
2021-09-02 21:14:26 +02:00
|
|
|
if (!MemorySegmentUtils.isSupported()) {
|
2021-09-03 02:22:55 +02:00
|
|
|
throw new UnsupportedOperationException("Foreign Memory Access API support is disabled."
|
2021-09-22 11:03:39 +02:00
|
|
|
+ " Please set \"" + MemorySegmentUtils.getSuggestedArgs() + "\"",
|
|
|
|
MemorySegmentUtils.getUnsupportedCause()
|
|
|
|
);
|
2021-09-02 17:15:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-12 02:55:18 +01:00
|
|
|
OptionsWithCache optionsWithCache = openRocksDb(path, databaseOptions);
|
2022-03-10 02:38:57 +01:00
|
|
|
var rocksdbOptions = optionsWithCache.options();
|
2020-12-07 22:15:18 +01:00
|
|
|
try {
|
|
|
|
List<ColumnFamilyDescriptor> descriptors = new LinkedList<>();
|
2022-03-10 02:38:57 +01:00
|
|
|
|
2021-01-31 12:43:28 +01:00
|
|
|
descriptors
|
|
|
|
.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY));
|
2020-12-07 22:15:18 +01:00
|
|
|
for (Column column : columns) {
|
2022-03-10 02:38:57 +01:00
|
|
|
var columnOptions = new ColumnFamilyOptions();
|
|
|
|
|
|
|
|
//noinspection ConstantConditions
|
|
|
|
if (databaseOptions.memtableMemoryBudgetBytes() != null) {
|
|
|
|
// 16MiB/256MiB of ram will be used for level style compaction
|
|
|
|
columnOptions.optimizeLevelStyleCompaction(databaseOptions
|
|
|
|
.memtableMemoryBudgetBytes()
|
|
|
|
.orElse(databaseOptions.lowMemory() ? 16L * SizeUnit.MB : 128L * SizeUnit.MB));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!databaseOptions.volumes().isEmpty()) {
|
|
|
|
int btmIndex = databaseOptions.volumes().size() - 1;
|
|
|
|
columnOptions.setCompressionType(databaseOptions.volumes().get(0).compression().getType());
|
|
|
|
columnOptions.setCompressionOptions(new CompressionOptions().setMaxDictBytes((int) (512L * SizeUnit.MB)));
|
|
|
|
columnOptions.setBottommostCompressionType(databaseOptions.volumes().get(btmIndex).compression().getType());
|
|
|
|
columnOptions.setBottommostCompressionOptions(new CompressionOptions().setMaxDictBytes((int) (512L * SizeUnit.MB)));
|
|
|
|
|
|
|
|
columnOptions.setCompressionPerLevel(databaseOptions.volumes().stream().map(v -> v.compression().getType()).toList());
|
|
|
|
} else {
|
|
|
|
columnOptions.setCompressionType(CompressionType.LZ4_COMPRESSION);
|
|
|
|
columnOptions.setCompressionOptions(new CompressionOptions().setMaxDictBytes((int) (512L * SizeUnit.MB)));
|
|
|
|
columnOptions.setBottommostCompressionType(CompressionType.LZ4HC_COMPRESSION);
|
|
|
|
columnOptions.setBottommostCompressionOptions(new CompressionOptions().setMaxDictBytes((int) (512L * SizeUnit.MB)));
|
|
|
|
}
|
|
|
|
|
|
|
|
final BlockBasedTableConfig tableOptions = new BlockBasedTableConfig();
|
|
|
|
if (databaseOptions.lowMemory()) {
|
|
|
|
tableOptions
|
|
|
|
.setIndexType(IndexType.kTwoLevelIndexSearch)
|
|
|
|
.setPartitionFilters(true)
|
|
|
|
.setCacheIndexAndFilterBlocks(databaseOptions.setCacheIndexAndFilterBlocks().orElse(true))
|
|
|
|
.setCacheIndexAndFilterBlocksWithHighPriority(true)
|
|
|
|
.setPinTopLevelIndexAndFilter(true)
|
|
|
|
.setPinL0FilterAndIndexBlocksInCache(true)
|
|
|
|
;
|
|
|
|
} else {
|
|
|
|
tableOptions
|
|
|
|
.setIndexType(IndexType.kTwoLevelIndexSearch)
|
|
|
|
.setPartitionFilters(true)
|
|
|
|
.setCacheIndexAndFilterBlocks(databaseOptions.setCacheIndexAndFilterBlocks().orElse( true))
|
|
|
|
.setCacheIndexAndFilterBlocksWithHighPriority(true)
|
|
|
|
.setPinTopLevelIndexAndFilter(true)
|
|
|
|
.setPinL0FilterAndIndexBlocksInCache(true)
|
|
|
|
;
|
|
|
|
final BloomFilter bloomFilter = new BloomFilter(3, false);
|
|
|
|
tableOptions.setFilterPolicy(bloomFilter);
|
|
|
|
tableOptions.setOptimizeFiltersForMemory(true);
|
|
|
|
}
|
2022-03-12 02:55:18 +01:00
|
|
|
tableOptions
|
|
|
|
.setBlockCacheCompressed(optionsWithCache.compressedCache())
|
|
|
|
.setBlockCache(optionsWithCache.standardCache())
|
|
|
|
.setBlockSize(16 * 1024); // 16KiB
|
2022-03-10 02:38:57 +01:00
|
|
|
|
|
|
|
columnOptions.setTableFormatConfig(tableOptions);
|
|
|
|
columnOptions.setCompactionPriority(CompactionPriority.MinOverlappingRatio);
|
|
|
|
|
2020-12-07 22:15:18 +01:00
|
|
|
descriptors
|
2022-03-10 02:38:57 +01:00
|
|
|
.add(new ColumnFamilyDescriptor(column.name().getBytes(StandardCharsets.US_ASCII), columnOptions));
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get databases directory path
|
2021-09-04 16:42:47 +02:00
|
|
|
Objects.requireNonNull(path);
|
2020-12-07 22:15:18 +01:00
|
|
|
Path databasesDirPath = path.toAbsolutePath().getParent();
|
|
|
|
String dbPathString = databasesDirPath.toString() + File.separatorChar + path.getFileName();
|
|
|
|
Path dbPath = Paths.get(dbPathString);
|
|
|
|
this.dbPath = dbPath;
|
2021-06-27 15:40:56 +02:00
|
|
|
|
|
|
|
// Set options
|
|
|
|
this.databaseOptions = databaseOptions;
|
|
|
|
|
|
|
|
int threadCap;
|
|
|
|
if (databaseOptions.lowMemory()) {
|
|
|
|
threadCap = Runtime.getRuntime().availableProcessors();
|
2021-06-27 16:33:23 +02:00
|
|
|
} else {
|
|
|
|
// 8 or more
|
|
|
|
threadCap = Math.max(8, Runtime.getRuntime().availableProcessors());
|
2021-06-27 15:40:56 +02:00
|
|
|
}
|
2021-09-08 23:51:05 +02:00
|
|
|
this.dbScheduler = Schedulers.boundedElastic(); /*Schedulers.newBoundedElastic(threadCap,
|
2021-09-07 11:26:10 +02:00
|
|
|
Schedulers.DEFAULT_BOUNDED_ELASTIC_QUEUESIZE,
|
|
|
|
"db-" + name,
|
|
|
|
60,
|
|
|
|
true
|
2021-09-08 23:51:05 +02:00
|
|
|
);*/
|
2021-06-27 15:40:56 +02:00
|
|
|
this.enableColumnsBug = "true".equals(databaseOptions.extraFlags().getOrDefault("enableColumnBug", "false"));
|
2020-12-07 22:15:18 +01:00
|
|
|
|
2022-03-02 12:34:30 +01:00
|
|
|
createIfNotExists(descriptors, rocksdbOptions, inMemory, dbPath, dbPathString);
|
2021-06-09 02:56:53 +02:00
|
|
|
|
2021-06-25 23:47:53 +02:00
|
|
|
while (true) {
|
|
|
|
try {
|
|
|
|
// a factory method that returns a RocksDB instance
|
2021-12-27 17:45:52 +01:00
|
|
|
if (databaseOptions.optimistic()) {
|
2022-03-10 02:38:57 +01:00
|
|
|
this.db = OptimisticTransactionDB.open(rocksdbOptions, dbPathString, descriptors, handles);
|
2021-12-27 17:45:52 +01:00
|
|
|
} else {
|
2022-03-10 02:38:57 +01:00
|
|
|
this.db = TransactionDB.open(rocksdbOptions,
|
2021-12-27 23:07:11 +01:00
|
|
|
new TransactionDBOptions()
|
|
|
|
.setWritePolicy(TxnDBWritePolicy.WRITE_COMMITTED)
|
|
|
|
.setTransactionLockTimeout(5000)
|
|
|
|
.setDefaultLockTimeout(5000),
|
2021-12-27 18:44:54 +01:00
|
|
|
dbPathString,
|
|
|
|
descriptors,
|
|
|
|
handles
|
|
|
|
);
|
2021-12-27 17:45:52 +01:00
|
|
|
}
|
2021-06-25 23:47:53 +02:00
|
|
|
break;
|
|
|
|
} catch (RocksDBException ex) {
|
|
|
|
switch (ex.getMessage()) {
|
|
|
|
case "Direct I/O is not supported by the specified DB." -> {
|
|
|
|
logger.warn(ex.getLocalizedMessage());
|
2021-06-27 15:40:56 +02:00
|
|
|
rocksdbOptions
|
2021-06-25 23:47:53 +02:00
|
|
|
.setUseDirectReads(false)
|
2021-06-27 15:06:48 +02:00
|
|
|
.setUseDirectIoForFlushAndCompaction(false)
|
2021-06-27 15:40:56 +02:00
|
|
|
.setAllowMmapReads(databaseOptions.allowMemoryMapping())
|
|
|
|
.setAllowMmapWrites(databaseOptions.allowMemoryMapping());
|
2021-06-25 23:47:53 +02:00
|
|
|
}
|
|
|
|
default -> throw ex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-12-07 22:15:18 +01:00
|
|
|
this.handles = new HashMap<>();
|
2022-03-02 12:34:30 +01:00
|
|
|
if (enableColumnsBug && !inMemory) {
|
2021-06-19 16:26:54 +02:00
|
|
|
for (int i = 0; i < columns.size(); i++) {
|
|
|
|
this.handles.put(columns.get(i), handles.get(i));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
handles: for (ColumnFamilyHandle handle : handles) {
|
|
|
|
for (Column column : columns) {
|
|
|
|
if (Arrays.equals(column.name().getBytes(StandardCharsets.US_ASCII), handle.getName())) {
|
|
|
|
this.handles.put(column, handle);
|
|
|
|
continue handles;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
2021-03-21 13:06:54 +01:00
|
|
|
// compactDb(db, handles);
|
2020-12-07 22:15:18 +01:00
|
|
|
flushDb(db, handles);
|
|
|
|
} catch (RocksDBException ex) {
|
|
|
|
throw new IOException(ex);
|
|
|
|
}
|
2022-01-15 20:00:10 +01:00
|
|
|
|
|
|
|
registerGauge(meterRegistry, name, "rocksdb.estimate-table-readers-mem");
|
|
|
|
registerGauge(meterRegistry, name, "rocksdb.size-all-mem-tables");
|
|
|
|
registerGauge(meterRegistry, name, "rocksdb.cur-size-all-mem-tables");
|
|
|
|
registerGauge(meterRegistry, name, "rocksdb.estimate-num-keys");
|
|
|
|
registerGauge(meterRegistry, name, "rocksdb.block-cache-usage");
|
|
|
|
registerGauge(meterRegistry, name, "rocksdb.block-cache-pinned-usage");
|
|
|
|
}
|
|
|
|
|
|
|
|
private void registerGauge(MeterRegistry meterRegistry, String name, String propertyName) {
|
|
|
|
meterRegistry.gauge("rocksdb.property.value",
|
|
|
|
List.of(Tag.of("db.name", name), Tag.of("db.property.name", propertyName)),
|
|
|
|
db,
|
|
|
|
database -> {
|
|
|
|
if (closed) {
|
|
|
|
return 0d;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
return database.getAggregatedLongProperty(propertyName);
|
|
|
|
} catch (RocksDBException e) {
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getDatabaseName() {
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2021-10-20 01:51:34 +02:00
|
|
|
private void flushAndCloseDb(RocksDB db, List<ColumnFamilyHandle> handles)
|
2020-12-07 22:15:18 +01:00
|
|
|
throws RocksDBException {
|
|
|
|
flushDb(db, handles);
|
|
|
|
|
|
|
|
for (ColumnFamilyHandle handle : handles) {
|
2021-07-06 22:27:03 +02:00
|
|
|
try {
|
|
|
|
handle.close();
|
|
|
|
} catch (Exception ex) {
|
|
|
|
logger.error("Can't close column family", ex);
|
|
|
|
}
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
2021-07-01 21:19:52 +02:00
|
|
|
try {
|
|
|
|
db.closeE();
|
|
|
|
} catch (RocksDBException ex) {
|
|
|
|
if ("Cannot close DB with unreleased snapshot.".equals(ex.getMessage())) {
|
2021-07-10 20:52:01 +02:00
|
|
|
snapshotsHandles.forEach((id, snapshot) -> {
|
2021-07-01 21:19:52 +02:00
|
|
|
try {
|
|
|
|
db.releaseSnapshot(snapshot);
|
|
|
|
} catch (Exception ex2) {
|
|
|
|
// ignore exception
|
|
|
|
logger.debug("Failed to release snapshot " + id, ex2);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
db.closeE();
|
|
|
|
}
|
|
|
|
throw ex;
|
|
|
|
}
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
2021-10-20 01:51:34 +02:00
|
|
|
private void flushDb(RocksDB db, List<ColumnFamilyHandle> handles) throws RocksDBException {
|
2021-09-05 14:23:46 +02:00
|
|
|
if (Schedulers.isInNonBlockingThread()) {
|
|
|
|
logger.error("Called flushDb in a nonblocking thread");
|
|
|
|
}
|
2020-12-07 22:15:18 +01:00
|
|
|
// force flush the database
|
2021-07-06 22:27:03 +02:00
|
|
|
try (var flushOptions = new FlushOptions().setWaitForFlush(true).setAllowWriteStall(true)) {
|
|
|
|
db.flush(flushOptions);
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
2021-07-06 22:27:03 +02:00
|
|
|
try (var flushOptions = new FlushOptions().setWaitForFlush(true).setAllowWriteStall(true)) {
|
|
|
|
db.flush(flushOptions, handles);
|
|
|
|
}
|
|
|
|
db.flushWal(true);
|
|
|
|
db.syncWal();
|
2020-12-07 22:15:18 +01:00
|
|
|
// end force flush
|
|
|
|
}
|
|
|
|
|
2021-04-03 19:09:06 +02:00
|
|
|
@SuppressWarnings("unused")
|
2021-10-20 01:51:34 +02:00
|
|
|
private void compactDb(TransactionDB db, List<ColumnFamilyHandle> handles) {
|
2021-09-05 14:23:46 +02:00
|
|
|
if (Schedulers.isInNonBlockingThread()) {
|
|
|
|
logger.error("Called compactDb in a nonblocking thread");
|
|
|
|
}
|
2021-03-19 20:55:38 +01:00
|
|
|
// force compact the database
|
|
|
|
for (ColumnFamilyHandle cfh : handles) {
|
|
|
|
var t = new Thread(() -> {
|
2021-03-21 13:06:54 +01:00
|
|
|
int r = ThreadLocalRandom.current().nextInt();
|
|
|
|
var s = StopWatch.createStarted();
|
2021-03-19 20:55:38 +01:00
|
|
|
try {
|
|
|
|
// Range rangeToCompact = db.suggestCompactRange(cfh);
|
2021-03-21 13:06:54 +01:00
|
|
|
logger.info("Compacting range {}", r);
|
|
|
|
db.compactRange(cfh, null, null, new CompactRangeOptions()
|
|
|
|
.setAllowWriteStall(true)
|
|
|
|
.setExclusiveManualCompaction(true)
|
|
|
|
.setChangeLevel(false));
|
2021-03-19 20:55:38 +01:00
|
|
|
} catch (RocksDBException e) {
|
|
|
|
if ("Database shutdown".equalsIgnoreCase(e.getMessage())) {
|
|
|
|
logger.warn("Compaction cancelled: database shutdown");
|
|
|
|
} else {
|
|
|
|
logger.warn("Failed to compact range", e);
|
|
|
|
}
|
|
|
|
}
|
2021-03-21 13:06:54 +01:00
|
|
|
logger.info("Compacted range {} in {} milliseconds", r, s.getTime(TimeUnit.MILLISECONDS));
|
2021-03-19 20:55:38 +01:00
|
|
|
}, "Compaction");
|
|
|
|
t.setDaemon(true);
|
|
|
|
t.start();
|
|
|
|
}
|
|
|
|
// end force compact
|
|
|
|
}
|
|
|
|
|
2022-03-12 02:55:18 +01:00
|
|
|
record OptionsWithCache(DBOptions options, Cache standardCache, Cache compressedCache) {}
|
2022-03-10 02:38:57 +01:00
|
|
|
|
|
|
|
private static OptionsWithCache openRocksDb(@Nullable Path path, DatabaseOptions databaseOptions) throws IOException {
|
2020-12-07 22:15:18 +01:00
|
|
|
// Get databases directory path
|
2021-07-10 20:52:01 +02:00
|
|
|
Path databasesDirPath;
|
|
|
|
if (path != null) {
|
|
|
|
databasesDirPath = path.toAbsolutePath().getParent();
|
|
|
|
// Create base directories
|
|
|
|
if (Files.notExists(databasesDirPath)) {
|
|
|
|
Files.createDirectories(databasesDirPath);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
databasesDirPath = null;
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// the Options class contains a set of configurable DB options
|
|
|
|
// that determines the behaviour of the database.
|
2022-03-10 02:38:57 +01:00
|
|
|
var options = new DBOptions();
|
2022-03-12 02:55:18 +01:00
|
|
|
options.setParanoidChecks(false);
|
2020-12-07 22:15:18 +01:00
|
|
|
options.setCreateIfMissing(true);
|
2021-07-06 22:27:03 +02:00
|
|
|
options.setCreateMissingColumnFamilies(true);
|
2022-03-10 02:38:57 +01:00
|
|
|
options.setInfoLogLevel(InfoLogLevel.ERROR_LEVEL);
|
2020-12-07 22:15:18 +01:00
|
|
|
options.setAvoidFlushDuringShutdown(false); // Flush all WALs during shutdown
|
|
|
|
options.setAvoidFlushDuringRecovery(false); // Flush all WALs during startup
|
2021-06-27 15:40:56 +02:00
|
|
|
options.setWalRecoveryMode(databaseOptions.absoluteConsistency()
|
2021-02-01 11:00:27 +01:00
|
|
|
? WALRecoveryMode.AbsoluteConsistency
|
|
|
|
: WALRecoveryMode.PointInTimeRecovery); // Crash if the WALs are corrupted.Default: TolerateCorruptedTailRecords
|
2020-12-07 22:15:18 +01:00
|
|
|
options.setDeleteObsoleteFilesPeriodMicros(20 * 1000000); // 20 seconds
|
|
|
|
options.setKeepLogFileNum(10);
|
2021-07-10 20:52:01 +02:00
|
|
|
|
2021-09-04 16:42:47 +02:00
|
|
|
Objects.requireNonNull(databasesDirPath);
|
|
|
|
Objects.requireNonNull(path.getFileName());
|
2021-12-27 16:33:31 +01:00
|
|
|
List<DbPath> paths = convertPaths(databasesDirPath, path.getFileName(), databaseOptions.volumes());
|
2021-06-09 02:56:53 +02:00
|
|
|
options.setDbPaths(paths);
|
2022-03-02 12:34:30 +01:00
|
|
|
options.setMaxOpenFiles(databaseOptions.maxOpenFiles().orElse(-1));
|
2022-03-10 02:38:57 +01:00
|
|
|
|
2022-01-12 16:18:31 +01:00
|
|
|
Cache blockCache;
|
2022-03-12 02:55:18 +01:00
|
|
|
Cache compressedCache;
|
2021-06-27 15:40:56 +02:00
|
|
|
if (databaseOptions.lowMemory()) {
|
2020-12-07 22:15:18 +01:00
|
|
|
// LOW MEMORY
|
|
|
|
options
|
2021-05-18 01:10:30 +02:00
|
|
|
.setBytesPerSync(0) // default
|
|
|
|
.setWalBytesPerSync(0) // default
|
2020-12-07 22:15:18 +01:00
|
|
|
.setIncreaseParallelism(1)
|
2022-03-10 02:38:57 +01:00
|
|
|
.setDbWriteBufferSize(8 * SizeUnit.MB)
|
2021-05-18 01:10:30 +02:00
|
|
|
.setWalTtlSeconds(0)
|
|
|
|
.setWalSizeLimitMB(0) // 16MB
|
|
|
|
.setMaxTotalWalSize(0) // automatic
|
|
|
|
;
|
2022-03-12 02:55:18 +01:00
|
|
|
blockCache = new ClockCache(databaseOptions.blockCache().orElse( 8L * SizeUnit.MB) / 2, -1, true);
|
|
|
|
compressedCache = new ClockCache(databaseOptions.blockCache().orElse( 8L * SizeUnit.MB) / 2, -1, true);
|
2021-07-17 11:52:08 +02:00
|
|
|
|
|
|
|
if (databaseOptions.useDirectIO()) {
|
|
|
|
options
|
|
|
|
// Option to enable readahead in compaction
|
|
|
|
// If not set, it will be set to 2MB internally
|
|
|
|
.setCompactionReadaheadSize(2 * 1024 * 1024) // recommend at least 2MB
|
|
|
|
// Option to tune write buffer for direct writes
|
|
|
|
.setWritableFileMaxBufferSize(1024 * 1024)
|
|
|
|
;
|
|
|
|
}
|
2020-12-07 22:15:18 +01:00
|
|
|
} else {
|
|
|
|
// HIGH MEMORY
|
|
|
|
options
|
|
|
|
.setIncreaseParallelism(Runtime.getRuntime().availableProcessors())
|
2022-01-12 16:18:31 +01:00
|
|
|
.setBytesPerSync(8 * SizeUnit.KB)
|
|
|
|
.setWalBytesPerSync(8 * SizeUnit.KB)
|
|
|
|
|
2021-05-18 01:10:30 +02:00
|
|
|
.setWalTtlSeconds(30) // flush wal after 30 seconds
|
2020-12-07 22:15:18 +01:00
|
|
|
.setWalSizeLimitMB(1024) // 1024MB
|
2022-01-12 16:18:31 +01:00
|
|
|
.setMaxTotalWalSize(2L * SizeUnit.GB) // 2GiB max wal directory size
|
2020-12-07 22:15:18 +01:00
|
|
|
;
|
2022-03-12 02:55:18 +01:00
|
|
|
blockCache = new ClockCache(databaseOptions.blockCache().orElse( 512 * SizeUnit.MB) / 2);
|
|
|
|
compressedCache = new ClockCache(databaseOptions.blockCache().orElse( 512 * SizeUnit.MB) / 2);
|
2021-06-25 23:47:53 +02:00
|
|
|
|
2021-06-27 15:40:56 +02:00
|
|
|
if (databaseOptions.useDirectIO()) {
|
2021-06-25 23:47:53 +02:00
|
|
|
options
|
|
|
|
// Option to enable readahead in compaction
|
|
|
|
// If not set, it will be set to 2MB internally
|
2021-07-17 11:52:08 +02:00
|
|
|
.setCompactionReadaheadSize(4 * 1024 * 1024) // recommend at least 2MB
|
2021-06-25 23:47:53 +02:00
|
|
|
// Option to tune write buffer for direct writes
|
2021-07-17 11:52:08 +02:00
|
|
|
.setWritableFileMaxBufferSize(4 * 1024 * 1024)
|
2021-06-25 23:47:53 +02:00
|
|
|
;
|
|
|
|
}
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
2021-12-27 16:33:31 +01:00
|
|
|
|
2022-01-12 16:18:31 +01:00
|
|
|
options.setWriteBufferManager(new WriteBufferManager(256L * 1024L * 1024L, blockCache));
|
|
|
|
|
2021-07-17 11:52:08 +02:00
|
|
|
if (databaseOptions.useDirectIO()) {
|
|
|
|
options
|
|
|
|
.setAllowMmapReads(false)
|
|
|
|
.setAllowMmapWrites(false)
|
|
|
|
.setUseDirectReads(true)
|
|
|
|
;
|
|
|
|
} else {
|
|
|
|
options
|
|
|
|
.setAllowMmapReads(databaseOptions.allowMemoryMapping())
|
|
|
|
.setAllowMmapWrites(databaseOptions.allowMemoryMapping());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!databaseOptions.allowMemoryMapping()) {
|
|
|
|
options.setUseDirectIoForFlushAndCompaction(true);
|
|
|
|
}
|
2020-12-07 22:15:18 +01:00
|
|
|
|
2022-03-12 02:55:18 +01:00
|
|
|
return new OptionsWithCache(options, blockCache, compressedCache);
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
2021-12-27 16:33:31 +01:00
|
|
|
private static List<DbPath> convertPaths(Path databasesDirPath, Path path, List<DatabaseVolume> volumes) {
|
|
|
|
var paths = new ArrayList<DbPath>(volumes.size());
|
|
|
|
if (volumes.isEmpty()) {
|
|
|
|
return List.of(new DbPath(databasesDirPath.resolve(path.getFileName() + "_hot"),
|
|
|
|
100L * 1024L * 1024L * 1024L), // 100GiB
|
|
|
|
new DbPath(databasesDirPath.resolve(path.getFileName() + "_cold"),
|
|
|
|
500L * 1024L * 1024L * 1024L), // 500GiB
|
|
|
|
new DbPath(databasesDirPath.resolve(path.getFileName() + "_colder"),
|
|
|
|
500L * 1024L * 1024L * 1024L)); // 500GiB
|
|
|
|
}
|
|
|
|
for (DatabaseVolume volume : volumes) {
|
|
|
|
Path volumePath;
|
|
|
|
if (volume.volumePath().isAbsolute()) {
|
|
|
|
volumePath = volume.volumePath();
|
|
|
|
} else {
|
|
|
|
volumePath = databasesDirPath.resolve(volume.volumePath());
|
|
|
|
}
|
|
|
|
paths.add(new DbPath(volumePath, volume.targetSizeBytes()));
|
|
|
|
}
|
|
|
|
return paths;
|
|
|
|
}
|
|
|
|
|
2021-04-30 19:15:04 +02:00
|
|
|
private void createIfNotExists(List<ColumnFamilyDescriptor> descriptors,
|
2022-03-10 02:38:57 +01:00
|
|
|
DBOptions options,
|
2022-03-02 12:34:30 +01:00
|
|
|
boolean inMemory,
|
2021-04-30 19:15:04 +02:00
|
|
|
Path dbPath,
|
|
|
|
String dbPathString) throws RocksDBException {
|
2022-03-02 12:34:30 +01:00
|
|
|
if (inMemory) {
|
2021-04-30 19:15:04 +02:00
|
|
|
return;
|
|
|
|
}
|
2020-12-07 22:15:18 +01:00
|
|
|
if (Files.notExists(dbPath)) {
|
|
|
|
// Check if handles are all different
|
|
|
|
var descriptorsSet = new HashSet<>(descriptors);
|
|
|
|
if (descriptorsSet.size() != descriptors.size()) {
|
|
|
|
throw new IllegalArgumentException("Descriptors must be unique!");
|
|
|
|
}
|
|
|
|
|
|
|
|
List<ColumnFamilyDescriptor> descriptorsToCreate = new LinkedList<>(descriptors);
|
|
|
|
descriptorsToCreate
|
|
|
|
.removeIf((cf) -> Arrays.equals(cf.getName(), DEFAULT_COLUMN_FAMILY.getName()));
|
|
|
|
|
2021-01-30 00:24:55 +01:00
|
|
|
/*
|
|
|
|
SkipStatsUpdateOnDbOpen = true because this RocksDB.open session is used only to add just some columns
|
2020-12-07 22:15:18 +01:00
|
|
|
*/
|
|
|
|
//var dbOptionsFastLoadSlowEdit = options.setSkipStatsUpdateOnDbOpen(true);
|
|
|
|
|
|
|
|
LinkedList<ColumnFamilyHandle> handles = new LinkedList<>();
|
|
|
|
|
2022-03-11 17:59:46 +01:00
|
|
|
this.db = RocksDB.open(new DBOptions(options).setCreateMissingColumnFamilies(true),
|
|
|
|
dbPathString,
|
|
|
|
descriptors,
|
|
|
|
handles
|
|
|
|
);
|
2020-12-07 22:15:18 +01:00
|
|
|
|
2021-06-27 15:40:56 +02:00
|
|
|
flushAndCloseDb(db, handles);
|
2021-10-21 10:00:39 +02:00
|
|
|
this.db = null;
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-01-31 15:47:48 +01:00
|
|
|
public Mono<LLLocalSingleton> getSingleton(byte[] singletonListColumnName, byte[] name, byte[] defaultValue) {
|
|
|
|
return Mono
|
2021-11-12 02:05:44 +01:00
|
|
|
.fromCallable(() -> new LLLocalSingleton(
|
|
|
|
getRocksDBColumn(db, getCfh(singletonListColumnName)),
|
2021-01-31 15:47:48 +01:00
|
|
|
(snapshot) -> snapshotsHandles.get(snapshot.getSequenceNumber()),
|
|
|
|
LLLocalKeyValueDatabase.this.name,
|
|
|
|
name,
|
2021-09-07 11:26:10 +02:00
|
|
|
dbScheduler,
|
2021-01-31 15:47:48 +01:00
|
|
|
defaultValue
|
|
|
|
))
|
2021-09-07 11:26:10 +02:00
|
|
|
.onErrorMap(cause -> new IOException("Failed to read " + Arrays.toString(name), cause))
|
|
|
|
.subscribeOn(dbScheduler);
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-02-13 01:31:24 +01:00
|
|
|
public Mono<LLLocalDictionary> getDictionary(byte[] columnName, UpdateMode updateMode) {
|
2021-01-31 15:47:48 +01:00
|
|
|
return Mono
|
2021-06-26 02:35:33 +02:00
|
|
|
.fromCallable(() -> new LLLocalDictionary(
|
|
|
|
allocator,
|
2021-10-20 01:51:34 +02:00
|
|
|
getRocksDBColumn(db, getCfh(columnName)),
|
2021-06-26 02:35:33 +02:00
|
|
|
name,
|
2022-03-02 12:34:30 +01:00
|
|
|
ColumnUtils.toString(columnName),
|
2021-09-07 11:26:10 +02:00
|
|
|
dbScheduler,
|
2021-06-26 02:35:33 +02:00
|
|
|
(snapshot) -> snapshotsHandles.get(snapshot.getSequenceNumber()),
|
2021-06-29 23:31:02 +02:00
|
|
|
updateMode,
|
|
|
|
databaseOptions
|
2021-09-07 11:26:10 +02:00
|
|
|
))
|
|
|
|
.subscribeOn(dbScheduler);
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
2021-10-20 01:51:34 +02:00
|
|
|
private RocksDBColumn getRocksDBColumn(RocksDB db, ColumnFamilyHandle cfh) {
|
|
|
|
if (db instanceof OptimisticTransactionDB optimisticTransactionDB) {
|
2021-10-30 11:13:46 +02:00
|
|
|
return new OptimisticRocksDBColumn(optimisticTransactionDB, databaseOptions, allocator, cfh, meterRegistry);
|
2021-12-27 18:44:54 +01:00
|
|
|
} else if (db instanceof TransactionDB transactionDB) {
|
|
|
|
return new PessimisticRocksDBColumn(transactionDB, databaseOptions, allocator, cfh, meterRegistry);
|
2021-10-20 01:51:34 +02:00
|
|
|
} else {
|
2021-10-30 11:13:46 +02:00
|
|
|
return new StandardRocksDBColumn(db, databaseOptions, allocator, cfh, meterRegistry);
|
2021-10-20 01:51:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-19 16:26:54 +02:00
|
|
|
private ColumnFamilyHandle getCfh(byte[] columnName) throws RocksDBException {
|
2022-03-02 12:34:30 +01:00
|
|
|
ColumnFamilyHandle cfh = handles.get(ColumnUtils.special(ColumnUtils.toString(columnName)));
|
2021-08-28 22:42:51 +02:00
|
|
|
assert enableColumnsBug || Arrays.equals(cfh.getName(), columnName);
|
2021-06-19 16:26:54 +02:00
|
|
|
return cfh;
|
|
|
|
}
|
|
|
|
|
2021-06-27 15:40:56 +02:00
|
|
|
public DatabaseOptions getDatabaseOptions() {
|
|
|
|
return databaseOptions;
|
|
|
|
}
|
|
|
|
|
2020-12-07 22:15:18 +01:00
|
|
|
@Override
|
2021-01-31 15:47:48 +01:00
|
|
|
public Mono<Long> getProperty(String propertyName) {
|
|
|
|
return Mono.fromCallable(() -> db.getAggregatedLongProperty(propertyName))
|
2021-09-07 11:26:10 +02:00
|
|
|
.onErrorMap(cause -> new IOException("Failed to read " + propertyName, cause))
|
|
|
|
.subscribeOn(dbScheduler);
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
2022-01-15 20:00:10 +01:00
|
|
|
@Override
|
|
|
|
public Mono<MemoryStats> getMemoryStats() {
|
|
|
|
return Mono
|
|
|
|
.fromCallable(() -> new MemoryStats(db.getAggregatedLongProperty("rocksdb.estimate-table-readers-mem"),
|
|
|
|
db.getAggregatedLongProperty("rocksdb.size-all-mem-tables"),
|
|
|
|
db.getAggregatedLongProperty("rocksdb.cur-size-all-mem-tables"),
|
|
|
|
db.getAggregatedLongProperty("rocksdb.estimate-num-keys"),
|
|
|
|
db.getAggregatedLongProperty("rocksdb.block-cache-usage"),
|
|
|
|
db.getAggregatedLongProperty("rocksdb.block-cache-pinned-usage")
|
|
|
|
))
|
|
|
|
.onErrorMap(cause -> new IOException("Failed to read memory stats", cause))
|
|
|
|
.subscribeOn(dbScheduler);
|
|
|
|
}
|
|
|
|
|
2021-06-27 15:06:48 +02:00
|
|
|
@Override
|
|
|
|
public Mono<Void> verifyChecksum() {
|
|
|
|
return Mono
|
|
|
|
.<Void>fromCallable(() -> {
|
|
|
|
db.verifyChecksum();
|
|
|
|
return null;
|
|
|
|
})
|
|
|
|
.onErrorMap(cause -> new IOException("Failed to verify checksum of database \""
|
2021-09-07 11:26:10 +02:00
|
|
|
+ getDatabaseName() + "\"", cause))
|
|
|
|
.subscribeOn(dbScheduler);
|
2021-06-27 15:06:48 +02:00
|
|
|
}
|
|
|
|
|
2021-05-03 21:41:51 +02:00
|
|
|
@Override
|
2021-08-29 23:18:03 +02:00
|
|
|
public BufferAllocator getAllocator() {
|
2021-05-03 21:41:51 +02:00
|
|
|
return allocator;
|
|
|
|
}
|
|
|
|
|
2021-10-30 11:13:46 +02:00
|
|
|
@Override
|
|
|
|
public MeterRegistry getMeterRegistry() {
|
|
|
|
return meterRegistry;
|
|
|
|
}
|
|
|
|
|
2020-12-07 22:15:18 +01:00
|
|
|
@Override
|
2021-01-30 01:42:37 +01:00
|
|
|
public Mono<LLSnapshot> takeSnapshot() {
|
|
|
|
return Mono
|
2021-12-30 18:20:56 +01:00
|
|
|
.fromCallable(() -> snapshotTime.recordCallable(() -> {
|
2021-01-30 01:42:37 +01:00
|
|
|
var snapshot = db.getSnapshot();
|
|
|
|
long currentSnapshotSequenceNumber = nextSnapshotNumbers.getAndIncrement();
|
|
|
|
this.snapshotsHandles.put(currentSnapshotSequenceNumber, snapshot);
|
|
|
|
return new LLSnapshot(currentSnapshotSequenceNumber);
|
2021-12-30 18:20:56 +01:00
|
|
|
}))
|
2021-09-07 11:26:10 +02:00
|
|
|
.subscribeOn(dbScheduler);
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-01-30 01:42:37 +01:00
|
|
|
public Mono<Void> releaseSnapshot(LLSnapshot snapshot) {
|
|
|
|
return Mono
|
2021-09-07 11:26:10 +02:00
|
|
|
.<Void>fromCallable(() -> {
|
2021-01-30 01:42:37 +01:00
|
|
|
Snapshot dbSnapshot = this.snapshotsHandles.remove(snapshot.getSequenceNumber());
|
|
|
|
if (dbSnapshot == null) {
|
|
|
|
throw new IOException("Snapshot " + snapshot.getSequenceNumber() + " not found!");
|
|
|
|
}
|
|
|
|
db.releaseSnapshot(dbSnapshot);
|
|
|
|
return null;
|
2021-09-07 11:26:10 +02:00
|
|
|
})
|
|
|
|
.subscribeOn(dbScheduler);
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-01-31 19:52:47 +01:00
|
|
|
public Mono<Void> close() {
|
|
|
|
return Mono
|
|
|
|
.<Void>fromCallable(() -> {
|
|
|
|
try {
|
2022-01-15 20:00:10 +01:00
|
|
|
closed = true;
|
2021-01-31 19:52:47 +01:00
|
|
|
flushAndCloseDb(db, new ArrayList<>(handles.values()));
|
|
|
|
deleteUnusedOldLogFiles();
|
|
|
|
} catch (RocksDBException e) {
|
|
|
|
throw new IOException(e);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
})
|
2021-09-07 11:26:10 +02:00
|
|
|
.onErrorMap(cause -> new IOException("Failed to close", cause))
|
|
|
|
.subscribeOn(dbScheduler);
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Call this method ONLY AFTER flushing completely a db and closing it!
|
|
|
|
*/
|
2021-01-30 00:24:55 +01:00
|
|
|
@SuppressWarnings("unused")
|
2020-12-07 22:15:18 +01:00
|
|
|
private void deleteUnusedOldLogFiles() {
|
|
|
|
Path basePath = dbPath;
|
|
|
|
try {
|
|
|
|
Files
|
|
|
|
.walk(basePath, 1)
|
|
|
|
.filter(p -> !p.equals(basePath))
|
|
|
|
.filter(p -> {
|
|
|
|
var fileName = p.getFileName().toString();
|
|
|
|
if (fileName.startsWith("LOG.old.")) {
|
|
|
|
var parts = fileName.split("\\.");
|
|
|
|
if (parts.length == 3) {
|
|
|
|
try {
|
|
|
|
long nameSuffix = Long.parseUnsignedLong(parts[2]);
|
|
|
|
return true;
|
|
|
|
} catch (NumberFormatException ex) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (fileName.endsWith(".log")) {
|
|
|
|
var parts = fileName.split("\\.");
|
|
|
|
if (parts.length == 2) {
|
|
|
|
try {
|
|
|
|
int name = Integer.parseUnsignedInt(parts[0]);
|
|
|
|
return true;
|
|
|
|
} catch (NumberFormatException ex) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
})
|
|
|
|
.filter(p -> {
|
|
|
|
try {
|
|
|
|
BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class);
|
|
|
|
if (attrs.isRegularFile() && !attrs.isSymbolicLink() && !attrs.isDirectory()) {
|
|
|
|
long ctime = attrs.creationTime().toMillis();
|
|
|
|
long atime = attrs.lastAccessTime().toMillis();
|
|
|
|
long mtime = attrs.lastModifiedTime().toMillis();
|
|
|
|
long lastTime = Math.max(Math.max(ctime, atime), mtime);
|
|
|
|
long safeTime;
|
|
|
|
if (p.getFileName().toString().startsWith("LOG.old.")) {
|
|
|
|
safeTime = System.currentTimeMillis() - Duration.ofHours(24).toMillis();
|
|
|
|
} else {
|
|
|
|
safeTime = System.currentTimeMillis() - Duration.ofHours(12).toMillis();
|
|
|
|
}
|
|
|
|
if (lastTime < safeTime) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (IOException ex) {
|
2021-03-19 20:55:38 +01:00
|
|
|
logger.error("Error when deleting unused log files", ex);
|
2020-12-07 22:15:18 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
})
|
|
|
|
.forEach(path -> {
|
|
|
|
try {
|
|
|
|
Files.deleteIfExists(path);
|
|
|
|
System.out.println("Deleted log file \"" + path + "\"");
|
|
|
|
} catch (IOException e) {
|
2021-09-10 12:13:52 +02:00
|
|
|
logger.error(MARKER_ROCKSDB, "Failed to delete log file \"" + path + "\"", e);
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
} catch (IOException ex) {
|
2021-09-10 12:13:52 +02:00
|
|
|
logger.error(MARKER_ROCKSDB, "Failed to delete unused log files", ex);
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|