Cache-Aside is the most commonly used caching pattern in system design.
The application itself is responsible for:
- Checking the cache
- Reading from the database when cache misses
- Putting the database result into cache
- Updating or invalidating the cache when data changes
The simple flow is:
Read:
Application → Cache
↓
Cache Hit → Return
↓
Cache Miss
↓
Database
↓
Update Cache
↓
Return
1. Cache-Aside Architecture
flowchart LR
U[User] --> A[ASP.NET Core API]
A --> C[Redis Cache]
C -->|Cache Hit| A
C -->|Cache Miss| D[SQL Server]
D --> C
C --> A
A --> U
The cache does not automatically load data from the database.
The application controls the entire process.
2. Cache Hit
Suppose Redis already contains:
product:1 → Laptop, 75000, stock 20
The application receives:
GET /products/1
Flow:
sequenceDiagram
participant U as User
participant A as API
participant C as Redis
U->>A: GET /products/1
A->>C: GET product:1
C-->>A: Product
A-->>U: Product Response
SQL Server is not contacted.
3. Cache Miss
Suppose Redis does not contain product:1.
sequenceDiagram
participant U as User
participant A as API
participant C as Redis
participant D as SQL Server
U->>A: GET /products/1
A->>C: GET product:1
C-->>A: MISS
A->>D: SELECT Product
D-->>A: Product
A->>C: SET product:1
A-->>U: Product Response
This is the key Cache-Aside pattern:
1. Check cache
2. Cache miss
3. Read database
4. Store result in cache
5. Return result
4. Complete SQL Server Example
CREATE DATABASE CacheAsideDemo;
GO
USE CacheAsideDemo;
GO
CREATE TABLE Products
(
ProductId INT PRIMARY KEY,
ProductName VARCHAR(100),
Price DECIMAL(10,2),
Stock INT
);
GO
INSERT INTO Products
(
ProductId,
ProductName,
Price,
Stock
)
VALUES
(1, 'Laptop', 75000, 20),
(2, 'Mobile', 30000, 50),
(3, 'Headphones', 3000, 100);
GO
SELECT ProductId, ProductName, Price, Stock
FROM Products
ORDER BY ProductId;
GO
Expected Output
ProductId ProductName Price Stock
--------- ----------- --------- -----
1 Laptop 75000.00 20
2 Mobile 30000.00 50
3 Headphones 3000.00 100
5. ASP.NET Core Cache-Aside Example
Using Redis through IDistributedCache:
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
options.InstanceName = "Shop:";
});
app.MapGet("/products/{id}", async (
int id,
IDistributedCache cache,
AppDbContext db) =>
{
string key = $"product:{id}";
// 1. Check cache
var cachedProduct = await cache.GetStringAsync(key);
if (cachedProduct != null)
{
return Results.Content(
cachedProduct,
"application/json");
}
// 2. Cache miss - read database
var product = await db.Products
.FirstOrDefaultAsync(x => x.ProductId == id);
if (product == null)
return Results.NotFound();
// 3. Store database result in cache
var json = JsonSerializer.Serialize(product);
await cache.SetStringAsync(
key,
json,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMinutes(10)
});
// 4. Return response
return Results.Content(
json,
"application/json");
});
6. First Request vs Second Request
First Request
GET /products/1
Redis
↓
MISS
SQL Server
↓
Laptop
Redis
↓
Store product:1
Response
↓
User
Second Request
GET /products/1
Redis
↓
HIT
Response
↓
User
The second request does not need SQL Server.
7. Updating Data — The Important Part
Suppose the product price changes.
Old price:
75000
New price:
70000
If we update only SQL Server:
flowchart LR
A[API] --> D[SQL Server]
D -->|Price = 70000| D
C[Redis] -->|Still 75000| A
Now the cache contains stale data.
This is called a cache consistency problem.
8. Cache-Aside Write Pattern
A common approach is:
sequenceDiagram
participant U as User
participant A as API
participant D as SQL Server
participant C as Redis
U->>A: Update Product
A->>D: UPDATE Product
D-->>A: Success
A->>C: DELETE product:1
C-->>A: Deleted
A-->>U: Success
Instead of updating the cache with the new value, we can invalidate it.
The next read will populate the cache again.
UPDATE Database
↓
DELETE Cache
↓
Next Read
↓
Cache Miss
↓
Read Database
↓
Populate Cache
9. Complete Update Example
app.MapPut("/products/{id}", async (
int id,
Product request,
IDistributedCache cache,
AppDbContext db) =>
{
var product = await db.Products
.FirstOrDefaultAsync(x => x.ProductId == id);
if (product == null)
return Results.NotFound();
// Update database
product.ProductName = request.ProductName;
product.Price = request.Price;
product.Stock = request.Stock;
await db.SaveChangesAsync();
// Invalidate cache
string key = $"product:{id}";
await cache.RemoveAsync(key);
return Results.Ok(product);
});
Now:
Database updated
↓
Cache deleted
↓
Next GET
↓
Cache MISS
↓
Read latest database value
↓
Populate cache
10. Why Delete Cache Instead of Updating It?
Updating both database and cache introduces more possibilities for inconsistency.
Database update succeeds
Cache update fails
Result:
Database = new value
Cache = old value
With invalidation:
Database update
↓
Delete cache
↓
Next request rebuilds cache
This is a common Cache-Aside approach, although production systems still need to consider failures between the database update and cache invalidation.
11. Cache-Aside with Multiple API Servers
flowchart LR
U[Users] --> LB[Load Balancer]
LB --> A1[API Server 1]
LB --> A2[API Server 2]
LB --> A3[API Server 3]
A1 --> R[Redis]
A2 --> R
A3 --> R
R --> DB[SQL Server]
All API servers share Redis.
For example:
Server 1 → Redis → product:1
Server 2 → Redis → product:1
Server 3 → Redis → product:1
Therefore the cache does not depend on which API server receives the request.
12. What Happens If Redis Goes Down?
A Cache-Aside application should treat the cache as an optimization, not the only source of data.
flowchart TD
A[API] --> C[Redis]
C -->|Available| H[Cache Hit]
C -->|Unavailable| F[Fallback]
F --> D[SQL Server]
D --> A
If Redis is unavailable:
Request
↓
Redis unavailable
↓
Read SQL Server
↓
Return response
However, if millions of requests simultaneously fall back to SQL Server, the database can become overloaded.
This leads to problems such as cache stampede, which we will cover later.
13. Advantages of Cache-Aside
- Simple to understand
- Application controls caching
- Only frequently accessed data needs to be cached
- Database remains the source of truth
- Works well with Redis
- Easy to introduce incrementally
- Cache failure can be handled with database fallback
14. Disadvantages
- Application code becomes responsible for caching
- Cache invalidation is difficult
- First request after expiration is slower
- Stale data is possible
- Cache miss storms can overload the database
- Read/write consistency needs careful design
15. Cache-Aside vs No Cache
| Without Cache | Cache-Aside |
|---|---|
| Every read may hit database | Application checks cache first |
| Higher database load | Lower database load |
| Simple consistency | Cache invalidation required |
| Higher latency for repeated reads | Lower latency for cache hits |
16. Real-World Scenario — Product Catalog
Suppose an e-commerce system receives:
100,000 product-read requests/sec
Most requests are for popular products.
Using Cache-Aside:
flowchart LR
U[100K Requests/sec] --> API[Product API]
API --> R[Redis]
R -->|95% Hit| H[95K Responses]
R -->|5% Miss| DB[SQL Server]
DB --> R
Approximately 95% of reads can be served from Redis in this example.
SQL Server receives only the cache misses rather than every request.
17. When Should We Use Cache-Aside?
Cache-Aside is a good fit when:
- Application has many reads
- Same data is requested repeatedly
- Data does not change every second
- Some temporary staleness is acceptable
- Application wants control over what is cached
Typical examples:
- Product catalog
- Customer profile
- Configuration
- Reference data
- Popular content
- Frequently accessed API responses
18. Interview Scenario
Interviewer: Explain Cache-Aside.
Answer:
“In Cache-Aside, the application checks the cache first. If the data exists, it returns the cached value. If there is a cache miss, the application reads from the database, stores the result in the cache, and returns it. During an update, a common approach is to update the database and invalidate the corresponding cache key so that the next read loads the latest value.”
19. Common Interview Questions
Q1. Who manages the cache in Cache-Aside?
The application manages it. The application explicitly reads, writes, and invalidates cache entries.
Q2. What happens on a cache miss?
Cache Miss
↓
Database
↓
Store in Cache
↓
Return Response
Q3. What happens when data changes?
A common strategy is to update the database and invalidate the corresponding cache key.
Q4. Is Redis responsible for reading SQL Server?
No. In Cache-Aside, the application reads SQL Server after a cache miss and then puts the result into Redis.
Q5. What is the biggest challenge?
Cache invalidation and consistency between the cache and the source of truth.
Q6. What happens if Redis fails?
The application can fall back to the database, but the system must protect the database from a sudden large number of cache misses.
Q7. Cache-Aside vs Read-Through?
In Cache-Aside, the application explicitly reads the database and populates the cache. In Read-Through, the cache layer itself is responsible for loading missing data from the underlying data store.
20. Key Takeaways
- Cache-Aside = Application manages the cache.
- Application checks cache first.
- Cache hit → return cached data.
- Cache miss → read database.
- After a miss, application populates cache.
- Database remains the source of truth.
- On updates, a common approach is database update followed by cache invalidation.
- Redis is commonly used as the distributed cache.
- Cache-Aside is simple and widely used.
- The major challenge is cache consistency and invalidation.
Conclusion
The most important Cache-Aside flow to remember for interviews is:
flowchart TD
A[Request] --> B[Check Cache]
B -->|HIT| C[Return Cached Data]
B -->|MISS| D[Read Database]
D --> E[Store in Cache]
E --> C
F[Update Request] --> G[Update Database]
G --> H[Invalidate Cache]
Key interview sentence:
“Cache-Aside is a caching pattern where the application manages the cache. It checks the cache first, reads from the database on a miss, stores the result in the cache, and commonly invalidates the cache after database updates. Redis is often used as the distributed cache.”