1
0
mirror of https://github.com/redis/go-redis.git synced 2025-07-16 13:21:51 +03:00
Files
go-redis/examples/client-side-cache/main.go
Nedyalko Dyakov 1f4537559a feat: implement client-side caching with Redis invalidation support
Add comprehensive client-side caching functionality that leverages the push notification
infrastructure for automatic cache invalidation.

Core Features:
- Local in-memory cache with configurable size and TTL
- Automatic Redis CLIENT TRACKING integration
- Real-time cache invalidation via push notifications
- LRU eviction policy for memory management
- Thread-safe operations with RWMutex
- Comprehensive statistics and monitoring

API Components:
- ClientSideCache: Main cache implementation
- ClientSideCacheOptions: Configuration options
- Client integration methods: EnableClientSideCache, DisableClientSideCache
- Convenience methods: CachedGet, CachedSet, CachedDel
- Statistics: GetStats with hits, misses, evictions, hit ratio

Implementation Details:
- Uses existing push notification system for invalidation
- Integrates with Redis CLIENT TRACKING (RESP3 required)
- Supports BCAST mode for prefix-based tracking
- Non-blocking invalidation processing
- Graceful fallback to Redis on cache misses
- Automatic cleanup on client close

Benefits:
- Significant performance improvements for read-heavy workloads
- Reduced Redis server load and network traffic
- Automatic cache coherence with real-time invalidation
- Transparent integration with existing Redis operations
- Zero configuration required (sensible defaults)

Test Coverage:
- Comprehensive unit tests for all cache operations
- Integration tests with real Redis instances
- Edge cases: expiration, eviction, invalidation
- Statistics verification and cache management
- Error handling and graceful degradation

Example Usage:
```go
// Enable client-side caching
client.EnableClientSideCache(&redis.ClientSideCacheOptions{
    MaxSize: 1000,
    DefaultTTL: 5 * time.Minute,
})

// Use cached operations
value, err := client.CachedGet(ctx, "key").Result()
err = client.CachedSet(ctx, "key", "value", time.Hour).Err()
```

Files Added:
- client_side_cache.go: Core implementation
- client_side_cache_test.go: Comprehensive tests
- examples/client-side-cache/: Working example with documentation

Integration:
- Leverages existing push notification infrastructure
- Updates shouldSkipNotification filtering (invalidate now processed)
- Maintains backward compatibility
- No breaking changes to existing APIs
2025-06-28 13:53:26 +03:00

176 lines
5.6 KiB
Go

package main
import (
"context"
"fmt"
"log"
"time"
"github.com/redis/go-redis/v9"
)
func main() {
fmt.Println("=== Redis Client-Side Caching Example ===")
// Create Redis client with RESP3 protocol (required for push notifications)
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Protocol: 3, // RESP3 required for client tracking and push notifications
})
defer client.Close()
ctx := context.Background()
// Test Redis connection
if err := client.Ping(ctx).Err(); err != nil {
log.Fatalf("Failed to connect to Redis: %v", err)
}
fmt.Println("✅ Connected to Redis")
// Enable client-side caching
err := client.EnableClientSideCache(&redis.ClientSideCacheOptions{
MaxSize: 1000, // Cache up to 1000 entries
DefaultTTL: 5 * time.Minute, // Default TTL for cached entries
EnableTracking: true, // Enable Redis CLIENT TRACKING
NoLoop: true, // Don't track keys modified by this client
TrackingPrefix: []string{"user:", "session:"}, // Only track specific prefixes
})
if err != nil {
log.Fatalf("Failed to enable client-side cache: %v", err)
}
defer client.DisableClientSideCache()
fmt.Println("✅ Client-side cache enabled")
// Example 1: Basic caching operations
fmt.Println("\n🔧 Example 1: Basic Caching Operations")
key := "user:123"
value := "John Doe"
// Set a value (stored in Redis and cached locally)
err = client.CachedSet(ctx, key, value, time.Hour).Err()
if err != nil {
log.Fatalf("Failed to set value: %v", err)
}
fmt.Printf("✅ Set %s = %s\n", key, value)
// Get the value (should be served from cache)
result, err := client.CachedGet(ctx, key).Result()
if err != nil {
log.Fatalf("Failed to get value: %v", err)
}
fmt.Printf("✅ Got %s = %s (from cache)\n", key, result)
// Example 2: Cache statistics
fmt.Println("\n🔧 Example 2: Cache Statistics")
cache := client.GetClientSideCache()
hits, misses, evictions, hitRatio, size := cache.GetStats()
fmt.Printf("📊 Cache Stats:\n")
fmt.Printf(" Hits: %d\n", hits)
fmt.Printf(" Misses: %d\n", misses)
fmt.Printf(" Evictions: %d\n", evictions)
fmt.Printf(" Hit Ratio: %.2f%%\n", hitRatio*100)
fmt.Printf(" Size: %d entries\n", size)
// Example 3: Multiple operations to show caching in action
fmt.Println("\n🔧 Example 3: Multiple Operations")
keys := []string{"user:100", "user:101", "user:102"}
values := []string{"Alice", "Bob", "Charlie"}
// Set multiple values
for i, k := range keys {
err = client.CachedSet(ctx, k, values[i], time.Hour).Err()
if err != nil {
log.Printf("Failed to set %s: %v", k, err)
}
}
fmt.Println("✅ Set multiple user values")
// Get values multiple times to show cache hits
for round := 1; round <= 3; round++ {
fmt.Printf("\n📋 Round %d - Getting values:\n", round)
for _, k := range keys {
start := time.Now()
result, err := client.CachedGet(ctx, k).Result()
duration := time.Since(start)
if err != nil {
log.Printf("Failed to get %s: %v", k, err)
continue
}
fmt.Printf(" %s = %s (took %v)\n", k, result, duration)
}
}
// Show updated statistics
hits, misses, evictions, hitRatio, size = cache.GetStats()
fmt.Printf("\n📊 Updated Cache Stats:\n")
fmt.Printf(" Hits: %d\n", hits)
fmt.Printf(" Misses: %d\n", misses)
fmt.Printf(" Evictions: %d\n", evictions)
fmt.Printf(" Hit Ratio: %.2f%%\n", hitRatio*100)
fmt.Printf(" Size: %d entries\n", size)
// Example 4: Cache invalidation
fmt.Println("\n🔧 Example 4: Cache Invalidation")
// Modify a value from another client to trigger invalidation
// (In a real scenario, this would be another application instance)
fmt.Println("📋 Simulating external modification...")
// Use regular Set to modify the value (this will trigger invalidation)
err = client.Set(ctx, "user:100", "Alice Updated", time.Hour).Err()
if err != nil {
log.Printf("Failed to update value: %v", err)
} else {
fmt.Println("✅ Updated user:100 externally")
// Give some time for invalidation to process
time.Sleep(100 * time.Millisecond)
// Get the value again (should fetch from Redis due to invalidation)
result, err := client.CachedGet(ctx, "user:100").Result()
if err != nil {
log.Printf("Failed to get updated value: %v", err)
} else {
fmt.Printf("✅ Got updated value: %s\n", result)
}
}
// Example 5: Cache management
fmt.Println("\n🔧 Example 5: Cache Management")
// Clear the cache
cache.Clear()
fmt.Println("✅ Cache cleared")
// Show final statistics
hits, misses, evictions, hitRatio, size = cache.GetStats()
fmt.Printf("📊 Final Cache Stats:\n")
fmt.Printf(" Hits: %d\n", hits)
fmt.Printf(" Misses: %d\n", misses)
fmt.Printf(" Evictions: %d\n", evictions)
fmt.Printf(" Hit Ratio: %.2f%%\n", hitRatio*100)
fmt.Printf(" Size: %d entries\n", size)
// Clean up test keys
fmt.Println("\n🧹 Cleaning up...")
allKeys := append(keys, key)
deleted, err := client.Del(ctx, allKeys...).Result()
if err != nil {
log.Printf("Failed to clean up keys: %v", err)
} else {
fmt.Printf("✅ Deleted %d keys\n", deleted)
}
fmt.Println("\n🎉 Client-Side Caching Example Complete!")
fmt.Println("\n📋 Key Benefits Demonstrated:")
fmt.Println(" ✅ Automatic local caching with Redis fallback")
fmt.Println(" ✅ Real-time cache invalidation via Redis push notifications")
fmt.Println(" ✅ Significant performance improvements for repeated reads")
fmt.Println(" ✅ Transparent integration with existing Redis operations")
fmt.Println(" ✅ Comprehensive statistics and monitoring")
fmt.Println(" ✅ Configurable cache size, TTL, and tracking options")
}