Skip to content

fix(l1): bound RocksDB index and filter block memory#6735

Merged
MegaRedHand merged 11 commits into
mainfrom
fix/rocksdb-bounded-index-filter-memory
Jun 5, 2026
Merged

fix(l1): bound RocksDB index and filter block memory#6735
MegaRedHand merged 11 commits into
mainfrom
fix/rocksdb-bounded-index-filter-memory

Conversation

@ilitteri

@ilitteri ilitteri commented May 27, 2026

Copy link
Copy Markdown
Collaborator

Motivation

ethrex's RocksDB backend ties resident memory to database size with no upper bound. With max_open_files(-1) every SST stays open, and RocksDB's default (cache_index_and_filter_blocks = false) keeps each open file's index and bloom-filter blocks pinned in heap for the reader's lifetime — outside the bounded LRU. As on-disk state grows, so does the in-heap footprint, with no ceiling: operationally indistinguishable from a memory leak, and on a large enough database it eventually exhausts the host. On a ~500 GB mainnet DB this reached ~8 GB of pinned index/filter blocks (~6 GB of it bloom filters), driving resident memory to ~20 GB.

Description

1. Route index and filter blocks through the shared block cache.
Enables cache_index_and_filter_blocks(true) + pin_l0_filter_and_index_blocks_in_cache(true) on every column family. RocksDB stops pinning every open SST's index/filter blocks in heap and instead routes them through its bounded LRU. Total RocksDB resident memory now tracks the block cache size, not the database size.

2. Expose the block cache size as a CLI option.
Adds --rocksdb.block-cache-size <BYTES> (env ETHREX_ROCKSDB_BLOCK_CACHE_SIZE), default 12 GiB, plumbed through a StoreConfig struct and *_with_config constructor variants on Store and the init_store / load_store / open_store helpers; the zero-config constructors keep working with the default and are unchanged for tests, tools, and L2 callers.

Because filter/index blocks now share one bounded cache with data blocks, the cache size governs a memory vs. block-import-throughput trade-off: a cache too small to hold the filter+index working set plus useful hot data will stall execution. The default is sized to run smoothly on a 32 GB host (see the sweep below); the CLI help text states the trade-off and only recommends lowering it on memory-constrained hosts.

Branch is merged up to current main (v15). RocksDB statistics (used to gather the hit-rate numbers below) are disabled in the production path.

Validation

Heap profile (the unbounded growth)

A jemalloc profile of the unfixed baseline attributed ~92% of resident memory to RocksDB, dominated by ~8 GB of index/bloom-filter blocks. With the fix applied, the PrefetchIndexAndFilterBlocks allocations drop from ~8 GB to under 1 GB — the rest is demand-loaded into the bounded cache via GetOrReadFilterBlock.

Block-import benchmark (mainnet head-following, 60-block window, same chain segment)

A too-small cache lets filter blocks displace data and slows import; a sufficiently large cache is at parity with the unbounded baseline:

Stock baseline (unbounded) Fix @ 4 GiB Fix @ 20 GiB
Median block-import 35.4 ms 53.0 ms 31.5 ms
Mean block-import 38.1 ms 66.2 ms 36.4 ms
Ratio vs baseline (median) 1.50× 0.89×
RSS at ~500 GB DB 16 GB, climbing 7 GB, bounded 27 GB, bounded

Cache-size sweep (32 GB host) — picking the default

To choose a default that runs smoothly on memory-constrained hosts, swept the cache under a 32 GiB cgroup cap on a synced mainnet node, 45-min steady-state windows:

cache filter-block hit NVMe read slow blocks (>100 ms) mem pressure OOM RocksDB resident
16 GiB 99.999% 49 IOPS 0 ~0 0 ~20.5 GB floor
12 GiB (default) 99.997% 45 IOPS 0 ~0 0 ~16.5 GB floor
8 GiB 99.972% 43 IOPS 0 ~0 0 ~12.5 GB floor

