GroupCache

cache libraryGoGolangdistributed cacheGooglememcached alternativethundering herd

GitHub Overview

golang/groupcache

groupcache is a caching and cache-filling library, intended as a replacement for memcached in many cases.

Stars13,262
Watchers470
Forks1,393
Created:July 22, 2013
Language:Go
License:Apache License 2.0

Topics

None

Star History

golang/groupcache Star History
Data as of: 10/22/2025, 10:05 AM

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
}