mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-21 18:11:27 -07:00
update skills
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
# Cloudflare Cache Reserve
|
||||
|
||||
**Persistent cache storage built on R2 for long-term content retention**
|
||||
|
||||
## Smart Shield Integration
|
||||
|
||||
Cache Reserve is part of **Smart Shield**, Cloudflare's comprehensive security and performance suite:
|
||||
|
||||
- **Smart Shield Advanced tier**: Includes 2TB Cache Reserve storage
|
||||
- **Standalone purchase**: Available separately if not using Smart Shield
|
||||
- **Migration**: Existing standalone customers can migrate to Smart Shield bundles
|
||||
|
||||
**Decision**: Already on Smart Shield Advanced? Cache Reserve is included. Otherwise evaluate standalone purchase vs Smart Shield upgrade.
|
||||
|
||||
## Overview
|
||||
|
||||
Cache Reserve is Cloudflare's persistent, large-scale cache storage layer built on R2. It acts as the ultimate upper-tier cache, storing cacheable content for extended periods (30+ days) to maximize cache hits, reduce origin egress fees, and shield origins from repeated requests for long-tail content.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### What is Cache Reserve?
|
||||
|
||||
- **Persistent storage layer**: Built on R2, sits above tiered cache hierarchy
|
||||
- **Long-term retention**: 30-day default retention, extended on each access
|
||||
- **Automatic operation**: Works seamlessly with existing CDN, no code changes required
|
||||
- **Origin shielding**: Dramatically reduces origin egress by serving cached content longer
|
||||
- **Usage-based pricing**: Pay only for storage + read/write operations
|
||||
|
||||
### Cache Hierarchy
|
||||
|
||||
```
|
||||
Visitor Request
|
||||
↓
|
||||
Lower-Tier Cache (closest to visitor)
|
||||
↓ (on miss)
|
||||
Upper-Tier Cache (closest to origin)
|
||||
↓ (on miss)
|
||||
Cache Reserve (R2 persistent storage)
|
||||
↓ (on miss)
|
||||
Origin Server
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **On cache miss**: Content fetched from origin <20><> written to Cache Reserve + edge caches simultaneously
|
||||
2. **On edge eviction**: Content may be evicted from edge cache but remains in Cache Reserve
|
||||
3. **On subsequent request**: If edge cache misses but Cache Reserve hits → content restored to edge caches
|
||||
4. **Retention**: Assets remain in Cache Reserve for 30 days since last access (configurable via TTL)
|
||||
|
||||
## When to Use Cache Reserve
|
||||
|
||||
```
|
||||
Need persistent caching?
|
||||
├─ High origin egress costs → Cache Reserve ✓
|
||||
├─ Long-tail content (archives, media libraries) → Cache Reserve ✓
|
||||
├─ Already using Smart Shield Advanced → Included! ✓
|
||||
├─ Video streaming with seeking (range requests) → ✗ Not supported
|
||||
├─ Dynamic/personalized content → ✗ Use edge cache only
|
||||
├─ Need per-request cache control from Workers → ✗ Use R2 directly
|
||||
└─ Frequently updated content (< 10hr lifetime) → ✗ Not eligible
|
||||
```
|
||||
|
||||
## Asset Eligibility
|
||||
|
||||
Cache Reserve only stores assets meeting **ALL** criteria:
|
||||
|
||||
- Cacheable per Cloudflare's standard rules
|
||||
- Minimum 10-hour TTL (36000 seconds)
|
||||
- `Content-Length` header present
|
||||
- Original files only (not transformed images)
|
||||
|
||||
### Eligibility Checklist
|
||||
|
||||
Use this checklist to verify if an asset is eligible:
|
||||
|
||||
- [ ] Zone has Cache Reserve enabled
|
||||
- [ ] Zone has Tiered Cache enabled (required)
|
||||
- [ ] Asset TTL ≥ 10 hours (36,000 seconds)
|
||||
- [ ] `Content-Length` header present on origin response
|
||||
- [ ] No `Set-Cookie` header (or uses private directive)
|
||||
- [ ] `Vary` header is NOT `*` (can be `Accept-Encoding`)
|
||||
- [ ] Not an image transformation variant (original images OK)
|
||||
- [ ] Not a range request (no HTTP 206 support)
|
||||
- [ ] Not O2O (Orange-to-Orange) proxied request
|
||||
|
||||
**All boxes must be checked for Cache Reserve eligibility.**
|
||||
|
||||
### Not Eligible
|
||||
|
||||
- Assets with TTL < 10 hours
|
||||
- Responses without `Content-Length` header
|
||||
- Image transformation variants (original images are eligible)
|
||||
- Responses with `Set-Cookie` headers
|
||||
- Responses with `Vary: *` header
|
||||
- Assets from R2 public buckets on same zone
|
||||
- O2O (Orange-to-Orange) setup requests
|
||||
- **Range requests** (video seeking, partial content downloads)
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Enable via Dashboard
|
||||
https://dash.cloudflare.com/caching/cache-reserve
|
||||
# Click "Enable Storage Sync" or "Purchase" button
|
||||
```
|
||||
|
||||
**Prerequisites:**
|
||||
- Paid Cache Reserve plan or Smart Shield Advanced required
|
||||
- Tiered Cache required for optimal performance
|
||||
|
||||
## Essential Commands
|
||||
|
||||
```bash
|
||||
# Check Cache Reserve status
|
||||
curl -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/cache/cache_reserve" \
|
||||
-H "Authorization: Bearer $API_TOKEN"
|
||||
|
||||
# Enable Cache Reserve
|
||||
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/cache/cache_reserve" \
|
||||
-H "Authorization: Bearer $API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"value": "on"}'
|
||||
|
||||
# Check asset cache status
|
||||
curl -I https://example.com/asset.jpg | grep -i cache
|
||||
```
|
||||
|
||||
## In This Reference
|
||||
|
||||
| Task | Files |
|
||||
|------|-------|
|
||||
| Evaluate if Cache Reserve fits your use case | README.md (this file) |
|
||||
| Enable Cache Reserve for your zone | README.md + [configuration.md](./configuration.md) |
|
||||
| Use with Workers (understand limitations) | [api.md](./api.md) |
|
||||
| Setup via SDKs or IaC (TypeScript, Python, Terraform) | [configuration.md](./configuration.md) |
|
||||
| Optimize costs and debug issues | [patterns.md](./patterns.md) + [gotchas.md](./gotchas.md) |
|
||||
| Understand eligibility and troubleshoot | [gotchas.md](./gotchas.md) → [patterns.md](./patterns.md) |
|
||||
|
||||
**Files:**
|
||||
- [configuration.md](./configuration.md) - Setup, API, SDKs, and Cache Rules
|
||||
- [api.md](./api.md) - Purging, monitoring, Workers integration
|
||||
- [patterns.md](./patterns.md) - Best practices, cost optimization, debugging
|
||||
- [gotchas.md](./gotchas.md) - Common issues, limitations, troubleshooting
|
||||
|
||||
## See Also
|
||||
- [r2](../r2/) - Cache Reserve built on R2 storage
|
||||
- [workers](../workers/) - Workers integration with Cache API
|
||||
194
.agents/skills/cloudflare-deploy/references/cache-reserve/api.md
Normal file
194
.agents/skills/cloudflare-deploy/references/cache-reserve/api.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# Cache Reserve API
|
||||
|
||||
## Workers Integration
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────┐
|
||||
│ CRITICAL: Workers Cache API ≠ Cache Reserve │
|
||||
│ │
|
||||
│ • Workers caches.default / cache.put() → edge cache ONLY │
|
||||
│ • Cache Reserve → zone-level setting, automatic, no per-req │
|
||||
│ • You CANNOT selectively write to Cache Reserve from Workers │
|
||||
│ • Cache Reserve works with standard fetch(), not cache.put() │
|
||||
└────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Cache Reserve is a **zone-level configuration**, not a per-request API. It works automatically when enabled for the zone:
|
||||
|
||||
### Standard Fetch (Recommended)
|
||||
|
||||
```typescript
|
||||
// Cache Reserve works automatically via standard fetch
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
// Standard fetch uses Cache Reserve automatically
|
||||
return await fetch(request);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Cache API Limitations
|
||||
|
||||
**IMPORTANT**: `cache.put()` is **NOT compatible** with Cache Reserve or Tiered Cache.
|
||||
|
||||
```typescript
|
||||
// ❌ WRONG: cache.put() bypasses Cache Reserve
|
||||
const cache = caches.default;
|
||||
let response = await cache.match(request);
|
||||
if (!response) {
|
||||
response = await fetch(request);
|
||||
await cache.put(request, response.clone()); // Bypasses Cache Reserve!
|
||||
}
|
||||
|
||||
// ✅ CORRECT: Use standard fetch for Cache Reserve compatibility
|
||||
return await fetch(request);
|
||||
|
||||
// ✅ CORRECT: Use Cache API only for custom cache namespaces
|
||||
const customCache = await caches.open('my-custom-cache');
|
||||
let response = await customCache.match(request);
|
||||
if (!response) {
|
||||
response = await fetch(request);
|
||||
await customCache.put(request, response.clone()); // Custom cache OK
|
||||
}
|
||||
```
|
||||
|
||||
## Purging and Cache Management
|
||||
|
||||
### Purge by URL (Instant)
|
||||
|
||||
```typescript
|
||||
// Purge specific URL from Cache Reserve immediately
|
||||
const purgeCacheReserveByURL = async (
|
||||
zoneId: string,
|
||||
apiToken: string,
|
||||
urls: string[]
|
||||
) => {
|
||||
const response = await fetch(
|
||||
`https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${apiToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ files: urls })
|
||||
}
|
||||
);
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
// Example usage
|
||||
await purgeCacheReserveByURL('zone123', 'token456', [
|
||||
'https://example.com/image.jpg',
|
||||
'https://example.com/video.mp4'
|
||||
]);
|
||||
```
|
||||
|
||||
### Purge by Tag/Host/Prefix (Revalidation)
|
||||
|
||||
```typescript
|
||||
// Purge by cache tag - forces revalidation, not immediate removal
|
||||
await fetch(
|
||||
`https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': `Bearer ${apiToken}`, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ tags: ['tag1', 'tag2'] })
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
**Purge behavior:**
|
||||
- **By URL**: Immediate removal from Cache Reserve + edge cache
|
||||
- **By tag/host/prefix**: Revalidation only, assets remain in storage (costs continue)
|
||||
|
||||
### Clear All Cache Reserve Data
|
||||
|
||||
```typescript
|
||||
// Requires Cache Reserve OFF first
|
||||
await fetch(
|
||||
`https://api.cloudflare.com/client/v4/zones/${zoneId}/cache/cache_reserve_clear`,
|
||||
{ method: 'POST', headers: { 'Authorization': `Bearer ${apiToken}` } }
|
||||
);
|
||||
|
||||
// Check status: GET same endpoint returns { state: "In-progress" | "Completed" }
|
||||
```
|
||||
|
||||
**Process**: Disable Cache Reserve → Call clear endpoint → Wait up to 24hr → Re-enable
|
||||
|
||||
## Monitoring and Analytics
|
||||
|
||||
### Dashboard Analytics
|
||||
|
||||
Navigate to **Caching > Cache Reserve** to view:
|
||||
|
||||
- **Egress Savings**: Total bytes served from Cache Reserve vs origin egress cost saved
|
||||
- **Requests Served**: Cache Reserve hits vs misses breakdown
|
||||
- **Storage Used**: Current GB stored in Cache Reserve (billed monthly)
|
||||
- **Operations**: Class A (writes) and Class B (reads) operation counts
|
||||
- **Cost Tracking**: Estimated monthly costs based on current usage
|
||||
|
||||
### Logpush Integration
|
||||
|
||||
```typescript
|
||||
// Logpush field: CacheReserveUsed (boolean) - filter for Cache Reserve hits
|
||||
// Query Cache Reserve hits in analytics
|
||||
const logpushQuery = `
|
||||
SELECT
|
||||
ClientRequestHost,
|
||||
COUNT(*) as requests,
|
||||
SUM(EdgeResponseBytes) as bytes_served,
|
||||
COUNT(CASE WHEN CacheReserveUsed = true THEN 1 END) as cache_reserve_hits,
|
||||
COUNT(CASE WHEN CacheReserveUsed = false THEN 1 END) as cache_reserve_misses
|
||||
FROM http_requests
|
||||
WHERE Timestamp >= NOW() - INTERVAL '24 hours'
|
||||
GROUP BY ClientRequestHost
|
||||
ORDER BY requests DESC
|
||||
`;
|
||||
|
||||
// Filter only Cache Reserve hits
|
||||
const crHitsQuery = `
|
||||
SELECT ClientRequestHost, COUNT(*) as requests, SUM(EdgeResponseBytes) as bytes
|
||||
FROM http_requests
|
||||
WHERE CacheReserveUsed = true AND Timestamp >= NOW() - INTERVAL '7 days'
|
||||
GROUP BY ClientRequestHost
|
||||
ORDER BY bytes DESC
|
||||
`;
|
||||
```
|
||||
|
||||
### GraphQL Analytics
|
||||
|
||||
```graphql
|
||||
query CacheReserveAnalytics($zoneTag: string, $since: string, $until: string) {
|
||||
viewer {
|
||||
zones(filter: { zoneTag: $zoneTag }) {
|
||||
httpRequests1dGroups(
|
||||
filter: { datetime_geq: $since, datetime_leq: $until }
|
||||
limit: 1000
|
||||
) {
|
||||
dimensions { date }
|
||||
sum {
|
||||
cachedBytes
|
||||
cachedRequests
|
||||
bytes
|
||||
requests
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Pricing
|
||||
|
||||
```typescript
|
||||
// Storage: $0.015/GB-month | Class A (writes): $4.50/M | Class B (reads): $0.36/M
|
||||
// Cache miss: 1A + 1B | Cache hit: 1B | Assets >1GB: proportionally more ops
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [README](./README.md) - Overview and core concepts
|
||||
- [Configuration](./configuration.md) - Setup and Cache Rules
|
||||
- [Patterns](./patterns.md) - Best practices and optimization
|
||||
- [Gotchas](./gotchas.md) - Common issues and troubleshooting
|
||||
@@ -0,0 +1,169 @@
|
||||
# Cache Reserve Configuration
|
||||
|
||||
## Dashboard Setup
|
||||
|
||||
**Minimum steps to enable:**
|
||||
|
||||
```bash
|
||||
# Navigate to dashboard
|
||||
https://dash.cloudflare.com/caching/cache-reserve
|
||||
|
||||
# Click "Enable Storage Sync" or "Purchase" button
|
||||
```
|
||||
|
||||
**Prerequisites:**
|
||||
- Paid Cache Reserve plan or Smart Shield Advanced required
|
||||
- Tiered Cache **required** for Cache Reserve to function optimally
|
||||
|
||||
## API Configuration
|
||||
|
||||
### REST API
|
||||
|
||||
```bash
|
||||
# Enable
|
||||
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/cache/cache_reserve" \
|
||||
-H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"value": "on"}'
|
||||
|
||||
# Check status
|
||||
curl -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/cache/cache_reserve" \
|
||||
-H "Authorization: Bearer $API_TOKEN"
|
||||
```
|
||||
|
||||
### TypeScript SDK
|
||||
|
||||
```bash
|
||||
npm install cloudflare
|
||||
```
|
||||
|
||||
```typescript
|
||||
import Cloudflare from 'cloudflare';
|
||||
|
||||
const client = new Cloudflare({
|
||||
apiToken: process.env.CLOUDFLARE_API_TOKEN,
|
||||
});
|
||||
|
||||
// Enable Cache Reserve
|
||||
await client.cache.cacheReserve.edit({
|
||||
zone_id: 'abc123',
|
||||
value: 'on',
|
||||
});
|
||||
|
||||
// Get Cache Reserve status
|
||||
const status = await client.cache.cacheReserve.get({
|
||||
zone_id: 'abc123',
|
||||
});
|
||||
console.log(status.value); // 'on' or 'off'
|
||||
```
|
||||
|
||||
### Python SDK
|
||||
|
||||
```bash
|
||||
pip install cloudflare
|
||||
```
|
||||
|
||||
```python
|
||||
from cloudflare import Cloudflare
|
||||
|
||||
client = Cloudflare(api_token=os.environ.get("CLOUDFLARE_API_TOKEN"))
|
||||
|
||||
# Enable Cache Reserve
|
||||
client.cache.cache_reserve.edit(
|
||||
zone_id="abc123",
|
||||
value="on"
|
||||
)
|
||||
|
||||
# Get Cache Reserve status
|
||||
status = client.cache.cache_reserve.get(zone_id="abc123")
|
||||
print(status.value) # 'on' or 'off'
|
||||
```
|
||||
|
||||
### Terraform
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
required_providers {
|
||||
cloudflare = {
|
||||
source = "cloudflare/cloudflare"
|
||||
version = "~> 4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "cloudflare" {
|
||||
api_token = var.cloudflare_api_token
|
||||
}
|
||||
|
||||
resource "cloudflare_zone_cache_reserve" "example" {
|
||||
zone_id = var.zone_id
|
||||
enabled = true
|
||||
}
|
||||
|
||||
# Tiered Cache is required for Cache Reserve
|
||||
resource "cloudflare_tiered_cache" "example" {
|
||||
zone_id = var.zone_id
|
||||
cache_type = "smart"
|
||||
}
|
||||
```
|
||||
|
||||
### Pulumi
|
||||
|
||||
```typescript
|
||||
import * as cloudflare from "@pulumi/cloudflare";
|
||||
|
||||
// Enable Cache Reserve
|
||||
const cacheReserve = new cloudflare.ZoneCacheReserve("example", {
|
||||
zoneId: zoneId,
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
// Enable Tiered Cache (required)
|
||||
const tieredCache = new cloudflare.TieredCache("example", {
|
||||
zoneId: zoneId,
|
||||
cacheType: "smart",
|
||||
});
|
||||
```
|
||||
|
||||
### Required API Token Permissions
|
||||
|
||||
- `Zone Settings Read`
|
||||
- `Zone Settings Write`
|
||||
- `Zone Read`
|
||||
- `Zone Write`
|
||||
|
||||
## Cache Rules Integration
|
||||
|
||||
Control Cache Reserve eligibility via Cache Rules:
|
||||
|
||||
```typescript
|
||||
// Enable for static assets
|
||||
{
|
||||
action: 'set_cache_settings',
|
||||
action_parameters: {
|
||||
cache_reserve: { eligible: true, minimum_file_ttl: 86400 },
|
||||
edge_ttl: { mode: 'override_origin', default: 86400 },
|
||||
cache: true
|
||||
},
|
||||
expression: '(http.request.uri.path matches "\\.(jpg|png|webp|pdf|zip)$")'
|
||||
}
|
||||
|
||||
// Disable for APIs
|
||||
{
|
||||
action: 'set_cache_settings',
|
||||
action_parameters: { cache_reserve: { eligible: false } },
|
||||
expression: '(http.request.uri.path matches "^/api/")'
|
||||
}
|
||||
|
||||
// Create via API: PUT to zones/{zone_id}/rulesets/phases/http_request_cache_settings/entrypoint
|
||||
```
|
||||
|
||||
## Wrangler Integration
|
||||
|
||||
Cache Reserve works automatically with Workers deployed via Wrangler. No special wrangler.jsonc configuration needed - enable Cache Reserve via Dashboard or API for the zone.
|
||||
|
||||
## See Also
|
||||
|
||||
- [README](./README.md) - Overview and core concepts
|
||||
- [API Reference](./api.md) - Purging and monitoring APIs
|
||||
- [Patterns](./patterns.md) - Best practices and optimization
|
||||
- [Gotchas](./gotchas.md) - Common issues and troubleshooting
|
||||
@@ -0,0 +1,132 @@
|
||||
# Cache Reserve Gotchas
|
||||
|
||||
## Common Errors
|
||||
|
||||
### "Assets Not Being Cached in Cache Reserve"
|
||||
|
||||
**Cause:** Asset is not cacheable, TTL < 10 hours, Content-Length header missing, or blocking headers present (Set-Cookie, Vary: *)
|
||||
**Solution:** Ensure minimum TTL of 10+ hours (`Cache-Control: public, max-age=36000`), add Content-Length header, remove Set-Cookie header, and set `Vary: Accept-Encoding` (not *)
|
||||
|
||||
### "Range Requests Not Working" (Video Seeking Fails)
|
||||
|
||||
**Cause:** Cache Reserve does **NOT** support range requests (HTTP 206 Partial Content)
|
||||
**Solution:** Range requests bypass Cache Reserve entirely. For video streaming with seeking:
|
||||
- Use edge cache only (shorter TTLs)
|
||||
- Consider R2 with direct access for range-heavy workloads
|
||||
- Accept that seekable content won't benefit from Cache Reserve persistence
|
||||
|
||||
### "Origin Bandwidth Higher Than Expected"
|
||||
|
||||
**Cause:** Cache Reserve fetches **uncompressed** content from origin, even though it serves compressed to visitors
|
||||
**Solution:**
|
||||
- If origin charges by bandwidth, factor in uncompressed transfer costs
|
||||
- Cache Reserve compresses for visitors automatically (saves visitor bandwidth)
|
||||
- Compare: origin egress savings vs higher uncompressed fetch costs
|
||||
|
||||
### "Cloudflare Images Not Caching with Cache Reserve"
|
||||
|
||||
**Cause:** Cloudflare Images with `Vary: Accept` header (format negotiation) is incompatible with Cache Reserve
|
||||
**Solution:**
|
||||
- Cache Reserve silently skips images with Vary for format negotiation
|
||||
- Original images (non-transformed) may still be eligible
|
||||
- Use Cloudflare Images variants or edge cache for transformed images
|
||||
|
||||
### "High Class A Operations Costs"
|
||||
|
||||
**Cause:** Frequent cache misses, short TTLs, or frequent revalidation
|
||||
**Solution:** Increase TTL for stable content (24+ hours), enable Tiered Cache to reduce direct Cache Reserve misses, or use stale-while-revalidate
|
||||
|
||||
### "Purge Not Working as Expected"
|
||||
|
||||
**Cause:** Purge by tag only triggers revalidation but doesn't remove from Cache Reserve storage
|
||||
**Solution:** Use purge by URL for immediate removal, or disable Cache Reserve then clear all data for complete removal
|
||||
|
||||
### "O2O (Orange-to-Orange) Assets Not Caching"
|
||||
|
||||
**Cause:** Orange-to-Orange (proxied zone requesting another proxied zone on Cloudflare) bypasses Cache Reserve
|
||||
**Solution:**
|
||||
- **What is O2O**: Zone A (proxied) → Zone B (proxied), both on Cloudflare
|
||||
- **Detection**: Check `cf-cache-status` for `BYPASS` and review request path
|
||||
- **Workaround**: Use R2 or direct origin access instead of O2O proxy chains
|
||||
|
||||
### "Cache Reserve must be OFF before clearing data"
|
||||
|
||||
**Cause:** Attempting to clear Cache Reserve data while it's still enabled
|
||||
**Solution:** Disable Cache Reserve first, wait briefly for propagation (5s), then clear data (can take up to 24 hours)
|
||||
|
||||
## Limits
|
||||
|
||||
| Limit | Value | Notes |
|
||||
|-------|-------|-------|
|
||||
| Minimum TTL | 10 hours (36000 seconds) | Assets with shorter TTL not eligible |
|
||||
| Default retention | 30 days (2592000 seconds) | Configurable |
|
||||
| Maximum file size | Same as R2 limits | No practical limit |
|
||||
| Purge/clear time | Up to 24 hours | Complete propagation time |
|
||||
| Plan requirement | Paid Cache Reserve or Smart Shield | Not available on free plans |
|
||||
| Content-Length header | Required | Must be present for eligibility |
|
||||
| Set-Cookie header | Blocks caching | Must not be present (or use private directive) |
|
||||
| Vary header | Cannot be * | Can use Vary: Accept-Encoding |
|
||||
| Image transformations | Variants not eligible | Original images only |
|
||||
| Range requests | NOT supported | HTTP 206 bypasses Cache Reserve |
|
||||
| Compression | Fetches uncompressed | Serves compressed to visitors |
|
||||
| Worker control | Zone-level only | Cannot control per-request |
|
||||
| O2O requests | Bypassed | Orange-to-Orange not eligible |
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **Official Docs**: https://developers.cloudflare.com/cache/advanced-configuration/cache-reserve/
|
||||
- **API Reference**: https://developers.cloudflare.com/api/resources/cache/subresources/cache_reserve/
|
||||
- **Cache Rules**: https://developers.cloudflare.com/cache/how-to/cache-rules/
|
||||
- **Workers Cache API**: https://developers.cloudflare.com/workers/runtime-apis/cache/
|
||||
- **R2 Documentation**: https://developers.cloudflare.com/r2/
|
||||
- **Smart Shield**: https://developers.cloudflare.com/smart-shield/
|
||||
- **Tiered Cache**: https://developers.cloudflare.com/cache/how-to/tiered-cache/
|
||||
|
||||
## Troubleshooting Flowchart
|
||||
|
||||
Asset not caching in Cache Reserve?
|
||||
|
||||
```
|
||||
1. Is Cache Reserve enabled for zone?
|
||||
→ No: Enable via Dashboard or API
|
||||
→ Yes: Continue to step 2
|
||||
|
||||
2. Is Tiered Cache enabled?
|
||||
→ No: Enable Tiered Cache (required!)
|
||||
→ Yes: Continue to step 3
|
||||
|
||||
3. Does asset have TTL ≥ 10 hours?
|
||||
→ No: Increase via Cache Rules (edge_ttl override)
|
||||
→ Yes: Continue to step 4
|
||||
|
||||
4. Is Content-Length header present?
|
||||
→ No: Fix origin to include Content-Length
|
||||
→ Yes: Continue to step 5
|
||||
|
||||
5. Is Set-Cookie header present?
|
||||
→ Yes: Remove Set-Cookie or scope appropriately
|
||||
→ No: Continue to step 6
|
||||
|
||||
6. Is Vary header set to *?
|
||||
→ Yes: Change to specific value (e.g., Accept-Encoding)
|
||||
→ No: Continue to step 7
|
||||
|
||||
7. Is this a range request?
|
||||
→ Yes: Range requests bypass Cache Reserve (not supported)
|
||||
→ No: Continue to step 8
|
||||
|
||||
8. Is this an O2O (Orange-to-Orange) request?
|
||||
→ Yes: O2O bypasses Cache Reserve
|
||||
→ No: Continue to step 9
|
||||
|
||||
9. Check Logpush CacheReserveUsed field
|
||||
→ Filter logs to see if assets ever hit Cache Reserve
|
||||
→ Verify cf-cache-status header (should be HIT after first request)
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [README](./README.md) - Overview and core concepts
|
||||
- [Configuration](./configuration.md) - Setup and Cache Rules
|
||||
- [API Reference](./api.md) - Purging and monitoring
|
||||
- [Patterns](./patterns.md) - Best practices and optimization
|
||||
@@ -0,0 +1,197 @@
|
||||
# Cache Reserve Patterns
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Enable Tiered Cache
|
||||
|
||||
```typescript
|
||||
// Cache Reserve is designed for use WITH Tiered Cache
|
||||
const configuration = {
|
||||
tieredCache: 'enabled', // Required for optimal performance
|
||||
cacheReserve: 'enabled', // Works best with Tiered Cache
|
||||
|
||||
hierarchy: [
|
||||
'Lower-Tier Cache (visitor)',
|
||||
'Upper-Tier Cache (origin region)',
|
||||
'Cache Reserve (persistent)',
|
||||
'Origin'
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Set Appropriate Cache-Control Headers
|
||||
|
||||
```typescript
|
||||
// Origin response headers for Cache Reserve eligibility
|
||||
const originHeaders = {
|
||||
'Cache-Control': 'public, max-age=86400', // 24hr (minimum 10hr)
|
||||
'Content-Length': '1024000', // Required
|
||||
'Cache-Tag': 'images,product-123', // Optional: purging
|
||||
'ETag': '"abc123"', // Optional: revalidation
|
||||
// Avoid: 'Set-Cookie' and 'Vary: *' prevent caching
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Use Cache Rules for Fine-Grained Control
|
||||
|
||||
```typescript
|
||||
// Different TTLs for different content types
|
||||
const cacheRules = [
|
||||
{
|
||||
description: 'Long-term cache for immutable assets',
|
||||
expression: '(http.request.uri.path matches "^/static/.*\\.[a-f0-9]{8}\\.")',
|
||||
action_parameters: {
|
||||
cache_reserve: { eligible: true },
|
||||
edge_ttl: { mode: 'override_origin', default: 2592000 }, // 30 days
|
||||
cache: true
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'Moderate cache for regular images',
|
||||
expression: '(http.request.uri.path matches "\\.(jpg|png|webp)$")',
|
||||
action_parameters: {
|
||||
cache_reserve: { eligible: true },
|
||||
edge_ttl: { mode: 'override_origin', default: 86400 }, // 24 hours
|
||||
cache: true
|
||||
}
|
||||
},
|
||||
{
|
||||
description: 'Exclude API from Cache Reserve',
|
||||
expression: '(http.request.uri.path matches "^/api/")',
|
||||
action_parameters: { cache_reserve: { eligible: false }, cache: false }
|
||||
}
|
||||
];
|
||||
```
|
||||
|
||||
### 4. Making Assets Cache Reserve Eligible from Workers
|
||||
|
||||
**Note**: This modifies response headers to meet eligibility criteria but does NOT directly control Cache Reserve storage (which is zone-level automatic).
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
const response = await fetch(request);
|
||||
if (!response.ok) return response;
|
||||
|
||||
const headers = new Headers(response.headers);
|
||||
headers.set('Cache-Control', 'public, max-age=36000'); // 10hr minimum
|
||||
headers.delete('Set-Cookie'); // Blocks caching
|
||||
|
||||
// Ensure Content-Length present
|
||||
if (!headers.has('Content-Length')) {
|
||||
const blob = await response.blob();
|
||||
headers.set('Content-Length', blob.size.toString());
|
||||
return new Response(blob, { status: response.status, headers });
|
||||
}
|
||||
|
||||
return new Response(response.body, { status: response.status, headers });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 5. Hostname Best Practices
|
||||
|
||||
Use Worker's hostname for efficient caching - avoid overriding hostname unnecessarily.
|
||||
|
||||
## Architecture Patterns
|
||||
|
||||
### Multi-Tier Caching + Immutable Assets
|
||||
|
||||
```typescript
|
||||
// Optimal: L1 (visitor) → L2 (region) → L3 (Cache Reserve) → Origin
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
const isImmutable = /\.[a-f0-9]{8,}\.(js|css|jpg|png|woff2)$/.test(url.pathname);
|
||||
const response = await fetch(request);
|
||||
|
||||
if (isImmutable) {
|
||||
const headers = new Headers(response.headers);
|
||||
headers.set('Cache-Control', 'public, max-age=31536000, immutable');
|
||||
return new Response(response.body, { status: response.status, headers });
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Cost Optimization
|
||||
|
||||
### Cost Calculator
|
||||
|
||||
```typescript
|
||||
interface CacheReserveEstimate {
|
||||
avgAssetSizeGB: number;
|
||||
uniqueAssets: number;
|
||||
monthlyReads: number;
|
||||
monthlyWrites: number;
|
||||
originEgressCostPerGB: number; // e.g., AWS: $0.09/GB
|
||||
}
|
||||
|
||||
function estimateMonthlyCost(input: CacheReserveEstimate) {
|
||||
// Cache Reserve pricing
|
||||
const storageCostPerGBMonth = 0.015;
|
||||
const classAPerMillion = 4.50; // writes
|
||||
const classBPerMillion = 0.36; // reads
|
||||
|
||||
// Calculate Cache Reserve costs
|
||||
const totalStorageGB = input.avgAssetSizeGB * input.uniqueAssets;
|
||||
const storageCost = totalStorageGB * storageCostPerGBMonth;
|
||||
const writeCost = (input.monthlyWrites / 1_000_000) * classAPerMillion;
|
||||
const readCost = (input.monthlyReads / 1_000_000) * classBPerMillion;
|
||||
|
||||
const cacheReserveCost = storageCost + writeCost + readCost;
|
||||
|
||||
// Calculate origin egress cost (what you'd pay without Cache Reserve)
|
||||
const totalTrafficGB = (input.monthlyReads * input.avgAssetSizeGB);
|
||||
const originEgressCost = totalTrafficGB * input.originEgressCostPerGB;
|
||||
|
||||
// Savings calculation
|
||||
const savings = originEgressCost - cacheReserveCost;
|
||||
const savingsPercent = ((savings / originEgressCost) * 100).toFixed(1);
|
||||
|
||||
return {
|
||||
cacheReserveCost: `$${cacheReserveCost.toFixed(2)}`,
|
||||
originEgressCost: `$${originEgressCost.toFixed(2)}`,
|
||||
monthlySavings: `$${savings.toFixed(2)}`,
|
||||
savingsPercent: `${savingsPercent}%`,
|
||||
breakdown: {
|
||||
storage: `$${storageCost.toFixed(2)}`,
|
||||
writes: `$${writeCost.toFixed(2)}`,
|
||||
reads: `$${readCost.toFixed(2)}`,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Example: Media library
|
||||
const mediaLibrary = estimateMonthlyCost({
|
||||
avgAssetSizeGB: 0.005, // 5MB images
|
||||
uniqueAssets: 10_000,
|
||||
monthlyReads: 5_000_000,
|
||||
monthlyWrites: 50_000,
|
||||
originEgressCostPerGB: 0.09, // AWS S3
|
||||
});
|
||||
|
||||
console.log(mediaLibrary);
|
||||
// {
|
||||
// cacheReserveCost: "$9.98",
|
||||
// originEgressCost: "$25.00",
|
||||
// monthlySavings: "$15.02",
|
||||
// savingsPercent: "60.1%",
|
||||
// breakdown: { storage: "$0.75", writes: "$0.23", reads: "$9.00" }
|
||||
// }
|
||||
```
|
||||
|
||||
### Optimization Guidelines
|
||||
|
||||
- **Set appropriate TTLs**: 10hr minimum, 24hr+ optimal for stable content, 30d max cautiously
|
||||
- **Cache high-value stable assets**: Images, media, fonts, archives, documentation
|
||||
- **Exclude frequently changing**: APIs, user-specific content, real-time data
|
||||
- **Compression note**: Cache Reserve fetches uncompressed from origin, serves compressed to visitors - factor in origin egress costs
|
||||
|
||||
## See Also
|
||||
|
||||
- [README](./README.md) - Overview and core concepts
|
||||
- [Configuration](./configuration.md) - Setup and Cache Rules
|
||||
- [API Reference](./api.md) - Purging and monitoring
|
||||
- [Gotchas](./gotchas.md) - Common issues and troubleshooting
|
||||
Reference in New Issue
Block a user