forked from Qortal/qortal
Browse Source
Initially just deleting old and unused AT states, to get this table under control. I have had to delete them individually as the table can't handle complex queries due to its size. Nodes in pruning mode will be unable to serve older blocks to peers.block-archive
CalDescent
3 years ago
8 changed files with 306 additions and 1 deletions
@ -0,0 +1,95 @@ |
|||||||
|
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 AtStatesPruner implements Runnable { |
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(AtStatesPruner.class); |
||||||
|
|
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
Thread.currentThread().setName("AT States pruner"); |
||||||
|
|
||||||
|
if (!Settings.getInstance().isPruningEnabled()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) { |
||||||
|
int pruneStartHeight = repository.getATRepository().getAtPruneHeight(); |
||||||
|
|
||||||
|
// repository.getATRepository().prepareForAtStatePruning();
|
||||||
|
// repository.saveChanges();
|
||||||
|
|
||||||
|
while (!Controller.isStopping()) { |
||||||
|
repository.discardChanges(); |
||||||
|
|
||||||
|
Thread.sleep(Settings.getInstance().getAtStatesPruneInterval()); |
||||||
|
|
||||||
|
if (PruneManager.getInstance().getBuiltLatestATStates() == false) { |
||||||
|
// Wait for latest AT states table to be built first
|
||||||
|
// This has a dependency on the AtStatesTrimmer running,
|
||||||
|
// which should be okay, given that it isn't something
|
||||||
|
// is disabled in normal operation.
|
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
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; |
||||||
|
|
||||||
|
long currentPrunableTimestamp = NTP.getTime() - Settings.getInstance().getAtStatesMaxLifetime(); |
||||||
|
// We want to keep AT states near the tip of our copy of blockchain so we can process/orphan nearby blocks
|
||||||
|
long chainPrunableTimestamp = chainTip.getTimestamp() - Settings.getInstance().getAtStatesMaxLifetime(); |
||||||
|
|
||||||
|
long upperPrunableTimestamp = Math.min(currentPrunableTimestamp, chainPrunableTimestamp); |
||||||
|
int upperPrunableHeight = repository.getBlockRepository().getHeightFromTimestamp(upperPrunableTimestamp); |
||||||
|
|
||||||
|
int upperBatchHeight = pruneStartHeight + Settings.getInstance().getAtStatesPruneBatchSize(); |
||||||
|
int upperPruneHeight = Math.min(upperBatchHeight, upperPrunableHeight); |
||||||
|
|
||||||
|
if (pruneStartHeight >= upperPruneHeight) |
||||||
|
continue; |
||||||
|
|
||||||
|
LOGGER.debug(String.format("Pruning AT states between blocks %d and %d...", pruneStartHeight, upperPruneHeight)); |
||||||
|
|
||||||
|
int numAtStatesPruned = repository.getATRepository().pruneAtStates(pruneStartHeight, upperPruneHeight); |
||||||
|
repository.saveChanges(); |
||||||
|
|
||||||
|
if (numAtStatesPruned > 0) { |
||||||
|
final int finalPruneStartHeight = pruneStartHeight; |
||||||
|
LOGGER.debug(() -> String.format("Pruned %d AT state%s between blocks %d and %d", |
||||||
|
numAtStatesPruned, (numAtStatesPruned != 1 ? "s" : ""), |
||||||
|
finalPruneStartHeight, upperPruneHeight)); |
||||||
|
} else { |
||||||
|
// Can we move onto next batch?
|
||||||
|
if (upperPrunableHeight > upperBatchHeight) { |
||||||
|
pruneStartHeight = upperBatchHeight; |
||||||
|
repository.getATRepository().setAtPruneHeight(pruneStartHeight); |
||||||
|
repository.getATRepository().prepareForAtStatePruning(); |
||||||
|
repository.saveChanges(); |
||||||
|
|
||||||
|
final int finalPruneStartHeight = pruneStartHeight; |
||||||
|
LOGGER.debug(() -> String.format("Bumping AT state base prune height to %d", finalPruneStartHeight)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (DataException e) { |
||||||
|
LOGGER.warn(String.format("Repository issue trying to prune AT states: %s", e.getMessage())); |
||||||
|
} catch (InterruptedException e) { |
||||||
|
// Time to exit
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,60 @@ |
|||||||
|
package org.qortal.controller.pruning; |
||||||
|
|
||||||
|
import org.qortal.controller.Controller; |
||||||
|
|
||||||
|
import org.qortal.data.block.BlockData; |
||||||
|
import org.qortal.repository.DataException; |
||||||
|
import org.qortal.repository.Repository; |
||||||
|
import org.qortal.settings.Settings; |
||||||
|
import org.qortal.utils.DaemonThreadFactory; |
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService; |
||||||
|
import java.util.concurrent.Executors; |
||||||
|
|
||||||
|
public class PruneManager { |
||||||
|
|
||||||
|
private static PruneManager instance; |
||||||
|
|
||||||
|
private boolean pruningEnabled = Settings.getInstance().isPruningEnabled(); |
||||||
|
private int pruneBlockLimit = Settings.getInstance().getPruneBlockLimit(); |
||||||
|
private boolean builtLatestATStates = false; |
||||||
|
|
||||||
|
private PruneManager() { |
||||||
|
// Start individual pruning processes
|
||||||
|
ExecutorService pruneExecutor = Executors.newCachedThreadPool(new DaemonThreadFactory()); |
||||||
|
pruneExecutor.execute(new AtStatesPruner()); |
||||||
|
} |
||||||
|
|
||||||
|
public static synchronized PruneManager getInstance() { |
||||||
|
if (instance == null) |
||||||
|
instance = new PruneManager(); |
||||||
|
|
||||||
|
return instance; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isBlockPruned(int height, Repository repository) throws DataException { |
||||||
|
if (!this.pruningEnabled) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
BlockData chainTip = Controller.getInstance().getChainTip(); |
||||||
|
if (chainTip == null) { |
||||||
|
throw new DataException("Unable to determine chain tip when checking if a block is pruned"); |
||||||
|
} |
||||||
|
|
||||||
|
final int ourLatestHeight = chainTip.getHeight(); |
||||||
|
final int latestUnprunedHeight = ourLatestHeight - this.pruneBlockLimit; |
||||||
|
|
||||||
|
return (height < latestUnprunedHeight); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public void setBuiltLatestATStates(boolean builtLatestATStates) { |
||||||
|
this.builtLatestATStates = builtLatestATStates; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean getBuiltLatestATStates() { |
||||||
|
return this.builtLatestATStates; |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue