# Dragonfly DB Integration ## Overview SEEN uses **Dragonfly DB** as a high-performance, Redis-compatible in-memory cache. Dragonfly provides superior performance and memory efficiency compared to traditional Redis, making it ideal for caching session data, catalog queries, and real-time download progress. ## Why Dragonfly? - **25x faster** than Redis on multi-core systems - **Lower memory footprint** (up to 30% less memory usage) - **100% Redis API compatible** - drop-in replacement - **Multi-threaded architecture** - better CPU utilization - **Built-in snapshot support** for persistence ## Architecture ``` ┌─────────────────────────────────────────────────────────┐ │ Cache Manager │ │ ┌──────────────┬──────────────┬──────────────────┐ │ │ │ Session │ Catalog │ Download │ │ │ │ Cache │ Cache │ Cache │ │ │ └──────────────┴──────────────┴──────────────────┘ │ │ │ │ │ Cache Service │ │ │ │ │ Dragonfly Client │ └─────────────────────────────────────────────────────────┘ │ Dragonfly DB ``` ## Components ### 1. Cache Manager (`cache/manager.go`) Central orchestrator for all caching operations: ```go manager, err := cache.NewManager(ctx, cfg.Cache, log) if err != nil { log.Fatal("dragonfly startup failed", zap.Error(err)) } defer manager.Close() // Access specialized caches sessionCache := manager.Session() catalogCache := manager.Catalog() downloadCache := manager.Download() ``` **Features:** - Automatic connection management - Background cleanup tasks - Health checks - Cache statistics - User data invalidation ### 2. Cache Service (`cache/service.go`) Low-level cache operations: ```go service := manager.Service() // Store data err := service.Set(ctx, "key", data, 5*time.Minute) // Retrieve data var result MyType err := service.Get(ctx, "key", &result) // Delete data err := service.Delete(ctx, "key1", "key2") // Atomic operations count, err := service.Increment(ctx, "counter") ok, err := service.SetNX(ctx, "lock:resource", "owner", 30*time.Second) ``` ### 3. Session Cache (`cache/session_cache.go`) Manages user sessions and authentication: ```go sessionCache := manager.Session() // Store session session := cache.SessionData{ SessionID: "session-123", UserID: "user-456", RefreshToken: "token", ExpiresAt: time.Now().Add(24 * time.Hour), } err := sessionCache.SetSession(ctx, session) // Retrieve session session, err := sessionCache.GetSession(ctx, "session-123") // Cache user data user := cache.UserData{ ID: "user-456", Email: "user@example.com", DisplayName: "User Name", Role: "user", } err := sessionCache.SetUser(ctx, user) // Rate limiting allowed, err := sessionCache.RateLimitCheck(ctx, "user-456", "api-call", 100, time.Minute) // Distributed locks lockID, acquired, err := sessionCache.AcquireLock(ctx, "resource", 30*time.Second) if acquired { defer sessionCache.ReleaseLock(ctx, "resource", lockID) // Do work } ``` ### 4. Catalog Cache (`cache/catalog_cache.go`) Caches catalog queries and user lists: ```go catalogCache := manager.Catalog() // Cache dashboard err := catalogCache.SetDashboard(ctx, userID, dashboardData) err := catalogCache.GetDashboard(ctx, userID, &dashboardData) // Cache discover sections err := catalogCache.SetDiscover(ctx, "sci-fi", "movie", 1, sections) err := catalogCache.GetDiscover(ctx, "sci-fi", "movie", 1, §ions) // Cache search results err := catalogCache.SetSearch(ctx, "neon", "", "all", results) err := catalogCache.GetSearch(ctx, "neon", "", "all", &results) // Cache watch later err := catalogCache.SetWatchLater(ctx, userID, items) err := catalogCache.GetWatchLater(ctx, userID, &items) // Cache continue watching err := catalogCache.SetContinueWatching(ctx, userID, items) err := catalogCache.GetContinueWatching(ctx, userID, &items) // Invalidate user catalog err := catalogCache.InvalidateUserCatalog(ctx, userID) ``` ### 5. Download Cache (`cache/download_cache.go`) Real-time download progress tracking: ```go downloadCache := manager.Download() // Store download progress progress := cache.DownloadProgress{ JobID: "job-123", Status: "downloading", ProgressPercent: 45, BytesTotal: 1000000000, BytesDownloaded: 450000000, DownloadSpeedMbps: 15.3, EtaSeconds: 120, } err := downloadCache.SetProgress(ctx, progress) // Retrieve progress progress, err := downloadCache.GetProgress(ctx, "job-123") // Update specific fields err := downloadCache.IncrementDownloadedBytes(ctx, "job-123", 1024*1024) err := downloadCache.SetDownloadSpeed(ctx, "job-123", 20.5) // Get active downloads activeDownloads, err := downloadCache.GetActiveDownloads(ctx) // Bulk operations err := downloadCache.BulkSetProgress(ctx, []cache.DownloadProgress{...}) // Cleanup stale data err := downloadCache.CleanupStaleProgress(ctx, 1*time.Hour) ``` ### 6. Key Builder (`cache/keys.go`) Consistent key naming: ```go kb := cache.NewKeyBuilder("seen") // Build keys sessionKey := kb.SessionKey("session-123") // Result: "seen:session:session-123" userKey := kb.UserKey("user-456") // Result: "seen:user:user-456" dashboardKey := kb.CatalogDashboardKey("user-456") // Result: "seen:catalog:dashboard:user-456" downloadKey := kb.DownloadJobKey("job-123") // Result: "seen:download:job:job-123" ``` ## Cache TTLs Default time-to-live values: ```go const ( TTLSession = 24 * time.Hour // User sessions TTLUser = 15 * time.Minute // User profile data TTLCatalog = 5 * time.Minute // Catalog queries TTLDownload = 30 * time.Second // Download progress TTLRateLimit = 1 * time.Minute // Rate limit counters TTLLock = 30 * time.Second // Distributed locks TTLSearch = 10 * time.Minute // Search results TTLRecommendation = 1 * time.Hour // Recommendations ) ``` ## Configuration Environment variables: ```bash SEEN_CACHE_ADDR=dragonfly:6379 SEEN_CACHE_PASSWORD= SEEN_CACHE_DB=0 ``` Docker Compose: ```yaml dragonfly: image: docker.dragonflydb.io/dragonflydb/dragonfly:latest container_name: seen-dragonfly command: ["--logtostderr", "--proactor_threads=2"] ports: - '6379:6379' healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 5s retries: 10 ``` ## Usage Examples ### Example 1: Caching Dashboard Data ```go func (h *CatalogHandler) Dashboard(c *gin.Context) { userID := getUserID(c) // Try cache first var dashboard DashboardPayload err := h.cacheManager.Catalog().GetDashboard(c.Request.Context(), userID, &dashboard) if err == nil { c.JSON(http.StatusOK, dashboard) return } // Cache miss - fetch from database dashboard = h.service.Dashboard() // Store in cache _ = h.cacheManager.Catalog().SetDashboard(c.Request.Context(), userID, dashboard) c.JSON(http.StatusOK, dashboard) } ``` ### Example 2: Session Management ```go func (s *AuthService) CreateSession(ctx context.Context, userID string) (*Session, error) { session := &Session{ ID: uuid.New().String(), UserID: userID, RefreshToken: generateToken(), ExpiresAt: time.Now().Add(24 * time.Hour), } // Store in database if err := s.repo.CreateSession(ctx, session); err != nil { return nil, err } // Cache session data sessionData := cache.SessionData{ SessionID: session.ID, UserID: session.UserID, RefreshToken: session.RefreshToken, ExpiresAt: session.ExpiresAt, } _ = s.cacheManager.Session().SetSession(ctx, sessionData) return session, nil } ``` ### Example 3: Real-time Download Progress ```go func (w *DownloadWorker) UpdateProgress(ctx context.Context, jobID string, downloaded int64) { // Update progress in cache for real-time updates err := w.cacheManager.Download().IncrementDownloadedBytes(ctx, jobID, downloaded) if err != nil { w.log.Error("failed to update download progress", zap.Error(err)) } // Periodically persist to database if downloaded%10485760 == 0 { // Every 10MB progress, _ := w.cacheManager.Download().GetProgress(ctx, jobID) _ = w.repo.UpdateJob(ctx, jobID, progress) } } ``` ### Example 4: Rate Limiting ```go func RateLimitMiddleware(cacheManager *cache.Manager) gin.HandlerFunc { return func(c *gin.Context) { userID := getUserID(c) allowed, err := cacheManager.Session().RateLimitCheck( c.Request.Context(), userID, "api-request", 100, // 100 requests time.Minute, // per minute ) if err != nil || !allowed { c.JSON(http.StatusTooManyRequests, gin.H{ "error": "rate limit exceeded", }) c.Abort() return } c.Next() } } ``` ### Example 5: Distributed Locking ```go func (s *DownloadService) StartDownload(ctx context.Context, jobID string) error { // Acquire lock to prevent duplicate processing lockID, acquired, err := s.cacheManager.Session().AcquireLock( ctx, fmt.Sprintf("download:%s", jobID), 30*time.Second, ) if err != nil { return err } if !acquired { return fmt.Errorf("download already in progress") } defer s.cacheManager.Session().ReleaseLock(ctx, fmt.Sprintf("download:%s", jobID), lockID) // Process download return s.processDownload(ctx, jobID) } ``` ## Background Tasks The cache manager runs automatic cleanup tasks: ```go // Runs every 5 minutes func (m *Manager) runCleanupTasks(ctx context.Context) { // Remove stale download progress (older than 1 hour) m.download.CleanupStaleProgress(ctx, 1*time.Hour) // Log cache statistics stats, _ := m.Stats(ctx) m.log.Debug("cache stats", zap.Any("stats", stats)) } ``` ## Health Checks ```go // Basic connectivity check err := cacheManager.Ping(ctx) // Comprehensive health check (read/write test) err := cacheManager.HealthCheck(ctx) // Get cache statistics stats, err := cacheManager.Stats(ctx) // Returns: dbSize, memory usage, keyspace info ``` ## Cache Invalidation ```go // Invalidate all user data err := cacheManager.InvalidateUser(ctx, userID) // Invalidate specific caches err := cacheManager.Catalog().InvalidateUserCatalog(ctx, userID) err := cacheManager.Download().InvalidateUserDownloads(ctx, userID) err := cacheManager.Session().InvalidateUserSessions(ctx, userID) // Delete by pattern err := cacheManager.DeleteKeysByPattern(ctx, "seen:catalog:*") ``` ## Performance Tips 1. **Use appropriate TTLs** - Balance freshness vs. cache hit rate 2. **Batch operations** - Use `MSet` and `MGet` for multiple keys 3. **Avoid scanning** - Use specific keys instead of pattern matching 4. **Monitor cache hit rate** - Adjust TTLs based on metrics 5. **Use compression** - For large objects, compress before caching ## Monitoring ```bash # Connect to Dragonfly CLI docker exec -it seen-dragonfly redis-cli # Check cache size DBSIZE # View memory usage INFO memory # List all keys (development only!) KEYS seen:* # Monitor commands in real-time MONITOR # Get cache statistics INFO stats ``` ## Best Practices 1. **Always set TTLs** - Prevent memory leaks 2. **Handle cache misses gracefully** - Always have a fallback 3. **Use namespaces** - Organize keys with prefixes 4. **Invalidate on updates** - Keep cache consistent 5. **Monitor performance** - Track hit rates and latency 6. **Use atomic operations** - For counters and locks 7. **Compress large values** - Reduce memory usage 8. **Batch operations** - Reduce network round trips ## Troubleshooting ### Cache not connecting ```bash # Check Dragonfly is running docker ps | grep dragonfly # Check logs docker logs seen-dragonfly # Test connection docker exec -it seen-dragonfly redis-cli ping ``` ### High memory usage ```bash # Check memory stats docker exec -it seen-dragonfly redis-cli INFO memory # Find large keys docker exec -it seen-dragonfly redis-cli --bigkeys # Clear cache (development only!) docker exec -it seen-dragonfly redis-cli FLUSHDB ``` ### Slow performance - Check network latency - Monitor CPU usage on Dragonfly container - Review TTL settings - Check for large values - Consider increasing `proactor_threads` ## Summary The Dragonfly integration provides: - ✅ High-performance caching (25x faster than Redis) - ✅ Session management with automatic expiry - ✅ Catalog query caching - ✅ Real-time download progress tracking - ✅ Rate limiting - ✅ Distributed locking - ✅ Automatic cleanup tasks - ✅ Health monitoring - ✅ Comprehensive API All cache operations are production-ready and fully integrated into the SEEN backend!