Skip to content

perf: remove pending cache - #2630

Draft
carneiro-cw wants to merge 6 commits into
mainfrom
remove_pending_cache
Draft

perf: remove pending cache#2630
carneiro-cw wants to merge 6 commits into
mainfrom
remove_pending_cache

Conversation

@carneiro-cw

@carneiro-cw carneiro-cw commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Currently a single slot read in a transaction results in at least 3 cache reads, and at most 4 cache reads, 1 temp read, 1 perm read and 2 cache writes. These changes make it so at least it results in 1 temp-read, and at most 1 temp, 1 cache and 1 perm read and 0 cache writes. (with temp + latest cache covering 100% of the reads the pending cache potentially covered)

The temp now acts as the "pending cache" effectively, and the latest cache is updated with slots that were touched alongside slots that were modified when we commit the block. The latest cache is also still updated by eth_call.

Local benchmarking showed a 7% increase in throughput after these changes.

We're likely to see some more gains by optimizing the temp storage's State merge operation and reads.

PR Type

Enhancement, Tests


Description

  • Rename evm_input/result to input/output fields

  • Introduce Full stage for richer Changes API

  • Remove pending cache and simplify storage flows

  • Update miner, importer, and stratus_storage accordingly


File Walkthrough

Relevant files
Enhancement
19 files
mod.rs
Rename transaction execution fields in EVM executor           
+5/-5     
transaction_execution.rs
Parameterize `Changes` with `Full` stage                                 
+8/-20   
util.rs
Use `input`/`output` in default_trace tracer                         
+4/-4     
mod.rs
Switch to `output` field in metrics and revert logging     
+3/-2     
changes.rs
Add `Full` stage and extend `Changes` methods                       
+127/-74
transaction_execution.rs
Rename `evm_input`/`result` to `input`/`output`                   
+15/-15 
fake_leader.rs
Remove pending cache, simplify fake leader import               
+5/-8     
miner.rs
Update block mining API with `Full` changes                           
+14/-23 
cache.rs
Remove old caches and fix insert_if_missing                           
+15/-56 
rocks_permanent.rs
Use `Changes` in genesis and block saves             
+3/-2     
rocks_state.rs
Update batch execution with `Changes`                   
+12/-10 
stratus_storage.rs
Drop pending cache, adjust resolve and cache logic             
+42/-90 
call.rs
Apply `output` changes in in-memory call storage                 
+6/-9     
transaction.rs
Switch to `input`/`output` in in-memory temp storage         
+20/-14 
block.rs
Rename transaction timestamp and input usage                         
+3/-3     
transaction_mined.rs
Map `input`/`output` in rocksdb conversion                             
+4/-4     
transaction_stage.rs
Use `output` in transaction stage to_result                           
+2/-2     
metrics_definitions.rs
Remove unused metric labels in finish_pending_block           
+1/-1     
events.rs
Use `input`/`output` fields in event parsing                         
+12/-12 
Additional files
5 files
mod.rs +1/-0     
mod.rs +5/-4     
transaction_mined.rs +17/-17 
resolve_pending.rs +16/-24 
mod.rs +11/-10 

@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 5 🔵🔵🔵🔵🔵
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Error Swallowing

The new read_temp method returns Option<Self> and drops any StorageError from temporary storage reads, conflating errors with a missing value. This hides underlying storage failures and can lead to silent data inconsistencies. Errors should be propagated or at least logged with a reason field.

    fn read_temp(s: &StratusStorage, key: Self::Key, kind: ExecutionKind) -> Option<Self>;
    /// Reads from permanent storage at the resolved mined point.
    fn read_perm(s: &StratusStorage, key: Self::Key, point: MinedPointInTime<'_>) -> Result<Self, StorageError>;
    /// Caches the value as a latest (mined tip) entry, if not already cached.
    fn cache_latest_if_missing(s: &StratusStorage, key: Self::Key, value: Self);
}

impl EntityRead for Account {
    type Key = Address;

