A distributed cache doesn’t just store data; it’s a carefully orchestrated network of nodes that collectively become your fast data layer, actively fighting latency before your application even has to ask the database.

Let’s see it in action. Imagine a simple web application needing user profiles. Without a cache, each request hits the database:

// No cache
func getUserProfile(userID string) (UserProfile, error) {
	db := connectToDatabase() // Slow
	profile, err := db.Query("SELECT * FROM users WHERE id = ?", userID)
	if err != nil {
		return UserProfile{}, err
	}
	return profile, nil
}

Now, with a distributed cache (let’s use Redis for this example):

// With Redis cache
func getUserProfileWithCache(userID string) (UserProfile, error) {
	redisClient := connectToRedis() // Fast
	cacheKey := fmt.Sprintf("user:%s", userID)

	// 1. Try cache first
	cachedProfileJSON, err := redisClient.Get(cacheKey).Result()
	if err == nil {
		var profile UserProfile
		json.Unmarshal([]byte(cachedProfileJSON), &profile)
		return profile, nil // Cache hit!
	}

	// 2. Cache miss, go to database
	db := connectToDatabase() // Slow
	profile, err := db.Query("SELECT * FROM users WHERE id = ?", userID)
	if err != nil {
		return UserProfile{}, err
	}

	// 3. Store in cache for next time (with an expiration)
	profileJSON, _ := json.Marshal(profile)
	redisClient.Set(cacheKey, profileJSON, 5*time.Minute) // Cache for 5 minutes

	return profile, nil
}

This pattern, often called "cache-aside," is fundamental. The application code explicitly checks the cache, falls back to the database on a miss, and then populates the cache. The "distributed" part comes from Redis itself. When you run multiple Redis instances, they can be configured to partition data across themselves (sharding) or replicate data (high availability).

The core problem this solves is I/O latency. Database operations, even on fast SSDs, are orders of magnitude slower than reading from RAM. By keeping frequently accessed data in memory across a cluster of machines, you dramatically reduce the time it takes to retrieve information. This allows your application to handle more concurrent users and perform complex operations faster.

Internally, a distributed cache like Redis uses a key-value store model. Each piece of data is associated with a unique key. When you GET user:123, Redis looks up that exact key in its in-memory hash tables. For distribution, two primary strategies emerge:

  1. Sharding (Partitioning): Data is split across multiple nodes. A client library or proxy (like Redis Cluster or Twemproxy) determines which node holds a given key based on a hashing algorithm (e.g., CRC16). user:123 might go to Node A, while user:456 goes to Node B. This scales read and write throughput by distributing the load.
  2. Replication: Each data item is copied to multiple nodes. A primary node handles writes, and one or more replica nodes passively receive updates. Reads can be served by either the primary or replicas. This scales read throughput and provides high availability; if a replica fails, another can take over.

The exact levers you control are critical:

  • Cache Key Design: How you name your keys (user:123, product:abc:details) directly impacts how data is organized and retrieved. Poorly designed keys can lead to "hot spots" where one node gets hammered.
  • Expiration (TTL - Time To Live): Setting appropriate TTLs is crucial. Too short, and you miss cache benefits. Too long, and you serve stale data. For user profiles, 5 minutes might be good; for product catalog updates, perhaps 30 seconds.
  • Eviction Policies: When the cache is full, what gets removed? LRU (Least Recently Used) is common – it discards the items that haven’t been accessed in the longest time. LFU (Least Frequently Used) discards items that have been accessed the fewest times.
  • Serialization Format: Storing data as JSON, Protocol Buffers, or a custom binary format impacts memory usage and parsing speed. Protocol Buffers are often more compact and faster to deserialize than JSON.
  • Cluster Topology: How many nodes? How many replicas per shard? This balances cost, performance, and availability. A common setup is 3 master nodes, each with 2 replicas, for a total of 9 nodes.

A common misconception is that sharding automatically provides high availability. While sharding distributes data, if a master node in a sharded cluster fails and has no replicas, the data on that node becomes inaccessible. True high availability requires replication alongside sharding. For example, Redis Cluster uses a primary-replica model where each master shard has at least one replica.

The next step after mastering basic distributed caching is understanding cache invalidation strategies beyond simple TTLs.

Want structured learning?

Take the full Caching-strategies course →