Skip to content

Invalidate CDN cache on plain document edits - #439

Open
eyeseast wants to merge 5 commits into
masterfrom
400-invalidate-cache
Open

Invalidate CDN cache on plain document edits#439
eyeseast wants to merge 5 commits into
masterfrom
400-invalidate-cache

Conversation

@eyeseast

@eyeseast eyeseast commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Implements part 5 of the caching refactor (plans/cache-refactor.md): extend CDN cache invalidation, which today only fires from redactions, to plain edits, access flips, deletion, and page modifications — and switch the API purge to a doc-{id} Cache-Tag so one purge clears the bare URL and every ?expand=/Origin variant at once (purge-by-tag is available on the Business plan as of 2025-04-01). This is the prerequisite for the age-based TTL tiers in part 4.

What changed

  • DocumentViewSet.retrieve emits Cache-Tag: doc-{id} on both the 200 and 304 branches, so cached API responses become purgeable by tag.
  • perform_update batches a purge for every plainly-edited (non-processing) document — covers title/description/metadata/data edits and, critically, public→private access flips (the privacy case). The processing-completion path keeps using the existing cache_dirty mechanism, so there's no double purge.
  • Document.destroy() and page modifications now trigger invalidation (page mods ride the existing cache_dirty processing-completion purge, the same way redaction does).
  • invalidate_cache task is now variadic (*document_pks) and batched: one task per request instead of one per document. It clears cache_dirty via a queryset .update() so a purge no longer bumps updated_at (which would reset the Last-Modified freshness signal from part 3 / demote the doc to the shortest TTL tier in part 4).
  • Fixed invalidate_cache purging the wrong URL set on access flips (it branched on the already-flipped access, leaving the public copy cached).
  • Extracted the purge helpers into documentcloud/documents/cache.py; CLOUDFLARE_PURGE_LIMIT is now a setting (default 100).

files and tags are sent as separate Cloudflare requests (the zone purge API is a oneOf), each chunked to the per-request cap.

Tests

New test_cache.py and test_tasks.py, plus DocumentViewSet tests for the Cache-Tag header (200 + 304), edit/access/bulk/destroy purge enqueue, and the page-modification flag. Full documents suite passes (442); pylint 10/10.

Notes

  • plans/cache-refactor.md is included and records the design, decisions, and two deferred follow-ups (unify the two staleness conventions on cache_dirty; a CacheTaggedMixin so NoteViewSet can adopt tagging + purge).
  • No schema migrations. No behavior change for authenticated traffic.

🤖 Generated with Claude Code

eyeseast and others added 4 commits July 30, 2026 17:13
Extends the existing (redaction-only) cache invalidation to plain edits,
deletion, and page modifications, and switches the API purge to a
`doc-{id}` Cache-Tag so one purge clears the bare URL and every
?expand=/Origin variant at once (available on the Business plan).

- DocumentViewSet.retrieve emits `Cache-Tag: doc-{id}` on 200 and 304
- perform_update batches a purge for every plainly-edited (non-processing)
  instance, covering title/metadata edits and public<->private flips
- Document.destroy() and page modifications now trigger invalidation
- invalidate_cache task is variadic + batched, and clears cache_dirty via
  a queryset update so it no longer bumps updated_at on every purge
- fix invalidate_cache always purging the wrong URL set on access flips
- move the purge helpers into documentcloud/documents/cache.py
- CLOUDFLARE_PURGE_LIMIT is now a setting (default 100)

Covered by test_cache.py, test_tasks.py, and new DocumentViewSet tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post-implementation cleanup pass (reuse/simplification/efficiency/altitude):

- load only pk/slug in the invalidate_cache task; invalidate_cache_batch
  never touches the heavy columns
- bind pk eagerly in the _update_cache on_commit closure so it doesn't
  pin the whole Document until commit (matches _invalidate_edited_cache)
- collapse the repeated requests.post patch in test_cache.py into a fixture

Also records two deferred follow-ups in the plan: unifying the two
staleness conventions on cache_dirty, and a CacheTaggedMixin so NoteViewSet
can adopt Cache-Tag + purge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@eyeseast
eyeseast requested review from duckduckgrayduck and mitchelljkotler and removed request for mitchelljkotler July 30, 2026 23:54
Use @pytest.mark.usefixtures for the mock_post side-effect fixture in the
CloudFront test, which asserts on boto3 rather than the requests mock.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant