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

feat(state): delete old database directories #4586

Merged
merged 14 commits into from
Jun 21, 2022
79 changes: 78 additions & 1 deletion zebra-state/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::path::PathBuf;
use std::{
fs::{remove_dir_all, DirEntry, ReadDir},
path::PathBuf,
};

use serde::{Deserialize, Serialize};
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved

Expand Down Expand Up @@ -57,6 +60,13 @@ pub struct Config {
///
/// Set to `None` by default: Zebra continues syncing indefinitely.
pub debug_stop_at_height: Option<u32>,

/// Whether to delete the old database directories when present.
///
/// Set to `true` by default. If this is set to `false`,
/// no check for old database versions will be made and nothing will be
/// deleted.
pub delete_old_database: bool,
}

fn gen_temp_path(prefix: &str) -> PathBuf {
Expand Down Expand Up @@ -108,6 +118,73 @@ impl Default for Config {
cache_dir,
ephemeral: false,
debug_stop_at_height: None,
delete_old_database: true,
}
}
}

/// Check if there are old database folders and delete them from the filesystem.
///
/// Iterate over the files and directories in the databases folder and delete if:
/// - The state directory exists.
/// - The entry is a directory.
/// - The directory name has a length of at least 2 characters.
/// - The directory name has a prefix `v`.
/// - The directory name without the prefix can be parsed as an unsigned number.
/// - The parsed number is lower than the hardcoded `DATABASE_FORMAT_VERSION`.
pub async fn check_and_delete_old_databases(config: Config) {
if config.ephemeral || !config.delete_old_database {
return;
}

info!("checking old database versions");
let cache_dir = config.cache_dir.join("state");
if let Some(read_dir) = read_dir(cache_dir.clone()) {
for entry in read_dir.flatten() {
if let Some(dir_name) = parse_dir_name(entry) {
if let Some(version_number) = parse_version_number(dir_name.clone()) {
if version_number < crate::constants::DATABASE_FORMAT_VERSION {
let delete_path = cache_dir.join(dir_name);
if remove_dir_all(delete_path.clone()).is_ok() {
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
info!("deleted outdated state directory {:?}", delete_path);
}
}
}
}
}
}
}

fn read_dir(cache_dir: PathBuf) -> Option<ReadDir> {
if cache_dir.exists() {
if let Ok(read_dir) = cache_dir.read_dir() {
return Some(read_dir);
}
}
None
}

fn parse_dir_name(entry: DirEntry) -> Option<String> {
if let Ok(file_type) = entry.file_type() {
if file_type.is_dir() {
if let Ok(dir_name) = entry.file_name().into_string() {
return Some(dir_name);
}
}
}
None
}

fn parse_version_number(dir_name: String) -> Option<u32> {
if dir_name.len() >= 2 && dir_name.starts_with('v') {
if let Some(potential_version_number) = dir_name.strip_prefix('v') {
return Some(
potential_version_number
.to_string()
.parse()
.unwrap_or(u32::MAX),
);
}
}
None
}
2 changes: 1 addition & 1 deletion zebra-state/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ mod util;
#[cfg(test)]
mod tests;

pub use config::Config;
pub use config::{check_and_delete_old_databases, Config};
pub use constants::MAX_BLOCK_REORG_HEIGHT;
pub use error::{BoxError, CloneError, CommitBlockError, ValidateContextError};
pub use request::{FinalizedBlock, HashOrHeight, PreparedBlock, ReadRequest, Request};
Expand Down
16 changes: 16 additions & 0 deletions zebrad/src/commands/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ impl StartCmd {
let config = app_config().clone();
info!(?config);

let mut old_databases_task_handle = tokio::spawn(
zebra_state::check_and_delete_old_databases(config.state.clone()).in_current_span(),
);

info!("initializing node state");
let (state_service, read_only_state_service, latest_chain_tip, chain_tip_change) =
zebra_state::init(config.state.clone(), config.network.network);
Expand Down Expand Up @@ -229,6 +233,8 @@ impl StartCmd {
// startup tasks
let groth16_download_handle_fused = (&mut groth16_download_handle).fuse();
pin!(groth16_download_handle_fused);
let old_databases_task_handle_fused = (&mut old_databases_task_handle).fuse();
pin!(old_databases_task_handle_fused);

// Wait for tasks to finish
let exit_status = loop {
Expand Down Expand Up @@ -291,6 +297,16 @@ impl StartCmd {
exit_when_task_finishes = false;
Ok(())
}

// The same for the old databases task, we expect it to finish while Zebra is running.
old_databases_result = &mut old_databases_task_handle_fused => {
old_databases_result
.unwrap_or_else(|_| panic!(
"unexpected panic deleting old database directories"));

exit_when_task_finishes = false;
Ok(())
}
};

// Stop Zebra if a task finished and returned an error,
Expand Down