diff --git a/CHANGELOG.md b/CHANGELOG.md index f906084db59..9f0f3d40763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ ### Bug fixes - Fix serialization of state overrides when `movePrecompileToAddress` is present [#8204](https://github.com/hyperledger/besu/pull/8024) +- Revise the approach for setting level_compaction_dynamic_level_bytes RocksDB configuration option [#8037](https://github.com/hyperledger/besu/pull/8037) ## 24.12.2 Hotfix diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticRocksDBColumnarKeyValueStorage.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticRocksDBColumnarKeyValueStorage.java index d2eaa350239..1e877c1738a 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticRocksDBColumnarKeyValueStorage.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticRocksDBColumnarKeyValueStorage.java @@ -57,7 +57,7 @@ public OptimisticRocksDBColumnarKeyValueStorage( try { db = - OptimisticTransactionDB.open( + RocksDBOpener.openOptimisticTransactionDBWithWarning( options, configuration.getDatabaseDir().toString(), columnDescriptors, columnHandles); initMetrics(); initColumnHandles(); diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java index 0bf107ddb0e..aa508f085c1 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java @@ -51,10 +51,12 @@ import org.rocksdb.ColumnFamilyHandle; import org.rocksdb.ColumnFamilyOptions; import org.rocksdb.CompressionType; +import org.rocksdb.ConfigOptions; import org.rocksdb.DBOptions; import org.rocksdb.Env; import org.rocksdb.LRUCache; import org.rocksdb.Options; +import org.rocksdb.OptionsUtil; import org.rocksdb.ReadOptions; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; @@ -76,9 +78,6 @@ public abstract class RocksDBColumnarKeyValueStorage implements SegmentedKeyValu /** RocksDb blockcache size when using the high spec option */ protected static final long ROCKSDB_BLOCKCACHE_SIZE_HIGH_SPEC = 1_073_741_824L; - /** RocksDb memtable size when using the high spec option */ - protected static final long ROCKSDB_MEMTABLE_SIZE_HIGH_SPEC = 536_870_912L; - /** Max total size of all WAL file, after which a flush is triggered */ protected static final long WAL_MAX_TOTAL_SIZE = 1_073_741_824L; @@ -186,15 +185,47 @@ public RocksDBColumnarKeyValueStorage( */ private ColumnFamilyDescriptor createColumnDescriptor( final SegmentIdentifier segment, final RocksDBConfiguration configuration) { - + boolean dynamicLevelBytes = true; + try { + ConfigOptions configOptions = new ConfigOptions(); + DBOptions dbOptions = new DBOptions(); + List cfDescriptors = new ArrayList<>(); + + String latestOptionsFileName = + OptionsUtil.getLatestOptionsFileName( + configuration.getDatabaseDir().toString(), Env.getDefault()); + LOG.trace("Latest OPTIONS file detected: " + latestOptionsFileName); + + String optionsFilePath = + configuration.getDatabaseDir().toString() + "/" + latestOptionsFileName; + OptionsUtil.loadOptionsFromFile(configOptions, optionsFilePath, dbOptions, cfDescriptors); + + LOG.trace("RocksDB options loaded successfully from: " + optionsFilePath); + + if (!cfDescriptors.isEmpty()) { + Optional matchedCfOptions = Optional.empty(); + for (ColumnFamilyDescriptor descriptor : cfDescriptors) { + if (Arrays.equals(descriptor.getName(), segment.getId())) { + matchedCfOptions = Optional.of(descriptor.getOptions()); + break; + } + } + if (matchedCfOptions.isPresent()) { + dynamicLevelBytes = matchedCfOptions.get().levelCompactionDynamicLevelBytes(); + LOG.trace("dynamicLevelBytes is set to an existing value : " + dynamicLevelBytes); + } + } + } catch (RocksDBException ex) { + // Options file is not found in the database + } BlockBasedTableConfig basedTableConfig = createBlockBasedTableConfig(segment, configuration); final var options = new ColumnFamilyOptions() .setTtl(0) .setCompressionType(CompressionType.LZ4_COMPRESSION) - .setTableFormatConfig(basedTableConfig); - + .setTableFormatConfig(basedTableConfig) + .setLevelCompactionDynamicLevelBytes(dynamicLevelBytes); if (segment.containsStaticData()) { options .setEnableBlobFiles(true) diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBOpener.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBOpener.java new file mode 100644 index 00000000000..b8c59d638d7 --- /dev/null +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBOpener.java @@ -0,0 +1,172 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.storage.rocksdb.segmented; + +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.rocksdb.ColumnFamilyDescriptor; +import org.rocksdb.ColumnFamilyHandle; +import org.rocksdb.DBOptions; +import org.rocksdb.OptimisticTransactionDB; +import org.rocksdb.RocksDBException; +import org.rocksdb.TransactionDB; +import org.rocksdb.TransactionDBOptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility class for opening RocksDB instances with a warning mechanism. + * + *

