Conversation
First step towards #1889: a CDN in front of the registry needs to be told what to drop when something changes, and telling it by URL does not scale - one published version changes the extension JSON, its target-platform and version variants, the version lists, the gallery's view of it, the namespace and the files, each a different URL and several of them carrying query parameters. Name the thing instead. Every response about a namespace or an extension now carries the keys it is made of - ns/<namespace> and ext/<namespace>/<name> - so a purge can name what changed and every cached response carrying that key goes, whatever its URL. The vocabulary matches what CacheService already evicts internally: extensions and namespaces, never single versions. Nothing acts on the header yet, and nothing here decides whether a response may be cached: that stays with the Cache-Control each endpoint sets. Keys are lower-cased because names are matched that way, and tagged in preHandle because a response that streams its body is committed before postHandle runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Second step of #1889, on top of the surrogate keys: something has to send the purge. CacheService already knows every point at which a cached answer stopped being true - a version published, an extension deprecated or deleted, a namespace changed - and a CDN is one more cache, so the purge goes out from there, in the same terms the responses are tagged with. Two things the service takes care of for its callers: - After the commit, never inside it. Those eviction points run inside a transaction, and a purge sent there races the commit: the CDN refetches, gets the row as it was, and caches that - leaving it staler than if nothing had been purged. Rolled back transactions purge nothing. - One purge per transaction. Publishing evicts the extension JSONs, the latest version and the namespace details separately: three calls naming two keys, sent once. The purge itself is a JobRunr job, so a CDN that is briefly unreachable is retried rather than losing the purge, and a slow API never holds up a publish. A purge that fails for good is counted, because it is invisible from outside: the registry answers correctly while the CDN keeps serving what it had. Only Fastly is implemented, and only where a service id and token are configured. Everything is off by default, so a registry with no CDN in front of it does no extra work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 15, 2026
netomi
added this pull request to stack #2218
September 15, 2026 19:58
This was referenced Sep 15, 2026
3 tasks
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.
Second step towards #1889. Stacked on #2214 — review that first; this PR's diff against it is the purge machinery alone.
What sends the purge
CacheServicealready knows every point at which a cached answer stopped being true: a version published, an extension deprecated or deleted, a namespace changed, a review posted. A CDN is one more cache, so the purge goes out from there, in the terms the responses are already tagged with (ext/<namespace>/<extension>,ns/<namespace>).Two things
CdnPurgeServicetakes care ofAfter the commit, never inside it. Those eviction points run inside
@Transactionalmethods. A purge sent there races the commit: the CDN refetches, gets the row as it was, and caches that — leaving it staler than if nothing had been purged at all. The keys are collected on the transaction and sent fromafterCompletion, and a transaction that rolled back purges nothing, because whatever it was going to change did not happen.One purge per transaction. Publishing a version evicts the extension JSONs, the latest version and the namespace details separately — three calls naming two keys. They are collected into a set and sent once.
Delivery
The purge is a JobRunr job, following the pattern already used for mail and migrations:
openvsx_cdn_purge_failed_keys_total), because it is invisible from the outside — the registry answers correctly while the CDN keeps serving what it had, and the only symptom is a reader seeing an old version until the cached response expires.Fastly's bulk purge takes at most 256 keys, so larger sets are split. Soft purge is on by default: it marks the cached responses stale instead of dropping them, so the CDN keeps serving while it refetches and publishing a popular extension does not send everyone asking for it to the origin at once.
Configuration
Everything is off unless a provider is named, so a registry with no CDN in front of it does no extra work and needs no configuration. A half-configured provider is treated as no provider rather than as a startup failure — the registry serves fine without purging, and failing to boot over a CDN credential would be a poor trade.
ovsx.cdn.purge.providerovsx.cdn.purge.fastly.service-idovsx.cdn.purge.fastly.api-tokenpurge_selectscope)ovsx.cdn.purge.fastly.softtrueovsx.cdn.purge.fastly.api-urlhttps://api.fastly.comAll five documented in
doc/configuration.md, under a new CDN Cache Purging section.Testing
CdnPurgeServiceTest— one purge for everything a transaction changed, nothing before the commit, nothing at all for a rollback, straight through outside a transaction, nothing without a configured provider, and no leakage of one transaction's keys into the next.FastlyCdnPurgeClientTest— the request Fastly actually receives (URL,Fastly-Key,Fastly-Soft-Purge, body), soft purge off, batching past 256 keys, and a failure surfacing rather than being swallowed.CdnPurgeJobRequestHandlerTest— counters on success and failure, the rethrow that lets JobRunr retry, and the case where the provider was unconfigured between a job being enqueued and it running.Full server suite green (1443 tests); pre-commit hooks pass, including the configuration-documentation check.
Next
The TTLs this makes safe to raise —
Surrogate-Controlfor the CDN kept separate from the shortCache-Controlbrowsers get, since a browser cache cannot be purged. Files stay out of it: they are served by redirect to storage, so their cached response carries no key and is not purgeable yet.🤖 Generated with Claude Code