-
Notifications
You must be signed in to change notification settings - Fork 4
Implement stale while revalidate (SWR) cache strategy #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Implement stale while revalidate (SWR) cache strategy #35
Conversation
979705c to
ae8746b
Compare
|
@NoahFisher I still have to deal with some rubocop offenses and so one final review of the test coverage but I could use some feedback on this draft, I also have one open question to double check in the PR description. |
da9e215 to
d798182
Compare
43fcf9b to
7ff496b
Compare
Add three new configuration options to support SWR caching: - cache_stale_while_revalidate: Enable/disable SWR (default: false) - cache_stale_ttl: Grace period for serving stale data (default: 300s) - cache_refresh_threads: Background thread pool size (default: 5) SWR caching requires Rails cache backend and will be validated during configuration. This enables serving stale data instantly while refreshing in the background for better performance.
Restructure CacheEntry to support stale-while-revalidate pattern: - Replace single expires_at with fresh_until and stale_until - Add fresh? method: entry is fresh and can be served immediately - Add stale? method: entry is stale but usable (revalidate in background) - Update expired? method: entry must be revalidated synchronously This three-state model enables SWR caching where stale entries can be served instantly while a background refresh occurs.
Add full SWR caching support with concurrent background refresh: - fetch_with_stale_while_revalidate: Main SWR fetch method - Three-state handling: FRESH (instant), STALE (instant + refresh), MISS (sync fetch) - Concurrent thread pool for background refresh operations - Stampede protection during background refresh - Logger integration for debugging cache states - Graceful shutdown of thread pool The adapter now accepts stale_ttl and refresh_threads parameters. When stale_ttl is set, SWR is enabled. When nil, falls back to standard fetch_with_lock behavior. This provides instant responses for 99% of requests by serving stale data while refreshing in the background.
Update Client to pass SWR configuration options to RailsCacheAdapter: - Pass stale_ttl (only when cache_stale_while_revalidate is enabled) - Pass refresh_threads for background thread pool size - Pass logger for debugging and error reporting Extract create_rails_cache_adapter method for clarity. When SWR is disabled, stale_ttl is nil, keeping standard behavior. Tests verify correct configuration propagation and thread pool creation based on SWR settings.
Refactor get_prompt to choose optimal caching strategy based on available cache capabilities: 1. SWR cache (fetch_with_stale_while_revalidate) - best performance 2. Distributed cache (fetch_with_lock) - stampede protection 3. Simple cache (get/set) - basic in-memory caching 4. No cache - direct API fetch Extract separate methods for each strategy for better maintainability and testability. The strategy is selected at runtime based on which methods the cache adapter responds to. This enables automatic use of SWR when available without breaking existing cache implementations.
This was the pattern already in place. This commit simply expands the scope to some of the new spec examples added, however, it would be interesting to discuss whether we need to allow for a larger number of memoized helpers or if some refactoring is needed for these specs.
7ff496b to
ab7628d
Compare
Add spec examples to verify that SWR (Stale-While-Revalidate) is correctly enabled only when stale_ttl is positive, and disabled for zero or negative values. Adds thread pool initialization coverage for both cache adapters. All 785 tests pass with 97.36% coverage maintained.
|
@NoahFisher, @kxzk I think this is in a good spot for some review. Let me know if there are any concerns or change suggestions you'd like me to address. Thanks. |
|
My last commit is disabling Rubocop's |
Will try and review today. |
|
|
||
| # NOTE: expires_in is accepted for interface compatibility with StaleWhileRevalidate | ||
| # but not used here since CacheEntry objects manage their own expiration times | ||
| _ = expires_in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still want to get rid of this bit. This is a result of a refactoring I did, but I think I can improve this bit a little further.
| # api_client.get_prompt("greeting") | ||
| # end | ||
| def fetch_with_stale_while_revalidate(key, &) | ||
| return fetch_with_lock(key, &) unless swr_enabled? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up adding this fallback but now am realizing that might've been over engineered and it may be more interesting to raise an error if SWR is disabled and this method called.
Then I can move fetch_with_lock back to the rails adapter as originally this functionality was not available in PromptCache.
Summary
Add support to Stale-While-Revalidate (SWR) prompt cache strategy based off the Design Document: docs/future-enhancements/STALE_WHILE_REVALIDATE_DESIGN.md
The GIF below shows the new feature in action against a self-hosted production instance of Langfuse using the following config:
📊 Impact
:railsbackend)concurrent-rubygem for thread pool managementUsage
Configure the client:
Once configured, SWR works transparently:
More details on the proposed documentation.