From 9382a4c7135b070b9d0ae3d8b56bf2e49c5fa115 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Sun, 1 Feb 2026 18:38:00 -0800 Subject: [PATCH] fix(prune): use batched pruning loop with edge feature to prevent OOM (#21649) Co-authored-by: Amp --- crates/cli/commands/src/prune.rs | 42 +++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/crates/cli/commands/src/prune.rs b/crates/cli/commands/src/prune.rs index b9085e7a8a..3f381c6989 100644 --- a/crates/cli/commands/src/prune.rs +++ b/crates/cli/commands/src/prune.rs @@ -1,4 +1,4 @@ -//! Command that runs pruning without any limits. +//! Command that runs pruning. use crate::common::{AccessRights, CliNodeTypes, EnvironmentArgs}; use clap::Parser; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; @@ -16,7 +16,7 @@ use reth_static_file::StaticFileProducer; use std::sync::Arc; use tracing::info; -/// Prunes according to the configuration without any limits +/// Prunes according to the configuration #[derive(Debug, Parser)] pub struct PruneCommand { #[command(flatten)] @@ -69,13 +69,43 @@ impl> PruneComma // Delete data which has been copied to static files. if let Some(prune_tip) = lowest_static_file_height { info!(target: "reth::cli", ?prune_tip, ?config, "Pruning data from database..."); - // Run the pruner according to the configuration, and don't enforce any limits on it + + // Use batched pruning with a limit to bound memory, running in a loop until complete. + const DELETE_LIMIT: usize = 200_000; let mut pruner = PrunerBuilder::new(config) - .delete_limit(usize::MAX) + .delete_limit(DELETE_LIMIT) .build_with_provider_factory(provider_factory); - pruner.run(prune_tip)?; - info!(target: "reth::cli", "Pruned data from database"); + let mut total_pruned = 0usize; + loop { + let output = pruner.run(prune_tip)?; + let batch_pruned: usize = output.segments.iter().map(|(_, seg)| seg.pruned).sum(); + total_pruned = total_pruned.saturating_add(batch_pruned); + + // Check if all segments are finished (not just the overall progress, + // since the pruner sets overall progress from the last segment only) + let all_segments_finished = + output.segments.iter().all(|(_, seg)| seg.progress.is_finished()); + + if all_segments_finished { + break; + } + + if batch_pruned == 0 { + return Err(eyre::eyre!( + "pruner made no progress but reported more data remaining; \ + aborting to prevent infinite loop" + )); + } + + info!( + target: "reth::cli", + batch_pruned, + total_pruned, + "Pruning batch complete, continuing..." + ); + } + info!(target: "reth::cli", total_pruned, "Pruned data from database"); } Ok(())