All three keep up with head-following: filters stay resident, disk is near-idle, zero slow blocks, no memory pressure or OOM under the 32 GB cap.

  • 8 GiB is the floorfilter.miss first starts rising (the high-priority pool nears the hot filter working set).
  • Bigger than 12 GiB gives no improvement — disk stays idle regardless, because the OS page cache backstops the (uncompressed) state CFs 1:1; extra block cache just relocates caching from the kernel to RocksDB. This holds on larger hosts too (page cache scales with RAM).
  • 12 GiB is the default — the smallest size that keeps the filter/index working set resident with margin, bounds RocksDB resident memory to ~12 GiB, and leaves comfortable page-cache headroom on a 32 GB host.

Trade-off

At today's ~500 GB mainnet DB, a 12 GiB cache bounds RocksDB resident memory forever regardless of database growth, versus the unfixed baseline's ~16 GB and climbing (state DBs only grow). Operators who need a lower ceiling can reduce the cache size at some throughput cost (≥ 8 GiB recommended); the help text documents this.

… instead of

pinning them per open file. With max_open_files(-1) every SST stays open, and the
RocksDB default (cache_index_and_filter_blocks = false) keeps each file's index and
filter blocks in heap for the reader's lifetime, so table memory grows without bound
with the number of SST files. On a 490 GB mainnet DB this reached ~8 GB of pinned
index/filter blocks (~6 GB of it bloom filters), driving resident memory to ~20 GB.

Enabling cache_index_and_filter_blocks moves index and filter blocks into the bounded
block cache, capping total table memory at the cache size. pin_l0_filter_and_index_blocks_in_cache
keeps the hottest level's metadata resident to avoid a read-latency cliff on the cache.
@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown

⚠️ Known Issues — intentionally skipped tests

Source: docs/known_issues.md

Known Issues

Tests intentionally excluded from CI. Source of truth for the Known
Issues
section the L1 workflow appends to each ef-tests job summary
and posts as a sticky PR comment.

EF Tests — Stateless coverage narrowed to EIP-8025 optional-proofs

make -C tooling/ef_tests/blockchain test calls test-stateless-zkevm
instead of test-stateless. The zkevm@v0.3.3 fixtures are filled against
bal@v5.6.1, out of sync with current bal spec; the broad target trips ~549
fixtures. Re-broaden once the zkevm bundle is regenerated.

Why and resolution path

PR #6527 broadened
test-stateless to extract the entire for_amsterdam/ tree from the
zkevm bundle and run all of it under --features stateless; combined with
this branch's bal-devnet-7 semantics that scope produces ~549
GasUsedMismatch / ReceiptsRootMismatch /
BlockAccessListHashMismatch failures.

test-stateless-zkevm filters cargo to the eip8025_optional_proofs
suite, which still validates the stateless harness without the bal-version
mismatch.

Re-broaden by switching test: back to test-stateless in
tooling/ef_tests/blockchain/Makefile once the zkevm bundle is regenerated
against the current bal spec.

@github-actions github-actions Bot added the L1 Ethereum client label May 27, 2026
@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown

Lines of code report

Total lines added: 104
Total lines removed: 0
Total lines changed: 104

Detailed view
+------------------------------------------+-------+------+
| File                                     | Lines | Diff |
+------------------------------------------+-------+------+
| ethrex/cmd/ethrex/cli.rs                 | 1259  | +23  |
+------------------------------------------+-------+------+
| ethrex/cmd/ethrex/initializers.rs        | 798   | +28  |
+------------------------------------------+-------+------+
| ethrex/cmd/ethrex/l2/initializers.rs     | 392   | +4   |
+------------------------------------------+-------+------+
| ethrex/crates/storage/backend/rocksdb.rs | 451   | +13  |
+------------------------------------------+-------+------+
| ethrex/crates/storage/store.rs           | 2902  | +36  |
+------------------------------------------+-------+------+

ilitteri and others added 7 commits May 29, 2026 13:03
(--rocksdb.block-cache-size, env ETHREX_ROCKSDB_BLOCK_CACHE_SIZE) with a default
of 20 GiB. Because the previous commit moved index and bloom-filter blocks into
the bounded block cache, the cache size now governs total RocksDB resident memory
and significantly influences block-import throughput. Measured on a synced mainnet
node: at a 4 GiB cache, filter blocks monopolize the cache and block exec is ~76%
slower than the unbounded baseline; at 20 GiB the cache comfortably holds the
filter + index working set plus the EVM's hot data and exec is at parity. The
help text spells the trade-off out explicitly and only recommends lowering it on
resource-constrained hosts.

