Caching in System Design : Cache Design Patterns
Cache design patterns commonly used in system design:
1. Cache-Aside Pattern
2. Read-Through Pattern
3. Write-Through Pattern
4. Write-Behind Pattern
1. Cache-Aside Pattern
What is Cache-Aside?
The Cache-Aside pattern, also known as Lazy Loading, is the most commonly used caching pattern.
In this pattern, the application code itself is responsible for managing the cache. The application reads
data from the cache first, and if the data is not found (i.e., a cache miss), it fetches the data from the
database and then stores it in the cache for future use.
How It Works:
The application checks if the requested data is in the cache.
If the data is found, it’s returned from the cache.
If the data is not in the cache (a cache miss), it is fetched from the database, and then the
cache is updated with the fetched data for future requests.
When to Use Cache-Aside:
When cache updates are infrequent.
When the application needs full control over when to refresh or load the cache.
When you want to cache only data that is frequently accessed.
Technologies for Implementing Cache-Aside:
Redis: Redis supports Cache-Aside pattern well. Developers can use commands
like GET to fetch data from the cache and SET to store data.
Memcached: Memcached can also implement this pattern for simple caching scenarios.
import redis
cache = redis.StrictRedis(host='localhost', port=6379, db=0)
def get_data_from_cache(key):
data = cache.get(key)
if data:
return data
else:
data = fetch_data_from_db(key)
cache.set(key, data)
return data
def fetch_data_from_db(key):
# Fetch data from the database (e.g., SQL query)
return "Fetched from DB"
2. Read-Through Pattern
What is Read-Through?
The Read-Through caching pattern automates the process of loading data into the cache. With this
pattern, the cache itself is responsible for loading data from the underlying data source (such as a
database) when the requested data is not present in the cache.
How It Works:
The application queries the cache.
If the data is present in the cache, it’s returned.
If the data is not present (a cache miss), the cache retrieves the data from the database,
stores it in the cache, and then returns the data to the application.
When to Use Read-Through:
When you want caching to be transparent to the application.
When you need the cache to automatically populate itself with data.
Technologies for Implementing Read-Through:
Spring Cache: Spring provides @Cacheable annotations to implement read-through
functionality, allowing the application to automatically load data from the cache.
@Cacheable(value = "dataCache", key = "#id")
public String getDataFromDatabase(String id) {
// Fetch data from the database
return "Fetched from DB";
}
3. Write-Through Pattern
What is Write-Through?
The Write-Through caching pattern ensures that data is written to the cache as soon as it is written to
the database. In other words, whenever data is updated in the database, it is also updated in the cache
immediately, keeping both the cache and the database in sync.
How It Works:
When an application writes or updates data, the data is first written to the cache.
Then, the data is also written to the database, either synchronously or asynchronously.
This ensures that the cache always has the most recent data.
When to Use Write-Through:
When you need to maintain cache consistency with the database for every write
operation.
When you need the cache to store data in a way that reflects the most recent changes.
Technologies for Implementing Write-Through:
Spring Cache: Write-through can be implemented using the @CachePut annotation,
ensuring data is written to both the cache and the database at the same time.
@CachePut(value = "dataCache", key = "#id")
public String updateDataInDatabase(String id, String data) {
// Update data in the database
return "Updated in DB";
}
4. Write-Behind Pattern
What is Write-Behind?
The Write-Behind caching pattern defers writing to the database until after the data has been written
to the cache. In this pattern, data is written to the cache first, and the cache asynchronously updates the
database in the background.
How It Works:
When data is written, it’s first stored in the cache.
The cache queues the write operation to the database, and the database is updated
asynchronously in the background.
This improves write performance, as the database is not immediately updated with every
write operation.
When to Use Write-Behind:
When you want to optimise write operations and avoid blocking the application with
database writes.
When you can tolerate eventual consistency between the cache and the database.
Technologies for Implementing Write-Behind:
Hazelcast: Hazelcast supports write-behind caching, where it stores the data in the cache
and then asynchronously updates the database.
Cache<String, String> cache = hazelcastInstance.getMap("dataCache");
cache.put("key", "value"); // Written to cache first
// Hazelcast asynchronously writes the data to the database
Conclusion
Caching is a powerful technique to optimise system performance, but choosing the right cache design
pattern is essential for achieving efficient and reliable systems. Depending on the use case, you may
need to implement one of the following patterns:
Cache-Aside: Application decides when to load and store data in the cache.
Read-Through: Cache handles reading and fetching data from the database.
Write-Through: Both the cache and the database are updated simultaneously when
writing.
Write-Behind: Data is written to the cache first, and the database is updated
asynchronously.