Skip to content

fix(cache): serve uncached instead of 500 when the backend fails - #259

Merged
allen0099 merged 1 commit into
masterfrom
fix/cache-fail-open-228
Sep 26, 2026
Merged

allen0099 merged 1 commit into
masterfrom
fix/cache-fail-open-228

Conversation

@allen0099

@allen0099 allen0099 commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

Closes #228.

Problem

@cache called backend.get() before the handler and backend.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

@cache fails open by default:

  • A read error is logged as a warning on fastapi_cachex.cache and treated as a cache miss, so the handler runs.
  • A write error is logged and the response is served unstored.
  • @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:

invalidate(), CacheManager, StateManager, CacheLock and 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 and Cache-Control, the handler runs again, and the warning is logged.
  • test_a_write_failure_does_not_hide_a_working_read
  • test_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 anchor when-the-backend-fails matches the English one.

Docs

  • HTTP_CACHING.md (EN and zh-TW): new "When the backend fails" section.
  • BACKENDS.md (EN and zh-TW): the Memcached item size limit is added to the limitations.
  • CHANGELOG: entry under ### Changed, since the default behaviour changes.
  • CLAUDE.md: one line.

@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
@allen0099 allen0099 added this to the 0.3.8 milestone Sep 26, 2026
@allen0099 allen0099 added the bug Something isn't working label Sep 26, 2026
@allen0099
allen0099 merged commit 2e5738b into master Sep 26, 2026
11 checks passed
@allen0099
allen0099 deleted the fix/cache-fail-open-228 branch September 26, 2026 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@cache: serve uncached instead of 500 when the backend fails

1 participant