--max-database-cache-size is applied per RocksDB instance, not as a process-wide budget
Summary
--max-database-cache-size is documented/used as a single cache-size knob, but CombinedDatabase::open opens multiple independent RocksDB instances and passes the same DatabaseConfig.cache_capacity
to each of them. That multiplies intended cache RAM by the number of DBs and can push the process toward OOM, especially when operators size the flag as if it were a global budget.
There is already an in-code acknowledgment:
// TODO: Use different cache sizes for different databases
in crates/fuel-core/src/combined_database.rs (CombinedDatabase::open).
RocksDB instances opened
CombinedDatabase::open creates a separate RocksDB for each logical database:
| # | Database | Always? |
|---|---|---|
| 1 | on_chain |
yes |
| 2 | off_chain |
yes |
| 3 | relayer |
yes |
| 4 | gas_price |
yes |
| 5 | compression |
yes |
| 6 | block_aggregation_storage |
only with rpc feature (enabled in production) |
So production-shaped builds open 6 RocksDBs; non-rpc rocksdb builds open 5. |
How the flag is wired
- CLI (
bin/fuel-core/src/cli/run.rs):--max-database-cache-size→max_database_cache_size: Option<usize>- Mapped into
CombinedDatabaseConfig.database_config.cache_capacity
CombinedDatabase::openclones that config for every DB:max_fdsis divided (saturating_div(4)) across DBscache_capacityis not divided — each DB gets the full value (DatabaseConfig { max_fds, ..database_config })
- Per-instance allocation (
crates/fuel-core/src/state/rocks_db.rsinRocksDb::open_with):- block cache =
capacity / 3 - row cache =
capacity / 3 - remaining ~
1/3left for other RocksDB memory use per the local comments
- block cache =
Impact
If an operator sets --max-database-cache-size=N expecting ~N bytes of RocksDB cache for the process:
- Actual configured cache budget is closer to
5×Nor6×N(one full capacity per DB), before other RocksDB / process overhead. - Smaller / colder DBs (e.g. relayer, gas price, compression) do not need the same cache as on-chain/off-chain, so RAM is wasted on them.
- To avoid OOM, operators must set a lower value than the “real” desired on-chain cache, which under-provisions the hot DBs. This is especially painful in memory-constrained environments (k8s limits, sentry/archive nodes) where OOMKill is a real failure mode.
Contrast with FD handling
File descriptors are already shared across DBs (max_fds / 4), while cache capacity is not. Note the divisor 4 also looks stale relative to the current 5–6 DB count — related cleanup opportunity when
fixing cache budgeting.
Expected behavior (proposal)
- Expose per-database cache knobs (or a weighted profile) instead of a single flat value that is for a single DB instance only.
Reproduction / inspection
- Start
fuel-corewith RocksDB and--max-database-cache-size=<N>. - Observe startup log from
CombinedDatabase::from_config(single cache size logged once). - Confirm in
CombinedDatabase::openthat eachDatabase::open_rocksdb(...)receives the samecache_capacity. - Compare RSS / cgroup memory vs
N— growth tracks roughlyinstances × N, notN.
Related code
bin/fuel-core/src/cli/run.rs—--max-database-cache-sizecrates/fuel-core/src/combined_database.rs—CombinedDatabase::open(TODO + sharedcache_capacity)crates/fuel-core/src/state/rocks_db.rs— block/row cache fromcache_capacity
Source: FuelLabs/fuel-core