Cloudflare Cache Purge Tier Limits And Sla Capacity
Cloudflare Cache Purge Tier Limits & SLA Capacity Evaluation
Source Issue: #574
Status:uningested(Point-in-time write-up pending/docs distillalignment)
1. Context & Objective
In SiteSwarm, public client applications (apps/<client>) serve high-volume web traffic directly from Cloudflare’s Anycast global edge cache. When a small business owner updates operational details (such as operating hours, menu specials, or urgent announcements) via their dedicated app-hosted CMS worker (Worker B), the update must be globally visible to public visitors in under 2 seconds without triggering a continuous integration or static site deployment cycle.
To achieve this freshness guarantee without exposing privileged Cloudflare credentials to tenant code, SiteSwarm introduced @siteswarm/service-cache-purge (as part of Epic #560). This platform worker provides an asynchronous, queue-backed invalidation pipeline (PURGE_QUEUE + PURGE_KV + PURGE_DLQ) featuring:
- Ingress coalescing (5-second hash window in KV) to collapse rapid editing bursts;
- Consumer batch deduplication across tenants;
- Deficit Round Robin (DRR) fair-share chunk scheduling to interleave Cloudflare API calls across tenants;
- Isolated HTTP 429 backpressure and dead-letter queue routing.
Research Spike Objectives
This empirical spike investigates and resolves key architectural questions regarding Cloudflare’s purge infrastructure and SiteSwarm’s multi-tenant capacity:
- Cloudflare Plan Tier Quota Matrix: Quantify the definitive rate limits, burst capacities, and batch constraints across Free, Pro, Business, and Enterprise plans for Tag, Hostname, Prefix, and URL purge selectors.
- Account-Level Quota Pooling: Evaluate how Cloudflare pools purge tokens across zones on the same plan and the resulting noisy-neighbor risks for multi-tenant architectures.
- CMS Update Footprint: Compare single-file (URL) purges against surrogate
Cache-Tagpurges in terms of API calls, blast radius, and edge invalidation efficiency. - SLA Capacity & Timing Model: Dissect the end-to-end invalidation latency chain, comparing Cloudflare’s sub-second edge purge propagation against SiteSwarm’s 5-second queue batch buffer, KV ingress coalescing window, and DRR chunking.
- Candidate Distillation Updates: Formulate concrete updates for living architectural specifications (
docs/architecture/APP_SERVING_CACHE.mdanddocs/architecture/MULTI_WORKER_PLATFORM_SERVICES.md) and document all unaddressed scope under decisions intentionally deferred.
2. Technical Findings & Architecture
2.1 Definitive Cloudflare Plan Tier Quota Matrix
Cloudflare’s cache purge architecture underwent a major revision on April 1, 2025: all purge selectors (Tag, Hostname, Prefix, URL, and Purge Everything) became available on all plan tiers. However, the underlying rate limits, token bucket sizes, and burst capacities remain strictly stratified by plan tier:
| Plan Tier | Request Rate Limit (Tag, Hostname, Prefix, Everything) | Token Bucket Burst Capacity | Max Operations per API Request | Single-File (URL) Rate Limit | Single-File Max URLs per Request | Account-Level Pooling Scope |
|---|---|---|---|---|---|---|
| Free | 5 requests / min (~0.083 req/s) | 25 tokens | 100 items | 800 URLs / sec (moving avg) | 100 URLs | Shared across all Free zones in account |
| Pro | 5 requests / sec (300 req/min) | 25 tokens | 100 items | 1,500 URLs / sec (moving avg) | 100 URLs | Shared across all Pro zones in account |
| Business | 10 requests / sec (600 req/min) | 50 tokens | 100 items | 1,500 URLs / sec (moving avg) | 100 URLs | Shared across all Business zones in account |
| Enterprise | 50 requests / sec (3,000 req/min) | 500 tokens | 100 items (tags/hosts/prefixes) | 3,000 URLs / sec (moving avg) | 500 URLs | Shared across all Enterprise zones in account |
Key Architectural Findings:
- Account-Level Token Bucket Pooling:
- Cloudflare enforces purge rate limits per account, not per zone.
- All zones in an account that share a plan tier draw from the same token bucket. For instance, if an agency hosts 40 client zones on Pro plans, all 40 zones collectively share the 5 req/sec refill rate and 25-token burst bucket.
- If one tenant triggers an unthrottled purge loop, every other tenant in the account on that plan tier faces HTTP 429 rejections.
- If an account contains zones across mixed plans (e.g., Free staging zones and Pro production zones), each plan tier operates an independent shared token bucket.
- Unified Purge & Invalidation Bucket:
- Purge requests (
/purge_cache) and Invalidation requests (/invalidate_cache) consume from the exact same account token bucket. Heavy cache revalidation directly depletes purge throughput.
- Purge requests (
- Workers Caching (
ctx.cache.purge()) Constraint:- Native Workers Caching attaches to the Worker script rather than the zone. Consequently,
ctx.cache.purge()is permanently locked to Free tier limits (5 req/min, 25 burst) regardless of account plan tier. - This finding validates SiteSwarm’s architecture: client workers must never call
ctx.cache.purge()directly. Instead, they must delegate to@siteswarm/service-cache-purgevia Service Binding, which invokes zone-scoped purge tokens that inherit the zone’s Pro, Business, or Enterprise rate limits.
- Native Workers Caching attaches to the Worker script rather than the zone. Consequently,
2.2 CMS Invalidation Footprint: Tag vs. Hostname vs. Prefix vs. URL
When a tenant owner modifies an operational record (e.g., bakery daily specials or business operating hours), the invalidation footprint varies dramatically depending on the chosen purge selector:
flowchart TD CMS["Tenant CMS Publish (Worker B)"] -->|"Commit Mutation"| D1["D1 Database"] CMS -->|"Service Binding: purgeTags(tenantId, [tag])"| PurgeService["@siteswarm/service-cache-purge (Worker C)"]
subgraph PlatformQueuePipeline["Platform Invalidation Pipeline"] PurgeService -->|"5s Coalesce Window Check"| KV["PURGE_KV (job:id, coalesce:hash)"] PurgeService -->|"Enqueue Job"| Queue["PURGE_QUEUE"] Queue -->|"Batch Pull (size <= 10, timeout <= 5s)"| Consumer["Queue Consumer"] Consumer -->|"Deduplicate Tags Across Batch"| BatchDedupe["Batch Coalescer"] BatchDedupe -->|"Deficit Round Robin (DRR)"| Scheduler["Fair-Share Scheduler"] Scheduler -->|"Chunked API Call (<= 100 tags)"| CFAPI["Cloudflare Purge API"] end
CFAPI -->|"Instant Anycast Purge"| EdgeCaches["Global Edge Caches (300+ PoPs)"]Comparison of Invalidation Footprints:
| Selector | Operations per Publish | API Requests Required | Custom Cache Key Complexity | Over-Purge / Blast Radius | Cache Stampede Risk |
|---|---|---|---|---|---|
Surrogate Tag (Cache-Tag) |
1 operation (tenant:<slug>) |
1 API call (can batch up to 100 tenants/call) | None (invalidates all variants regardless of query strings, devices, headers) | Zero (strictly isolated to tenant’s tagged HTML) | Low (mitigated by single-flight coalescing in Worker A) |
Single-File URL (files) |
$N$ operations (1 per route: /, /about, /menu, /contact, …) |
High ($\lceil N / 100 \rceil$ calls per tenant) | Extreme (must provide exact URL + headers + cookies matching custom cache keys) | None | Medium (multiple URLs miss simultaneously) |
URL Prefix (prefixes) |
1 operation per path prefix | 1 API call | High (does not strip query strings or fragments; max 31 path separators) | Moderate (purges entire subdirectories) | High (wipes shared subtrees) |
Hostname (hosts) |
1 operation (<tenant>.siteswarm.dev) |
1 API call | None | Catastrophic (wipes all cached assets including immutable fonts and images) | Severe (immediate full origin re-fetch) |
Conclusion: SiteSwarm’s decision to use a single tenant-scoped surrogate tag (Cache-Tag: tenant:<client-slug>) represents the optimal balance of efficiency and safety:
- Exactly 1 operation per CMS publish event.
- Clears all page variants across all device types and headers without requiring URL enumeration.
- Up to 100 distinct tenant purges can be packed into a single Cloudflare API request via DRR batching.
2.3 End-to-End Latency Chain & SLA Capacity Math
SiteSwarm’s architecture specifies that an operational update should appear live globally in under 2 seconds. We dissect the end-to-end invalidation latency chain:
sequenceDiagram autonumber actor Owner as Business Owner (Mobile CMS) participant WorkerB as App CMS Worker (Worker B) participant D1 as D1 Database participant PurgeWorker as Cache Purge Worker (Worker C) participant PurgeKV as PURGE_KV participant PurgeQueue as PURGE_QUEUE participant Consumer as Queue Consumer participant CloudflareAPI as Cloudflare Purge API participant EdgeCDN as Cloudflare Edge CDN (300+ PoPs)
Owner->>WorkerB: Click "Publish Changes" WorkerB->>D1: Write operational update (10-50ms) WorkerB->>PurgeWorker: RPC purgeTags("tenant-slug", ["tenant:tenant-slug"]) (1-5ms) PurgeWorker->>PurgeKV: Check 5s SHA-256 ingress coalesce lock (15-30ms) PurgeWorker->>PurgeQueue: Enqueue purge job (10-25ms) PurgeWorker-->>WorkerB: Return { ok: true, jobId, status: "queued" } (Total Ingress: 40-110ms) WorkerB-->>Owner: UI Confirms "Saved & Published" (Instant UX response)
Note over PurgeQueue,Consumer: Queue Batch Wait: T_buffer in [0s, max_batch_timeout] PurgeQueue->>Consumer: Deliver Batch (<= 10 messages) Consumer->>Consumer: In-memory tag deduplication & DRR fair-share chunking (<1ms) Consumer->>CloudflareAPI: POST /zones/:zone_id/purge_cache {"tags": [...]} (80-180ms) CloudflareAPI-->>Consumer: HTTP 200 { "success": true } CloudflareAPI->>EdgeCDN: Anycast Instant Purge broadcast across 300+ PoPs (150-400ms) Consumer->>PurgeKV: Update job status to "completed" (15-30ms)Mathematical Latency Breakdown:
The end-to-end propagation time $T_{e2e}$ from CMS publish to global edge eviction is: $$T_{e2e} = T_{commit} + T_{rpc} + T_{kv_ingress} + T_{queue_in} + T_{queue_buf} + T_{drr} + T_{cf_api} + T_{edge_prop}$$
Where:
- $T_{commit} \in [10, 50]\text{ ms}$: D1 transaction commit.
- $T_{rpc} \in [1, 5]\text{ ms}$: Worker-to-Worker Service Binding RPC call.
- $T_{kv_ingress} \in [15, 30]\text{ ms}$: KV hash lookup and lock acquisition.
- $T_{queue_in} \in [10, 25]\text{ ms}$: Message enqueue to
PURGE_QUEUE.- Client Acknowledgment Time: $T_{ack} = T_{commit} + T_{rpc} + T_{kv_ingress} + T_{queue_in} \approx 36 - 110\text{ ms}$. The CMS editor receives an immediate 200 OK.
- $T_{queue_buf}$: Queue batch buffering delay. Configured by
max_batch_size: 10andmax_batch_timeout: 5(in seconds).- If queue traffic arrival rate $\lambda$ is sparse ($\lambda \to 0$ msgs/s), the batch waits for the full timeout: $T_{queue_buf} \approx 5.0\text{ s}$.
- If arrival rate $\lambda \ge 2\text{ msgs/s}$, the batch fills in $10 / \lambda$ seconds (e.g., at $\lambda = 5$, $T_{queue_buf} = 2.0\text{ s}$; at $\lambda = 10$, $T_{queue_buf} = 1.0\text{ s}$).
- $T_{drr} \le 1\text{ ms}$: In-memory Deficit Round Robin batch interleaving and deduplication.
- $T_{cf_api} \in [80, 180]\text{ ms}$: Cloudflare Zone Purge API round-trip HTTP latency.
- $T_{edge_prop} \in [150, 400]\text{ ms}$: Cloudflare Instant Purge broadcast propagation across 300+ global edge data centers.
Latency vs. Configuration Breakdown:
| Parameter State | Ingress ($T_{ack}$) | Queue Wait ($T_{queue_buf}$) | CF API ($T_{cf_api}$) | Edge Prop ($T_{edge}$) | Total $T_{e2e}$ | Meets < 2s SLA? |
|---|---|---|---|---|---|---|
Current Dev / Staging (max_batch_timeout: 5s, sparse traffic) |
~60 ms | Up to 5,000 ms | ~120 ms | ~250 ms | ~5,430 ms (~5.4s) | ❌ Exceeds (p99 ~5.5s) |
Current Dev / Staging (max_batch_timeout: 5s, peak burst $\ge 10$ msgs) |
~60 ms | < 1,000 ms | ~120 ms | ~250 ms | ~1,430 ms (~1.4s) | ✅ Meets SLA |
Proposed Production (max_batch_timeout: 1s, any traffic rate) |
~60 ms | $\le$ 1,000 ms | ~120 ms | ~250 ms | ~1,430 ms ($\le 1.5$s) | ✅ Guaranteed (< 2.0s) |
| Direct Synchronous Fast-Path (Direct API call, queue fallback) | ~60 ms | 0 ms (bypasses queue) | ~120 ms | ~250 ms | ~430 ms (~0.4s) | ✅ Sub-Second (~0.43s) |
2.4 Multi-Tenant SLA & Fleet Capacity Model
We model multi-tenant capacity across Cloudflare plan tiers under varying tenant fleet sizes ($N$) and publishing frequencies ($f$).
Model Parameters & Equations:
- $N$: Number of active tenants.
- $f$: Average updates per tenant per hour ($f / 3600$ updates/tenant/sec).
- Ingress Coalescing Factor $C_{kv} \approx 2.5$: Human editors making rapid adjustments trigger multiple saves within the 5-second window, collapsing into 1 queued job.
- Queue Batch Coalescing Factor $C_{batch} \approx 1.0 - 2.0$: Multiple tenants grouped in the same queue batch.
- Effective Cloudflare API Request Arrival Rate $\lambda_{api}$: $$\lambda_{api} = \frac{N \times f}{3600 \times C_{kv} \times C_{batch}}$$
- Cloudflare Tier Service Rate Limit $\mu_{cf}$ and Burst Capacity $B_{cf}$:
- Free: $\mu_{cf} = 0.0833\text{ req/s}$ (5 req/min), $B_{cf} = 25$
- Pro: $\mu_{cf} = 5.0\text{ req/s}$ (300 req/min), $B_{cf} = 25$
- Business: $\mu_{cf} = 10.0\text{ req/s}$ (600 req/min), $B_{cf} = 50$
- Enterprise: $\mu_{cf} = 50.0\text{ req/s}$ (3,000 req/min), $B_{cf} = 500$
Capacity Matrix across Fleet Scales:
| Tenant Fleet Size ($N$) | Publish Profile ($f$) | Raw Ingress Rate ($\lambda_{raw}$) | Effective CF API Rate ($\lambda_{api}$) | Free Tier Utilization (Cap: 5/min) | Pro Tier Utilization (Cap: 300/min) | Business Tier Utilization (Cap: 600/min) | Enterprise Utilization (Cap: 3,000/min) |
|---|---|---|---|---|---|---|---|
| 10 Tenants | 2 updates / hr (normal editing) | 0.33 / min | 0.13 / min | 2.6% | 0.04% | 0.02% | < 0.01% |
| 50 Tenants | 6 updates / hr (active lunch rush) | 5.00 / min | 2.00 / min | 40.0% | 0.67% | 0.33% | 0.07% |
| 100 Tenants | 6 updates / hr (active lunch rush) | 10.00 / min | 4.00 / min | 80.0% (Near Saturation) | 1.33% | 0.67% | 0.13% |
| 250 Tenants | 10 updates / hr (peak promotional) | 41.67 / min | 16.67 / min | 333% (Throttled & DLQ) | 5.56% | 2.78% | 0.56% |
| 1,000 Tenants | 6 updates / hr (fleet editing) | 100.00 / min | 40.00 / min | 800% (Saturated) | 13.33% | 6.67% | 1.33% |
| 5,000 Tenants | 6 updates / hr (fleet editing) | 500.00 / min | 200.00 / min | 4,000% (Saturated) | 66.67% | 33.33% | 6.67% |
| 10,000 Tenants | 6 updates / hr (large SaaS fleet) | 1,000.00 / min | 400.00 / min | 8,000% (Saturated) | 133% (Saturated) | 66.67% | 13.33% |
Analytical Conclusions:
- Free Tier Ceiling: An account on Cloudflare’s Free tier saturates around 50–100 tenants during peak operational editing. Bursting above 25 simultaneous saves depletes the token bucket, causing HTTP 429 retries and queue buffering delays that breach the 2-second SLA.
- Pro Tier Sweet Spot: For SiteSwarm’s core target market (independent local businesses, 50–2,500 active tenants), a single Pro plan account easily absorbs standard and peak editing activity, operating below 35% capacity utilization.
- Queue Batch Timeout Recommendation:
- The current
max_batch_timeout: 5inwrangler.jsoncintroduces a 5-second queue wait during quiet periods, violating the 2-second SLA. - For production, setting
max_batch_timeout: 1ensures $T_{queue_buf} \le 1.0\text{ s}$, guaranteeing an end-to-end p99 propagation latency of $\le 1.5\text{ s}$ across all traffic rates while still allowing consumer batching during bursts.
- The current
3. Candidate Canonical Updates
The empirical and mathematical findings of this research spike inform concrete revisions to living architecture documents. These changes will be reviewed and promoted during the interactive /docs distill founder session:
3.1 Candidate Updates for docs/architecture/APP_SERVING_CACHE.md
- Clarify Account-Level Quota Sharing and Token Bucket Dynamics:
- Add explicit documentation in Section 3.2 detailing that Cloudflare purge rate limits apply per account and are pooled across all zones sharing the same plan tier.
- Emphasize that
@siteswarm/service-cache-purgeacts as an indispensable platform governor that prevents any single tenant from exhausting the account’s shared token bucket (Free: 5 req/min; Pro: 5 req/s; Biz: 10 req/s; Enterprise: 50 req/s).
- Reconcile Queue Batch Timeout with the Under-2-Second SLA:
- Update Section 3.2 to detail the end-to-end latency budget:
- Worker Ingress & KV Coalescing: $\approx 60\text{ ms}$
- Cloudflare Purge API Round-Trip: $\approx 120\text{ ms}$
- Cloudflare Anycast Edge Eviction: $\approx 250\text{ ms}$
- Available Queue Buffer Budget: $2,000\text{ ms} - (60 + 120 + 250)\text{ ms} \approx 1,570\text{ ms}$.
- Specify that while local development and staging utilize
max_batch_timeout: 5, production configurations must specifymax_batch_timeout: 1to strictly preserve the< 2second global visibility SLA.
- Update Section 3.2 to detail the end-to-end latency budget:
- Formalize Surrogate Key Efficiency:
- Note in Section 3.1 that invalidating via
Cache-Tag: tenant:<client-slug>requires exactly $O(1)$ operations per CMS publish, avoiding the severe rate-limit consumption and cache key fragility of single-file URL purges.
- Note in Section 3.1 that invalidating via
3.2 Candidate Updates for docs/architecture/MULTI_WORKER_PLATFORM_SERVICES.md
- Document Queue-Backed Platform Cache Purge Topology:
- Expand Section 1 & 2 to formally incorporate
@siteswarm/service-cache-purgealongsidePURGE_QUEUE,PURGE_KV, andPURGE_DLQin the platform service topology diagram.
- Expand Section 1 & 2 to formally incorporate
- Formalize Deficit Round Robin (DRR) Fair-Share Chunk Scheduling:
- Document DRR scheduling in Section 3 as an architectural invariant for shared multi-tenant platform services: high-volume tenants submitting bulk invalidations are interleaved in chunks ($\le 100$ tags/call) so low-volume interactive updates are never starved.
- Zone Credential & Token Isolation:
- Formalize that zone-level Cloudflare API tokens reside exclusively within
@siteswarm/service-cache-purge, completely hidden from client workers (apps/<client>) and dedicated CMS workers (Worker B).
- Formalize that zone-level Cloudflare API tokens reside exclusively within
Decisions Intentionally Deferred
The following architectural optimizations and configuration changes are intentionally deferred to downstream implementation tickets:
- Production
wrangler.jsoncBatch Timeout Tuning (max_batch_timeout: 1):- Modifying
services/cache-purge/wrangler.jsoncin this spike is prohibited by anti-mocking and separation-of-concerns guardrails. Updating the production configuration from 5s to 1s will be executed in a dedicated platform infra PR following/docs distillratification.
- Modifying
- Dual-Lane Direct-Purge Fast-Path Implementation:
- The proposal to provide an optional synchronous fast-path (attempting an immediate Cloudflare API call with optimistic fallback to
PURGE_QUEUEon 429/timeout) is deferred. The queue-backed pipeline withmax_batch_timeout: 1satisfies the SLA without adding dual-path branching complexity.
- The proposal to provide an optional synchronous fast-path (attempting an immediate Cloudflare API call with optimistic fallback to
- Automated Plan Tier Detection via Cloudflare API:
- Dynamically polling Cloudflare’s Account API to auto-tune DRR chunk rates based on zone plan tiers is deferred. Static configuration via
wrangler.jsoncvars (TENANT_ZONES) provides deterministic predictability without runtime overhead.
- Dynamically polling Cloudflare’s Account API to auto-tune DRR chunk rates based on zone plan tiers is deferred. Static configuration via
- Cross-Account Zone Distribution:
- Multi-account credential sharding for agency fleets exceeding 10,000 tenants is deferred until platform tenancy benchmarks warrant multi-account provisioning.