Read-Through Cache
establishedSummary
Place a cache in front of the database such that all reads go to the cache; on a miss, the cache itself fetches the data from the database, populates itself, and returns the result: the application never reads the database directly and cache population is handled automatically by the cache layer.
Problem
Cache-aside requires the application to implement the check-miss-populate pattern in every code path that reads data. This is repetitive, error-prone, and creates a race condition (multiple concurrent readers can all trigger a database fetch for the same cache miss). Read-through centralizes this logic in the cache layer.
Description
In cache-aside, the application manages the cache explicitly: check cache, on miss read database, populate cache. In read-through, the cache is interposed between the application and the database. The application calls only the cache; if data is absent, the cache delegates to the database internally, caches the result, and returns it. The application code is not aware of whether the response came from cache or database.
Read-through provides a simpler programming model: the application has one read interface. It avoids the cache-aside thundering herd issue: when multiple concurrent requests miss on the same key simultaneously, only one cache-to-database fetch is issued (the cache coalesces concurrent misses). Other requesters wait for that single fetch to complete.
Read-through is natively provided by some caching systems: Amazon ElastiCache with DAX (DynamoDB Accelerator), Redis Enterprise's RediSearch, and some CDN configurations. In application-layer read-through, a caching library (Caffeine for JVM, django-cacheops) intercepts reads and manages population internally.
Read-through works best for data that is read-heavy, changes infrequently, and can tolerate bounded staleness. It does not naturally handle cache invalidation on writes, that requires a write-through or write-invalidation strategy applied alongside.
A Caffeine LoadingCache (JVM) is a concrete example: build the cache with a loader function (`.build(key -> userRepository.findById(key))`), and every `cache.get(userId)` call is either a hit or an automatic database fetch through that loader. Python's django-cacheops offers the equivalent via a `@cached_as` decorator on the read function. AWS DynamoDB Accelerator (DAX) is a fully managed read-through and write-through cache for DynamoDB: applications talk to DAX through its SDK, and cache population and invalidation are handled by the service rather than application code.
Tradeoffs
One read interface regardless of whether data is in cache
Automatic cache population on misses; no manual populate logic
Built-in miss coalescing prevents a thundering herd on the same key
The first request for any key always pays the miss penalty
Cache invalidation must be implemented separately (write-through or TTL-based expiry)
The cache layer is now a required dependency; if it fails and the fallback is not handled, reads fail entirely
Opaque cache population makes it less obvious when stale data is being served
When to use
Read access pattern is highly repetitive (same keys read by many requests)
Cache hit ratio improves as the same keys are read repeatedly; read-through's automatic population fills the cache quickly on startup
Cache miss handling should be transparent to application code
Simplifies application logic by hiding the cache-or-database routing decision
Concurrent miss coalescing is needed (many requests for the same missing key simultaneously)
Read-through caches typically serialize concurrent misses on the same key to a single database fetch
Cache layer supports read-through semantics natively (DAX, Caffeine LoadingCache)
Implementing read-through manually in application code negates much of its simplicity benefit
When not to use
Data has highly variable or unpredicatable access patterns
Read-through populates the cache on every miss; if data is rarely reread, the cache fill-and-evict cycle provides no benefit
Application requires fine-grained control over when database reads occur
Read-through triggers database reads transparently; use cache-aside when you need explicit control
Operational Requirements
Pre-warm the cache on startup
Needed if cold-start miss latency is unacceptable for the workload.
Monitor cache hit ratio by key space
A low hit ratio indicates TTLs are too short or the working set exceeds cache capacity.
Combine read-through with write-through or invalidation on writes
Prevents stale reads, since read-through alone does not handle invalidation.
Characteristics
Relationships
Complements
Basis
Read-through caching is documented in AWS DAX documentation, Caffeine documentation, and Redis Enterprise documentation; the pattern is well-established in both academic caching literature and industry practice
Related Architecture Knowledge
Outbound: this entity affects
Read-through handles cache population on miss; write-behind handles cache population on write. Together they form a complete transparent cache layer.
Full relationship →Inbound: affects this entity
Read-heavy API workloads benefit from read-through caching which automatically populates the cache on miss, reducing database read pressure and eliminating the miss coalescing problem under high concurrency.
Full relationship →Used In Architecture Scenarios
Read-Heavy Application
A CMS for publishing and serving structured content: articles, documentation, product pages, and localized variants: where read APIs serve 50–100x more traffic than editorial write APIs. PostgreSQL stores the content graph (articles, authors, categories, taxonomy) and workflow state (draft, in-review, scheduled, published). Redis caches published content objects for read APIs. Elasticsearch powers full-text content search with faceting and relevance ranking. Cache invalidation on publish must be fast and complete; N+1 query patterns on content relationship traversal are the dominant database performance risk during reads.
AI / RAG Application
A low-latency feature serving platform for ML model inference, providing both batch (offline) and real-time (online) feature access with strict training-serving consistency. Redis serves the hot feature cache with p99 latency targets below 5ms for online inference requests; PostgreSQL provides point-in-time feature lookups for offline training jobs with temporal consistency guarantees; Cassandra stores high-cardinality feature entities at write scale beyond PostgreSQL's single-primary ceiling; Kafka streams feature computation events from online feature pipelines to update the cache; Qdrant stores vector features for embedding-based model inputs and similarity lookups; ClickHouse serves feature analytics and drift monitoring across training dataset populations. Feature versioning is first-class: every feature value is tagged with a pipeline_version and computed_at timestamp to support model reproducibility and training-serving skew diagnosis.