Skip to content

Commit 6f442f2

Browse files
Improve database compaction and prune-states (#5142)
* Fix no-op state prune check * Compact freezer DB after pruning * Refine DB compaction * Add blobs-db options to inspect/compact * Better key size * Fix compaction end key
1 parent e470596 commit 6f442f2

File tree

5 files changed

+127
-41
lines changed

5 files changed

+127
-41
lines changed

beacon_node/store/src/hot_cold_store.rs

+3
Original file line numberDiff line numberDiff line change
@@ -2376,6 +2376,9 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
23762376
self.cold_db.do_atomically(cold_ops)?;
23772377
}
23782378

2379+
// In order to reclaim space, we need to compact the freezer DB as well.
2380+
self.cold_db.compact()?;
2381+
23792382
Ok(())
23802383
}
23812384
}

beacon_node/store/src/leveldb_store.rs

+9-19
Original file line numberDiff line numberDiff line change
@@ -154,25 +154,15 @@ impl<E: EthSpec> KeyValueStore<E> for LevelDB<E> {
154154
self.transaction_mutex.lock()
155155
}
156156

157-
/// Compact all values in the states and states flag columns.
158-
fn compact(&self) -> Result<(), Error> {
159-
let endpoints = |column: DBColumn| {
160-
(
161-
BytesKey::from_vec(get_key_for_col(column.as_str(), Hash256::zero().as_bytes())),
162-
BytesKey::from_vec(get_key_for_col(
163-
column.as_str(),
164-
Hash256::repeat_byte(0xff).as_bytes(),
165-
)),
166-
)
167-
};
168-
169-
for (start_key, end_key) in [
170-
endpoints(DBColumn::BeaconStateTemporary),
171-
endpoints(DBColumn::BeaconState),
172-
endpoints(DBColumn::BeaconStateSummary),
173-
] {
174-
self.db.compact(&start_key, &end_key);
175-
}
157+
fn compact_column(&self, column: DBColumn) -> Result<(), Error> {
158+
// Use key-size-agnostic keys [] and 0xff..ff with a minimum of 32 bytes to account for
159+
// columns that may change size between sub-databases or schema versions.
160+
let start_key = BytesKey::from_vec(get_key_for_col(column.as_str(), &[]));
161+
let end_key = BytesKey::from_vec(get_key_for_col(
162+
column.as_str(),
163+
&vec![0xff; std::cmp::max(column.key_size(), 32)],
164+
));
165+
self.db.compact(&start_key, &end_key);
176166
Ok(())
177167
}
178168

beacon_node/store/src/lib.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,22 @@ pub trait KeyValueStore<E: EthSpec>: Sync + Send + Sized + 'static {
8080
/// this method. In future we may implement a safer mandatory locking scheme.
8181
fn begin_rw_transaction(&self) -> MutexGuard<()>;
8282

83-
/// Compact the database, freeing space used by deleted items.
84-
fn compact(&self) -> Result<(), Error>;
83+
/// Compact a single column in the database, freeing space used by deleted items.
84+
fn compact_column(&self, column: DBColumn) -> Result<(), Error>;
85+
86+
/// Compact a default set of columns that are likely to free substantial space.
87+
fn compact(&self) -> Result<(), Error> {
88+
// Compact state and block related columns as they are likely to have the most churn,
89+
// i.e. entries being created and deleted.
90+
for column in [
91+
DBColumn::BeaconState,
92+
DBColumn::BeaconStateSummary,
93+
DBColumn::BeaconBlock,
94+
] {
95+
self.compact_column(column)?;
96+
}
97+
Ok(())
98+
}
8599

86100
/// Iterate through all keys and values in a particular column.
87101
fn iter_column<K: Key>(&self, column: DBColumn) -> ColumnIter<K> {

beacon_node/store/src/memory_store.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ impl<E: EthSpec> KeyValueStore<E> for MemoryStore<E> {
108108
self.transaction_mutex.lock()
109109
}
110110

111-
fn compact(&self) -> Result<(), Error> {
111+
fn compact_column(&self, _column: DBColumn) -> Result<(), Error> {
112112
Ok(())
113113
}
114114
}

database_manager/src/lib.rs

+98-19
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,15 @@ pub fn inspect_cli_app<'a, 'b>() -> App<'a, 'b> {
7777
Arg::with_name("freezer")
7878
.long("freezer")
7979
.help("Inspect the freezer DB rather than the hot DB")
80-
.takes_value(false),
80+
.takes_value(false)
81+
.conflicts_with("blobs-db"),
82+
)
83+
.arg(
84+
Arg::with_name("blobs-db")
85+
.long("blobs-db")
86+
.help("Inspect the blobs DB rather than the hot DB")
87+
.takes_value(false)
88+
.conflicts_with("freezer"),
8189
)
8290
.arg(
8391
Arg::with_name("output-dir")
@@ -88,6 +96,34 @@ pub fn inspect_cli_app<'a, 'b>() -> App<'a, 'b> {
8896
)
8997
}
9098

99+
pub fn compact_cli_app<'a, 'b>() -> App<'a, 'b> {
100+
App::new("compact")
101+
.setting(clap::AppSettings::ColoredHelp)
102+
.about("Compact database manually")
103+
.arg(
104+
Arg::with_name("column")
105+
.long("column")
106+
.value_name("TAG")
107+
.help("3-byte column ID (see `DBColumn`)")
108+
.takes_value(true)
109+
.required(true),
110+
)
111+
.arg(
112+
Arg::with_name("freezer")
113+
.long("freezer")
114+
.help("Inspect the freezer DB rather than the hot DB")
115+
.takes_value(false)
116+
.conflicts_with("blobs-db"),
117+
)
118+
.arg(
119+
Arg::with_name("blobs-db")
120+
.long("blobs-db")
121+
.help("Inspect the blobs DB rather than the hot DB")
122+
.takes_value(false)
123+
.conflicts_with("freezer"),
124+
)
125+
}
126+
91127
pub fn prune_payloads_app<'a, 'b>() -> App<'a, 'b> {
92128
App::new("prune-payloads")
93129
.alias("prune_payloads")
@@ -162,6 +198,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
162198
.subcommand(migrate_cli_app())
163199
.subcommand(version_cli_app())
164200
.subcommand(inspect_cli_app())
201+
.subcommand(compact_cli_app())
165202
.subcommand(prune_payloads_app())
166203
.subcommand(prune_blobs_app())
167204
.subcommand(prune_states_app())
@@ -251,6 +288,7 @@ pub struct InspectConfig {
251288
skip: Option<usize>,
252289
limit: Option<usize>,
253290
freezer: bool,
291+
blobs_db: bool,
254292
/// Configures where the inspect output should be stored.
255293
output_dir: PathBuf,
256294
}
@@ -261,6 +299,7 @@ fn parse_inspect_config(cli_args: &ArgMatches) -> Result<InspectConfig, String>
261299
let skip = clap_utils::parse_optional(cli_args, "skip")?;
262300
let limit = clap_utils::parse_optional(cli_args, "limit")?;
263301
let freezer = cli_args.is_present("freezer");
302+
let blobs_db = cli_args.is_present("blobs-db");
264303

265304
let output_dir: PathBuf =
266305
clap_utils::parse_optional(cli_args, "output-dir")?.unwrap_or_else(PathBuf::new);
@@ -270,39 +309,28 @@ fn parse_inspect_config(cli_args: &ArgMatches) -> Result<InspectConfig, String>
270309
skip,
271310
limit,
272311
freezer,
312+
blobs_db,
273313
output_dir,
274314
})
275315
}
276316