    fn read_temp(s: &StratusStorage, address: Address, kind: ExecutionKind) -> Option<Self> {
        tracing::debug!(storage = %label::TEMP, %address, "reading account");
        timed(|| s.temp.read_account(address, kind)).with(|m| {
            if m.result.is_some() {
                metrics::inc_storage_read_account(m.elapsed, label::TEMP, PointInTime::Pending, true);
            }
        })
    }
Missing Error Logging

In finish_pending_block, errors from self.temp.finish_pending_block() are no longer logged or surfaced. Without logging the reason, failures in finishing the pending block will go unnoticed. Add a structured error log (e.g., tracing::error!(reason = ?e)) inside the timed callback.

pub fn finish_pending_block(&self) -> (PendingBlock, Changes<Full>) {
    #[cfg(feature = "tracing")]
    let _span = tracing::info_span!("storage::finish_pending_block", block_number = tracing::field::Empty).entered();
    tracing::debug!(storage = %label::TEMP, "finishing pending block");

    let result = timed(|| self.temp.finish_pending_block()).with(|m| {
        metrics::inc_storage_finish_pending_block(m.elapsed);
    });
Unstructured Tracing

The tracing::warn! call in save_execution uses a formatted string "Failed transaction contains {} slot change(s)" with a positional placeholder. For structured tracing, emit slot_changes = total_slot_changes as a field rather than using {} formatting.

if !tx.output.result.is_success() {
    let total_slot_changes: usize = changes.slots.len();

    if total_slot_changes > 0 {
        tracing::warn!(?tx, "Failed transaction contains {} slot change(s)", total_slot_changes);
    }

@github-actions

Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Correctly extract slot values

The code attempts to call a non-existent .value() on CompleteValue. Extract the
inner SlotValue via .clone().take_value() before inserting.

src/eth/storage/cache.rs [75-77]

 for ((address, index), value) in changes.slots.iter() {
-    slot_cache.insert((*address, *index), *value.value());
+    let slot_val = value.clone().take_value();
+    slot_cache.insert((*address, *index), slot_val);
 }
Suggestion importance[1-10]: 7

__

Why: The code calls a non-existent .value() on CompleteValue; using .clone().take_value() properly retrieves the inner SlotValue for insertion and fixes the compilation and runtime behavior.

Medium

@stratus-benchmark

Copy link
Copy Markdown

Benchmark:
Run ID: bench-28d4a294

Git Info:

Leader Stats:
RPS Stats: Max: 8152.00, Min: 1480.00, Avg: 3037.73, StdDev: 351.83
TPS Stats: Max: 3557.00, Min: 213.00, Avg: 2992.01, StdDev: 300.85

Follower Stats:
Imported Blocks/s: Max: 8.00, Min: 3.00, Avg: 5.85, StdDev: 1.26
Imported Transactions/s: Max: 24309.00, Min: 8883.00, Avg: 17491.73, StdDev: 3826.05

Plots:

@stratus-benchmark

Copy link
Copy Markdown

Benchmark:
Run ID: bench-e064e3b5

Git Info:

Leader Stats:
RPS Stats: Max: 6747.00, Min: 1614.00, Avg: 2726.78, StdDev: 288.19
TPS Stats: Max: 3037.00, Min: 752.00, Avg: 2685.50, StdDev: 235.35

Follower Stats:
Imported Blocks/s: Max: 8.00, Min: 3.00, Avg: 6.47, StdDev: 1.41
Imported Transactions/s: Max: 22340.00, Min: 8215.00, Avg: 17370.06, StdDev: 3734.74

Plots:

@stratus-benchmark

Copy link
Copy Markdown

Benchmark:
Run ID: bench-9c4318e3

Git Info:

Leader Stats:
RPS Stats: Max: 10162.00, Min: 1495.00, Avg: 2875.98, StdDev: 456.51
TPS Stats: Max: 3211.00, Min: 195.00, Avg: 2821.09, StdDev: 275.64

Follower Stats:
Imported Blocks/s: Max: 9.00, Min: 2.00, Avg: 5.85, StdDev: 1.55
Imported Transactions/s: Max: 25916.00, Min: 5593.00, Avg: 16492.52, StdDev: 4581.75

Plots:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant