Read Replicas vs Caching: When to Use Each

Choose replicas, caches, or both using a per-read consistency contract, safe routing, invalidation controls, cold-path capacity tests, and a staged rollout checklist.

trigger="Read traffic is a measured constraint, and the team is considering replicas or cached results." owner="The backend lead responsible for the read contract and data correctness." participants={["Database owner", "Platform engineer", "Security reviewer", "Product owner", "On-call operator"]} prerequisites={[ "Read/write workload measurements, query fingerprints, and known latency objectives.", "A classification of freshness, authorization, tenant isolation, and read-after-write requirements.", "A representative test environment with controllable replica lag and cache failure." ]} outputs={[ "A per-operation routing and freshness contract with a documented source of truth.", "A cache-key and invalidation specification where caching is selected.", "Failure-test results, capacity limits, and a reversible read-path rollout." ]} doneWhen={[ "Protected reads cannot silently take a stale cache or replica path.", "Concurrent writes, invalidation loss, replica lag, and cold-cache conditions have passed their agreed tests.", "Fallback traffic stays within the authoritative store's capacity budget.", "The operator can disable each optimization independently and reconcile any affected results." ]} />

The choice begins with what a read is allowed to return

Read replicas execute queries against another copy of database state. Caches reuse selected representations or computed results. Either can reduce pressure on a primary database, but they solve different workload problems and introduce different correctness obligations.

Use this playbook after identifying reads as a constraint. It is not a reason to add both technologies to every service. A better query, a suitable index, or bounded reporting workload may address the issue with fewer moving parts.

The examples assume one authoritative write path and optional asynchronous replicas. For PostgreSQL, streaming replication is asynchronous by default; the configured commit and replication modes matter. Other databases and managed services need their own documented consistency assessment. PostgreSQL's standby documentation is the primary reference for the PostgreSQL-specific statements here.

1. Create a per-operation read contract

The application owner and product owner define the result a reader may receive. Security signs off where stale authorization, tenant boundaries, or sensitive representations are involved. Do this before choosing a time-to-live, or TTL.

| Read category | Example intent | Default design question | | --- | --- | --- | | Current authoritative state | Confirm an inventory decision or enforce a permission change | What transaction or authoritative read establishes the required state? | | Read after own write | Show the record the user just saved | How will the read observe that commit? | | Bounded-stale view | Browse a catalogue with an accepted freshness allowance | How is source freshness measured and enforced? | | Snapshot or report | Reproduce a report for a defined data cutoff | Which snapshot or watermark makes the result interpretable? | | Reusable public representation | Read an unchanged public document | What versions and invalidation events affect the representation? |

“Strong consistency” is too vague for an implementation ticket. Specify whether the operation needs the user's own write, the latest committed value, a consistent multi-record snapshot, or an atomic read-and-update decision. Reading the primary alone does not make a multi-step business operation atomic; transaction isolation and concurrency controls still matter.

For each operation record the source of truth, acceptable staleness, authorization behavior, client-visible fallback, and owner. If no one can accept stale output, do not infer permission to serve it during an outage.

2. Choose the mechanism from measured work

| Dimension | Read replica | Cache | | --- | --- | --- | | Reuse | Executes eligible queries on another database copy | Reuses a representation or computation | | Query flexibility | Supports the replica's allowed query workload | Limited to the key and representation design | | Freshness risk | Replication delay, replay behavior, and transaction visibility | Fill races, invalidation delay, TTL, and source freshness | | Capacity risk | Replica saturation or rerouted traffic after failure | Miss storms, eviction, hot keys, and origin overload | | Write implications | Primary still handles writes in this topology | Application or platform must keep representations acceptable | | Operational work | Replication, access, backups as needed, failover distinctions | Key lifecycle, memory, permissions, observability, and recovery |

Consider a replica when expensive but freshness-tolerant reads compete with transactional work and still need database query flexibility. Consider a cache when many requests reuse a result and the invalidation contract is manageable. Test both assumptions with representative demand. Neither a particular cache-hit ratio nor a fixed replica count establishes success.

A replica used for reporting is not automatically a suitable failover target. Confirm promotion support, durability exposure, capacity, and operations separately. Likewise, a disposable cache should not quietly become the only durable location for session or business state.

3. Route by authorization and consistency first

The routing policy must evaluate the operation's requirements before attempting a cache hit. A fast hit is still wrong if the caller needs a newer value or is no longer authorized.

"type": "flow", "title": "Policy comes before a read optimization", "steps": [ ], "caption": "This is evaluation order, not a chain that sends every request through a cache and replica. The routing table defines the branches." }} />

