Caching & Cloudflare Invalidation¶
7inOne serves anonymous traffic through a Cloudflare CDN in front of the
Plone backend. To keep the CDN cache consistent with content changes, the
caching feature tags every cacheable response and purges the matching
cache entries on Cloudflare whenever the underlying content changes. A Redis
job queue decouples the (potentially slow) Cloudflare API calls from the
request that triggered them and handles time-based (scheduled) purges.
Architecture Overview¶
The feature combines three moving parts:
Cache-Tag headers – every anonymous
GETresponse is tagged with the context’s UID andportal_type, so Cloudflare stores those tags alongside the cached object.CloudFlareClient – a Zope utility that talks to the Cloudflare purge_cache API. It can purge by tag, by URL prefix, or purge everything for a zone.
Redis (RQ) job queue – content-change events enqueue a background job that POSTs to the
@invalidateREST service, which collects the UIDs to purge and calls the client. Future-dated changes (effective / expiration / event end) are scheduled on a separate queue.
┌───────────────┐ content change ┌──────────────┐ enqueue ┌──────────┐
│ Plone event │──────────────────▶│ invalidate │────────────▶│ Redis │
│ subscribers │ │ hook() │ │ (RQ) │
└───────────────┘ └──────────────┘ └────┬─────┘
│ POST @invalidate
▼
┌──────────────┐ purge ┌──────────────┐
│ UID collector│────────────▶│ Cloudflare │
│ (per type) │ by tag │ purge API │
└──────────────┘ └──────────────┘
Note
Only anonymous GET responses are cached and tagged. Authenticated
backend traffic is never tagged and never cached at the CDN, so the whole
invalidation machinery only ever has to worry about the anonymous view of
the site.
Cache-Tag Header Model¶
For every successful anonymous GET request to a content object or the site
root, a publication subscriber sets two equivalent response headers:
Cache-Tag– consumed by Cloudflare for tag-based purging.X-Cache-Tag– the same value, exposed for debugging/inspection.
The default tag set for a content response is:
the context UID
the normalized portal_type (e.g.
contentpage,file)
Individual endpoints may add their own tags on top of these. For example the
@banner, @responsible and @responsible-backreferences endpoints append
generic tags (banner, responsible, responsible-backreferences) so that a
single change can invalidate every cached response that embedded that data,
regardless of which page it appeared on. When multiple tags apply to one
response they are merged into a single comma-separated Cache-Tag header.
Tag-Based vs. URL/Prefix Purging¶
The CloudFlareClient supports three purge strategies. Which one is used
depends on what changed.
Tag-based purge (purge_cache_by_tags / purge_cache_by_objs)
: The default mechanism. Given a set of UIDs (and generic tags), Cloudflare
drops every cached object carrying one of those tags. This is how normal
content edits are invalidated. Tags are purged in batches of at most 100
per Cloudflare request.
URL/prefix purge (purge_cache_by_prefixes)
: Used where there is no Cache-Tag to match – most importantly for
redirector (alternative URL) entries and the manual control-panel buttons.
A prefix is the host + path portion of a URL.
Purge everything (purge_everything)
: A full zone flush, triggered only by the Full Cache Purge button in the
control panel.
Important
Tag-based purging is zone-wide. A Cache-Tag purge clears the matching
objects across all subdomains of the zone in a single call – you never have
to enumerate subdomains for tag purges.
URL/prefix purging is not. A prefix purge only clears the exact host you name, so every subdomain that may have served the content has to be listed explicitly. This is why redirector invalidation expands each path into one URL per configured subdomain (see Subdomain Configuration).
404 responses have no Cache-Tag. Cloudflare cannot tag a not-found response, so a cached 404 can only be cleared via URL/prefix purging, never by tag.
Subdomain Configuration¶
Cloudflare credentials and per-domain settings live in a single JSON registry
record, wcs.backend.caching.mapping. Each top-level key is a registrable
domain mapped to its Cloudflare zone and the subdomains served from it:
{
"webcloud7.ch": {
"zoneid": "5d6d31b39f784ef1e6920e9fbfeaa9ab",
"apikey": "SHcw9lp0BjMv_R_-4IS26x2oNiqHZfwha6RtCAEm",
"subdomains": ["www"]
}
}
zoneid– the Cloudflare zone ID used in the purge API URL.apikey– the Cloudflare API token (sent as a Bearer token).subdomains– the subdomains served from this zone. Defaults to["www"]when omitted.
The subdomains list only matters for URL/prefix purges, where each path
is expanded into https://<subdomain>.<domain>/<path> for every entry. For
tag-based purges the list is irrelevant, because a tag purge already covers
the whole zone.
The Caching Control Panel¶
A dedicated control panel, 7inOne Caching (@@caching-controlpanel,
restricted to Manage portal), exposes both the configuration and a set of
manual purge actions.
Configuration section
Field |
Registry record |
Purpose |
|---|---|---|
Enable cache invalidation |
|
Master switch. While off, all automatic invalidation is skipped. |
API base URL |
|
The public API domain (used for redirector and API Cache purges). |
Cloudflare configuration |
|
The domain → zone/apikey/subdomains JSON shown above. |
The client only considers itself correctly configured when the domain extracted from the API base URL is present as a key in the mapping; otherwise automatic invalidation is treated as disabled and logged as a warning.
Manual invalidation section
API Cache – prefix-purges the configured API base URL.
Backend Cache – prefix-purges the backend site URL.
Full Cache Purge – purges everything for the configured preview (frontend) domain. The button is only shown when the preview domain is part of the mapping.
These buttons are an operational escape hatch; day-to-day invalidation is fully automatic via the collector model below.
The Collector Model¶
When something has to be invalidated, the system does not simply purge the one
object that changed. Each content type knows best which other cached
responses depend on it, so an IUIDPurgeCollector adapter is looked up for the
changed object and asked for the complete set of UIDs (and generic tags) to
purge. The @invalidate service feeds that set straight into a tag-based
Cloudflare purge.
Base collector¶
The default collector (DxUIDContentCollector, registered for all Dexterity
content) always returns:
the object’s own UID, plus
the UID of its parent, plus
the UIDs of all back-references to it (filtered by view permission and active/effective dates), plus
for Simplelayout pages, the UIDs of all blocks on the page.
A block collector additionally always pulls in its parent page, and the site root collector additionally pulls in the site’s default page. This recursive walk ensures that listing pages, referencing pages and embedding blocks are refreshed alongside the edited object.
Type-specific collectors¶
Several content types extend the base collector to cover the places their data is surfaced:
Collector |
Triggered for |
Adds to the purge set |
|---|---|---|
|
Banner |
The generic |
|
News items |
The UIDs of every News-listing block whose query would include this news item. |
|
Media folders (and the files/images inside) |
The media folder’s back-references and parent, so listing blocks and pages embedding the folder are refreshed when a file/image is added or changed. |
|
Pages with the Responsible Unit behavior |
The generic |
|
Content with flat-topic support |
The UIDs of every (anonymous-visible) TopicFilter block whose first result page actually contains this content, verified against Elasticsearch. |
What triggers a collection¶
Content-lifecycle event subscribers decide when a collection/purge runs:
Workflow transitions on published content, and working-copy apply (staging) – purge immediately.
Modification of content that has no workflow state – only schedules a future purge if the content has a future effective/expiration date.
Deletion / trashing of published content – purge immediately (trashing an already-trashed object is ignored).
Media folder file/image add or modify, and image crop changes – purge immediately.
Redirector (alternative URL) additions – purge synchronously by URL prefix, expanded across all configured subdomains.
Time-based purges (future effective date, expiration date, or event end) are enqueued on the important Redis queue and fire at the scheduled moment, so a page becomes visible or disappears from the cache exactly when it should.
The @invalidate REST Service¶
@invalidate is the single entry point that performs a tag-based purge for a
context. It runs the collector for the context, logs the resolved objects, and
asks the Cloudflare client to purge the collected UIDs. The background Redis
jobs described above call this service; you can also call it directly to force
an invalidation of a specific object and everything that depends on it.
The service is a POST to the object’s URL and returns the UIDs that were
purged together with the raw Cloudflare API responses.
JavaScript
async function invalidate(objectUrl, auth) {
const response = await fetch(`${objectUrl}/@invalidate`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Authorization': `Basic ${auth}`
}
});
return response.json();
}
// Usage
const result = await invalidate(
'https://backend.example.ch/Plone/topics/my-page',
btoa('admin:secret')
);
console.log(result.uids_to_purch); // ["abc123...", "def456...", "banner"]
console.log(result.cloudflare_response); // raw Cloudflare purge responses
Python
import requests
response = requests.post(
'https://backend.example.ch/Plone/topics/my-page/@invalidate',
headers={'Accept': 'application/json'},
auth=('admin', 'secret'),
)
data = response.json()
print(data['uids_to_purch'])
Example response:
{
"uids_to_purch": [
"8f1d2c3b4a5e6f7081920a1b2c3d4e5f",
"1a2b3c4d5e6f70819203a4b5c6d7e8f9",
"banner"
],
"cloudflare_response": [
{
"result": {"id": "5d6d31b39f784ef1e6920e9fbfeaa9ab"},
"success": true,
"errors": [],
"messages": []
}
]
}
uids_to_purch is the full set the collector resolved for the context (own
UID, parent, back-references, blocks, and any type-specific tags).
cloudflare_response is a list with one entry per Cloudflare purge request –
more than one when the tag count exceeds the 100-tags-per-request limit and the
purge is split into batches.