Caching Strategies for Effective System Design

Niraj Ranasinghe
7 min readFeb 26, 2024

In system design, a cache acts as a temporary storage area that stores frequently accessed data. It works as a mediator between the main memory and the requesting client, holding copies of data to speed up retrieval. To put it simply, a cache is like a fast-access memory that stores commonly used information so it can be retrieved quickly when needed. As a software engineer focused on system design, understanding how caching works is essential for optimizing performance.I will discuss few useful caching strategies in this post.

Generated with AI

Caching is very important for improving system performance by reducing the time it takes to access data. Imagine a library where popular books are kept on a special shelf near the entrance. This makes it faster for people to find and borrow them, rather than having to search through the entire library every time. Similarly, caching stores frequently accessed data closer to the user, cutting down on the time needed to retrieve it from the main storage. This leads to faster response times, smoother user experiences, and overall better system efficiency.

Understanding Cache

How Cache Works in System Architecture

When data is requested, the cache checks if it already contains a copy. If it does, the data is retrieved quickly, bypassing the need to access the original source. If not, the cache fetches the data and stores a copy for future requests. This mechanism helps minimize the time and resources required to access frequently used information, thereby enhancing system performance.

Benefits of Implementing Cache in Systems

Implementing cache in systems offers many benefits. Firstly, it reduces latency by providing faster access to frequently accessed data, leading to improved response times and user experiences. Additionally, caching helps alleviate the load on backend systems and databases by reducing the number of requests they receive. This can result in cost savings and improved scalability, as systems can handle more concurrent users efficiently.

Examples of Cache

Caching in Web Browsers

Browser cache is like a storage space within your web browser where it stores copies of web pages, images, scripts, and other resources you’ve recently visited or downloaded. When you revisit a website, instead of re-downloading everything from scratch, your browser checks its cache to see if it already has the requested content. If it does, the browser loads the page much faster since it doesn’t need to fetch all the resources again.

The primary benefit of browser cache is faster page loading times and reduced server load, leading to a smoother browsing experience. However, sometimes outdated content may be displayed if the cache isn’t refreshed frequently. This can be a drawback, especially for dynamic websites where content changes frequently.

Database Query Cache

Database query cache stores the results of frequently executed database queries in memory. When the same query is issued again, the database server can retrieve the results from the cache rather than re-executing the query. This significantly reduces the database load and improves query response times, especially for read-heavy applications.

Popular database management systems like MySQL, PostgreSQL, and SQL Server offer query caching mechanisms to optimize performance. we can configure the cache size and expiration policies based on our application requirements.

CDN Caching

Content Delivery Networks (CDNs) cache static assets like images, CSS files, and JavaScript across distributed servers worldwide. When a user requests content from a website, the CDN serves the cached content from the server closest to the user’s location. This minimizes latency and speeds up content delivery.

CDN caching drastically improves website performance by reducing load times and enhancing user experience. It also offloads traffic from the origin server, making the website more resilient to traffic spikes and ensuring consistent performance even during peak usage periods.

Cache Invalidation and Eviction

Importance of Cache Invalidation

Cache invalidation is crucial for maintaining data consistency within a system. As data changes over time, it’s essential to invalidate or update cached copies to ensure that users receive the most up-to-date information. Without proper cache invalidation mechanisms, users may encounter outdated or inconsistent data, leading to confusion and mistrust in the system.

Strategies for Cache Invalidation

Time-Based Invalidation : Time-based cache invalidation involves setting expiration times for cached data. Once the expiration time elapses, the cached data is considered stale and needs to be refreshed from the source. This strategy ensures that data remains fresh within a predefined timeframe, although it may not always reflect real-time changes.

Event-Based Invalidation : Event-based cache invalidation relies on specific events or triggers to invalidate cached data. These events could include data updates, inserts, or deletes. When a relevant event occurs, the associated cached data is invalidated to reflect the latest changes. This approach offers more granular control over cache invalidation and ensures data consistency in real-time.

Cache Eviction Policies

