Amortize STATIC_REVALIDATE with a per-file TTL and single cache lookup#244
Merged
Conversation
Performance: - STATIC_REVALIDATE=on now re-checks a cached file's mtime at most once every 3 seconds per file (STATIC_REVALIDATE_TTL) instead of stat()-ing on every request. Within the window the entry is served under a shared read lock with no syscall; ContentEntry carries a last_checked: Instant to bound the check. - Combine the 304 check and content fetch into one FileCache::lookup() returning a Lookup enum, so a served static hit performs at most one stat() (previously check_304_headers() + get_content() could each stat()). serve() now makes a single cache access. - Single-flight revalidation via claim-then-stat: the slow path claims the window under the write lock (bumping last_checked) before releasing it and running stat() unlocked, so a thundering herd on one file collapses to one syscall per window and the syscall never holds the content lock. - Restore LRU promotion in revalidation mode via get_mut() on the slow path, so a hot file is promoted to MRU once per window and is not evicted ahead of colder, later-inserted entries under byte-budget pressure. Refactor: - Replace FileCache.validate_content: bool with revalidate_ttl: Option<Duration>; add with_revalidation_ttl() (None disables, Some(ZERO) revalidates every hit). with_revalidation(_, bool) maps on to the 3s TTL. - Remove the private revalidate_locked() helper; lookup() is now the single revalidation path. get_content() and check_not_modified() become #[cfg(test)] thin wrappers over lookup(), eliminating duplicated revalidate-then-build logic. - Guard slow-path eviction on the stat'd mtime so a concurrently-reinserted fresh entry is not discarded. Docs: - Update static-files and configuration references (en/ru/zh) and CHANGELOG to describe the once-per-3s window and within-3s visibility instead of per-hit revalidation. Default remains off. Tests: - Add test_content_cache_revalidation_window_keeps_entry (change masked within the window) and test_revalidation_promotes_hot_entry_on_access (hot entry survives eviction); switch the immediate-eviction tests to a zero-TTL cache.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes
STATIC_REVALIDATE=oncheap enough to be a sane choice in development by amortizing its filesystem check, while collapsing the static-serve cache path to a single access. The default staysoff.Previously
STATIC_REVALIDATE=onperformed astat()on every cache hit (and the serve path couldstat()twice — once for the 304 check, once for the content fetch), all under a write lock. This made the mode too expensive to leave on.What changed
Per-file TTL revalidation. A cached file's mtime is re-checked at most once every 3 seconds per file (
STATIC_REVALIDATE_TTL), not per request. Within the window the entry is served under a shared read lock with no syscall. On-disk changes become visible within 3 seconds.Single combined lookup.
FileCache::lookup()resolves the conditional (304) check and the content fetch in one cache access and returns aLookupenum, so a served static hit performs at most onestat()(was up to two).serve()now makes one cache access.Single-flight revalidation (claim-then-stat). When the window expires, the slow path claims it under the write lock (bumping
last_checked) before releasing the lock and runningstat()unlocked. Concurrent callers then see a fresh window and skip their own syscall, so a thundering herd on one file collapses to a singlestat()per window — and the syscall never holds the content lock.LRU promotion preserved. The slow path uses
get_mut()to promote a hot file to MRU once per window, so it is not evicted ahead of colder, later-inserted entries under the 64 MiB byte-budget pressure.Consolidation. The private
revalidate_locked()helper is removed;lookup()is the single revalidation path.get_content()/check_not_modified()are now#[cfg(test)]thin wrappers overlookup(), removing duplicated revalidate-then-build logic.Tradeoffs (intentional)
stat()is a synchronousstd::fscall on the Tokio worker (notspawn_blocking): on a local FS a stat is far cheaper than blocking-pool dispatch, and it now runs at most once per window per file. Deployments with a slow/network static root (NFS, sshfs) should keep revalidation off.Docs
static-filesandconfigurationreferences (en/ru/zh) and the CHANGELOG now describe the once-per-3s window and within-3s visibility instead of per-hit revalidation.Testing
cargo fmt --check,cargo clippy --all-targets --no-default-features -D warnings— cleancargo test --no-default-features— 865 lib tests pass, no failures across the suitetest_content_cache_revalidation_window_keeps_entry,test_revalidation_promotes_hot_entry_on_access; immediate-eviction tests switched to a zero-TTL cache.