1
0
mirror of https://github.com/redis/go-redis.git synced 2025-07-28 06:42:00 +03:00

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
This commit is contained in:
Nedyalko Dyakov
2025-06-28 13:53:26 +03:00
parent f4ff2d667c
commit 1f4537559a
7 changed files with 1097 additions and 2 deletions

View File

@ -210,6 +210,9 @@ type baseClient struct {
// Push notification processing
pushProcessor PushNotificationProcessorInterface
// Client-side cache for automatic caching with Redis invalidation
clientSideCache *ClientSideCache
}
func (c *baseClient) clone() *baseClient {
@ -835,11 +838,83 @@ func (c *Client) RegisterPushNotificationHandler(pushNotificationName string, ha
return c.pushProcessor.RegisterHandler(pushNotificationName, handler, protected)
}
// UnregisterPushNotificationHandler unregisters a handler for a specific push notification name.
// Returns an error if no handler is registered for this push notification name or if the handler is protected.
func (c *Client) UnregisterPushNotificationHandler(pushNotificationName string) error {
// Check if we have a processor that supports unregistration
if processor, ok := c.pushProcessor.(interface {
UnregisterHandler(pushNotificationName string) error
}); ok {
return processor.UnregisterHandler(pushNotificationName)
}
return fmt.Errorf("push notification processor does not support unregistration")
}
// GetPushNotificationProcessor returns the push notification processor.
func (c *Client) GetPushNotificationProcessor() PushNotificationProcessorInterface {
return c.pushProcessor
}
// EnableClientSideCache enables client-side caching with Redis invalidation support.
// This creates a local cache that automatically invalidates entries when Redis sends
// invalidation notifications through CLIENT TRACKING.
func (c *Client) EnableClientSideCache(opts *ClientSideCacheOptions) error {
if c.clientSideCache != nil {
return fmt.Errorf("client-side cache is already enabled")
}
cache, err := NewClientSideCache(c, opts)
if err != nil {
return err
}
c.clientSideCache = cache
return nil
}
// DisableClientSideCache disables client-side caching and cleans up resources.
func (c *Client) DisableClientSideCache() error {
if c.clientSideCache == nil {
return nil // Already disabled
}
err := c.clientSideCache.Close()
c.clientSideCache = nil
return err
}
// GetClientSideCache returns the client-side cache if enabled, nil otherwise.
func (c *Client) GetClientSideCache() *ClientSideCache {
return c.clientSideCache
}
// CachedGet retrieves a value using client-side caching if enabled, otherwise falls back to regular Get.
// This is a convenience method that automatically uses the cache when available.
func (c *Client) CachedGet(ctx context.Context, key string) *StringCmd {
if c.clientSideCache != nil {
return c.clientSideCache.Get(ctx, key)
}
return c.Get(ctx, key)
}
// CachedSet stores a value using client-side caching if enabled, otherwise falls back to regular Set.
// This is a convenience method that automatically updates the cache when available.
func (c *Client) CachedSet(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd {
if c.clientSideCache != nil {
return c.clientSideCache.Set(ctx, key, value, expiration)
}
return c.Set(ctx, key, value, expiration)
}
// CachedDel deletes keys using client-side caching if enabled, otherwise falls back to regular Del.
// This is a convenience method that automatically updates the cache when available.
func (c *Client) CachedDel(ctx context.Context, keys ...string) *IntCmd {
if c.clientSideCache != nil {
return c.clientSideCache.Del(ctx, keys...)
}
return c.Del(ctx, keys...)
}
// GetPushNotificationHandler returns the handler for a specific push notification name.
// Returns nil if no handler is registered for the given name.
func (c *Client) GetPushNotificationHandler(pushNotificationName string) PushNotificationHandler {