Skip to content

Add CLI and environment passthrough for RocksDB and ScyllaDB tuning options#6477

Draft
ma2bd wants to merge 1 commit into
mainfrom
mb/rocksdb-tuning-opts
Draft

Add CLI and environment passthrough for RocksDB and ScyllaDB tuning options#6477
ma2bd wants to merge 1 commit into
mainfrom
mb/rocksdb-tuning-opts

Conversation

@ma2bd

@ma2bd ma2bd commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Motivation

Many RocksDB and ScyllaDB tuning knobs are currently hardcoded in the backend
build()/session-construction code, so operators cannot adapt them to their
hardware or workload without recompiling. We want a way to pass a much larger
set of database options via CLI and environment, with the hard constraint that
changing an option must always be safe — it must never alter the on-disk
storage format or table schema.

Proposal

Add a generic key=value passthrough per backend, wired through the existing
CommonStorageOptionsadd_common_storage_options plumbing:

  • --rocksdb-opt KEY=VALUE (repeatable) / LINERA_ROCKSDB_OPTS (comma-separated).
    Supports the safe runtime/new-data knobs: write_buffer_size,
    max_write_buffer_number, the level_zero_* triggers, parallelism,
    max_background_jobs, max_subcompactions, target_file_size_base,
    block_cache_size, write_buffer_manager_size, max_open_files,
    block_size, bloom_filter_bits_per_key, compression_type.
  • --scylladb-opt KEY=VALUE (repeatable) / LINERA_SCYLLADB_OPTS.
    Supports driver/session options: consistency, request_timeout_ms,
    connection_pool_size_per_host, compression, token_aware.

Each backend parses its pairs into a typed *TuningOptions struct; unset fields
keep the current hardcoded default, so default behavior is unchanged. Unknown
keys, malformed pairs, and unparseable values are rejected with descriptive
errors. For RocksDB, format-defining keys (prefix_extractor, format_version,
compaction_style, whole_key_filtering, …) are explicitly rejected — this is
the safety guarantee. ScyllaDB create-time keyspace/table properties
(replication, compaction, table compression, gc_grace_seconds) are
intentionally out of scope here since they only apply at namespace creation.

Test Plan

  • New unit tests for both parsers: typed parsing, whitespace trimming, empty
    input, and every rejection path (unknown key, malformed pair, unparseable
    value, unknown enum token, format-sensitive keys, zero pool size).
  • A test that opens a real RocksDB store with overrides applied (exercises the
    conditionally-set max_open_files and cache-size paths through DB::open).
  • cargo clippy clean (with the repo's -D warnings) on linera-views,
    linera-storage-runtime, linera-indexer for rocksdb, scylladb, and the
    combined dual-feature build.
  • CLI.md regenerated; nightly rustfmt clean.

Release Plan

  • Nothing to do / These changes follow the usual release cycle.

Links

@github-actions

Copy link
Copy Markdown

Instruction Count Benchmark Results

Baseline: 2cb8d0afb3

Deterministic metrics — reproducible across runs (34 benchmarks)
Benchmark Instructions Total R+W
BucketQueueView
delete_500_from_1000 19,309 (No change) 28,738 (No change)
front_100_from_1000 5,618 (No change) 8,305 (No change)
pre_save_1000 41,856 (No change) 58,695 (No change)
push_1000 23,027 (-0.08%) 31,850 (-0.08%)
Cold Load
load_1000 690,668 (-0.00%) 1,006,134 (-0.00%)
CollectionView
indices_100 190,942 (+0.13%) 265,314 (+0.13%)
load_all_100_from_storage 618,638 (No change) 873,233 (No change)
load_all_100_in_memory 340,856 (No change) 477,890 (No change)
pre_save_100 258,767 (-0.01%) 357,786 (-0.01%)
try_load_10_from_100 101,174 (No change) 142,965 (No change)
MapView
contains_key_10_from_100 52,101 (No change) 73,487 (No change)
contains_key_10_from_1000 351,805 (No change) 497,010 (No change)
get_10_from_100 54,667 (No change) 77,326 (No change)
get_10_from_1000 354,362 (No change) 500,845 (No change)
get_100_missing_from_1000 599,266 (-0.00%) 832,117 (-0.00%)
indices_100 99,886 (No change) 137,775 (No change)
indices_1000 944,579 (-0.00%) 1,319,341 (-0.00%)
insert_100 254,653 (+0.01%) 347,556 (+0.01%)
insert_1000 2,922,806 (+0.00%) 3,915,144 (+0.00%)
post_save_1000 1,020,722 (-0.00%) 1,472,720 (-0.00%)
pre_save_100 322,026 (-0.01%) 447,667 (-0.01%)
pre_save_1000 3,277,197 (No change) 4,612,212 (No change)
remove_500_from_1000 1,191,908 (No change) 1,643,807 (No change)
QueueView
delete_500_from_1000 11,157 (No change) 13,231 (No change)
front_100_from_1000 7,818 (-0.24%) 12,017 (-0.20%)
pre_save_1000 1,114,522 (+0.00%) 1,588,028 (+0.00%)
push_1000 22,971 (+0.08%) 31,773 (+0.08%)
ReentrantCollectionView
contains_key_10_from_100 142,408 (No change) 202,216 (No change)
indices_100 235,831 (+0.01%) 330,249 (+0.01%)
load_all_100_from_storage 777,847 (No change) 1,097,423 (No change)
load_all_100_in_memory 415,401 (-0.00%) 573,659 (-0.00%)
pre_save_100 342,976 (+0.01%) 477,458 (+0.01%)
RegisterView
get_set_100 83,830 (+0.02%) 118,850 (+0.02%)
pre_save 5,411 (-0.35%) 7,981 (-0.30%)

Regression threshold: 1%${\color{red}\textbf{red}}$ = regression, ${\color{green}\textbf{green}}$ = improvement.

Cache-dependent metrics — expect fluctuations between runs (34 benchmarks)
Benchmark L1 Hits LLC Hits RAM Hits Est. Cycles
BucketQueueView
delete_500_from_1000 28,546 (+0.00%) 37 (${\color{green}\textbf{-2.63\%%}}$) 155 (No change) 34,156 (-0.01%)
front_100_from_1000 8,136 (-0.04%) 40 (${\color{red}\textbf{+8.11\%%}}$) 129 (No change) 12,851 (+0.09%)
pre_save_1000 58,224 (No change) 91 (No change) 380 (No change) 71,979 (No change)
push_1000 31,657 (-0.06%) 43 (${\color{green}\textbf{-10.42\%%}}$) 150 (-0.66%) 37,122 (-0.21%)
Cold Load
load_1000 997,608 (-0.00%) 8,357 (-0.04%) 169 (-0.59%) 1,045,308 (-0.01%)
CollectionView
indices_100 264,049 (+0.13%) 876 (+0.11%) 389 (${\color{red}\textbf{+1.57\%%}}$) 282,044 (+0.20%)
load_all_100_from_storage 868,741 (-0.00%) 3,836 (+0.03%) 656 (No change) 910,881 (+0.00%)
load_all_100_in_memory 475,767 (No change) 1,380 (No change) 743 (No change) 508,672 (No change)
pre_save_100 355,864 (-0.00%) 1,333 (-0.67%) 589 (-0.17%) 383,144 (-0.02%)
try_load_10_from_100 142,104 (+0.00%) 636 (-0.16%) 225 (No change) 153,159 (-0.00%)
MapView
contains_key_10_from_100 73,196 (-0.00%) 94 (${\color{red}\textbf{+2.17\%%}}$) 197 (No change) 80,561 (+0.01%)
contains_key_10_from_1000 493,841 (+0.00%) 2,972 (-0.03%) 197 (No change) 515,596 (-0.00%)
get_10_from_100 77,027 (-0.00%) 93 (${\color{red}\textbf{+2.20\%%}}$) 206 (No change) 84,702 (+0.01%)
get_10_from_1000 497,664 (No change) 2,976 (No change) 205 (No change) 519,719 (No change)
get_100_missing_from_1000 828,915 (-0.00%) 2,978 (-0.13%) 224 (-0.44%) 851,645 (-0.01%)
indices_100 137,139 (+0.00%) 240 (-0.41%) 396 (No change) 152,199 (-0.00%)
indices_1000 1,311,696 (-0.00%) 6,466 (-0.02%) 1,179 (-0.08%) 1,385,291 (-0.00%)
insert_100 346,820 (+0.00%) 98 (${\color{red}\textbf{+15.29\%%}}$) 638 (+0.16%) 369,640 (+0.03%)
insert_1000 3,908,080 (+0.00%) 3,095 (-0.23%) 3,969 (+0.03%) 4,062,470 (+0.00%)
post_save_1000 1,461,338 (-0.00%) 11,208 (-0.04%) 174 (-0.57%) 1,523,468 (-0.00%)
pre_save_100 446,308 (-0.00%) 758 (${\color{green}\textbf{-1.17\%%}}$) 601 (-0.17%) 471,133 (-0.02%)
pre_save_1000 4,598,314 (+0.00%) 10,093 (-0.01%) 3,805 (No change) 4,781,954 (-0.00%)
remove_500_from_1000 1,639,422 (+0.00%) 4,212 (-0.05%) 173 (No change) 1,666,537 (-0.00%)
QueueView
delete_500_from_1000 13,065 (+0.02%) 35 (${\color{green}\textbf{-7.89\%%}}$) 131 (No change) 17,825 (-0.07%)
front_100_from_1000 11,833 (-0.10%) 27 (${\color{green}\textbf{-28.95\%%}}$) 157 (-0.63%) 17,463 (-0.58%)
pre_save_1000 1,583,325 (+0.00%) 2,751 (+0.59%) 1,952 (+0.05%) 1,665,400 (+0.01%)
push_1000 31,569 (+0.04%) 53 (${\color{red}\textbf{+20.45\%%}}$) 151 (+0.67%) 37,119 (+0.25%)
ReentrantCollectionView
contains_key_10_from_100 200,984 (-0.01%) 1,032 (${\color{red}\textbf{+1.38\%%}}$) 200 (No change) 213,144 (+0.03%)
indices_100 328,688 (+0.01%) 1,201 (+0.33%) 360 (+0.28%) 347,293 (+0.02%)
load_all_100_from_storage 1,090,351 (-0.00%) 6,684 (+0.01%) 388 (No change) 1,137,351 (+0.00%)
load_all_100_in_memory 571,557 (-0.00%) 1,572 (-0.44%) 530 (-0.19%) 597,967 (-0.01%)
pre_save_100 474,389 (+0.00%) 2,387 (+0.21%) 682 (+0.15%) 510,194 (+0.02%)
RegisterView
get_set_100 118,632 (+0.01%) 42 (${\color{red}\textbf{+40.00\%%}}$) 176 (+0.57%) 125,002 (+0.08%)
pre_save 7,792 (-0.18%) 33 (${\color{green}\textbf{-21.43\%%}}$) 156 (-0.64%) 13,417 (-0.70%)

Cache metrics fluctuate because anything that changes the virtual memory layout
shifts which data lands on which cache lines, changing the L1/LLC/RAM distribution.
Probable causes: ASLR (even across identical binaries), executable binary size changes,
shared library size changes, and even filename length differences.

Cachegrind simulates a two-level cache (L1 + LLC) auto-detected from the host CPU.
Est. Cycles = L1 hits + 5 × LLC hits + 35 × RAM hits.

Runner cache sizes: L1d cache: 64 KiB (2 instances);L1i cache: 64 KiB (2 instances) L2 cache: 1 MiB (2 instances);L3 cache: 32 MiB (1 instance)

pull Bot pushed a commit to Mu-L/linera-protocol that referenced this pull request Jun 29, 2026
…-io#6511)

## Motivation

RocksDB backs the Linera client's local store, and the PM workers *are*
the client — so it's
now production-critical infrastructure. Yet the binary exports nothing
about RocksDB's internal
engine state (block-cache effectiveness, compaction / write
amplification, write stalls, LSM
structure). We have the metering-wrapper operation latencies but not the
engine internals needed
to understand or tune RocksDB — e.g. the write-stall / compaction
behaviour behind recent worker
incidents. Validators get rich ScyllaDB dashboards; the client's RocksDB
deserves the same.

## Proposal

Add opt-in export of RocksDB's internal statistics as Prometheus
metrics, off by default:

- Two flags on `CommonStorageOptions`: `--rocksdb-enable-statistics`
(default off) and
`--rocksdb-statistics-level` (default `except-histogram-or-timers`; one
of the six RocksDB
`StatsLevel`s). Off by default so clients that don't scrape metrics pay
nothing; enabled
  explicitly on the workers.
- `RocksDbStoreInternalConfig` gains `enable_statistics` +
`statistics_level` (`#[serde(default)]`),
wired in `build()` to `enable_statistics()` / `set_statistics_level()`.
- A lazy Prometheus `Collector` (behind `with_metrics`) that reads
cumulative tickers
(`get_ticker_count`) and instantaneous LSM state (`GetIntProperty`) at
scrape time — no
background task — exporting `linera_rocksdb_*` metrics: block-cache
hit/miss, compaction
read/write bytes, write-stall micros, flush bytes, bloom/memtable hits,
plus L0 file count,
pending-compaction bytes, write-stopped, SST sizes, memtable size, etc.

The default level (tickers only) is cheap — the op-latency histograms
(the expensive part) are
deliberately skipped, since we already export them from the metering
wrapper; bumping the level
turns on the engine histograms for latency investigation. Cumulative
tickers are exported as
gauges (the `prometheus` crate's `IntCounter` can't be set to an
external absolute without
stateful delta-tracking); they're monotonic so `rate()` works across
restarts. No format-defining
option is touched — this only toggles statistics collection.

## Test Plan

- Unit tests: the level `ValueEnum` parses and maps 1:1 to the views
enum; statistics are off by
default; the collector's metric definitions build unique, valid gauges.
- `cargo clippy --all-targets -- -D warnings` clean on `linera-views`
and `linera-storage-runtime`;
`cargo fmt`; `cargo doc` strict; `cargo machete`; `CLI.md` regenerated.
- The default-off path leaves `build()` behaviour identical (the new
blocks are no-ops when
  `enable_statistics` is false).

## Release Plan
- These changes should be backported to the latest `testnet` branch (the
workers run on
`testnet_conway`). No SDK or validator hotfix needed — this is
client/worker observability only.

## Links

- Complements linera-io#6477 (RocksDB/ScyllaDB tuning-option passthrough): these
metrics provide the
observability to drive those tuning knobs. No logical dependency; the
overlapping files will be
  rebased on whichever lands first.
- Follow-up: a RocksDB Grafana dashboard once the metrics are live;
optionally expose `PerfContext`
  for deep latency profiling.
- [reviewer
checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
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