Plumbed through a new StoreConfig struct (exposed from ethrex-storage) and
Store::new_with_config / new_from_genesis_with_config /
{init,load,open}_store_with_config variants. The existing zero-config
constructors continue to use the default and remain unchanged for tests and
tools, so callers that don't need to override the cache size are unaffected.
…ndex-filter-memory

# Conflicts:
#	cmd/ethrex/initializers.rs
@iovoid iovoid force-pushed the fix/rocksdb-bounded-index-filter-memory branch 2 times, most recently from 2f10db8 to b1851f2 Compare June 4, 2026 20:10
@iovoid iovoid force-pushed the fix/rocksdb-bounded-index-filter-memory branch from b1851f2 to f235e8e Compare June 4, 2026 20:35
@iovoid iovoid marked this pull request as ready for review June 4, 2026 20:49
@iovoid iovoid requested review from a team as code owners June 4, 2026 20:49
@ethrex-project-sync ethrex-project-sync Bot moved this to In Review in ethrex_l1 Jun 4, 2026
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

🤖 Kimi Code Review

Overall Assessment: Well-structured PR that introduces configurable RocksDB block cache sizing with sensible defaults and good documentation. The change from hardcoded 4GB to configurable 12GB with proper index/filter block caching is a significant improvement for mainnet stability.

Critical Issue: 32-bit Compatibility

  1. cmd/ethrex/cli.rs:103 & crates/storage/store.rs:85 — The use of usize for rocksdb_block_cache_size and the 12 GiB default constant will cause compile-time overflows on 32-bit targets (where usize is 32 bits).
    • Fix: Define the constant and CLI field as u64, then convert to usize when calling Cache::new_lru_cache (with try_into().unwrap_or(usize::MAX) or similar). While Ethereum nodes typically run on 64-bit systems, the code should either be portable or explicitly gated with compile-time assertions.

Code Quality & Safety

  1. crates/storage/backend/rocksdb.rs:95-102 — The closure configure_block_cache correctly captures the block_cache reference. However, ensure that block_cache (the Cache object) outlives all column family descriptors. The current structure looks correct as the Cache is held by the RocksDBBackend struct and the CF descriptors are consumed during DB::open_cf_descriptors.

  2. crates/storage/store.rs:1665 — The #[cfg_attr(not(feature = "rocksdb"), allow(unused_variables))] attribute on config is appropriate, but consider adding a debug assertion or comment noting that the config is silently ignored when the rocksdb feature is disabled.

Performance & Observability

  1. Memory Pre-allocation — The documentation correctly notes that the LRU cache size is a ceiling, not a pre-allocation. Verify that Cache::new_lru_cache in the rust-rocksdb binding behaves as expected (it does not mmap or pre-fault the memory).

  2. Migration Safety — The change to cache_index_and_filter_blocks(true) alters memory pressure characteristics. Previously, filters were pinned unbounded; now they contend with data blocks. This is the intended behavior, but operators downgrading from this version to a previous version may see different memory profiles.

Nits

  1. docs/CLI.md — Contains trailing whitespace on blank lines (e.g., line 31, 36). This is cosmetic but unnecessary.

  2. Constant NamingDEFAULT_ROCKSDB_BLOCK_CACHE_SIZE_BYTES is clear, but consider adding _64 suffix or documentation explicitly stating "this value requires 64-bit architecture" until Item 1 is resolved.

Security: No vulnerabilities introduced. The cache size is purely a local performance tunable and does not affect consensus or network validation.

Recommendation: Address Item 1 (32-bit compatibility) before merge, or explicitly document that ethrex requires 64-bit architectures. The rest of the PR is solid and ready for merge.


Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

🤖 Claude Code Review

Now I have all the information needed for a thorough review. Here is the complete analysis:


PR #6735fix(l1): bound RocksDB index and filter block memory

Overview