This class provides methods to open RocksDB databases ({@link OptimisticTransactionDB} and + * {@link TransactionDB}) while monitoring the operation's duration. If the database takes longer + * than a predefined delay (default: 60 seconds) to open, a warning message is logged. This warning + * helps identify potential delays caused by factors such as RocksDB compaction. + */ +public class RocksDBOpener { + + /** + * Default delay (in seconds) after which a warning is logged if the database opening operation + * has not completed. + */ + public static final int DEFAULT_DELAY = 60; + + /** + * Warning message logged when the database opening operation takes longer than the predefined + * delay. + */ + public static final String WARN_MESSAGE = + "Opening RocksDB database is taking longer than 60 seconds... " + + "This may be due to prolonged RocksDB compaction. Please wait until the end of the compaction. " + + "No action is needed from the user."; + + private static final Logger LOG = LoggerFactory.getLogger(RocksDBOpener.class); + + /** + * Default constructor. + * + *

This is a utility class and is not meant to be instantiated directly. + */ + private RocksDBOpener() { + // Default constructor for RocksDBOpener + } + + /** + * Opens an {@link OptimisticTransactionDB} instance with a warning mechanism. + * + *

If the database opening operation takes longer than {@link #DEFAULT_DELAY} seconds, a + * warning message is logged indicating a possible delay caused by compaction. + * + * @param options the {@link DBOptions} instance used to configure the database. + * @param dbPath the file path to the RocksDB database directory. + * @param columnDescriptors a list of {@link ColumnFamilyDescriptor} objects for the column + * families to open. + * @param columnHandles a list of {@link ColumnFamilyHandle} objects to be populated with the + * opened column families. + * @return an instance of {@link OptimisticTransactionDB}. + * @throws RocksDBException if the database cannot be opened. + */ + public static OptimisticTransactionDB openOptimisticTransactionDBWithWarning( + final DBOptions options, + final String dbPath, + final List columnDescriptors, + final List columnHandles) + throws RocksDBException { + return openDBWithWarning( + () -> OptimisticTransactionDB.open(options, dbPath, columnDescriptors, columnHandles)); + } + + /** + * Opens a {@link TransactionDB} instance with a warning mechanism. + * + *

If the database opening operation takes longer than {@link #DEFAULT_DELAY} seconds, a + * warning message is logged indicating a possible delay caused by compaction. + * + * @param options the {@link DBOptions} instance used to configure the database. + * @param transactionDBOptions the {@link TransactionDBOptions} for transaction-specific + * configuration. + * @param dbPath the file path to the RocksDB database directory. + * @param columnDescriptors a list of {@link ColumnFamilyDescriptor} objects for the column + * families to open. + * @param columnHandles a list of {@link ColumnFamilyHandle} objects to be populated with the + * opened column families. + * @return an instance of {@link TransactionDB}. + * @throws RocksDBException if the database cannot be opened. + */ + public static TransactionDB openTransactionDBWithWarning( + final DBOptions options, + final TransactionDBOptions transactionDBOptions, + final String dbPath, + final List columnDescriptors, + final List columnHandles) + throws RocksDBException { + return openDBWithWarning( + () -> + TransactionDB.open( + options, transactionDBOptions, dbPath, columnDescriptors, columnHandles)); + } + + /** + * A generic method to open a RocksDB database with a warning mechanism. + * + *

If the operation takes longer than {@link #DEFAULT_DELAY} seconds, a warning message is + * logged. + * + * @param dbOperation a lambda or method reference representing the database opening logic. + * @param the type of the database being opened (e.g., {@link OptimisticTransactionDB} or + * {@link TransactionDB}). + * @return an instance of the database being opened. + * @throws RocksDBException if the database cannot be opened. + */ + private static T openDBWithWarning(final DBOperation dbOperation) throws RocksDBException { + AtomicBoolean operationCompleted = new AtomicBoolean(false); + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + scheduler.schedule( + () -> { + if (!operationCompleted.get()) { + LOG.warn(WARN_MESSAGE); + } + }, + DEFAULT_DELAY, + TimeUnit.SECONDS); + + try { + T db = dbOperation.open(); + operationCompleted.set(true); + return db; + } finally { + scheduler.shutdown(); + } + } + + /** + * Functional interface representing a database opening operation. + * + * @param the type of the database being opened. + */ + @FunctionalInterface + private interface DBOperation { + /** + * Opens the database. + * + * @return the opened database instance. + * @throws RocksDBException if an error occurs while opening the database. + */ + T open() throws RocksDBException; + } +} diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorage.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorage.java index 60879b658e6..035adbfa589 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorage.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorage.java @@ -56,7 +56,7 @@ public TransactionDBRocksDBColumnarKeyValueStorage( try { db = - TransactionDB.open( + RocksDBOpener.openTransactionDBWithWarning( options, txOptions, configuration.getDatabaseDir().toString(),