Least Recently Used (LRU) : LRU eviction policy removes the least recently accessed items from the cache when it reaches its capacity limit. This ensures that the most recently accessed data remains in the cache, optimizing cache utilization and minimizing cache misses.

First-In-First-Out (FIFO) : FIFO eviction policy evicts the oldest items from the cache when it reaches its capacity limit. Data items are removed in the order they were first added to the cache, regardless of how frequently they are accessed. While simple to implement, FIFO may not always prioritize the most relevant or frequently accessed data.

Least Frequently Used (LFU) : LFU eviction policy removes the least frequently accessed items from the cache when it reaches its capacity limit. This ensures that items with low access frequencies are evicted, freeing up space for more frequently accessed data. LFU is effective in scenarios where access patterns vary over time.

Random Eviction : Random eviction policy randomly selects items from the cache for eviction when it reaches its capacity limit. While straightforward to implement, random eviction may not prioritize data that is more likely to be accessed in the future, potentially leading to suboptimal cache utilization.

Common Cache Patterns

Cache-Aside Pattern

The cache-aside pattern, also known as lazy loading, involves the application code directly interacting with the cache. When data is requested, the application first checks the cache. If the data is found, it’s returned to the user. If not, the application retrieves the data from the primary data source, stores it in the cache, and then returns it to the user. This pattern is suitable for scenarios where the cache contains frequently accessed data, improving response times and reducing load on the primary data source.

Cache-Aside Pattern

Write-Through and Write-Behind Caching

Write-through caching involves writing data to both the cache and the primary data source simultaneously. This ensures that the cache and the primary data source remain consistent but may introduce latency due to the dual-write operation. Write-behind caching, on the other hand, writes data to the cache first and then asynchronously updates the primary data source. While write-behind caching reduces latency, it may increase the risk of data inconsistency in case of system failures or crashes.

Write-Through and Write-Behind Caching

Read-Through and Read-Ahead Caching

Read-through caching involves reading data from the cache. If the data is not found, it’s retrieved from the primary data source, stored in the cache, and returned to the user. This pattern is suitable for read-heavy workloads where caching frequently accessed data can improve performance. Read-ahead caching anticipates future data access patterns and preloads data into the cache before it’s requested. This helps reduce latency by ensuring that the required data is readily available when needed.

Read-Through and Read-Ahead Caching

Cache Implementation in a WEB API

This is a very simple implementation of usage of caching in a WEB API.

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;

namespace CacheSample.API.Controllers
{
[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{
private readonly MyDbContext _dbContext;
private readonly IMemoryCache _cache;

public UserController(MyDbContext dbContext, IMemoryCache cache)
{
_dbContext = dbContext;
_cache = cache;
}


// Cache-Aside Pattern
[HttpGet("cache-aside/{id}")]
public async Task<IActionResult> GetWithCacheAside(int id)
{
string cacheKey = $"User_{id}";
if (!_cache.TryGetValue(cacheKey, out User user))
{
user = await _dbContext.Users.FindAsync(id);
if (user != null)
{
_cache.Set(cacheKey, user, TimeSpan.FromMinutes(60));
}
}
return Ok(user);
}

// Write-Through Caching
[HttpPost("write-through")]
public async Task<IActionResult> AddUserWriteThrough(User newUser)
{
_dbContext.Users.Add(newUser);
await _dbContext.SaveChangesAsync();

string cacheKey = $"User_{newUser.Id}";
_cache.Set(cacheKey, newUser, TimeSpan.FromMinutes(60));

return Ok(newUser);
}

}
}

Conclusion

In summary, caching strategies are essential for making systems faster and more efficient. By picking the right approach and staying updated on new trends, we can ensure smoother and more responsive user experiences in our applications.

A big shoutout to the AI tools for their contribution to improving this post. I hope you found it helpful! Your support means a lot to me. Keep an eye out for more exciting content in my future posts!

Happy coding!

--

--

Niraj Ranasinghe

I love sharing insights on Software Development, Emerging Tech, and Industry Trends. Join me on my journey to explore the exciting World of Technology!