fix(cache): serve uncached instead of 500 when the backend fails - #259
Merged
Merged
Conversation
@cache now fails open: a backend error on read is logged and treated as a miss, one on write is logged and the response is served unstored. This also covers Memcached rejecting a response over its item size limit. @cache(fail_open=False) lets the error propagate as before. Closes #228
This was referenced Sep 26, 2026
This was referenced Sep 26, 2026
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.
Closes #228.
Problem
@cachecalledbackend.get()before the handler andbackend.set()after it with no error handling. When the backend raised, every cached route returned 500, even when the handler had already produced a good response. A healthy Memcached does the same for any response over its 1 MB item size (MemcacheServerError: object too large for cache).Fix
@cachefails open by default:fastapi_cachex.cacheand treated as a cache miss, so the handler runs.@cache(fail_open=False)lets the error propagate as before, for routes that should fail loudly.Only the backend calls themselves are wrapped. Handler exceptions, request-validation errors and decoration-time
CacheXErrors are unaffected. A write failure does not stop reads: an entry stored earlier is still served.Not in this PR
The issue lists two more ideas, now tracked separately:
BackendUnavailableError(CacheXError)that Redis and Memcached raise for transport errors (Raise a backend-independent BackendUnavailableError for transport failures #260). It would change the exception types thatCacheManager,StateManagerand sessions surface, so it needs its own design.@cacheit is now covered by fail-open. ForCacheManagerandset()callers, silently dropping a write is a behaviour choice of its own.invalidate(),CacheManager,StateManager,CacheLockand sessions still raise backend errors. The docs say so.Tests
tests/test_cache_backend_failure.py:test_failing_backend_serves_the_handler_response[get|set|both]: 200 with ETag andCache-Control, the handler runs again, and the warning is logged.test_a_write_failure_does_not_hide_a_working_readtest_fail_open_false_propagates_the_error[get|set]test_response_over_the_memcached_item_size_is_served_unstored: a live Memcached test with a 2 MB body.Mutation check: with the old
cache.py, the three fail-open cases and the live Memcached test fail. The opt-out and read-still-works tests pass on both, since they guard the new behaviour. The full suite passes against live Redis and Memcached: 939 passed, 99.96% coverage. Both docs builds pass with--strict, and the zh-TW anchorwhen-the-backend-failsmatches the English one.Docs
### Changed, since the default behaviour changes.