277317
pub fn inspect_db<E: EthSpec>(
278318
inspect_config: InspectConfig,
279319
client_config: ClientConfig,
280-
runtime_context: &RuntimeContext<E>,
281-
log: Logger,
282320
) -> Result<(), String> {
283-
let spec = runtime_context.eth2_config.spec.clone();
284321
let hot_path = client_config.get_db_path();
285322
let cold_path = client_config.get_freezer_db_path();
286323
let blobs_path = client_config.get_blobs_db_path();
287324

288-
let db = HotColdDB::<E, LevelDB<E>, LevelDB<E>>::open(
289-
&hot_path,
290-
&cold_path,
291-
&blobs_path,
292-
|_, _, _| Ok(()),
293-
client_config.store,
294-
spec,
295-
log,
296-
)
297-
.map_err(|e| format!("{:?}", e))?;
298-
299325
let mut total = 0;
300326
let mut num_keys = 0;
301327

302328
let sub_db = if inspect_config.freezer {
303-
&db.cold_db
329+
LevelDB::<E>::open(&cold_path).map_err(|e| format!("Unable to open freezer DB: {e:?}"))?
330+
} else if inspect_config.blobs_db {
331+
LevelDB::<E>::open(&blobs_path).map_err(|e| format!("Unable to open blobs DB: {e:?}"))?
304332
} else {
305-
&db.hot_db
333+
LevelDB::<E>::open(&hot_path).map_err(|e| format!("Unable to open hot DB: {e:?}"))?
306334
};
307335

308336
let skip = inspect_config.skip.unwrap_or(0);
@@ -385,6 +413,50 @@ pub fn inspect_db<E: EthSpec>(
385413
Ok(())
386414
}
387415

416+
pub struct CompactConfig {
417+
column: DBColumn,
418+
freezer: bool,
419+
blobs_db: bool,
420+
}
421+
422+
fn parse_compact_config(cli_args: &ArgMatches) -> Result<CompactConfig, String> {
423+
let column = clap_utils::parse_required(cli_args, "column")?;
424+
let freezer = cli_args.is_present("freezer");
425+
let blobs_db = cli_args.is_present("blobs-db");
426+
Ok(CompactConfig {
427+
column,
428+
freezer,
429+
blobs_db,
430+
})
431+
}
432+
433+
pub fn compact_db<E: EthSpec>(
434+
compact_config: CompactConfig,
435+
client_config: ClientConfig,
436+
log: Logger,
437+
) -> Result<(), Error> {
438+
let hot_path = client_config.get_db_path();
439+
let cold_path = client_config.get_freezer_db_path();
440+
let blobs_path = client_config.get_blobs_db_path();
441+
let column = compact_config.column;
442+
443+
let (sub_db, db_name) = if compact_config.freezer {
444+
(LevelDB::<E>::open(&cold_path)?, "freezer_db")
445+
} else if compact_config.blobs_db {
446+
(LevelDB::<E>::open(&blobs_path)?, "blobs_db")
447+
} else {
448+
(LevelDB::<E>::open(&hot_path)?, "hot_db")
449+
};
450+
info!(
451+
log,
452+
"Compacting database";
453+
"db" => db_name,
454+
"column" => ?column
455+
);
456+
sub_db.compact_column(column)?;
457+
Ok(())
458+
}
459+
388460
pub struct MigrateConfig {
389461
to: SchemaVersion,
390462
}
@@ -538,7 +610,10 @@ pub fn prune_states<E: EthSpec>(
538610
// Check that the user has confirmed they want to proceed.
539611
if !prune_config.confirm {
540612
match db.get_anchor_info() {
541-
Some(anchor_info) if anchor_info.state_upper_limit == STATE_UPPER_LIMIT_NO_RETAIN => {
613+
Some(anchor_info)
614+
if anchor_info.state_lower_limit == 0
615+
&& anchor_info.state_upper_limit == STATE_UPPER_LIMIT_NO_RETAIN =>
616+
{
542617
info!(log, "States have already been pruned");
543618
return Ok(());
544619
}
@@ -586,7 +661,11 @@ pub fn run<T: EthSpec>(cli_args: &ArgMatches<'_>, env: Environment<T>) -> Result
586661
}
587662
("inspect", Some(cli_args)) => {
588663
let inspect_config = parse_inspect_config(cli_args)?;
589-
inspect_db(inspect_config, client_config, &context, log)
664+
inspect_db::<T>(inspect_config, client_config)
665+
}
666+
("compact", Some(cli_args)) => {
667+
let compact_config = parse_compact_config(cli_args)?;
668+
compact_db::<T>(compact_config, client_config, log).map_err(format_err)
590669
}
591670
("prune-payloads", Some(_)) => {
592671
prune_payloads(client_config, &context, log).map_err(format_err)

0 commit comments

Comments
 (0)