Loading content...
Not all cache invalidations are created equal. When content becomes stale, you face a fundamental choice: immediately remove the cached content (hard purge) or mark it as stale while keeping it available as a fallback (soft purge). This distinction might seem subtle, but it profoundly affects system resilience, user experience, and origin server load.
Imagine a scenario where your origin server is experiencing elevated latency. With hard purge, every request for invalidated content hammers the struggling origin directly. With soft purge, edge nodes can still serve stale content while attempting background revalidation—protecting both users and infrastructure during stress.
Understanding when to use each approach is what separates CDN operators who merely configure caching from those who architect resilient content delivery at scale.
By the end of this page, you will deeply understand both hard and soft purge mechanisms, their internal operations at the edge node level, performance implications, and the decision framework for selecting the appropriate strategy for any content type or operational scenario.
A hard purge (also called instant purge or immediate invalidation) completely removes content from the CDN edge cache. Once executed, the cached content ceases to exist at that edge location—subsequent requests must fetch fresh content from origin.
The Hard Purge Process:
When a hard purge instruction reaches an edge node:
This is the most intuitive form of cache invalidation—content is gone, and the next request gets fresh data.
12345678910111213141516171819202122
Timeline: Hard Purge Behavior at Edge Node T0: Cached content exists (Age: 45min, TTL: 60min) |T1: Hard purge instruction arrives |T2: Cache entry DELETED immediately |T3: Request arrives for content │ ├─► Edge cache: MISS (no entry exists) │ └─► Origin fetch initiated (SYNCHRONOUS) │ ├─► If origin healthy: Fresh response (200ms latency) │ └─► Cache populated with new content │ └─► If origin DOWN: ERROR returned to client (no fallback!) └─► User sees error page Key Insight: Hard purge trades resilience for immediacy. There is no safety net if origin fails.Hard purge creates a 'thundering herd' scenario. When popular content is hard-purged across all edge nodes simultaneously, the next wave of requests all hit origin. For viral content, this can mean thousands of concurrent origin requests where previously there were zero. Plan origin capacity accordingly.
A soft purge (also called stale purge or mark-stale) doesn't delete cached content—it marks it as stale while retaining the cached copy. The edge server will attempt to revalidate with origin, but can fall back to serving stale content if origin is unavailable or slow.
This approach implements the stale-while-revalidate pattern at the CDN level:
The Soft Purge Process:
123456789101112131415161718192021222324
Timeline: Soft Purge Behavior at Edge Node T0: Cached content exists (Age: 45min, TTL: 60min) |T1: Soft purge instruction arrives |T2: Cache entry marked STALE (but retained in cache) |T3: Request arrives for content │ ├─► Edge cache: STALE (entry exists but expired) │ ├─► IMMEDIATELY return stale content to user (<1ms) │ └─► BACKGROUND origin fetch initiated (ASYNC) │ ├─► If origin healthy: Cache updated with fresh content │ └─► Next request gets fresh content │ └─► If origin DOWN: Stale content continues serving └─► System degrades gracefully, users unaffected Key Insight: Soft purge prioritizes availability and user experience. Freshness is eventually consistent.The Grace Period Concept:
Soft purge introduces the concept of a stale grace period—the time window during which stale content can be served while revalidation occurs. This is typically configured as a CDN setting:
These settings determine how long the CDN will use stale content as a fallback, providing a configurable tradeoff between freshness and availability.
For most content types, soft purge should be your default strategy. Reserve hard purge for scenarios where serving stale content would cause actual harm. This mindset dramatically improves system resilience while rarely impacting user experience.
Understanding the precise behavioral differences between hard and soft purge enables informed decision-making for any invalidation scenario.
| Characteristic | Hard Purge | Soft Purge |
|---|---|---|
| Cache Entry After Purge | Deleted completely | Marked stale, retained |
| First Request Response Time | Origin fetch latency (100-500ms+) | Cached stale content (<1ms) |
| Origin Load | Immediate synchronous requests | Background async requests |
| Origin Failure Behavior | Error returned to user | Stale content served |
| Content Freshness Guarantee | Immediate upon origin response | Eventually consistent |
| Thundering Herd Risk | High for popular content | Low (request coalescing) |
| Storage Impact | Immediate space reclamation | Delayed until stale expires |
| Propagation Latency | Similar (3-10 seconds) | Similar (3-10 seconds) |
| Typical Use Case | Security/legal compliance | Routine content updates |
Request Flow Comparison:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
// Simplified edge server request handling logic interface CacheEntry { content: Buffer; headers: Headers; cachedAt: Date; ttl: number; stale: boolean; // Soft purge sets this to true} async function handleRequest(request: Request, cache: EdgeCache): Promise<Response> { const cacheKey = computeCacheKey(request); const entry = cache.get(cacheKey); // HARD PURGE: Entry doesn't exist if (!entry) { // Synchronous origin fetch - user waits const response = await fetchFromOrigin(request); cache.set(cacheKey, createCacheEntry(response)); return response; } // SOFT PURGE: Entry exists but marked stale if (entry.stale || isExpired(entry)) { // Background revalidation - user doesn't wait triggerBackgroundRevalidation(request, cacheKey); // Check stale-while-revalidate window if (withinStaleWindow(entry)) { // Serve stale immediately return createResponse(entry, { 'X-Cache': 'STALE' }); } // Stale window exceeded, must revalidate synchronously const response = await fetchFromOrigin(request); cache.set(cacheKey, createCacheEntry(response)); return response; } // Fresh cache hit return createResponse(entry, { 'X-Cache': 'HIT' });} async function triggerBackgroundRevalidation( request: Request, cacheKey: string): Promise<void> { // Non-blocking origin fetch setImmediate(async () => { try { const response = await fetchFromOrigin(request); if (response.ok) { cache.set(cacheKey, createCacheEntry(response)); } } catch (error) { // Origin failed, stale content continues serving metrics.increment('background_revalidation.failed'); } });}When soft purge triggers background revalidation, most CDNs coalesce multiple concurrent requests into a single origin fetch. 1,000 concurrent requests for stale content result in 1 origin request, not 1,000. This is a massive origin protection benefit that hard purge cannot provide.
The choice between hard and soft purge has measurable performance implications that compound across high-traffic systems.
User-Perceived Latency:
Consider a product page with 10,000 requests per minute during a flash sale:
Hard Purge Scenario:
Soft Purge Scenario:
| Metric | Hard Purge | Soft Purge | Impact |
|---|---|---|---|
| p50 User Latency | 350ms | 4ms | 87.5x improvement |
| p99 User Latency | 1,200ms | 8ms | 150x improvement |
| Origin Requests/sec (peak) | 500 RPS | 1 RPS | 500x reduction |
| Error Rate During Purge | 0.5-5% | ~0% | Eliminated |
| Time to Full Freshness | ~5 seconds | ~5.2 seconds | Nearly equivalent |
Origin Protection:
Soft purge's request coalescing provides crucial origin protection during invalidation events:
1234567891011121314151617181920212223
Hard Purge: Origin Request Pattern (high-traffic content)═══════════════════════════════════════════════════════════Time | Edge Requests | Origin Requests | Origin Load───────┼───────────────┼─────────────────┼─────────────────T+0s | 0 | 0 | BaselineT+5s | 500 | 500 | ████████████████████ SPIKE!T+6s | 450 | 50 | █████ ElevatedT+7s | 420 | 10 | █ NormalizingT+8s | 400 | 5 | Normal───────┴───────────────┴─────────────────┴───────────────── Soft Purge: Origin Request Pattern (same traffic)═══════════════════════════════════════════════════════════Time | Edge Requests | Origin Requests | Origin Load───────┼───────────────┼─────────────────┼─────────────────T+0s | 0 | 0 | BaselineT+5s | 500 | 1 | Normal (coalesced)T+6s | 450 | 0 | Normal (cache warm)T+7s | 420 | 0 | NormalT+8s | 400 | 0 | Normal───────┴───────────────┴─────────────────┴───────────────── 500 origin requests vs 1 origin request for identical trafficWhen hard purging popular content during peak traffic, the resulting origin load spike can trigger cascading failures: origin overload → increased latency → timeout failures → retry storms → complete outage. Soft purge prevents this cascade by design.
Implementing intelligent purge type selection requires understanding your content's characteristics and building adaptive systems that choose the right approach.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
interface ContentMetadata { contentType: string; sensitivity: 'public' | 'sensitive' | 'critical'; popularity: number; // Requests per minute lastModified: Date;} interface PurgeDecision { type: 'hard' | 'soft'; reason: string; riskLevel: 'low' | 'medium' | 'high';} class PurgeTypeSelector { /** * Determines optimal purge type based on content characteristics * and current system state. */ selectPurgeType( content: ContentMetadata, context: PurgeContext ): PurgeDecision { // HARD PURGE: Security/Legal requirements override all if (content.sensitivity === 'critical') { return { type: 'hard', reason: 'Critical sensitivity requires immediate removal', riskLevel: this.assessOriginRisk(content.popularity) }; } // HARD PURGE: Explicit security incident flag if (context.isSecurityIncident) { return { type: 'hard', reason: 'Security incident mandates hard purge', riskLevel: 'high' // Accept origin load risk }; } // SOFT PURGE: High popularity content (origin protection) if (content.popularity > 1000) { return { type: 'soft', reason: `High traffic content (${content.popularity} RPM) - origin protection`, riskLevel: 'low' }; } // SOFT PURGE: Origin health is degraded if (context.originHealthScore < 0.8) { return { type: 'soft', reason: `Origin degraded (health: ${context.originHealthScore}) - preserve availability`, riskLevel: 'low' }; } // SOFT PURGE: Recent content (likely being actively edited) const contentAgeMinutes = (Date.now() - content.lastModified.getTime()) / 60000; if (contentAgeMinutes < 5) { return { type: 'soft', reason: 'Recently modified content - may be rapid editing', riskLevel: 'low' }; } // Default: SOFT PURGE for routine updates return { type: 'soft', reason: 'Standard content update - soft purge preferred', riskLevel: 'low' }; } private assessOriginRisk(popularity: number): PurgeDecision['riskLevel'] { if (popularity > 5000) return 'high'; if (popularity > 500) return 'medium'; return 'low'; }} // Usage in purge workflowclass PurgeWorkflow { constructor( private selector: PurgeTypeSelector, private cdnClient: CDNClient, private metrics: MetricsClient ) {} async executePurge( urls: string[], metadata: ContentMetadata[], context: PurgeContext ): Promise<PurgeResult> { // Group URLs by optimal purge type const hardPurgeUrls: string[] = []; const softPurgeUrls: string[] = []; for (let i = 0; i < urls.length; i++) { const decision = this.selector.selectPurgeType(metadata[i], context); if (decision.type === 'hard') { hardPurgeUrls.push(urls[i]); if (decision.riskLevel === 'high') { this.metrics.recordHighRiskHardPurge(urls[i]); } } else { softPurgeUrls.push(urls[i]); } } // Execute purges by type const results: PurgeResult = { hard: [], soft: [] }; if (hardPurgeUrls.length > 0) { results.hard = await this.cdnClient.purge({ urls: hardPurgeUrls, type: 'hard' }); } if (softPurgeUrls.length > 0) { results.soft = await this.cdnClient.purge({ urls: softPurgeUrls, type: 'soft' }); } return results; }}The best purge systems don't use a single strategy—they adapt based on content type, current traffic, origin health, and operational context. Build intelligence into your purge workflows rather than hardcoding purge types.
Different CDN providers implement soft and hard purge with varying terminology, features, and behaviors. Understanding these differences is crucial when working across multiple providers or evaluating CDN options.
| Provider | Hard Purge Term | Soft Purge Term | Key Features |
|---|---|---|---|
| Fastly | Purge (immediate) | Soft Purge | Instant purge (<150ms), native soft purge support |
| Cloudflare | Purge | N/A (via Cache-Control) | No native soft purge; use stale-while-revalidate headers |
| AWS CloudFront | Invalidation | N/A | No soft purge; invalidation only. Use versioned URLs instead. |
| Akamai | Fast Purge (delete) | Fast Purge (invalidate) | Invalidate marks stale; delete removes completely |
| Varnish | purge | softpurge (via VMOD) | Requires softpurge VMOD for soft purge behavior |
Fastly: The Gold Standard for Purge Granularity
Fastly provides the most sophisticated purge options, including native soft purge support with sub-second propagation:
# Hard purge
PURGE /path/to/content HTTP/1.1
Host: example.com
Fastly-Key: your-api-key
# Soft purge (mark stale, serve while revalidating)
PURGE /path/to/content HTTP/1.1
Host: example.com
Fastly-Key: your-api-key
Fastly-Soft-Purge: 1
AWS CloudFront: Invalidation-Only Architecture
CloudFront doesn't support soft purge natively. Invalidations are always hard—content is removed from edge caches. To achieve soft-purge-like behavior, you must:
/assets/main.v123.css) for cache bustingstale-while-revalidate origin headersAkamai: Invalidate vs Delete
Akamai's Fast Purge offers two distinct actions:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
interface UnifiedPurgeRequest { urls: string[]; type: 'hard' | 'soft'; priority?: 'normal' | 'urgent';} class MultiCDNPurgeClient { private providers: Map<string, CDNProviderClient>; constructor(providers: CDNProviderClient[]) { this.providers = new Map(providers.map(p => [p.name, p])); } async purge(request: UnifiedPurgeRequest): Promise<void> { const promises = Array.from(this.providers.values()).map(provider => this.purgeProvider(provider, request) ); await Promise.all(promises); } private async purgeProvider( provider: CDNProviderClient, request: UnifiedPurgeRequest ): Promise<void> { switch (provider.name) { case 'fastly': // Native soft purge support await provider.purge(request.urls, { soft: request.type === 'soft' }); break; case 'cloudflare': // No soft purge - always hard, rely on origin headers await provider.purge(request.urls); if (request.type === 'soft') { console.warn('Cloudflare: Soft purge simulated via origin headers'); } break; case 'cloudfront': // Invalidation only (hard purge) await provider.createInvalidation(request.urls); if (request.type === 'soft') { console.warn('CloudFront: No soft purge support. Consider versioned URLs.'); } break; case 'akamai': // Explicit invalidate vs delete if (request.type === 'hard') { await provider.fastPurge(request.urls, { action: 'delete' }); } else { await provider.fastPurge(request.urls, { action: 'invalidate' }); } break; default: throw new Error(`Unknown CDN provider: ${provider.name}`); } }}When evaluating CDN providers, purge capabilities should be a key criterion. Native soft purge support (Fastly, Akamai) provides better origin protection than providers requiring workarounds (CloudFront, Cloudflare). Factor this into architecture decisions.
Sophisticated invalidation architectures often combine hard and soft purge in layered strategies that optimize for both freshness and resilience.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
class EscalatingPurgeStrategy { constructor( private cdnClient: CDNClient, private verifier: PurgeVerifier, private softPurgeTimeout: number = 30000 // 30 seconds ) {} async purgeWithEscalation(urls: string[]): Promise<void> { // Phase 1: Soft purge for immediate stale but available content const softPurgeId = await this.cdnClient.purge({ urls, type: 'soft' }); console.log(`Initiated soft purge: ${softPurgeId}`); // Wait for soft purge propagation await this.cdnClient.waitForCompletion(softPurgeId); // Phase 2: Allow time for background revalidation await this.waitForRevalidation(urls); // Phase 3: Verify freshness achieved const verified = await this.verifier.verifyFreshness(urls); if (verified.allFresh) { console.log('Soft purge revalidation successful'); return; } // Phase 4: Escalate to hard purge for any stale URLs const staleUrls = verified.staleUrls; if (staleUrls.length > 0) { console.warn(`Escalating to hard purge for ${staleUrls.length} URLs`); await this.cdnClient.purge({ urls: staleUrls, type: 'hard' }); metrics.increment('purge.escalation.hard', { count: staleUrls.length }); } } private async waitForRevalidation(urls: string[]): Promise<void> { // Poll until revalidation complete or timeout const startTime = Date.now(); while (Date.now() - startTime < this.softPurgeTimeout) { const status = await this.checkRevalidationStatus(urls); if (status.allRevalidated) { return; } await sleep(2000); // Check every 2 seconds } }}Soft purge with escalation provides the best of both worlds: immediate availability with eventual freshness guarantee. If background revalidation fails (e.g., origin transient error), escalation ensures content eventually refreshes.
Use this decision framework to systematically choose the appropriate purge type for any content invalidation scenario:
123456789101112131415161718192021222324252627282930313233343536373839404142
PURGE TYPE DECISION TREE═══════════════════════════════════════════════════════════════════ START: Content needs invalidation │ ├─► Is this a security incident? (malware, data leak, etc.) │ │ │ ├─► YES ──► HARD PURGE (immediate removal required) │ │ │ └─► NO ──► Continue evaluation │ ├─► Is there legal/compliance requirement for immediate removal? │ │ │ ├─► YES ──► HARD PURGE (legal mandate) │ │ │ └─► NO ──► Continue evaluation │ ├─► Would serving stale content cause user harm or logical errors? │ │ │ ├─► YES ──► HARD PURGE (user safety) │ │ │ └─► NO ──► Continue evaluation │ ├─► Is origin currently healthy? (response time, error rate) │ │ │ ├─► NO ──► SOFT PURGE (preserve availability) │ │ │ └─► YES ──► Continue evaluation │ ├─► Is this high-traffic content? (>1000 RPM) │ │ │ ├─► YES ──► SOFT PURGE (origin protection) │ │ │ └─► NO ──► Continue evaluation │ └─► DEFAULT ──► SOFT PURGE (standard content update) QUICK REFERENCE:• Hard Purge: Security, legal, actively harmful content• Soft Purge: Everything else When in doubt, soft purge is safer.What's Next:
With purge types mastered, the next page explores Tag-Based Invalidation—a powerful abstraction that allows logical grouping of cached content for efficient, targeted invalidation at scale.
You now understand the critical distinction between hard and soft purge, their performance implications, implementation patterns, and how to make data-driven decisions about which approach to use for any invalidation scenario.