This chapter covers the Cache-Aside pattern using Azure Cache for Redis, a fundamental data caching strategy for improving application performance and scalability in Azure. For the AZ-305 exam, understanding cache-aside is critical because it appears in multiple objective domains, particularly 4.4 (Design a caching solution). Approximately 5-10% of exam questions touch on caching patterns, with cache-aside being the most frequently tested. You will need to know its mechanism, when to apply it, how it differs from other patterns, and how to implement it with Azure Cache for Redis.
Jump to a section
Imagine a university library where students frequently request the same popular textbooks. The library has a reserve desk that holds a limited number of high-demand books. When a student asks for a book, the librarian first checks the reserve desk. If the book is there (a cache hit), the student gets it instantly without going to the main stacks. If not (a cache miss), the librarian retrieves it from the main stacks, gives it to the student, and also places a copy on the reserve desk for future requests. The reserve desk has a limited shelf; when it's full, the librarian removes the least recently used book to make space. Students never need to know whether a book came from the reserve desk or the main stacks—they just get the book. The librarian's process mirrors the cache-aside pattern: the application first checks the cache, and only on a miss does it query the database and then populate the cache. The cache eviction policy (LRU) ensures the most popular items stay available. This analogy captures the core mechanism: the cache is a transient, high-speed store that the application explicitly manages, with the database as the authoritative source of truth.
What is the Cache-Aside Pattern?
The cache-aside pattern (also known as lazy loading) is a caching strategy where the application code explicitly manages the cache. The application first checks the cache for data. If found (cache hit), the data is returned directly. If not found (cache miss), the application retrieves the data from the primary data store (e.g., Azure SQL Database, Cosmos DB), stores it in the cache, and then returns it. The cache is not automatically synchronized with the data store; the application is responsible for populating and invalidating cache entries.
This pattern is ideal for read-heavy workloads where data is accessed frequently but updated infrequently. It reduces latency and load on the backend database by serving repeated requests from the faster cache tier.
How It Works Internally
Consider an e-commerce application that displays product details. The flow is:
1. Application receives a request for product ID 123.
2. Application calls cache.Get("product:123") on the Redis cache.
3. If the key exists, Redis returns the serialized product data (e.g., JSON). The application deserializes it and returns the response. This is a cache hit.
4. If the key does not exist (cache miss), the application queries the database: SELECT * FROM Products WHERE Id = 123.
5. The application receives the result, serializes it, and calls cache.Set("product:123", serializedData, expiryTime) to populate the cache.
6. The application returns the data to the client.
Key characteristics:
The cache is populated lazily, only when data is first requested.
Cache entries have a time-to-live (TTL) to prevent stale data. Default TTL in many Redis client libraries is 0 (no expiry), but best practice is to set a TTL (e.g., 1 hour).
When data is updated, the application must invalidate the corresponding cache entry (delete or update it) to maintain consistency.
Key Components, Values, Defaults, and Timers
Azure Cache for Redis tiers: - Basic: Single node, no SLA, not for production. - Standard: Two nodes (primary/replica), 99.9% SLA. - Premium: Includes data persistence, clustering, and VNet support. - Enterprise/Enterprise Flash: Supports Redis modules like RediSearch and RedisBloom.
Default configuration values:
- maxmemory-policy: Default is volatile-lru (evict keys with TTL set using LRU). Other policies: allkeys-lru, volatile-ttl, noeviction.
- timeout: Default 0 (no timeout).
- databases: Default 16.
TTL best practices:
- Set TTL to a value that balances freshness and performance. Common values: 300 seconds (5 minutes) for frequently changing data, 3600 seconds (1 hour) for stable reference data.
- Use SETEX command to set key with expiry in one atomic operation.
Cache invalidation strategies: - TTL-based: Data expires automatically. - Event-driven: When data is updated, publish a message (e.g., via Azure Event Grid) to invalidate the cache. - Write-through: Application updates the cache synchronously when writing to the database (combine with cache-aside for reads).
Configuration and Verification Commands
Using StackExchange.Redis (C#):
IDatabase cache = Connection.GetDatabase();
string key = "product:123";
string cachedData = cache.StringGet(key);
if (cachedData != null)
{
return Deserialize<Product>(cachedData);
}
else
{
Product product = await database.GetProductAsync(123);
string serialized = Serialize(product);
cache.StringSet(key, serialized, TimeSpan.FromMinutes(10));
return product;
}Using redis-cli:
redis-cli -h mycache.redis.cache.windows.net -p 6380 -a <access-key>
# Check if key exists
EXISTS product:123
# Get value
GET product:123
# Set key with TTL
SETEX product:123 600 "{\"id\":123,\"name\":\"Widget\"}"
# Delete key
DEL product:123Monitoring commands:
- INFO stats – shows cache hits/misses.
- MONITOR – real-time command stream (avoid in production).
Interaction with Related Technologies
Azure SQL Database: Cache-aside reduces read load on SQL. Use with read replicas for scaling reads.
Azure Cosmos DB: Cache-aside works well with Cosmos DB's point reads. Combine with change feed to invalidate cache when data changes.
Azure App Service: Typically the application layer that implements the pattern.
Azure Functions: Can implement cache-aside for serverless architectures.
Azure API Management: Can add caching at the API gateway level (different pattern).
Important Considerations
Stale data: Cache-aside can serve stale data if TTL is too long and data changes frequently. Choose TTL based on business tolerance for staleness.
Cache miss storm: When a popular cache entry expires, many requests may trigger a cache miss simultaneously, causing a spike in database load. Mitigate by using a mutex or locking mechanism to ensure only one request populates the cache (e.g., using SETNX in Redis).
Data consistency: Cache-aside provides eventual consistency. For strong consistency, use a different pattern like write-through or consider not caching.
Serialization overhead: Serializing/deserializing adds CPU cost. Choose efficient serializers like MessagePack or Protobuf over JSON for high throughput.
Connection management: Use connection multiplexing in clients like StackExchange.Redis to avoid creating many connections.
Common Exam Traps
Confusing cache-aside with read-through: In read-through, the cache itself queries the database on a miss. In cache-aside, the application does it. Azure Cache for Redis does not support read-through natively; it must be implemented in the application.
Assuming automatic write-through: Cache-aside does not automatically update the cache on writes. The application must explicitly invalidate or update the cache.
Forgetting TTL: Many exam questions highlight that without TTL, cached data can become stale and never refresh until manual eviction.
Mixing patterns: The exam may describe a scenario and ask which pattern to use. Cache-aside is for read-heavy, eventually consistent workloads. Write-through is for write-heavy scenarios needing immediate consistency.
Application receives a data request
The application (e.g., a web API) gets a request for a specific data item, such as a product ID. It first determines the cache key, typically a string like 'product:123'. The key should be consistent and unique. The application then attempts to retrieve the data from the Azure Cache for Redis instance using a GET command. At the network level, the client sends a TCP packet to the Redis endpoint (port 6380 for SSL). If the key exists, Redis returns the value along with a status code. The application checks for a non-null result; if found, it deserializes the data and returns it, completing the request without touching the database.
Cache miss triggers database query
If the key is not found in Redis (cache miss), the application proceeds to query the primary data store. This could be an Azure SQL Database, Cosmos DB, or any other persistent store. The application executes a query (e.g., 'SELECT * FROM Products WHERE Id = 123'). The database returns the result. This step adds latency (typically 5-50 ms for a well-indexed query) and consumes database resources (DTUs/RUs). During a cache miss storm, multiple concurrent requests for the same missing key can all query the database simultaneously, overwhelming it. To prevent this, the application should implement a distributed lock (e.g., using SETNX in Redis) so only one request queries the database.
Data is stored in cache with TTL
After retrieving the data from the database, the application serializes it (e.g., into JSON) and stores it in Azure Cache for Redis using a SETEX command, which sets the key with an expiration time (TTL). The TTL value is critical: too short reduces cache effectiveness, too long risks serving stale data. Typical TTLs range from 60 seconds to 24 hours depending on data volatility. The command is atomic, meaning the key and TTL are set together. The application should also handle serialization errors gracefully, perhaps by not caching if serialization fails. After storing, the application returns the deserialized data to the client.
Subsequent requests hit the cache
For subsequent requests for the same data within the TTL window, the application will find the key in Redis (cache hit). Redis serves the data directly from memory (typically <1 ms). The application deserializes and returns it. This reduces latency and offloads the database. The cache hit ratio is a key performance metric; a high ratio (e.g., >90%) indicates effective caching. The application should monitor cache hit rate using Azure Monitor or Redis INFO stats. If the hit ratio is low, consider adjusting TTL, key design, or pre-warming the cache.
Data update triggers cache invalidation
When the underlying data changes (e.g., a product price is updated), the application must invalidate the corresponding cache entry to maintain consistency. The simplest approach is to delete the cache key using the DEL command. The next read will then cause a cache miss and fetch the updated data. Alternatively, the application can update the cache directly (write-through), but this adds latency to the write operation. For eventual consistency, deleting is preferred. In some designs, the database itself triggers invalidation via change data capture (CDC) or Azure Event Grid. The application must ensure that cache invalidation happens atomically with the database update to prevent race conditions.
Scenario 1: E-commerce Product Catalog
A large online retailer uses Azure Cache for Redis to cache product details, pricing, and inventory status. The product database is Azure SQL Database with millions of SKUs. The application implements cache-aside with a TTL of 5 minutes for product details and 1 minute for inventory (which changes more frequently). During a flash sale, traffic spikes to 100,000 requests per second. Without caching, the SQL database would be overwhelmed (it can handle about 10,000 QPS). With cache-aside, the cache hit ratio exceeds 95%, reducing database load to 5,000 QPS. The cache is a Premium tier with clustering (6 shards) to handle the throughput. A common misconfiguration is not setting a TTL, causing stale prices to be served for hours after a sale ends. The engineering team monitors cache hit rate and eviction rate using Azure Monitor; if evictions increase, they add more cache capacity.
Scenario 2: Social Media Feed Aggregation
A social media platform aggregates posts from multiple sources into a user feed. The feed data is stored in Azure Cosmos DB (API for MongoDB). The application uses cache-aside with Redis to cache the aggregated feed for each user. The cache key is 'feed:{userId}'. TTL is set to 60 seconds because feeds change frequently. When a user posts, the application invalidates the feeds of all followers (a fan-out operation) by deleting their cache keys. This is done asynchronously using Azure Functions. A pitfall is the 'thundering herd' problem: when a popular user posts, many followers' cache keys expire simultaneously, causing a flood of cache misses. The team mitigates this by using a mutex lock (SETNX) to allow only one request per user to regenerate the feed. They also use Redis Streams to queue invalidation events.
Scenario 3: Financial Market Data
A trading platform caches real-time stock quotes using Azure Cache for Redis. Data comes from a third-party API and is stored in Azure SQL Database for historical analysis. The cache-aside pattern is used for low-latency reads. TTL is set to 2 seconds to ensure quotes are never more than 2 seconds old (acceptable for the application). The database is updated every second via a separate process. The application uses a write-through approach for cache updates on the rare occasions it writes data (e.g., user preferences), but for quotes, it relies on TTL expiry. A common failure is network latency between the application and Redis; they deploy Redis in a Premium tier with VNet injection to minimize latency. They also use Redis Cluster to distribute load across multiple nodes. Misconfiguration of the eviction policy (e.g., using 'noeviction' without enough memory) leads to write failures, causing cache updates to fail silently.
AZ-305 Exam Focus on Cache-Aside Pattern
The AZ-305 exam tests cache-aside under objective 4.4: Design a caching solution. Questions often present a scenario with specific performance requirements and ask you to choose the appropriate caching pattern. Key points tested:
Pattern identification: You must distinguish cache-aside from read-through, write-through, write-behind, and cache-aside with lazy loading. The exam loves to describe a scenario where the application checks the cache first, then the database on a miss, and populates the cache. That is cache-aside.
Consistency implications: Cache-aside provides eventual consistency. If a question requires strong consistency (e.g., financial transactions), cache-aside is wrong. The exam may ask: "Which pattern ensures the cache is always consistent with the database?" Answer: write-through.
TTL and staleness: Many exam questions include TTL values. Common trap: setting TTL too long leading to stale data. The correct answer often involves setting an appropriate TTL based on the data's change frequency. The exam may ask: "How do you ensure cached data is refreshed periodically?" Answer: Set a TTL.
Cache invalidation: Questions about updating data: "When a product price changes, what should the application do?" The correct answer is to delete the cache key (or update it) during the write operation. A wrong answer is to wait for TTL expiry.
Azure Cache for Redis tiers: Know which tier supports clustering (Premium), persistence (Premium), and VNet (Premium). The exam may ask: "Which tier supports data persistence for disaster recovery?" Answer: Premium.
6. Common wrong answers: - Choosing read-through when the application is responsible for populating the cache. - Selecting write-behind (asynchronous write) when the question specifies data must not be lost. - Assuming cache-aside automatically updates the cache on writes. - Forgetting that cache-aside requires explicit code in the application.
7. Edge cases: - Cache miss storm: The exam may ask how to handle many concurrent misses for the same key. Answer: Use a distributed lock or a mutex. - Data consistency during write: If a user updates their profile and immediately reads it, cache-aside may return stale data if the cache was not invalidated. The correct approach is to invalidate the cache during the write.
Eliminating wrong answers: Focus on who triggers the database query. In cache-aside, it's the application. In read-through, it's the cache provider. In write-through, the cache is updated synchronously on writes. If the question says "the application checks the cache first," it's cache-aside. If it says "the cache automatically loads missing data," it's read-through.
Cache-aside pattern: application checks cache first, then database on miss, and populates cache.
Always set a TTL on cached data to prevent staleness; typical values: 60-3600 seconds.
Cache invalidation must be done explicitly on data updates (delete or update cache key).
Azure Cache for Redis does not support read-through natively; cache-aside is the standard pattern.
Use a distributed lock (e.g., SETNX) to prevent cache miss storms for popular keys.
Monitor cache hit ratio; a ratio <80% may indicate poor cache design or wrong TTL.
Premium tier supports clustering, persistence, and VNet for production workloads.
Cache-aside provides eventual consistency; use write-through for strong consistency.
These come up on the exam all the time. Here's how to tell them apart.
Cache-Aside (Lazy Loading)
Application explicitly checks cache first; on miss, queries database and populates cache.
Cache is populated lazily only when data is first requested.
Provides eventual consistency.
Commonly used with Azure Cache for Redis.
Requires application code to handle cache misses and invalidation.
Read-Through Caching
Cache itself queries the database on a miss, transparent to the application.
Cache can be pre-populated or populated on demand.
Also provides eventual consistency.
Often implemented with a caching library or proxy (e.g., NCache).
Simpler application code but more complex cache infrastructure.
Mistake
Cache-aside automatically updates the cache when data changes in the database.
Correct
Cache-aside does NOT automatically update the cache. The application must explicitly invalidate or update the cache when data changes. If the database is updated externally, the cache becomes stale until TTL expiry or manual invalidation.
Mistake
Azure Cache for Redis supports read-through natively.
Correct
Azure Cache for Redis does NOT support read-through natively. Read-through requires a custom implementation in the application or using a proxy like RedisGears. The cache-aside pattern is the standard approach with Redis.
Mistake
Setting no TTL on cache entries is fine because the cache will evict old entries when full.
Correct
Without TTL, cache entries can become stale and never refresh. Eviction only removes entries when memory is full, not based on freshness. Always set a TTL to ensure data eventually refreshes, even if the eviction policy is LRU.
Mistake
Cache-aside provides strong consistency between cache and database.
Correct
Cache-aside provides eventual consistency at best. There is a window between a database write and cache invalidation where the cache serves stale data. For strong consistency, use write-through or avoid caching.
Mistake
The cache-aside pattern is only for read-heavy workloads and cannot handle writes.
Correct
Cache-aside can handle writes by invalidating the cache on write operations. For read-heavy workloads with occasional writes, it works well. For write-heavy workloads, consider write-through or write-behind patterns.
Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.
The cache-aside pattern is a caching strategy where the application code checks the cache (e.g., Azure Cache for Redis) for data. On a cache miss, the application retrieves data from the primary database, stores it in the cache, and returns it. The application is responsible for invalidating or updating the cache when data changes. This pattern is ideal for read-heavy workloads with occasional updates.
In your application code, use a Redis client library (e.g., StackExchange.Redis for .NET). First, attempt to get the key with GET. If null, query the database, serialize the result, and store it with SETEX (set with expiry). On data updates, delete the cache key with DEL. Example: if (cache.StringGet(key) == null) { var data = db.Query(id); cache.StringSet(key, serialize(data), TimeSpan.FromMinutes(10)); }
TTL depends on how frequently data changes and how stale you can tolerate. For reference data that rarely changes, use hours (e.g., 3600 seconds). For frequently updated data like inventory, use minutes (e.g., 300 seconds). A good starting point is 600 seconds (10 minutes) and adjust based on cache hit ratio and data freshness requirements.
When data is updated, the application should delete the corresponding cache key (using DEL) during the same transaction as the database update. Alternatively, update the cache directly (write-through) if the application can tolerate the extra latency. For eventual consistency, deletion is simpler and avoids double writes.
In cache-aside, the application explicitly checks the cache and queries the database on a miss. In read-through, the cache itself queries the database transparently. Cache-aside gives the application more control and is simpler to implement with Redis. Read-through requires a caching library or proxy that supports it.
Use a distributed lock (e.g., SETNX in Redis) to ensure only one request regenerates the cache entry for a given key. Other requests wait or return a fallback. This prevents a thundering herd from overwhelming the database. Alternatively, use a mutex in application code or pre-warm the cache before high-traffic events.
Yes, cache-aside works well with Azure SQL Database. It reduces read load by caching query results. Ensure you set appropriate TTL and invalidate cache on data changes. For complex queries, consider caching the serialized result set. Monitor database DTU consumption to see the impact.
You've just covered Cache-Aside Pattern with Azure Cache for Redis — now see how well it sticks with free AZ-305 practice questions. Full explanations included, no account needed.
Done with this chapter?