This PR fixes an unbounded memory growth bug in the RocksDB backend. With max_open_files(-1) (every SST stays open) and the previous default of cache_index_and_filter_blocks = false, each open SST's index and bloom-filter blocks were pinned in heap for the file reader's lifetime — outside the bounded LRU cache. On a ~500 GB mainnet DB this accumulated ~8 GB of pinned filter/index blocks. The fix routes those blocks through the shared LRU cache and exposes cache size as a tunable CLI option.

The benchmarks and analysis in the PR description are thorough and well-reasoned. The implementation is clean.


Code Correctness

cache_index_and_filter_blocks + pin_l0_filter_and_index_blocks_in_cache combination
This is the correct pairing. Without pin_l0_filter_and_index_blocks_in_cache(true), a heavily-loaded LRU could evict L0 filter/index blocks and trigger read latency spikes on the hottest compaction level. Having both is the right call.

configure_block_cache closure (crates/storage/backend/rocksdb.rs:238–242)
Captures block_cache by shared reference. The closure is defined and consumed within the same function scope, so this is correct and there are no lifetime issues. It cleanly DRYs what was previously seven near-identical block_opts.set_block_cache(&block_cache) call sites.

new_with_config migration path (store.rs:1681–1688)
The config is correctly threaded into the migration RocksDBBackend::open call, so even during an upgrade the cache is bounded. Previously the migration also used the hardcoded 4 GiB path (now also fixed). Good.

l1_committer.rs:1277
Uses Store::new(path, InMemory) — the InMemory engine ignores config.rocksdb_block_cache_size, so this is unaffected by the change. No update needed there.


Medium Priority Issue

No lower-bound guard on --rocksdb.block-cache-size

Cache::new_lru_cache(block_cache_size) is called with a raw usize from the CLI. Passing 0 would create a degenerate LRU with zero capacity. The RocksDB C++ NewLRUCache(0) is implementation-defined — some versions return a valid-but-useless cache object, others have been observed to panic or return null in the Rust binding.

The help text says "≥ 8 GiB recommended" but nothing stops an operator from passing --rocksdb.block-cache-size 1 and hitting a confusing failure well after startup.

Suggested fix in cmd/ethrex/cli.rs:

value_parser = |s: &str| -> Result<usize, String> {
    let v: usize = s.parse().map_err(|e| format!("{e}"))?;
    if v == 0 {
        return Err("block-cache-size must be > 0".into());
    }
    Ok(v)
},

Or at minimum, add a runtime assertion in RocksDBBackend::open before the Cache::new_lru_cache call.


Minor Points

read_chain_id_from_db uses DEFAULT_ROCKSDB_BLOCK_CACHE_SIZE_BYTES for a one-shot read (store.rs:3633)

The comment correctly notes the LRU is a ceiling rather than a pre-allocated pool. However, it still creates the full LRU metadata structure (shard array, etc.) for what is a single key lookup. A tiny constant like 64 * 1024 * 1024 (64 MB) would express the intent more clearly and avoid any overhead from the LRU initialization, without changing semantics.

Test functions use the full 12 GiB constant (backend/rocksdb.rs:511, 558)

Both merge_operator_survives_flush_and_compaction and merge_operator_dedupes_across_compaction use DEFAULT_ROCKSDB_BLOCK_CACHE_SIZE_BYTES. Since the LRU is not pre-allocated, this does not affect test resource usage. It is functionally correct. A dedicated #[cfg(test)] const TEST_BLOCK_CACHE_SIZE: usize = 8 * 1024 * 1024; would make test intent clearer, but this is stylistic.

Unusual parameter attribute on new_with_config (store.rs:1644)

#[cfg_attr(not(feature = "rocksdb"), allow(unused_variables))] config: StoreConfig,

This is valid Rust but placing a #[cfg_attr] on a function parameter is uncommon enough to surprise readers. A short inline comment explaining why (// unused when the rocksdb feature is disabled) would help. Alternatively, the body could use let _ = config; inside the non-rocksdb arm, which is more visible.

Default impl field ordering in cli.rs (cli.rs:514)

The rocksdb_block_cache_size field is placed after force in the struct but after datadir in the Default impl. This is benign (Rust struct initialization is order-independent) but a minor inconsistency that could confuse a future reviewer adding a field.


