Skip to content
Merged
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
29 changes: 29 additions & 0 deletions crates/stages/types/src/id.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::{collections::HashMap, sync::OnceLock};

/// Stage IDs for all known stages.
///
/// For custom stages, use [`StageId::Other`]
Expand Down Expand Up @@ -27,6 +31,12 @@ pub enum StageId {
Other(&'static str),
}

/// One-time-allocated stage ids encoded as raw Vecs, useful for database
/// clients to reference them for queries instead of encoding anew per query
/// (sad heap allocation required).
#[cfg(feature = "std")]
static ENCODED_STAGE_IDS: OnceLock<HashMap<StageId, Vec<u8>>> = OnceLock::new();

impl StageId {
/// All supported Stages
pub const ALL: [Self; 15] = [
Expand Down Expand Up @@ -98,6 +108,25 @@ impl StageId {
pub const fn is_finish(&self) -> bool {
matches!(self, Self::Finish)
}

/// Get a pre-encoded raw Vec, for example, to be used as the DB key for
/// `tables::StageCheckpoints` and `tables::StageCheckpointProgresses`
pub fn get_pre_encoded(&self) -> Option<&Vec<u8>> {
#[cfg(not(feature = "std"))]
{
None
}
#[cfg(feature = "std")]
ENCODED_STAGE_IDS
.get_or_init(|| {
let mut map = HashMap::with_capacity(Self::ALL.len());
for stage_id in Self::ALL {
map.insert(stage_id, stage_id.to_string().into_bytes());
}
map
})
.get(self)
}
}

impl core::fmt::Display for StageId {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1642,7 +1642,11 @@ impl<TX: DbTx + 'static, N: NodeTypesForProvider> BlockBodyIndicesProvider

impl<TX: DbTx, N: NodeTypes> StageCheckpointReader for DatabaseProvider<TX, N> {
fn get_stage_checkpoint(&self, id: StageId) -> ProviderResult<Option<StageCheckpoint>> {
Ok(self.tx.get::<tables::StageCheckpoints>(id.to_string())?)
Copy link
Copy Markdown
Contributor Author

@hai-rise hai-rise Sep 5, 2025

Choose a reason for hiding this comment

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

This heap-allocated per database query, even when the hottest queries have "constant" stage ids like "Finish", which should be (pre)allocated only once.

This is also our initial interested hot path, get_pre_encoded can be applied to more places -- anywhere that gets from checkpoint tables.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

actually, isn't this the same as .as_str().as_bytes() ?

then we dont even need the map

EDIT: nvm we need the vec

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's unfortunate to use heap types for table keys and their encoding, but the overhead also isn't big enough to migrate. We live with it for another day!

Ok(if let Some(encoded) = id.get_pre_encoded() {
self.tx.get_by_encoded_key::<tables::StageCheckpoints>(encoded)?
} else {
self.tx.get::<tables::StageCheckpoints>(id.to_string())?
})
}

/// Get stage checkpoint progress.
Expand Down