| Requirement | Allowed path | If that path cannot satisfy it | | --- | --- | --- | | Authoritative or protected current read | Approved authoritative read or transactional operation | Return the documented unavailable or denied outcome | | Read after a known commit | Authoritative path, or a replica proven to have applied the required commit | Wait only within the deadline, then use an admitted authoritative read or fail explicitly | | Bounded-stale result | Cache or replica with adequate freshness evidence | Bypass within capacity, or return the agreed unavailable result | | Explicit historical snapshot | Source that can identify the required snapshot | Do not silently substitute a current or unrelated cached result |

A short timer after a write is not proof of replica catch-up. If the platform supports a commit-position check, verify that the selected replica has applied the relevant position and that the proof remains valid for the active topology. Handle failover and timeline changes explicitly. Otherwise keep the read on the authoritative path for the consistency-sensitive operation.

Do not interpret an unavailable lag metric as zero lag. Decide whether that uncertainty forces primary routing, rejects the read, or permits an explicitly accepted degraded view. Include the selected policy in test cases.

4. Design cache keys and freshness evidence

A cache key must distinguish everything that changes the permitted representation. Relevant dimensions may include tenant, resource identifier, representation version, locale, query parameters, and authorization scope or policy version. Avoid embedding raw credentials, personal data, or bearer tokens in keys and telemetry.

Authorization must still be valid when the result is served. Putting a permission version in a key helps only if the serving path obtains and checks the right version. Security-sensitive caching needs its own reviewed policy, including revocation and failure behavior; it is not covered by a generic application TTL.

Keep the source version or watermark with the value when the read contract needs it. Cache insertion time alone does not prove data freshness. A newly inserted result from a lagging replica can already be stale. Set a TTL as one lifecycle control, not as a claim that every returned value is at most that many seconds behind the primary.

Microsoft's cache-aside guidance describes loading on a miss and invalidating after the backing store changes, while noting that cache-aside does not guarantee consistency. In a workload with concurrent writers or delayed events, additional versioning or a stricter read path may be necessary.

5. Specify write, invalidation, and reconciliation behavior

A database commit and a separate cache update are not automatically atomic. Calling a design “write-through” does not remove the failure between those two operations or order concurrent writers. Define what the application acknowledges, how it repairs stale representations, and which reads must bypass the cache.

"type": "svg-architecture", "title": "Keep the database authoritative", "nodes": [ ], "links": [ ], "caption": "Illustrative asynchronous invalidation. The outbox makes event intent durable, not the cache immediately consistent. Protected reads still bypass this path." }} />

For a cache-aside design, commit the source change before invalidating. Then test the harder race: a reader starts loading the old value, a writer commits and invalidates, and the reader finishes by refilling the old value. A simple delete does not prevent that sequence.

Depending on the contract, use a reviewed version-aware update protocol, a generation scheme whose authoritative version is checked, or accept and bound the stale behavior explicitly. If none can satisfy the requirement, bypass the cache for that operation. Test the selected protocol instead of describing versioned keys as an automatic guarantee.

Where a database change must reliably trigger invalidation, a transactional outbox can persist the change and event intent in one database transaction. The publisher and consumer still need duplicate handling, ordering rules, progress monitoring, and repair. AWS's outbox guidance covers the dual-write problem and duplicate-event considerations.

6. Protect the origin when optimizations fail

Estimate and test the traffic that reaches the authoritative store when the cache is empty or replicas are removed. Limit concurrent fills, coalesce identical in-flight requests where appropriate, and prevent retries from multiplying demand. Add bounded queues and admission controls rather than assuming the database can absorb every miss.

A stale-while-revalidate policy can help only for reads whose contract permits the stale representation. It is not a general fallback for permission checks or correctness-sensitive business decisions. Ensure a background refresh has its own timeout and concurrency limit.

Monitor hit/miss volume, origin request rate, oldest invalidation backlog, freshness violations, eviction, pool wait, and rejected requests. Redis's eviction documentation explains memory-policy choices. Eviction is not automatically a fault, but its effect on origin load and cache behavior must fit the selected policy. With a no-eviction configuration, handle rejected writes rather than assuming all insertions succeed.

7. Run the failure and correctness matrix

The backend owner defines assertions; the platform owner supplies controlled faults. Test with more than one application instance so local state does not conceal distributed races.

