Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report estimated disk space freed up by x-trie-log prune subcommand #6483

Merged
merged 15 commits into from
Feb 1, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public static class Unstable {

@CommandLine.Option(
hidden = true,
names = {BONSAI_LIMIT_TRIE_LOGS_ENABLED},
names = {BONSAI_LIMIT_TRIE_LOGS_ENABLED, "--Xbonsai-trie-log-pruning-enabled"},
description =
"Limit the number of trie logs that are retained. (default: ${DEFAULT-VALUE})")
private boolean bonsaiLimitTrieLogsEnabled = DEFAULT_BONSAI_LIMIT_TRIE_LOGS_ENABLED;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,53 @@
import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;

import org.bouncycastle.util.Arrays;
import org.rocksdb.ColumnFamilyDescriptor;
import org.rocksdb.ColumnFamilyHandle;
import org.rocksdb.Options;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** RocksDB Usage subcommand helper methods for formatting and printing. */
public class RocksDbUsageHelper {
private static final Logger LOG = LoggerFactory.getLogger(RocksDbUsageHelper.class);
/** RocksDB subcommand helper methods. */
public class RocksDbHelper {
private static final Logger LOG = LoggerFactory.getLogger(RocksDbHelper.class);

static void forEachColumnFamily(
final String dbPath, final BiConsumer<RocksDB, ColumnFamilyHandle> task) {
RocksDB.loadLibrary();
Options options = new Options();
options.setCreateIfMissing(true);

// Open the RocksDB database with multiple column families
List<byte[]> cfNames;
try {
cfNames = RocksDB.listColumnFamilies(options, dbPath);
} catch (RocksDBException e) {
throw new RuntimeException(e);
}
final List<ColumnFamilyHandle> cfHandles = new ArrayList<>();
final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>();
for (byte[] cfName : cfNames) {
cfDescriptors.add(new ColumnFamilyDescriptor(cfName));
}
try (final RocksDB rocksdb = RocksDB.openReadOnly(dbPath, cfDescriptors, cfHandles)) {
for (ColumnFamilyHandle cfHandle : cfHandles) {
task.accept(rocksdb, cfHandle);
}
} catch (RocksDBException e) {
throw new RuntimeException(e);
} finally {
for (ColumnFamilyHandle cfHandle : cfHandles) {
cfHandle.close();
}
}
}

static void printUsageForColumnFamily(
final RocksDB rocksdb, final ColumnFamilyHandle cfHandle, final PrintWriter out)
Expand Down Expand Up @@ -62,7 +98,7 @@ static void printUsageForColumnFamily(
}
}

private static String formatOutputSize(final long size) {
static String formatOutputSize(final long size) {
if (size > (1024 * 1024 * 1024)) {
long sizeInGiB = size / (1024 * 1024 * 1024);
return sizeInGiB + " GiB";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,7 @@
import org.hyperledger.besu.cli.util.VersionProvider;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import org.rocksdb.ColumnFamilyDescriptor;
import org.rocksdb.ColumnFamilyHandle;
import org.rocksdb.Options;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
import picocli.CommandLine;
import picocli.CommandLine.Command;
Expand Down Expand Up @@ -82,34 +76,17 @@ public void run() {
.concat("/")
.concat(DATABASE_PATH);

RocksDB.loadLibrary();
Options options = new Options();
options.setCreateIfMissing(true);

// Open the RocksDB database with multiple column families
List<byte[]> cfNames;
try {
cfNames = RocksDB.listColumnFamilies(options, dbPath);
} catch (RocksDBException e) {
throw new RuntimeException(e);
}
final List<ColumnFamilyHandle> cfHandles = new ArrayList<>();
final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>();
for (byte[] cfName : cfNames) {
cfDescriptors.add(new ColumnFamilyDescriptor(cfName));
}
RocksDbUsageHelper.printTableHeader(out);
try (final RocksDB rocksdb = RocksDB.openReadOnly(dbPath, cfDescriptors, cfHandles)) {
for (ColumnFamilyHandle cfHandle : cfHandles) {
RocksDbUsageHelper.printUsageForColumnFamily(rocksdb, cfHandle, out);
}
} catch (RocksDBException e) {
throw new RuntimeException(e);
} finally {
for (ColumnFamilyHandle cfHandle : cfHandles) {
cfHandle.close();
}
}
RocksDbHelper.printTableHeader(out);

RocksDbHelper.forEachColumnFamily(
dbPath,
(rocksdb, cfHandle) -> {
try {
RocksDbHelper.printUsageForColumnFamily(rocksdb, cfHandle, out);
} catch (RocksDBException e) {
throw new RuntimeException(e);
}
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,12 @@ public class TrieLogHelper {
private static final int ROCKSDB_MAX_INSERTS_PER_TRANSACTION = 1000;
private static final Logger LOG = LoggerFactory.getLogger(TrieLogHelper.class);

void prune(
boolean prune(
final DataStorageConfiguration config,
final BonsaiWorldStateKeyValueStorage rootWorldStateStorage,
final MutableBlockchain blockchain,
final Path dataDirectoryPath) {

final String batchFileNameBase =
dataDirectoryPath.resolve(DATABASE_PATH).resolve(TRIE_LOG_FILE).toString();

Expand All @@ -82,10 +83,14 @@ void prune(
lastBlockNumberToRetainTrieLogsFor,
rootWorldStateStorage,
layersToRetain)) {
return;
return false;
}

final long numberOfBatches = calculateNumberOfBatches(layersToRetain);
LOG.info(
"Starting pruning: retain {} trie logs, processing in {} batches...",
layersToRetain,
numberOfBatches);

processTrieLogBatches(
rootWorldStateStorage,
Expand All @@ -102,16 +107,19 @@ void prune(
.count();
if (countAfterPrune == layersToRetain) {
if (deleteFiles(batchFileNameBase, numberOfBatches)) {
LOG.info("Prune ran successfully. Enjoy some disk space back! \uD83D\uDE80");
return true;
} else {
throw new IllegalStateException(
"There was an error deleting the trie log backup files. Please ensure besu is working before deleting them manually.");
}
} else {
throw new IllegalStateException(
String.format(
"Remaining trie logs (%d) did not match %s (%d). Trie logs backup files have not been deleted, it is safe to rerun the subcommand.",
countAfterPrune, BONSAI_STORAGE_FORMAT_MAX_LAYERS_TO_LOAD, layersToRetain));
"Remaining trie logs (%d) did not match %s (%d). Trie logs backup files (in %s) have not been deleted, it is safe to rerun the subcommand.",
countAfterPrune,
BONSAI_STORAGE_FORMAT_MAX_LAYERS_TO_LOAD,
layersToRetain,
batchFileNameBase));
}
}

Expand All @@ -131,7 +139,7 @@ private void processTrieLogBatches(
final List<Hash> trieLogKeys =
getTrieLogKeysForBlocks(blockchain, firstBlockOfBatch, lastBlockOfBatch);

LOG.info("Saving trie logs to retain in file (batch {})...", batchNumber);
LOG.info("Saving trie logs to retain in file {} (batch {})...", batchFileName, batchNumber);
saveTrieLogBatches(batchFileName, rootWorldStateStorage, trieLogKeys);
}

Expand Down Expand Up @@ -319,7 +327,7 @@ private void saveTrieLogsInFile(

File file = new File(batchFileName);
if (file.exists()) {
LOG.error("File already exists, skipping file creation");
LOG.warn("File already exists {}, skipping file creation", batchFileName);
return;
}

Expand Down Expand Up @@ -354,7 +362,7 @@ private void saveTrieLogsAsRlpInFile(
final String batchFileName) {
File file = new File(batchFileName);
if (file.exists()) {
LOG.error("File already exists, skipping file creation");
LOG.warn("File already exists {}, skipping file creation", batchFileName);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.hyperledger.besu.cli.subcommands.storage.RocksDbHelper.formatOutputSize;
import static org.hyperledger.besu.controller.BesuController.DATABASE_PATH;
import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_LOG_STORAGE;

import org.hyperledger.besu.cli.util.VersionProvider;
import org.hyperledger.besu.controller.BesuController;
Expand All @@ -31,10 +34,14 @@
import java.io.PrintWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator;
import org.rocksdb.RocksDBException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;
import picocli.CommandLine.Command;
Expand All @@ -54,6 +61,8 @@
})
public class TrieLogSubCommand implements Runnable {

private static final Logger LOG = LoggerFactory.getLogger(TrieLogSubCommand.class);

@SuppressWarnings("UnusedVariable")
@ParentCommand
private static StorageSubCommand parentCommand;
Expand Down Expand Up @@ -124,12 +133,67 @@ public void run() {
final Path dataDirectoryPath =
Paths.get(
TrieLogSubCommand.parentCommand.parentCommand.dataDir().toAbsolutePath().toString());

LOG.info("Estimating trie logs size before pruning...");
jframe marked this conversation as resolved.
Show resolved Hide resolved
long sizeBefore = estimatedSizeOfTrieLogs();
LOG.info("Estimated trie logs size before pruning: {}", formatOutputSize(sizeBefore));

final TrieLogHelper trieLogHelper = new TrieLogHelper();
trieLogHelper.prune(
context.config(),
context.rootWorldStateStorage(),
context.blockchain(),
dataDirectoryPath);
boolean success =
trieLogHelper.prune(
context.config(),
context.rootWorldStateStorage(),
context.blockchain(),
dataDirectoryPath);

if (success) {
LOG.info("Finished pruning. Re-estimating trie logs size...");
jframe marked this conversation as resolved.
Show resolved Hide resolved
final long sizeAfter = estimatedSizeOfTrieLogs();
LOG.info(
"Estimated trie logs size after pruning: {} (0 B estimate is normal when using default settings)",
formatOutputSize(sizeAfter));
long estimatedSaving = sizeBefore - sizeAfter;
LOG.info(
"Prune ran successfully. We estimate you freed up {}! \uD83D\uDE80",
formatOutputSize(estimatedSaving));
spec.commandLine()
.getOut()
.printf(
"Prune ran successfully. We estimate you freed up %s! \uD83D\uDE80\n",
formatOutputSize(estimatedSaving));
}
}

private long estimatedSizeOfTrieLogs() {
final String dbPath =
TrieLogSubCommand.parentCommand
.parentCommand
.dataDir()
.toString()
.concat("/")
.concat(DATABASE_PATH);

AtomicLong estimatedSaving = new AtomicLong(0L);
try {
RocksDbHelper.forEachColumnFamily(
dbPath,
(rocksdb, cfHandle) -> {
try {
if (Arrays.equals(cfHandle.getName(), TRIE_LOG_STORAGE.getId())) {
estimatedSaving.set(
Long.parseLong(
rocksdb.getProperty(cfHandle, "rocksdb.estimate-live-data-size")));
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
}
} catch (RocksDBException | NumberFormatException e) {
throw new RuntimeException(e);
}
});
} catch (Exception e) {
LOG.warn("Error while estimating trie log size, returning 0 for estimate", e);
return 0L;
}

return estimatedSaving.get();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,8 @@ public void mismatchInPrunedTrieLogCountShouldNotDeleteFiles(final @TempDir Path
nonValidatingTrieLogHelper.prune(
dataStorageConfiguration, inMemoryWorldStateSpy, blockchain, dataDir))
.isInstanceOf(RuntimeException.class)
.hasMessage(
"Remaining trie logs (0) did not match --bonsai-historical-block-limit (3). Trie logs backup files have not been deleted, it is safe to rerun the subcommand.");
.hasMessageContaining(
"Remaining trie logs (0) did not match --bonsai-historical-block-limit (3)");
}

@Test
Expand Down
Loading