forked from Qortal/qortal
Prune all blocks up until the blockPruneLimit
By default, this leaves only the last 1450 blocks in the database. Only applies when pruning mode is enabled.
This commit is contained in:
parent
3400e36ac4
commit
1b4c75a76e
86
src/main/java/org/qortal/controller/pruning/BlockPruner.java
Normal file
86
src/main/java/org/qortal/controller/pruning/BlockPruner.java
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package org.qortal.controller.pruning;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.qortal.controller.Controller;
|
||||||
|
import org.qortal.data.block.BlockData;
|
||||||
|
import org.qortal.repository.DataException;
|
||||||
|
import org.qortal.repository.Repository;
|
||||||
|
import org.qortal.repository.RepositoryManager;
|
||||||
|
import org.qortal.settings.Settings;
|
||||||
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
|
public class BlockPruner implements Runnable {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(BlockPruner.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Thread.currentThread().setName("Block pruner");
|
||||||
|
|
||||||
|
if (!Settings.getInstance().isPruningEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
int pruneStartHeight = repository.getBlockRepository().getBlockPruneHeight();
|
||||||
|
|
||||||
|
while (!Controller.isStopping()) {
|
||||||
|
repository.discardChanges();
|
||||||
|
|
||||||
|
Thread.sleep(Settings.getInstance().getBlockPruneInterval());
|
||||||
|
|
||||||
|
BlockData chainTip = Controller.getInstance().getChainTip();
|
||||||
|
if (chainTip == null || NTP.getTime() == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Don't even attempt if we're mid-sync as our repository requests will be delayed for ages
|
||||||
|
if (Controller.getInstance().isSynchronizing())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Prune all blocks up until our latest minus pruneBlockLimit
|
||||||
|
final int ourLatestHeight = chainTip.getHeight();
|
||||||
|
final int upperPrunableHeight = ourLatestHeight - Settings.getInstance().getPruneBlockLimit();
|
||||||
|
|
||||||
|
int upperBatchHeight = pruneStartHeight + Settings.getInstance().getBlockPruneBatchSize();
|
||||||
|
int upperPruneHeight = Math.min(upperBatchHeight, upperPrunableHeight);
|
||||||
|
|
||||||
|
if (pruneStartHeight >= upperPruneHeight) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debug(String.format("Pruning blocks between %d and %d...", pruneStartHeight, upperPruneHeight));
|
||||||
|
|
||||||
|
int numBlocksPruned = repository.getBlockRepository().pruneBlocks(pruneStartHeight, upperPruneHeight);
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
if (numBlocksPruned > 0) {
|
||||||
|
final int finalPruneStartHeight = pruneStartHeight;
|
||||||
|
LOGGER.debug(() -> String.format("Pruned %d block%s between %d and %d",
|
||||||
|
numBlocksPruned, (numBlocksPruned != 1 ? "s" : ""),
|
||||||
|
finalPruneStartHeight, upperPruneHeight));
|
||||||
|
} else {
|
||||||
|
// Can we move onto next batch?
|
||||||
|
if (upperPrunableHeight > upperBatchHeight) {
|
||||||
|
pruneStartHeight = upperBatchHeight;
|
||||||
|
repository.getBlockRepository().setBlockPruneHeight(pruneStartHeight);
|
||||||
|
repository.saveChanges();
|
||||||
|
|
||||||
|
final int finalPruneStartHeight = pruneStartHeight;
|
||||||
|
LOGGER.debug(() -> String.format("Bumping block base prune height to %d", finalPruneStartHeight));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We've pruned up to the upper prunable height
|
||||||
|
// Back off for a while to save CPU for syncing
|
||||||
|
Thread.sleep(10*60*1000L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (DataException e) {
|
||||||
|
LOGGER.warn(String.format("Repository issue trying to prune blocks: %s", e.getMessage()));
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Time to exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -23,6 +23,7 @@ public class PruneManager {
|
|||||||
// Start individual pruning processes
|
// Start individual pruning processes
|
||||||
ExecutorService pruneExecutor = Executors.newCachedThreadPool(new DaemonThreadFactory());
|
ExecutorService pruneExecutor = Executors.newCachedThreadPool(new DaemonThreadFactory());
|
||||||
pruneExecutor.execute(new AtStatesPruner());
|
pruneExecutor.execute(new AtStatesPruner());
|
||||||
|
pruneExecutor.execute(new BlockPruner());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized PruneManager getInstance() {
|
public static synchronized PruneManager getInstance() {
|
||||||
|
@ -166,6 +166,20 @@ public interface BlockRepository {
|
|||||||
*/
|
*/
|
||||||
public BlockData getDetachedBlockSignature(int startHeight) throws DataException;
|
public BlockData getDetachedBlockSignature(int startHeight) throws DataException;
|
||||||
|
|
||||||
|
|
||||||
|
/** Returns height of first prunable block. */
|
||||||
|
public int getBlockPruneHeight() throws DataException;
|
||||||
|
|
||||||
|
/** Sets new base height for block pruning.
|
||||||
|
* <p>
|
||||||
|
* NOTE: performs implicit <tt>repository.saveChanges()</tt>.
|
||||||
|
*/
|
||||||
|
public void setBlockPruneHeight(int pruneHeight) throws DataException;
|
||||||
|
|
||||||
|
/** Prunes full block data between passed heights. Returns number of pruned rows. */
|
||||||
|
public int pruneBlocks(int minHeight, int maxHeight) throws DataException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves block into repository.
|
* Saves block into repository.
|
||||||
*
|
*
|
||||||
|
@ -509,6 +509,53 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBlockPruneHeight() throws DataException {
|
||||||
|
String sql = "SELECT block_prune_height FROM DatabaseInfo";
|
||||||
|
|
||||||
|
try (ResultSet resultSet = this.repository.checkedExecute(sql)) {
|
||||||
|
if (resultSet == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return resultSet.getInt(1);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to fetch block prune height from repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlockPruneHeight(int pruneHeight) throws DataException {
|
||||||
|
// trimHeightsLock is to prevent concurrent update on DatabaseInfo
|
||||||
|
// that could result in "transaction rollback: serialization failure"
|
||||||
|
synchronized (this.repository.trimHeightsLock) {
|
||||||
|
String updateSql = "UPDATE DatabaseInfo SET block_prune_height = ?";
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.repository.executeCheckedUpdate(updateSql, pruneHeight);
|
||||||
|
this.repository.saveChanges();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
repository.examineException(e);
|
||||||
|
throw new DataException("Unable to set block prune height in repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int pruneBlocks(int minHeight, int maxHeight) throws DataException {
|
||||||
|
// Don't prune the genesis block
|
||||||
|
if (minHeight <= 1) {
|
||||||
|
minHeight = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return this.repository.delete("Blocks", "height BETWEEN ? AND ?", minHeight, maxHeight);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to prune blocks from repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BlockData getDetachedBlockSignature(int startHeight) throws DataException {
|
public BlockData getDetachedBlockSignature(int startHeight) throws DataException {
|
||||||
String sql = "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks "
|
String sql = "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks "
|
||||||
|
@ -870,6 +870,7 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
case 35:
|
case 35:
|
||||||
// Support for pruning
|
// Support for pruning
|
||||||
stmt.execute("ALTER TABLE DatabaseInfo ADD AT_prune_height INT NOT NULL DEFAULT 0");
|
stmt.execute("ALTER TABLE DatabaseInfo ADD AT_prune_height INT NOT NULL DEFAULT 0");
|
||||||
|
stmt.execute("ALTER TABLE DatabaseInfo ADD block_prune_height INT NOT NULL DEFAULT 0");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -106,24 +106,32 @@ public class Settings {
|
|||||||
/** Max number of AT states to trim in one go. */
|
/** Max number of AT states to trim in one go. */
|
||||||
private int atStatesTrimLimit = 4000; // records
|
private int atStatesTrimLimit = 4000; // records
|
||||||
|
|
||||||
/** Whether we should prune old data to reduce database size
|
|
||||||
* This prevents the node from being able to serve older blocks */
|
|
||||||
private boolean pruningEnabled = false;
|
|
||||||
/** The amount of recent blocks we should keep when pruning */
|
|
||||||
private int pruneBlockLimit = 1440;
|
|
||||||
|
|
||||||
/** How often to attempt AT state pruning (ms). */
|
|
||||||
private long atStatesPruneInterval = 3219L; // milliseconds
|
|
||||||
/** Block height range to scan for trimmable AT states.<br>
|
|
||||||
* This has a significant effect on execution time. */
|
|
||||||
private int atStatesPruneBatchSize = 10; // blocks
|
|
||||||
|
|
||||||
/** How often to attempt online accounts signatures trimming (ms). */
|
/** How often to attempt online accounts signatures trimming (ms). */
|
||||||
private long onlineSignaturesTrimInterval = 9876L; // milliseconds
|
private long onlineSignaturesTrimInterval = 9876L; // milliseconds
|
||||||
/** Block height range to scan for trimmable online accounts signatures.<br>
|
/** Block height range to scan for trimmable online accounts signatures.<br>
|
||||||
* This has a significant effect on execution time. */
|
* This has a significant effect on execution time. */
|
||||||
private int onlineSignaturesTrimBatchSize = 100; // blocks
|
private int onlineSignaturesTrimBatchSize = 100; // blocks
|
||||||
|
|
||||||
|
|
||||||
|
/** Whether we should prune old data to reduce database size
|
||||||
|
* This prevents the node from being able to serve older blocks */
|
||||||
|
private boolean pruningEnabled = false;
|
||||||
|
/** The amount of recent blocks we should keep when pruning */
|
||||||
|
private int pruneBlockLimit = 1450;
|
||||||
|
|
||||||
|
/** How often to attempt AT state pruning (ms). */
|
||||||
|
private long atStatesPruneInterval = 3219L; // milliseconds
|
||||||
|
/** Block height range to scan for prunable AT states.<br>
|
||||||
|
* This has a significant effect on execution time. */
|
||||||
|
private int atStatesPruneBatchSize = 10; // blocks
|
||||||
|
|
||||||
|
/** How often to attempt block pruning (ms). */
|
||||||
|
private long blockPruneInterval = 3219L; // milliseconds
|
||||||
|
/** Block height range to scan for prunable blocks.<br>
|
||||||
|
* This has a significant effect on execution time. */
|
||||||
|
private int blockPruneBatchSize = 10000; // blocks
|
||||||
|
|
||||||
|
|
||||||
// Peer-to-peer related
|
// Peer-to-peer related
|
||||||
private boolean isTestNet = false;
|
private boolean isTestNet = false;
|
||||||
/** Port number for inbound peer-to-peer connections. */
|
/** Port number for inbound peer-to-peer connections. */
|
||||||
@ -540,6 +548,15 @@ public class Settings {
|
|||||||
return this.atStatesTrimLimit;
|
return this.atStatesTrimLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getOnlineSignaturesTrimInterval() {
|
||||||
|
return this.onlineSignaturesTrimInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOnlineSignaturesTrimBatchSize() {
|
||||||
|
return this.onlineSignaturesTrimBatchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean isPruningEnabled() {
|
public boolean isPruningEnabled() {
|
||||||
return this.pruningEnabled;
|
return this.pruningEnabled;
|
||||||
}
|
}
|
||||||
@ -556,12 +573,12 @@ public class Settings {
|
|||||||
return this.atStatesPruneBatchSize;
|
return this.atStatesPruneBatchSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getOnlineSignaturesTrimInterval() {
|
public long getBlockPruneInterval() {
|
||||||
return this.onlineSignaturesTrimInterval;
|
return this.blockPruneInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getOnlineSignaturesTrimBatchSize() {
|
public int getBlockPruneBatchSize() {
|
||||||
return this.onlineSignaturesTrimBatchSize;
|
return this.blockPruneBatchSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user