| Scenario | Assertion required before rollout | | --- | --- | | Read immediately after a write | The operation observes its required state, not a stale hit | | Replica lag or unavailable lag telemetry | Routing follows the explicit uncertainty policy | | Permission revocation | No previously cached representation bypasses the current access decision | | Two concurrent writers | Cache state cannot violate the agreed ordering or staleness contract | | In-flight old fill after invalidation | The selected race-control mechanism works or the read bypasses caching | | Lost, duplicate, or reordered invalidation | Repair completes without replacing a newer value with an older one | | Empty cache or cache timeout | Admitted origin work stays within the database budget | | Replica withdrawal | Only eligible traffic moves, and pool limits remain valid | | Tenant boundary collision | Keys and authorization prevent cross-tenant results | | Topology change | Commit-position evidence is not reused outside its valid context |

Save request traces, record versions, and workload configuration with the results. A test that only checks response latency cannot establish correctness. Avoid logging sensitive values when identifiers and hashes can support the comparison.

8. Roll out one read family at a time

Introduce independent controls for replica routing and cache use. Start with a bounded, low-risk read category. Compare correctness against the authoritative source where that comparison is safe and meaningful, accounting for deliberately accepted staleness. Limit comparison traffic so validation itself does not overload the writer.

Stop expansion when correctness checks fail, the origin approaches its approved limit, or latency exceeds the agreed objective. Disable the affected optimization first, but only if the fallback capacity allows it. Otherwise shed lower-priority reads or return a controlled unavailable result.

A routing rollback does not undo decisions already made from stale data. Identify affected operations and reconcile business effects with their owners. For a poisoned cache entry, invalidate the affected namespace or version with controlled warming; a global flush can create an avoidable origin surge.

8.1 Prove the contract with a reader acceptance suite

Build the acceptance suite around business reads, not infrastructure components. Each test names the authoritative value, the allowed staleness, the caller's authorization context, the optimized route used, and the observable result. Include at least one case where returning an older value is safe and one where it would cause an incorrect decision.

Useful scenarios include:

  • a user reads immediately after their own write;
  • two tenants use the same object identifier or query shape;
  • a permission is revoked while an older representation remains cached;
  • a replica falls behind during a write-heavy period;
  • an invalidation event is delayed, duplicated or delivered out of order;
  • a popular key expires while many callers arrive together; and
  • the cache or replica is unavailable while the authoritative store is near its connection limit.

For each scenario, define whether the system must use the writer, may serve a bounded stale result, should shed the request, or must return a controlled error. Test the routing decision as well as the returned value. A correct record delivered to an unauthorized caller is still a failure.

Keep a small set of synthetic identities and records for continuous verification, but do not let synthetic traffic bypass normal policy enforcement. When a comparison query reads the writer, bound its concurrency and cost. Correctness validation that destabilizes the authoritative path defeats the purpose.

8.2 Assign operational signals to decisions

Dashboard labels should tell an operator what action is possible. Track replica replay lag or freshness evidence, cache hit ratio by read family, origin connections and latency, invalidation age, fill concurrency, evictions, and correctness mismatches. Aggregate hit rate alone can hide that the most valuable operation is unsafe or that low-value traffic dominates the cache.

Map signals to explicit responses. Rising replica lag may disable only strict-freshness routes. A correctness mismatch should stop expansion and preserve evidence. Origin saturation may require admission control before a cache bypass. A surge in evictions may call for key review, memory policy adjustment or removal of low-value entries, not an automatic global flush.

Name the person who can disable each route, the person who owns reconciliation, and the approver for expansion. If the team cannot act on a signal during support hours and after hours, it is telemetry without an operating model.

Read-path specification template

Operation / owner / business intent:
Authoritative source and transaction requirements:
Authorization and tenant boundary:
Allowed freshness / read-after-write requirement:
Eligible cache and replica paths:
Key dimensions / representation version:
Freshness evidence and behavior when evidence is missing:
Write acknowledgement / invalidation / ordering protocol:
Cold-path concurrency and connection budget:
Failure response and independent disable controls:
Correctness tests / load-test evidence:
Rollback limits / reconciliation owner:

Completion and further work

"Every optimized read has an accepted consistency, authorization, and freshness contract.", "The router evaluates the contract before returning any cache or replica result.", "Cache keys separate tenants and representations without storing secrets.", "Invalidation loss, concurrent fills, and reordered events have tested behavior.", "Cold-cache and replica-failure tests protect authoritative writes.", "Operators can disable each optimization, control warming, and investigate affected results." ]} />

Use Database Scaling Strategies if the constraint is broader than reads. Ampity's scalability and performance analysis is the related service for a workload-specific decision and test plan.

Primary references

Check the selected database, driver, cache, and managed-service versions before implementation. No routing diagram or cache label substitutes for testing the workload's specific consistency contract.