Stage 1 · Code
Working with Databases
Caching and Read Patterns
Not every database read deserves a cache, but repeated reads of the same stable data often do. This lesson explains when caching helps, what it complicates, and how read-through and write-through patterns behave in practice.
Why Repeat Reads Happen
Many applications read the same data far more often than they change it. A pricing page may load the same subscription plan details thousands of times per minute. A dashboard may ask for the same team metadata on every refresh. A feature-flag lookup may happen on every incoming request while the underlying data changes only a few times a day.
Caching is the idea of remembering a previous answer so you do not keep making the database do identical work. The win is lower latency and less read pressure on the database. The cost is complexity: the cache can go stale, the process can restart and forget everything, and write paths must now think about more than one place where data might live.
A cache is not a badge of sophistication. It is a response to a specific workload shape: repeated reads of data that is expensive enough, popular enough, or stable enough that remembering it is worth the extra moving parts.
Building a Small In-Process Cache
The simplest cache lives inside your Go process. That means no network hop to a separate cache service, and it connects nicely to your earlier lessons about maps and mutexes. The tradeoff is scope: each process keeps its own copy. If you run five application instances, you now have five small independent caches rather than one shared truth.
| Cache location | Strength | Tradeoff |
|---|---|---|
| In-process cache | Very fast and simple to add | Each app instance has its own copy, so invalidation and memory use are per-process |
| External cache | Shared across instances and can survive app restarts | Adds a network dependency and another system to operate |
Invalidation Is the Hard Part
The joke that 'cache invalidation is hard' survives because it is true in a very specific way. Storing old answers is easy. Knowing exactly when an old answer stopped being trustworthy is the hard part. You are no longer just asking, 'what is the data?' You are asking, 'how long can I safely believe yesterday's answer?'
- Time-based expiry: keep each item for a fixed TTL such as 30 seconds or 5 minutes, then force a refresh.
- Explicit invalidation on write: when the application updates the database, it also removes or refreshes the matching cache entry.
- Hybrid approach: use explicit invalidation for common writes and a TTL as a safety net in case one invalidation path is missed.
Imagine a team directory page cached for 10 minutes. That may be fine if team names almost never change. It may be terrible if engineers expect a new on-call contact to appear immediately during an incident. The right invalidation strategy depends on how stale data would hurt the user, not just on what is easiest to implement.
A cache can make your app feel faster while silently serving outdated information. Performance only counts as an improvement if the returned data is still acceptable for that use case.
Read-Through and Write-Through
Read-through means the caller asks the cache-backed layer for data and does not care whether the answer came from memory or from the database. On a miss, that layer loads the value from the database, stores it in the cache, and returns it. Write-through means the write path updates the database and then updates the cache as part of the same higher-level operation, so future reads can hit memory immediately.
Quiz and Practice
When does caching usually help most?
Hands-On Project
Implement `GetOrLoad`. Return the cached value when present. On a miss, call the loader, store the result with the TTL, and return it.
Summary and Key Takeaways
- Caching is most useful when the same data is read repeatedly and freshness requirements allow reuse.
- An in-process cache can be as simple as a map protected by a mutex, but it is local to one app instance.
- Time-based expiry and explicit invalidation are two common ways to decide when cached data should stop being trusted.
- Read-through caches populate themselves on misses; write-through caches refresh cached values during writes.
- A cache is only an improvement if the performance gain does not break the correctness the user expects.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.