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

Implemented database optimization routine #721

Open
wants to merge 4 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Enhancements

- Implemented database optimization routine (#721).

### Fixes

- Faucet webpage is missing `background.png` and `favicon.ico` (#672).
Expand Down
4 changes: 3 additions & 1 deletion bin/node/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ mod tests {
database_filepath = "local.sqlite3"
genesis_filepath = "genesis.dat"
blockstore_dir = "blocks"
db_optimization_interval_secs = 86400
"#,
)?;

Expand All @@ -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,
},
}
);
Expand Down
17 changes: 15 additions & 2 deletions crates/store/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
))
}
}
Expand All @@ -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
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions crates/store/src/db/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 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)?;
Expand Down
21 changes: 20 additions & 1 deletion crates/store/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down Expand Up @@ -515,6 +517,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
// ---------------------------------------------------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion crates/store/src/db/sql/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ macro_rules! subst {
///
/// # Usage:
///
/// ```
/// ```ignore
/// insert_sql!(users { id, first_name, last_name, age });
/// ```
/// which generates:
Expand Down
2 changes: 1 addition & 1 deletion crates/store/src/server/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}))
}

Expand Down
30 changes: 30 additions & 0 deletions crates/store/src/server/db_maintenance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use std::{sync::Arc, time::Duration};

use tracing::{error, info};

use crate::{state::State, COMPONENT};

pub struct DbMaintenance {
state: Arc<State>,
optimization_interval: Duration,
}

impl DbMaintenance {
pub fn new(state: Arc<State>, 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"),
}
}
Comment on lines +22 to +28
Copy link
Contributor

@Mirko-von-Leipzig Mirko-von-Leipzig Mar 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using trace events to communicate the time start and end, prefer using a span to cover it instead.

This essentially means we want a new root span to cover the self.state.optimize_db call.

Suggested change
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"),
}
}
let root_span = tracing::info_span!("optimize_database", interval=self.optimization_interval.as_secs_f32());
self.state.optimize_db()
.instrument(root_span)
.inspect_err(|err| root_span.set_error(err))
.await;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general the store component still needs a telemetry make-over but this seems like and important one to trace so that we can get the actual performance impact of it.

}
}
22 changes: 18 additions & 4 deletions crates/store/src/server/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use std::sync::Arc;
use std::{sync::Arc, time::Duration};

use miden_node_proto::generated::store::api_server;
use miden_node_utils::errors::ApiError;
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.
Expand All @@ -18,6 +22,7 @@ mod api;
/// components.
pub struct Store {
api_service: api_server::ApiServer<api::StoreApi>,
db_maintenance_service: DbMaintenance,
listener: TcpListener,
}

Expand All @@ -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
Expand All @@ -54,13 +63,18 @@ 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.
/// 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> {
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)
Expand Down
10 changes: 6 additions & 4 deletions crates/store/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -962,10 +962,7 @@ impl State {
from_block: BlockNumber,
to_block: BlockNumber,
) -> Result<Option<AccountDelta>, 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.
Expand All @@ -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
Expand Down
Loading