Cache-Aside Pattern in Python: Per-Service Mock
A Python mock of the cache-aside pattern for a single microservice—lazy-load from a database into an in-memory cache and invalidate on updates.
Python code
34 linesclass ServiceCache:
def __init__(self):
self.database = {"user:1": "Alice", "user:2": "Bob", "user:3": "Charlie"}
self.cache = {}
def get_user(self, user_id):
cache_key = f"user:{user_id}"
if cache_key in self.cache:
print(f"CACHE HIT: {cache_key}")
return self.cache[cache_key]
print(f"CACHE MISS: {cache_key}, loading from database")
user = self.database.get(cache_key)
if user:
self.cache[cache_key] = user
return user
def update_user(self, user_id, new_name):
cache_key = f"user:{user_id}"
self.database[cache_key] = new_name
self.cache.pop(cache_key, None)
print(f"UPDATED {cache_key}, invalidated cache")
if __name__ == "__main__":
service = ServiceCache()
print("First read (cache miss):", service.get_user(1))
print("Second read (cache hit):", service.get_user(1))
print("Third read (cache hit):", service.get_user(1))
service.update_user(1, "Alice Smith")
print("After update (cache miss):", service.get_user(1))
print("After update (cache hit):", service.get_user(1))
Output
First read (cache miss): CACHE MISS: user:1, loading from database
Alice
Second read (cache hit): CACHE HIT: user:1
Alice
Third read (cache hit): CACHE HIT: user:1
Alice
UPDATED user:1, invalidated cache
After update (cache miss): CACHE MISS: user:1, loading from database
Alice Smith
After update (cache hit): CACHE HIT: user:1
Alice Smith
How it works
The ServiceCache class wraps a simulated database dict and an in-memory cache dict. Reads implement cache-aside: check the cache first; on a miss, load from the database and populate the cache. Writes update the source of truth and invalidate the stale cache entry, ensuring the next read is a miss and fetches fresh data. The cache-key prefix user: keeps the mock realistic and readable. This design isolates caching logic per service, which matches microservice boundaries.
Common mistakes
- Forgetting to invalidate the cache on writes, causing stale reads.
- Only implementing cache-aside for reads but not handling cache invalidation on updates.
- Using a global cache across services instead of a per-service cache, creating cross-service coupling.
Variations
- Use a TTL (time-to-live) on cache entries and treat expired keys as misses.
- Replace the in-memory dict with redis or memcached for a distributed cache.
Real-world use cases
- A user profile service that caches frequently-read records to reduce database load in production.
- An order service that invalidates the cache line item data whenever inventory changes.
- A pricing service that caches computed quotes and clears them on price updates.
Sponsored
More from Microservices patterns
- BFF aggregation pattern: combine multiple service responses in Python easy
- Backward Compatible Schema Evolution in Python medium
- Bulkhead Thread Pool per Service Mock in Python medium
- CQRS with Separate Read and Write Repositories in Python medium
- Consumer Driven Contract Pact Mock in Python medium
- Correlation ID HTTP header mock in Python easy
Keep learning
Related tutorials and quizzes for this topic.