GroupCache
GitHub Overview
golang/groupcache
groupcache is a caching and cache-filling library, intended as a replacement for memcached in many cases.
Topics
Star History
Cache Library
GroupCache
Overview
GroupCache is a distributed caching and cache-filling library, intended as a replacement for a pool of memcached nodes in many cases. It provides a data loading mechanism with caching and de-duplication that works across a set of peer processes. It was developed by Brad Fitzpatrick, the author of memcache at Google.
Details
GroupCache is a client library as well as a server. It connects to its own peers, forming a distributed cache and implementing an In Code Distributed Cache (ICDC) where every application instance becomes a cache node.
Whereas memcached just says "Sorry, cache miss", often resulting in a thundering herd of database loads from an unbounded number of clients, GroupCache coordinates cache fills such that only one load in one process of an entire replicated set of processes populates the cache, then multiplexes the loaded value to all callers. This design prevents thundering herd problems and avoids overloading machine's CPU and/or NIC by very popular keys/values.
Pros and Cons
Pros
- Thundering herd protection for highly efficient cache fills
- Automatic mirroring of super-hot items to prevent hot spotting
- Distributed architecture for improved scalability
- Production-proven at Google (dl.google.com, Blogger, etc.)
- High performance through in-process caching and peer communication
- Reliable design by the original memcached author
- Optimized for read-heavy workloads
Cons
- No cache expiration times or explicit cache evictions
- No CAS (Compare-and-Swap) or Increment/Decrement functionality
- No cache invalidation or value updating capabilities
- Not suitable for applications other than read-heavy workloads
- More complex configuration and deployment than other cache systems
- Requires peer communication setup and management
Reference Links
Code Examples
Basic Setup
package main
import (
"context"
"fmt"
"log"
"net/http"
"github.com/golang/groupcache"
)
var (
// Global GroupCache instance
thumbnails *groupcache.Group
)
func main() {
// HTTP pool configuration (for peer communication)
peers := groupcache.NewHTTPPool("http://localhost:8080")
// Set peer list
peers.Set("http://localhost:8080", "http://localhost:8081", "http://localhost:8082")
// Create group
thumbnails = groupcache.NewGroup("thumbnails", 64<<20, groupcache.GetterFunc(
func(ctx context.Context, key string, dest groupcache.Sink) error {
// Cache miss handling (data retrieval logic)
thumbnail, err := generateThumbnail(key)
if err != nil {
return err
}
// Set data to sink
dest.SetBytes(thumbnail)
return nil
},
))
// Start HTTP server
http.Handle("/_groupcache/", peers)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Data Retrieval Implementation
// Thumbnail generation example (fetch from actual data source)
func generateThumbnail(imagePath string) ([]byte, error) {
// Load from database or filesystem
log.Printf("Generating thumbnail for: %s", imagePath)
// Simulate heavy processing
time.Sleep(100 * time.Millisecond)
// In real implementation, use image processing library
thumbnail := []byte(fmt.Sprintf("thumbnail_data_for_%s", imagePath))
return thumbnail, nil
}
// Get data from GroupCache
func getThumbnail(imagePath string) ([]byte, error) {
var data []byte
err := thumbnails.Get(context.Background(), imagePath, groupcache.AllocatingByteSliceSink(&data))
if err != nil {
return nil, err
}
return data, nil
}
HTTP API Integration
// RESTful API integration example
func thumbnailHandler(w http.ResponseWriter, r *http.Request) {
imagePath := r.URL.Query().Get("path")
if imagePath == "" {
http.Error(w, "Missing path parameter", http.StatusBadRequest)
return
}
// Get from GroupCache
thumbnailData, err := getThumbnail(imagePath)
if err != nil {
http.Error(w, fmt.Sprintf("Error: %v", err), http.StatusInternalServerError)
return
}
// Return response
w.Header().Set("Content-Type", "image/jpeg")
w.Header().Set("Cache-Control", "public, max-age=3600")
w.Write(thumbnailData)
}
func main() {
// GroupCache configuration (as mentioned above)
setupGroupCache()
// Register API endpoints
http.HandleFunc("/api/thumbnail", thumbnailHandler)
http.Handle("/_groupcache/", peers)
log.Printf("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Multiple Group Management
var (
userCache *groupcache.Group
productCache *groupcache.Group
configCache *groupcache.Group
)
func initializeCaches() {
// User information cache
userCache = groupcache.NewGroup("users", 32<<20, groupcache.GetterFunc(
func(ctx context.Context, key string, dest groupcache.Sink) error {
user, err := fetchUserFromDatabase(key)
if err != nil {
return err
}
userData, _ := json.Marshal(user)
dest.SetBytes(userData)
return nil
},
))
// Product information cache
productCache = groupcache.NewGroup("products", 64<<20, groupcache.GetterFunc(
func(ctx context.Context, key string, dest groupcache.Sink) error {
product, err := fetchProductFromAPI(key)
if err != nil {
return err
}
productData, _ := json.Marshal(product)
dest.SetBytes(productData)
return nil
},
))
// Configuration cache (infrequently changing data)
configCache = groupcache.NewGroup("config", 8<<20, groupcache.GetterFunc(
func(ctx context.Context, key string, dest groupcache.Sink) error {
config, err := loadConfigFromFile(key)
if err != nil {
return err
}
dest.SetString(config)
return nil
},
))
}
Peer Discovery and Cluster Management
// Dynamic peer discovery example
type PeerManager struct {
pool *groupcache.HTTPPool
peers []string
}
func NewPeerManager(selfAddr string) *PeerManager {
return &PeerManager{
pool: groupcache.NewHTTPPool(selfAddr),
}
}
func (pm *PeerManager) UpdatePeers(newPeers []string) {
pm.peers = newPeers
pm.pool.Set(pm.peers...)
log.Printf("Updated peers: %v", pm.peers)
}
// Dynamic peer discovery in Kubernetes environment
func (pm *PeerManager) DiscoverKubernetesPeers() {
// Use Kubernetes API to discover Pods
pods, err := getGroupCachePods()
if err != nil {
log.Printf("Error discovering peers: %v", err)
return
}
var peerAddrs []string
for _, pod := range pods {
if pod.Status.Phase == "Running" {
addr := fmt.Sprintf("http://%s:8080", pod.Status.PodIP)
peerAddrs = append(peerAddrs, addr)
}
}
pm.UpdatePeers(peerAddrs)
}
Statistics and Monitoring
// Get GroupCache statistics
func getGroupCacheStats() map[string]groupcache.Stats {
stats := make(map[string]groupcache.Stats)
// Collect statistics for each group
if userCache != nil {
stats["users"] = userCache.Stats
}
if productCache != nil {
stats["products"] = productCache.Stats
}
return stats
}
// Expose statistics via HTTP endpoint
func statsHandler(w http.ResponseWriter, r *http.Request) {
stats := getGroupCacheStats()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(stats)
}
// Health check endpoint
func healthHandler(w http.ResponseWriter, r *http.Request) {
// Check GroupCache health status
stats := getGroupCacheStats()
healthy := true
for groupName, stat := range stats {
// Basic health check
if stat.Gets.Get() == 0 && time.Since(startTime) > 5*time.Minute {
log.Printf("Group %s has no requests", groupName)
healthy = false
}
}
if healthy {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
} else {
w.WriteHeader(http.StatusServiceUnavailable)
w.Write([]byte("Not healthy"))
}
}
Error Handling and Fallback
// Robust data retrieval implementation
func robustGetThumbnail(imagePath string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var data []byte
err := thumbnails.Get(ctx, imagePath, groupcache.AllocatingByteSliceSink(&data))
switch err {
case nil:
return data, nil
case context.DeadlineExceeded:
log.Printf("Timeout getting thumbnail for %s", imagePath)
// Fallback: return default image
return getDefaultThumbnail(), nil
default:
log.Printf("Error getting thumbnail for %s: %v", imagePath, err)
return nil, err
}
}
// Circuit breaker pattern implementation
type CircuitBreaker struct {
failures int
lastFailure time.Time
threshold int
timeout time.Duration
}
func (cb *CircuitBreaker) Call(fn func() error) error {
if cb.failures >= cb.threshold {
if time.Since(cb.lastFailure) < cb.timeout {
return fmt.Errorf("circuit breaker open")
}
// Attempt recovery
cb.failures = 0
}
err := fn()
if err != nil {
cb.failures++
cb.lastFailure = time.Now()
return err
}
cb.failures = 0
return nil
}