All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ojalgo.rocksdb.RocksMap Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 1997-2025 Optimatika
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.ojalgo.rocksdb;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.AbstractMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

import org.ojalgo.concurrent.Parallelism;
import org.ojalgo.concurrent.ProcessingService;
import org.ojalgo.function.special.PowerOf2;
import org.ojalgo.netio.ShardedFile;
import org.rocksdb.CompressionType;
import org.rocksdb.Options;
import org.rocksdb.ReadOptions;
import org.rocksdb.RocksDB;
import org.rocksdb.WriteOptions;

/**
 * 

* A {@link RocksDB} based {@link Map} implementation. *

* Largely functions as a normal {@link Map}, but there are a few differences. For instance {@link #size()} * and {@link #isEmpty()} always return the same dummy values (maximum size and not empty). Further the * {@link #put(Object, Object)} and {@link #remove(Object)} methods always return null. *

* null keys and/or values are not permitted. *

* Particular attention has been given to maximise bulk loading performance. To make use of this, specify that * you are going to bulk load, and use multiple shards: *

    *
  1. {@link RocksMap.Builder#mode(Mode)}
  2. *
  3. {@link RocksMap.Mode#LOAD}
  4. *
  5. {@link RocksMap.Builder#shards(int)}
  6. *
*/ public abstract class RocksMap extends AbstractMap implements AutoCloseable { public static final class Builder { private final Configuration myConfiguration; private final File myDirectory; private ExecutorService myExecutor = null; private final Converter myKeyConverter; private Mode myMode = Mode.MIXED; private transient ProcessingService myProcessor = null; private final Converter myValueConverter; Builder(final Builder template, final Converter kConverter, final Converter vConverter) { super(); myDirectory = template.getDirectory(); myConfiguration = template.getConfiguration(); myKeyConverter = kConverter; myValueConverter = vConverter; } Builder(final File dir, final Converter kConverter, final Converter vConverter) { super(); myDirectory = dir; myConfiguration = new Configuration(); myKeyConverter = kConverter; myValueConverter = vConverter; } public RocksMap build() { if (myConfiguration.numberOfShards > 1) { return this.buildSharded(); } else { return this.buildSingle(); } } public Builder compression(final CompressionType type) { myConfiguration.compression = type; return this; } public Builder executor(final ExecutorService executor) { myExecutor = executor; myProcessor = null; return this; } public Builder key(final Converter kConverter) { Objects.requireNonNull(kConverter); return new Builder<>(this, kConverter, myValueConverter); } public Builder key(final Function toBytesFunction, final Function toInstanceFunction) { Objects.requireNonNull(toBytesFunction); Objects.requireNonNull(toInstanceFunction); return this.key(new ComposedConverter<>(toBytesFunction, toInstanceFunction)); } /** * When bulk loading key-value pairs are put in batches to the underlying {@link RocksDB} instance – * what's the max batch size */ public Builder maxBatchSize(final int value) { myConfiguration.maxBatchSize = value; return this; } /** * Default for RocksDB is -1 which means "infinite" – that can be problematic in some situations. */ public Builder maxOpenFiles(final int value) { myConfiguration.maxOpenFiles = value; return this; } public Builder mode(final Mode mode) { Objects.requireNonNull(mode); myMode = mode; return this; } /** * If using {@link Map#entrySet()}, {@link Map#forEach(BiConsumer)} or any other method that requires * iterating through the entire database, it is probably better to set this to false. */ public Builder pointLookup(final boolean flag) { myConfiguration.pointLookup = flag; return this; } /** * https://github.com/facebook/rocksdb/wiki/Block-Cache * * @param blockCacheSize MB */ public Builder pointLookup(final long blockCacheSize) { myConfiguration.pointLookup = true; myConfiguration.blockCacheSizeInMB = blockCacheSize; return this; } /** * For best performance one should not have too many threads writing to the same {@link RocksDB} * instance. With high level of concurreny it is better to have a set of {@link RocksDB} * instances/shards used in parallel. This is what you define here. */ public Builder shards(final int nbShards) { myConfiguration.numberOfShards = PowerOf2.adjustUp(nbShards); return this; } public Builder value(final Converter vConverter) { Objects.requireNonNull(vConverter); return new Builder<>(this, myKeyConverter, vConverter); } public Builder value(final Function toBytesFunction, final Function toInstanceFunction) { Objects.requireNonNull(toBytesFunction); Objects.requireNonNull(toInstanceFunction); return this.value(new ComposedConverter<>(toBytesFunction, toInstanceFunction)); } private RocksMap buildSharded() { int nbShards = myConfiguration.numberOfShards; ShardedFile shardsInfo = ShardedFile.of(new File(myDirectory, "shard"), nbShards); @SuppressWarnings("unchecked") SingleDB[] singles = (SingleDB[]) new SingleDB[nbShards]; for (int i = 0; i < nbShards; i++) { singles[i] = new SingleDB<>(shardsInfo.shard(i), myKeyConverter, myValueConverter, myMode, myConfiguration, this.getProcessor()); } return new ShardedDB<>(singles, this.getProcessor()); } private RocksMap buildSingle() { return new SingleDB<>(myDirectory, myKeyConverter, myValueConverter, myMode, myConfiguration, this.getProcessor()); } Configuration getConfiguration() { return myConfiguration; } File getDirectory() { return myDirectory; } Mode getMode() { return myMode; } ProcessingService getProcessor() { if (myProcessor == null) { if (myExecutor != null) { myProcessor = new ProcessingService(myExecutor); } else { myProcessor = ProcessingService.newInstance("RocksMap-" + myDirectory.getName()); } } return myProcessor; } } public interface Converter { byte[] toBytes(T instance); T toInstance(byte[] bytes); } public enum Mode { /** * Write-only, bulk loading *

* If you use this, then {@link RocksMap#switchMode(Mode)} before accessing data. * * @see https://github.com/facebook/rocksdb/wiki/RocksDB-FAQ */ LOAD(true, false), /** * Read-only */ LOOKUP(false, true), /** * Normal usage, mixed reading and writing */ MIXED(false, false); private final boolean myBulkLoad; private final boolean myReadOnly; Mode(final boolean bulk, final boolean readOnly) { myBulkLoad = bulk; myReadOnly = readOnly; } public boolean isBulkLoad() { return myBulkLoad; } public boolean isReadOnly() { return myReadOnly; } ReadOptions newReadOptions(final Configuration configuration) { return new ReadOptions(); } Options newRocksOptions(final Configuration configuration) { Options options = new Options(); if (configuration.pointLookup) { options = options.optimizeForPointLookup(configuration.blockCacheSizeInMB); } if (this.isBulkLoad()) { options = options.prepareForBulkLoad(); } options.setCreateIfMissing(true); if (configuration.maxOpenFiles > 0) { options.setMaxOpenFiles(configuration.maxOpenFiles); } if (configuration.compression != null) { options.setCompressionType(configuration.compression); } int parallelism = Math.min(Math.max(2, configuration.numberOfShards), Parallelism.CORES.getAsInt()); options.setMaxBackgroundJobs(parallelism); options.setMaxSubcompactions(parallelism); return options; } WriteOptions newWriteOptions(final Configuration configuration) { WriteOptions options = new WriteOptions(); options.setDisableWAL(true); options.setSync(false); return options; } } static final class ComposedConverter implements Converter { private final Function myToBytesFunction; private final Function myToInstanceFunction; ComposedConverter(final Function toBytesFunction, final Function toInstanceFunction) { super(); myToBytesFunction = toBytesFunction; myToInstanceFunction = toInstanceFunction; } @Override public byte[] toBytes(final T instance) { return myToBytesFunction.apply(instance); } @Override public T toInstance(final byte[] bytes) { return myToInstanceFunction.apply(bytes); } } static final class Configuration { long blockCacheSizeInMB = 64; CompressionType compression = null; int maxBatchSize = 2048; int maxOpenFiles = -1; int numberOfShards = 1; boolean pointLookup = true; } static final Converter STRING_CONVERTER = new Converter<>() { @Override public byte[] toBytes(final String instance) { return instance.getBytes(StandardCharsets.UTF_8); } @Override public String toInstance(final byte[] bytes) { return new String(bytes, StandardCharsets.UTF_8); } }; static { RocksDB.loadLibrary(); } /** * Returns a {@link RocksMap.Builder} instance to configure the {@link RocksMap}. The default type, for * both the keys and values, is {@link String}. You change the types by specifying the converters using * {@link Builder#key(Converter)} and {@link Builder#value(Converter)}. */ public static Builder newBuilder(final File rocksDir) { return new Builder<>(rocksDir, STRING_CONVERTER, RocksMap.STRING_CONVERTER); } private final ProcessingService myProcessor; RocksMap(final ProcessingService processor) { super(); myProcessor = processor; } public abstract void compact(); /** * Always return false. * * @see java.util.AbstractMap#isEmpty() */ @Override public final boolean isEmpty() { return false; } /** * Always returns {@link Integer#MAX_VALUE}. * * @see java.util.AbstractMap#size() */ @Override public final int size() { return Integer.MAX_VALUE; } /** * Switch usage mode. Will change various options. If necessary (it most likely is) the underlying * {@link RocksDB} instance will be closed and reopened. *

* In particular this is important when swiching to/from {@link Mode#LOAD}. */ public abstract void switchMode(Mode mode); final ExecutorService getExecutor() { return myProcessor.getExecutor(); } final void process(final List work, final Consumer processor) { myProcessor.process(work, processor); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy