Skip to content

cache: avoid repeated multi-level capability checks - #2910

Open
codeandsolder wants to merge 2 commits into
mozilla:mainfrom
codeandsolder:upstream-pr/multilevel-cache-mode-once
Open

codeandsolder wants to merge 2 commits into
mozilla:mainfrom
codeandsolder:upstream-pr/multilevel-cache-mode-once

Conversation

@codeandsolder

Copy link
Copy Markdown
Contributor

Fixes #2818.

Summary

  • cache each multi-level backend's effective CacheMode in an async OnceCell
  • reuse the cached mode on writes instead of calling Storage::check() per write
  • skip levels known to be read-only while preserving the configured write-error policy
  • preserve underlying write-error sources when adding level context

A normal server startup still performs the existing storage capability check before serving. Required probe failures keep their existing startup-failure behavior; tolerated failures remain best-effort.

Validation

  • full Rust 1.99 library suite: 577 passed, 0 failed
  • focused Rust 1.99 regressions verify a backend mode is checked once across multiple writes and tolerated check failures are not re-probed per write
  • cargo fmt and git diff --check
  • Mozilla Rust 1.91 exact CI Clippy gate passes

@codecov-commenter

codecov-commenter commented Oct 18, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.38272% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 75.40%. Comparing base (d72ff0b) to head (becbbba).

Files with missing lines Patch % Lines
src/cache/multilevel.rs 98.90% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2910      +/-   ##
==========================================
- Coverage   78.13%   75.40%   -2.74%     
==========================================
  Files          74       74              
  Lines       43570    43637      +67     
==========================================
- Hits        34043    32904    -1139     
- Misses       9527    10733    +1206     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codeandsolder-git-wrangler
codeandsolder-git-wrangler Bot force-pushed the upstream-pr/multilevel-cache-mode-once branch 3 times, most recently from d3b2f5d to 669e5a8 Compare October 18, 2026 07:19
Comment thread src/cache/multilevel.rs
Ok(())
}

fn record_write_result(

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.

the L0 branch and write_remaining_levels_async still do the same stats bookkeeping by hand, could they use this too?

Comment thread src/cache/multilevel.rs Outdated
if result.is_err() {
inc_stat!(stats, write_failures, 1);
}
result.with_context(|| format!("failed to write to cache level {idx}"))?;

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.

the server logs this with {} (server.rs:1705), so only "failed to write to cache level N" shows up now, without the cause.
could you please keep the cause in the message?

Comment thread src/cache/multilevel.rs Outdated
result.with_context(|| format!("failed to write to cache level {idx}"))?;

inc_stat!(stats, writes, 1);
let duration_nanos = u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX);

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.

the other places still use as u64, please do the same everywhere

Comment thread src/cache/multilevel.rs Outdated
if let Some(l0) = self.levels.first() {
// Check if L0 is read-only before attempting write
if matches!(l0.check().await, Ok(CacheMode::ReadOnly)) {
if matches!(self.level_mode(0).await?, CacheMode::ReadOnly) {

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.

before, a failing check still tried the L0 write; now the put fails, and since errors aren't cached the next write probes again.
is that on purpose?

Comment thread src/cache/multilevel_test.rs Outdated
}
}

async fn assert_check_is_readwrite(storage: &MultiLevelStorage) {

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.

this refactor of the existing tests isn't related, please drop it from this PR

@codeandsolder-git-wrangler
codeandsolder-git-wrangler Bot force-pushed the upstream-pr/multilevel-cache-mode-once branch from 669e5a8 to 581be09 Compare October 18, 2026 09:10
Comment thread src/cache/multilevel.rs
}
}

async fn write_level_mode(&self, idx: usize) -> CacheMode {

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.

level_mode and write_level_mode are almost the same, could they be one function?

@codeandsolder-git-wrangler
codeandsolder-git-wrangler Bot force-pushed the upstream-pr/multilevel-cache-mode-once branch from 581be09 to 4b3f91f Compare October 18, 2026 20:11
Comment thread src/cache/multilevel.rs Outdated
}

async fn level_mode(&self, idx: usize, tolerate_check_failure: bool) -> Result<CacheMode> {
if let Some(mode) = self.level_modes[idx].get() {

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.

two concurrent first writes will both run check() here.
could it be one get_or_try_init instead? shorter too

Comment thread src/cache/multilevel.rs Outdated

match self.levels[idx].check().await {
Ok(mode) => Ok(*self.level_modes[idx].get_or_init(|| async { mode }).await),
Err(error) if !tolerate_check_failure && self.check_failure_is_fatal(idx) => Err(error),

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.

the old "Error checking required cache level N" warning is gone, so this error no longer says which level failed.
could you please add the level as context?

Comment thread src/cache/multilevel.rs

/// Write to levels starting from `start_idx` asynchronously
async fn write_remaining_levels_async(&self, key: &str, data: &Bytes, start_idx: usize) {
async fn write_remaining_levels_async(

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.

with true, level_mode never returns Err, so this Result and the ? at the callers are dead, no?

.unwrap();
});

assert_eq!(l0.check_count(), 1);

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.

please also check that both entries actually ended up in l0

@codeandsolder-git-wrangler
codeandsolder-git-wrangler Bot force-pushed the upstream-pr/multilevel-cache-mode-once branch from a7ce19a to becbbba Compare October 20, 2026 01:26
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.

Multi-level calls check() on every write, adding a remote read+write per compilation

3 participants