Caching external data
Purpose: Choose the right cache for upstream data: in-memory, repository, or HTTP-layer, with invalidation that authors understand.
Who this page is for
| Audience | Why it matters to you |
|---|---|
| Backend engineers | Anyone calling an upstream more than once |
The options
| Cache | Scope | Survives restart | Good for | Watch out |
|---|---|---|---|---|
| In-memory (Caffeine/Guava in an OSGi service) | Per instance | No | Small, hot, TTL-tolerant data (rate tables) | Per-publisher inconsistency windows |
| Repository (/var/phi/cache nodes) | Shared via replication or per instance | Yes | Larger datasets, author-visible data | Writes create repo churn; GC/purge needed |
| Dispatcher/CDN (cache the AJAX endpoint) | Edge | n/a | Per-URL JSON responses | Invalidation = flush design |
| Client-side (browser fetch + cache headers) | User | n/a | Personalised/live data | Not for SEO-relevant content |
Standard PHI pattern: rates sync
Premium rates change daily upstream. Instead of render-time calls:
Scheduled job (02:00) ──▶ rating API ──▶ write rates to /var/phi/rates (author)
└─▶ replicate to publish
Render path: model reads /var/phi/rates — pure repo read, no HTTP, no fallback complexity
The render path never knows the upstream exists. This "sync to content, render from content" pattern is the single most reliable AEM integration shape — use it whenever data freshness tolerates a schedule.
TTL + stale-while-revalidate for in-memory caches
Serve stale on refresh failure (up to a hard cap) and refresh asynchronously — this pairs the cache with the circuit breaker so brief upstream blips are invisible.
Invalidation honesty
Every cache you add is a "why is it showing the old premium?" ticket waiting. For each cache, document: TTL, manual flush method (JMX operation / admin URL), and who may use it. Put it in the runbook, not tribal memory.