What's Done Well

  • The StoreConfig / _with_config pattern is backward-compatible: every existing caller (tests, tools, L2 paths) that doesn't need the config keeps working unchanged by falling through to StoreConfig::default().
  • Both L1 (initializers.rs:590–595) and L2 (l2/initializers.rs:183–186) initialization paths are updated consistently.
  • The removed commented-out statistics code (// opts.enable_statistics()) is clean housekeeping.
  • CLI flag documentation is thorough and clearly explains the memory vs. throughput trade-off, the measured floor (8 GiB), and the reasoning behind the 12 GiB default.
  • DEFAULT_ROCKSDB_BLOCK_CACHE_SIZE_BYTES is a named constant (not a magic literal), properly re-exported from lib.rs, and used consistently across the migration bench tool, tests, and the chain-ID utility.

Summary

The fix is correct, well-benchmarked, and the implementation is clean. The only actionable item worth addressing before merge is the zero-value guard on block_cache_size — the rest are minor style notes. Overall this is a high-quality change.


Automated review by Claude (Anthropic) · sonnet · custom prompt

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

🤖 Codex Code Review

  1. cmd/ethrex/cli.rs:860, cmd/ethrex/cli.rs:988, and cmd/ethrex/cli.rs:1182 still open the store through init_store / load_store, which hard-code StoreConfig::default(). The new global --rocksdb.block-cache-size option is only threaded into the main L1/L2 startup paths, but the same Options object also drives import, import-bench, and export (cmd/ethrex/cli.rs:656-757). That means operators can explicitly lower the cache size and still get the 12 GiB default during long-running import/export workflows. On memory-constrained hosts this is a real operability/perf regression. These helpers should take StoreConfig (or the parsed cache size) and use the _with_config variants too.

Open question:

  • Was the flag intentionally meant to affect only node startup? If not, the subcommand paths above should be wired the same way as init_l1 / init_l2.

Aside from that, I did not find consensus/EVM/security-impacting issues in this PR; the storage change itself looks internally consistent. I could not run cargo check in this environment because cargo/rustup need writable home/git cache paths and dependency fetching is blocked here.


Automated review by OpenAI Codex · gpt-5.4 · custom prompt

@greptile-apps

greptile-apps Bot commented Jun 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes an unbounded memory growth issue in the RocksDB backend where index and bloom-filter blocks were pinned in heap per open SST file (outside the bounded LRU), growing proportionally with database size. The fix routes all index/filter blocks through the shared block cache via cache_index_and_filter_blocks(true) + pin_l0_filter_and_index_blocks_in_cache(true), and exposes cache size as a CLI option (--rocksdb.block-cache-size, default 12 GiB).

  • crates/storage/backend/rocksdb.rs: Replaces the hardcoded 4 GiB set_block_cache-only config with a configure_block_cache closure that also enables cache_index_and_filter_blocks and pin_l0_filter_and_index_blocks_in_cache on every column family; the cache size is now caller-supplied.
  • crates/storage/store.rs + lib.rs: Introduces StoreConfig and DEFAULT_ROCKSDB_BLOCK_CACHE_SIZE_BYTES, and adds new_with_config / new_from_genesis_with_config variants; all existing no-arg callers are unchanged.
  • cmd/ethrex/: Threads the CLI-supplied cache size through StoreConfig in both L1 and L2 init paths; zero-config callers (tests, tooling) keep using the default.

Confidence Score: 4/5

Safe to merge; the change correctly bounds RocksDB resident memory and is backward-compatible.

The core fix is sound and well-validated with extensive benchmark data. Both comments relate to the same gap in configure_block_cache: without cache_index_and_filter_blocks_with_high_priority and a high_pri_pool_ratio on the cache, non-L0 filter/index blocks share equal eviction priority with data blocks. The 12 GiB default keeps this from mattering in the mainnet sweep, but a workload with heavy data-block traffic could push filter/index blocks for deeper LSM levels out of cache.

crates/storage/backend/rocksdb.rs — the configure_block_cache closure and the cache construction line above it.

Important Files Changed

Filename Overview
crates/storage/backend/rocksdb.rs Core fix: routes index/filter blocks through bounded LRU via cache_index_and_filter_blocks(true) + pin_l0_filter_and_index_blocks_in_cache(true) on every CF; cache size is now caller-supplied. Missing cache_index_and_filter_blocks_with_high_priority means non-L0 filter/index blocks compete equally with data blocks under cache pressure.
crates/storage/store.rs Adds StoreConfig, DEFAULT_ROCKSDB_BLOCK_CACHE_SIZE_BYTES, and new_with_config/new_from_genesis_with_config variants; existing new/new_from_genesis correctly delegate to the new variants with defaults.
crates/storage/lib.rs Re-exports DEFAULT_ROCKSDB_BLOCK_CACHE_SIZE_BYTES and StoreConfig at the crate root; clean and minimal change.
cmd/ethrex/cli.rs Adds --rocksdb.block-cache-size CLI arg with env var ETHREX_ROCKSDB_BLOCK_CACHE_SIZE, default from the storage constant, and a well-written help string explaining the trade-off.
cmd/ethrex/initializers.rs Adds _with_config variants for all store init helpers; legacy helpers delegate to them with StoreConfig::default(); init_l1 threads the CLI-supplied cache size through correctly.
cmd/ethrex/l2/initializers.rs L2 init path switched to init_store_with_config, picking up the CLI-supplied block cache size from L1Options; correct plumbing.
tooling/migrations/src/bin/bench_migration.rs Updated call site for the new RocksDBBackend::open signature; uses the public default constant.
docs/CLI.md Auto-generated CLI help updated to include the new --rocksdb.block-cache-size option; trailing whitespace in existing entries is a side-effect of regeneration.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
crates/storage/backend/rocksdb.rs:113-117
Without `set_cache_index_and_filter_blocks_with_high_priority(true)` (paired with a `high_pri_pool_ratio` on the cache), all non-L0 filter/index blocks compete equally with data blocks in the LRU. Under a read-heavy workload that touches many L1+ SST files (e.g. state queries or a fast-sync replay), data blocks can displace filter/index blocks from the cache, increasing filter misses and triggering extra disk reads — the same class of performance degradation the PR fixes for the baseline. Adding high-priority routing for index/filter blocks gives the eviction policy a knob to keep them resident without growing the cache.

```suggestion
        let configure_block_cache = |block_opts: &mut BlockBasedOptions| {
            block_opts.set_block_cache(&block_cache);
            block_opts.set_cache_index_and_filter_blocks(true);
            block_opts.set_cache_index_and_filter_blocks_with_high_priority(true);
            block_opts.set_pin_l0_filter_and_index_blocks_in_cache(true);
        };
```

### Issue 2 of 2
crates/storage/backend/rocksdb.rs:102
**Cache `high_pri_pool_ratio` not set**

`pin_l0_filter_and_index_blocks_in_cache` pins L0 filter/index blocks via a ref-count, but the effectiveness of `set_cache_index_and_filter_blocks_with_high_priority` (if added) depends on the cache having a non-zero high-priority pool. Without `Cache::new_lru_cache` accepting a `high_pri_pool_ratio`, or using `LruCacheOptions` to set it, the high-priority designation is a no-op. The RocksDB recommendation for this configuration is to use a 10% high-priority pool (e.g. `0.1`) so filter/index blocks are protected from eviction by data blocks when the cache is under pressure.

Reviews (1): Last reviewed commit: "fix(ci): sync CLI.md help, fix rocksdb t..." | Re-trigger Greptile

Comment on lines +113 to +117
let configure_block_cache = |block_opts: &mut BlockBasedOptions| {
block_opts.set_block_cache(&block_cache);
block_opts.set_cache_index_and_filter_blocks(true);
block_opts.set_pin_l0_filter_and_index_blocks_in_cache(true);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Without set_cache_index_and_filter_blocks_with_high_priority(true) (paired with a high_pri_pool_ratio on the cache), all non-L0 filter/index blocks compete equally with data blocks in the LRU. Under a read-heavy workload that touches many L1+ SST files (e.g. state queries or a fast-sync replay), data blocks can displace filter/index blocks from the cache, increasing filter misses and triggering extra disk reads — the same class of performance degradation the PR fixes for the baseline. Adding high-priority routing for index/filter blocks gives the eviction policy a knob to keep them resident without growing the cache.

Suggested change
let configure_block_cache = |block_opts: &mut BlockBasedOptions| {
block_opts.set_block_cache(&block_cache);
block_opts.set_cache_index_and_filter_blocks(true);
block_opts.set_pin_l0_filter_and_index_blocks_in_cache(true);
};
let configure_block_cache = |block_opts: &mut BlockBasedOptions| {
block_opts.set_block_cache(&block_cache);
block_opts.set_cache_index_and_filter_blocks(true);
block_opts.set_cache_index_and_filter_blocks_with_high_priority(true);
block_opts.set_pin_l0_filter_and_index_blocks_in_cache(true);
};
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/storage/backend/rocksdb.rs
Line: 113-117

Comment:
Without `set_cache_index_and_filter_blocks_with_high_priority(true)` (paired with a `high_pri_pool_ratio` on the cache), all non-L0 filter/index blocks compete equally with data blocks in the LRU. Under a read-heavy workload that touches many L1+ SST files (e.g. state queries or a fast-sync replay), data blocks can displace filter/index blocks from the cache, increasing filter misses and triggering extra disk reads — the same class of performance degradation the PR fixes for the baseline. Adding high-priority routing for index/filter blocks gives the eviction policy a knob to keep them resident without growing the cache.

```suggestion
        let configure_block_cache = |block_opts: &mut BlockBasedOptions| {
            block_opts.set_block_cache(&block_cache);
            block_opts.set_cache_index_and_filter_blocks(true);
            block_opts.set_cache_index_and_filter_blocks_with_high_priority(true);
            block_opts.set_pin_l0_filter_and_index_blocks_in_cache(true);
        };
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same as the other, those two options have default values

// the size (see the `--rocksdb.block-cache-size` CLI flag); a value that is too
// small relative to the filter + working-set size will degrade block-import
// throughput (filter blocks displace data blocks, EVM reads spill to disk).
let block_cache = Cache::new_lru_cache(block_cache_size);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Cache high_pri_pool_ratio not set

pin_l0_filter_and_index_blocks_in_cache pins L0 filter/index blocks via a ref-count, but the effectiveness of set_cache_index_and_filter_blocks_with_high_priority (if added) depends on the cache having a non-zero high-priority pool. Without Cache::new_lru_cache accepting a high_pri_pool_ratio, or using LruCacheOptions to set it, the high-priority designation is a no-op. The RocksDB recommendation for this configuration is to use a 10% high-priority pool (e.g. 0.1) so filter/index blocks are protected from eviction by data blocks when the cache is under pressure.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/storage/backend/rocksdb.rs
Line: 102

Comment:
**Cache `high_pri_pool_ratio` not set**

`pin_l0_filter_and_index_blocks_in_cache` pins L0 filter/index blocks via a ref-count, but the effectiveness of `set_cache_index_and_filter_blocks_with_high_priority` (if added) depends on the cache having a non-zero high-priority pool. Without `Cache::new_lru_cache` accepting a `high_pri_pool_ratio`, or using `LruCacheOptions` to set it, the high-priority designation is a no-op. The RocksDB recommendation for this configuration is to use a 10% high-priority pool (e.g. `0.1`) so filter/index blocks are protected from eviction by data blocks when the cache is under pressure.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

set_cache_index_and_filter_blocks_with_high_priority is enabled by default and high_pri_pool_ratio has a default value of 0.5

Copilot AI review requested due to automatic review settings June 5, 2026 13:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

@MegaRedHand MegaRedHand left a comment

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.

LGTM

@MegaRedHand MegaRedHand enabled auto-merge June 5, 2026 16:13
@MegaRedHand MegaRedHand added this pull request to the merge queue Jun 5, 2026
Merged via the queue into main with commit 68a7d3b Jun 5, 2026
62 of 63 checks passed
@MegaRedHand MegaRedHand deleted the fix/rocksdb-bounded-index-filter-memory branch June 5, 2026 18:58
@github-project-automation github-project-automation Bot moved this from In Review to Done in ethrex_l1 Jun 5, 2026
edg-l added a commit that referenced this pull request Jun 9, 2026
edg-l added a commit that referenced this pull request Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L1 Ethereum client

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

7 participants