From 623c4f7388f9b982679c185662cfcba9fb4e816b Mon Sep 17 00:00:00 2001 From: polydez <155382956+polydez@users.noreply.github.com> Date: Wed, 26 Feb 2025 22:28:18 +0500 Subject: [PATCH 1/7] feat: implement DB optimization routine --- bin/node/src/config.rs | 4 ++- crates/store/src/config.rs | 17 ++++++++-- crates/store/src/db/migrations.rs | 14 +++++++++ crates/store/src/db/mod.rs | 17 ++++++++++ crates/store/src/db/sql/utils.rs | 2 +- crates/store/src/server/api.rs | 2 +- crates/store/src/server/db_maintenance.rs | 30 ++++++++++++++++++ crates/store/src/server/mod.rs | 38 ++++++++++++++++++----- crates/store/src/state.rs | 10 +++--- 9 files changed, 118 insertions(+), 16 deletions(-) create mode 100644 crates/store/src/server/db_maintenance.rs diff --git a/bin/node/src/config.rs b/bin/node/src/config.rs index 4fef3981f..b94eb301f 100644 --- a/bin/node/src/config.rs +++ b/bin/node/src/config.rs @@ -101,6 +101,7 @@ mod tests { database_filepath = "local.sqlite3" genesis_filepath = "genesis.dat" blockstore_dir = "blocks" + db_optimization_interval_secs = 86400 "#, )?; @@ -120,7 +121,8 @@ mod tests { endpoint: Url::parse("https://127.0.0.1:8080").unwrap(), database_filepath: "local.sqlite3".into(), genesis_filepath: "genesis.dat".into(), - blockstore_dir: "blocks".into() + blockstore_dir: "blocks".into(), + db_optimization_interval_secs: 24 * 60 * 60, }, } ); diff --git a/crates/store/src/config.rs b/crates/store/src/config.rs index 3a065bcf3..216586adc 100644 --- a/crates/store/src/config.rs +++ b/crates/store/src/config.rs @@ -21,13 +21,25 @@ pub struct StoreConfig { pub genesis_filepath: PathBuf, /// Block store directory pub blockstore_dir: PathBuf, + /// Database optimization interval in seconds + pub db_optimization_interval_secs: u64, } impl Display for StoreConfig { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!( - "{{ endpoint: \"{}\", database_filepath: {:?}, genesis_filepath: {:?}, blockstore_dir: {:?} }}", - self.endpoint, self.database_filepath, self.genesis_filepath, self.blockstore_dir + "{{ \ + endpoint: \"{}\", \ + database_filepath: {:?}, \ + genesis_filepath: {:?}, \ + blockstore_dir: {:?}, \ + db_optimization_interval_secs: {} \ + }}", + self.endpoint, + self.database_filepath, + self.genesis_filepath, + self.blockstore_dir, + self.db_optimization_interval_secs )) } } @@ -41,6 +53,7 @@ impl Default for StoreConfig { database_filepath: PathBuf::from(NODE_STORE_DIR.to_string() + "miden-store.sqlite3"), genesis_filepath: PathBuf::from(NODE_STORE_DIR.to_string() + "genesis.dat"), blockstore_dir: PathBuf::from(NODE_STORE_DIR.to_string() + "blocks"), + db_optimization_interval_secs: 24 * 60 * 60, // 24 hours } } } diff --git a/crates/store/src/db/migrations.rs b/crates/store/src/db/migrations.rs index ef7547ee2..74d51a755 100644 --- a/crates/store/src/db/migrations.rs +++ b/crates/store/src/db/migrations.rs @@ -75,6 +75,20 @@ pub fn apply_migrations(conn: &mut Connection) -> super::Result<()> { Settings::set_value(conn, DB_MIGRATION_HASH_FIELD, &new_hash)?; } + info!(target: COMPONENT, "Starting database optimization"); + + // Run full database optimization. This will shrink the database and run indexes analysis for + // the query planner. This will also increase the `schema_version` value. + // + // We should run full database optimization in following cases: + // 1. Once schema was changed, especially new indexes were created. + // 2. After restarting of the node, on first connection established. + // + // More info: https://www.sqlite.org/pragma.html#pragma_optimize + conn.execute("PRAGMA optimize = 0x10002;", ())?; + + info!(target: COMPONENT, "Finished database optimization"); + let new_schema_version = schema_version(conn)?; debug!(target: COMPONENT, new_schema_version, "Updating schema version in settings table"); Settings::set_value(conn, DB_SCHEMA_VERSION_FIELD, &new_schema_version)?; diff --git a/crates/store/src/db/mod.rs b/crates/store/src/db/mod.rs index 3b2027455..0f90dcbb1 100644 --- a/crates/store/src/db/mod.rs +++ b/crates/store/src/db/mod.rs @@ -515,6 +515,23 @@ impl Db { .map_err(|err| DatabaseError::InteractError(err.to_string()))? } + /// Runs database optimization. + #[instrument(target = COMPONENT, skip_all, err)] + pub async fn optimize(&self) -> Result<(), DatabaseError> { + self.pool + .get() + .await? + .interact(move |conn| -> Result<()> { + conn.execute("PRAGMA optimize;", ()) + .map(|_| ()) + .map_err(DatabaseError::SqliteError) + }) + .await + .map_err(|err| { + DatabaseError::InteractError(format!("Database optimization task failed: {err}")) + })? + } + // HELPERS // --------------------------------------------------------------------------------------------- diff --git a/crates/store/src/db/sql/utils.rs b/crates/store/src/db/sql/utils.rs index 284c3dde8..7b0608268 100644 --- a/crates/store/src/db/sql/utils.rs +++ b/crates/store/src/db/sql/utils.rs @@ -47,7 +47,7 @@ macro_rules! subst { /// /// # Usage: /// -/// ``` +/// ```ignore /// insert_sql!(users { id, first_name, last_name, age }); /// ``` /// which generates: diff --git a/crates/store/src/server/api.rs b/crates/store/src/server/api.rs index f69c8779a..5cc26e160 100644 --- a/crates/store/src/server/api.rs +++ b/crates/store/src/server/api.rs @@ -487,7 +487,7 @@ impl api_server::Api for StoreApi { Ok(Response::new(GetAccountProofsResponse { block_num: block_num.as_u32(), - account_proofs: infos.into_iter().map(Into::into).collect(), + account_proofs: infos.into_iter().collect(), })) } diff --git a/crates/store/src/server/db_maintenance.rs b/crates/store/src/server/db_maintenance.rs new file mode 100644 index 000000000..ae1286755 --- /dev/null +++ b/crates/store/src/server/db_maintenance.rs @@ -0,0 +1,30 @@ +use std::{sync::Arc, time::Duration}; + +use tracing::{error, info}; + +use crate::{state::State, COMPONENT}; + +pub struct DbMaintenance { + state: Arc, + optimization_interval: Duration, +} + +impl DbMaintenance { + pub fn new(state: Arc, optimization_interval: Duration) -> Self { + Self { state, optimization_interval } + } + + /// Runs infinite maintenance loop. + pub async fn run(&self) { + loop { + tokio::time::sleep(self.optimization_interval).await; + + info!(target: COMPONENT, "Starting database optimization"); + + match self.state.optimize_db().await { + Ok(_) => info!(target: COMPONENT, "Finished database optimization"), + Err(err) => error!(target: COMPONENT, %err, "Database optimization failed"), + } + } + } +} diff --git a/crates/store/src/server/mod.rs b/crates/store/src/server/mod.rs index 2b65a1dc2..df0080514 100644 --- a/crates/store/src/server/mod.rs +++ b/crates/store/src/server/mod.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; use miden_node_proto::generated::store::api_server; use miden_node_utils::errors::ApiError; @@ -6,9 +6,13 @@ use tokio::net::TcpListener; use tokio_stream::wrappers::TcpListenerStream; use tracing::info; -use crate::{blocks::BlockStore, config::StoreConfig, db::Db, state::State, COMPONENT}; +use crate::{ + blocks::BlockStore, config::StoreConfig, db::Db, server::db_maintenance::DbMaintenance, + state::State, COMPONENT, +}; mod api; +mod db_maintenance; /// Represents an initialized store component where the RPC connection is open, but not yet actively /// responding to requests. @@ -18,6 +22,7 @@ mod api; /// components. pub struct Store { api_service: api_server::ApiServer, + db_maintenance_service: DbMaintenance, listener: TcpListener, } @@ -40,6 +45,10 @@ impl Store { .map_err(|err| ApiError::DatabaseConnectionFailed(err.to_string()))?, ); + let db_maintenance_service = DbMaintenance::new( + Arc::clone(&state), + Duration::from_secs(config.db_optimization_interval_secs), + ); let api_service = api_server::ApiServer::new(api::StoreApi { state }); let addr = config @@ -54,18 +63,33 @@ impl Store { info!(target: COMPONENT, "Database loaded"); - Ok(Self { api_service, listener }) + Ok(Self { + api_service, + db_maintenance_service, + listener, + }) } /// Serves the store's RPC API. /// /// Note: this blocks until the server dies. pub async fn serve(self) -> Result<(), ApiError> { - tonic::transport::Server::builder() + let db_maintenance_service = self.db_maintenance_service.run(); + let api_service = tonic::transport::Server::builder() .trace_fn(miden_node_utils::tracing::grpc::store_trace_fn) .add_service(self.api_service) - .serve_with_incoming(TcpListenerStream::new(self.listener)) - .await - .map_err(ApiError::ApiServeFailed) + .serve_with_incoming(TcpListenerStream::new(self.listener)); + + tokio::select! { + api_service = api_service => { + api_service.map_err(ApiError::ApiServeFailed) + }, + + _ = db_maintenance_service => { + info!(target: COMPONENT, "Database maintenance service shutdown"); + + Ok(()) + }, + } } } diff --git a/crates/store/src/state.rs b/crates/store/src/state.rs index d1ec2b48f..5925d7a47 100644 --- a/crates/store/src/state.rs +++ b/crates/store/src/state.rs @@ -962,10 +962,7 @@ impl State { from_block: BlockNumber, to_block: BlockNumber, ) -> Result, DatabaseError> { - self.db - .select_account_state_delta(account_id, from_block, to_block) - .await - .map_err(Into::into) + self.db.select_account_state_delta(account_id, from_block, to_block).await } /// Loads a block from the block store. Return `Ok(None)` if the block is not found. @@ -983,6 +980,11 @@ impl State { pub async fn latest_block_num(&self) -> BlockNumber { self.inner.read().await.latest_block_num() } + + /// Runs database optimization. + pub async fn optimize_db(&self) -> Result<(), DatabaseError> { + self.db.optimize().await + } } // UTILITIES From 32b7ce9762c0d42ec3def6d2c6ce41c43176e7b3 Mon Sep 17 00:00:00 2001 From: polydez <155382956+polydez@users.noreply.github.com> Date: Wed, 26 Feb 2025 22:36:09 +0500 Subject: [PATCH 2/7] chore: update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81da63401..ffa634db2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Enhancements + +- Implemented database optimization routine (#721). + ### Fixes - Faucet webpage is missing `background.png` and `favicon.ico` (#672). From bb3d7ae25ff149b0e588ab3827948f46dbbee1f9 Mon Sep 17 00:00:00 2001 From: polydez <155382956+polydez@users.noreply.github.com> Date: Thu, 27 Feb 2025 16:33:28 +0500 Subject: [PATCH 3/7] Update crates/store/src/server/mod.rs Co-authored-by: Mirko <48352201+Mirko-von-Leipzig@users.noreply.github.com> --- crates/store/src/server/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/store/src/server/mod.rs b/crates/store/src/server/mod.rs index df0080514..81b8b6d5a 100644 --- a/crates/store/src/server/mod.rs +++ b/crates/store/src/server/mod.rs @@ -86,7 +86,7 @@ impl Store { }, _ = db_maintenance_service => { - info!(target: COMPONENT, "Database maintenance service shutdown"); + error!(target: COMPONENT, "Database maintenance service crashed"); Ok(()) }, From f5506b3407155c1d54ad7be9d29f04c79181d356 Mon Sep 17 00:00:00 2001 From: polydez <155382956+polydez@users.noreply.github.com> Date: Thu, 27 Feb 2025 16:51:29 +0500 Subject: [PATCH 4/7] fix: address review comments --- crates/store/src/db/migrations.rs | 4 ++-- crates/store/src/db/mod.rs | 4 +++- crates/store/src/server/db_maintenance.rs | 2 +- crates/store/src/server/mod.rs | 22 ++++++---------------- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/crates/store/src/db/migrations.rs b/crates/store/src/db/migrations.rs index 74d51a755..023ac93a4 100644 --- a/crates/store/src/db/migrations.rs +++ b/crates/store/src/db/migrations.rs @@ -77,8 +77,8 @@ pub fn apply_migrations(conn: &mut Connection) -> super::Result<()> { info!(target: COMPONENT, "Starting database optimization"); - // Run full database optimization. This will shrink the database and run indexes analysis for - // the query planner. This will also increase the `schema_version` value. + // Run full database optimization. This will run indexes analysis for the query planner. + // This will also increase the `schema_version` value. // // We should run full database optimization in following cases: // 1. Once schema was changed, especially new indexes were created. diff --git a/crates/store/src/db/mod.rs b/crates/store/src/db/mod.rs index 0f90dcbb1..4ca34cfcf 100644 --- a/crates/store/src/db/mod.rs +++ b/crates/store/src/db/mod.rs @@ -222,7 +222,9 @@ impl Db { }) .await .map_err(|e| { - HookError::Message(format!("Loading carray module failed: {e}").into()) + HookError::Message( + format!("Failed to configure connection: {e}").into(), + ) })?; Ok(()) diff --git a/crates/store/src/server/db_maintenance.rs b/crates/store/src/server/db_maintenance.rs index ae1286755..13a840a48 100644 --- a/crates/store/src/server/db_maintenance.rs +++ b/crates/store/src/server/db_maintenance.rs @@ -15,7 +15,7 @@ impl DbMaintenance { } /// Runs infinite maintenance loop. - pub async fn run(&self) { + pub async fn run(self) { loop { tokio::time::sleep(self.optimization_interval).await; diff --git a/crates/store/src/server/mod.rs b/crates/store/src/server/mod.rs index 81b8b6d5a..0b5b4845f 100644 --- a/crates/store/src/server/mod.rs +++ b/crates/store/src/server/mod.rs @@ -70,26 +70,16 @@ impl Store { }) } - /// Serves the store's RPC API. + /// Serves the store's RPC API and DB maintenance background task. /// /// Note: this blocks until the server dies. pub async fn serve(self) -> Result<(), ApiError> { - let db_maintenance_service = self.db_maintenance_service.run(); - let api_service = tonic::transport::Server::builder() + tokio::spawn(self.db_maintenance_service.run()); + tonic::transport::Server::builder() .trace_fn(miden_node_utils::tracing::grpc::store_trace_fn) .add_service(self.api_service) - .serve_with_incoming(TcpListenerStream::new(self.listener)); - - tokio::select! { - api_service = api_service => { - api_service.map_err(ApiError::ApiServeFailed) - }, - - _ = db_maintenance_service => { - error!(target: COMPONENT, "Database maintenance service crashed"); - - Ok(()) - }, - } + .serve_with_incoming(TcpListenerStream::new(self.listener)) + .await + .map_err(ApiError::ApiServeFailed) } } From 6f336830e61c11f9ab95891f9ee1fb4b81f852ad Mon Sep 17 00:00:00 2001 From: polydez <155382956+polydez@users.noreply.github.com> Date: Tue, 11 Mar 2025 15:44:45 +0500 Subject: [PATCH 5/7] refactor: log database optimization in span --- crates/store/src/server/db_maintenance.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/store/src/server/db_maintenance.rs b/crates/store/src/server/db_maintenance.rs index 13a840a48..99cde3818 100644 --- a/crates/store/src/server/db_maintenance.rs +++ b/crates/store/src/server/db_maintenance.rs @@ -1,8 +1,8 @@ use std::{sync::Arc, time::Duration}; -use tracing::{error, info}; +use miden_node_utils::tracing::OpenTelemetrySpanExt; -use crate::{state::State, COMPONENT}; +use crate::state::State; pub struct DbMaintenance { state: Arc, @@ -19,11 +19,14 @@ impl DbMaintenance { loop { tokio::time::sleep(self.optimization_interval).await; - info!(target: COMPONENT, "Starting database optimization"); + let root_span = tracing::info_span!( + "optimize_database", + interval = self.optimization_interval.as_secs_f32() + ); - match self.state.optimize_db().await { - Ok(_) => info!(target: COMPONENT, "Finished database optimization"), - Err(err) => error!(target: COMPONENT, %err, "Database optimization failed"), + { + let _enter = root_span.enter(); + self.state.optimize_db().await.unwrap_or_else(|err| root_span.set_error(&err)); } } } From b3dc9f92d26872948ff929655f92b15afa1beb33 Mon Sep 17 00:00:00 2001 From: polydez <155382956+polydez@users.noreply.github.com> Date: Tue, 11 Mar 2025 15:47:41 +0500 Subject: [PATCH 6/7] format: format using rustfmt --- crates/store/src/server/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/store/src/server/mod.rs b/crates/store/src/server/mod.rs index 0dd1b58e0..5401ac0be 100644 --- a/crates/store/src/server/mod.rs +++ b/crates/store/src/server/mod.rs @@ -7,7 +7,10 @@ use tokio_stream::wrappers::TcpListenerStream; use tower_http::trace::TraceLayer; use tracing::info; -use crate::{COMPONENT, blocks::BlockStore, config::StoreConfig, db::Db, server::db_maintenance::DbMaintenance, state::State}; +use crate::{ + COMPONENT, blocks::BlockStore, config::StoreConfig, db::Db, + server::db_maintenance::DbMaintenance, state::State, +}; mod api; mod db_maintenance; From 71626e0da14ad3bd0b6d5a85afe4add75161b47b Mon Sep 17 00:00:00 2001 From: polydez <155382956+polydez@users.noreply.github.com> Date: Tue, 11 Mar 2025 17:25:04 +0500 Subject: [PATCH 7/7] refactor: instrument `optimize_db` --- crates/store/src/server/db_maintenance.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/store/src/server/db_maintenance.rs b/crates/store/src/server/db_maintenance.rs index 99cde3818..1552ff01b 100644 --- a/crates/store/src/server/db_maintenance.rs +++ b/crates/store/src/server/db_maintenance.rs @@ -1,6 +1,7 @@ use std::{sync::Arc, time::Duration}; use miden_node_utils::tracing::OpenTelemetrySpanExt; +use tracing::{Instrument, Span}; use crate::state::State; @@ -23,11 +24,11 @@ impl DbMaintenance { "optimize_database", interval = self.optimization_interval.as_secs_f32() ); - - { - let _enter = root_span.enter(); - self.state.optimize_db().await.unwrap_or_else(|err| root_span.set_error(&err)); - } + self.state + .optimize_db() + .instrument(root_span) + .await + .unwrap_or_else(|err| Span::current().set_error(&err)); } } }