# Redis Go Client Development Guide **Library**: `github.com/redis/go-redis/v9` **Used In**: MVP - Caching, sessions, match results **Purpose**: Redis client for caching and real-time data --- ## Where It's Used - **Match results caching** (TTL: 5-15 minutes) - **Session management** - **Rate limiting** - **Pub/sub for real-time updates** (future) --- ## Official Documentation - **GitHub**: https://github.com/redis/go-redis - **GoDoc**: https://pkg.go.dev/github.com/redis/go-redis/v9 - **Redis Docs**: https://redis.io/docs/ - **Commands Reference**: https://redis.io/commands/ --- ## Installation ```bash go get github.com/redis/go-redis/v9 ``` --- ## Key Concepts ### 1. Client Setup ```go import ( "context" "github.com/redis/go-redis/v9" ) func NewRedisClient(addr, password string, db int) (*redis.Client, error) { rdb := redis.NewClient(&redis.Options{ Addr: addr, // "localhost:6379" Password: password, // "" for no password DB: db, // 0 }) // Verify connection ctx := context.Background() if err := rdb.Ping(ctx).Err(); err != nil { return nil, err } return rdb, nil } ``` ### 2. Basic Operations ```go ctx := context.Background() // Set value err := rdb.Set(ctx, "key", "value", 0).Err() // Set with expiration err := rdb.Set(ctx, "key", "value", 5*time.Minute).Err() // Get value val, err := rdb.Get(ctx, "key").Result() if err == redis.Nil { // Key doesn't exist } // Delete err := rdb.Del(ctx, "key").Err() // Check if key exists exists, err := rdb.Exists(ctx, "key").Result() // Set if not exists (NX) err := rdb.SetNX(ctx, "key", "value", time.Minute).Err() ``` ### 3. Hash Operations ```go // Set hash field err := rdb.HSet(ctx, "business:123", "name", "Factory A").Err() // Set multiple hash fields err := rdb.HSet(ctx, "business:123", map[string]interface{}{ "name": "Factory A", "email": "contact@factorya.com", }).Err() // Get hash field name, err := rdb.HGet(ctx, "business:123", "name").Result() // Get all hash fields business, err := rdb.HGetAll(ctx, "business:123").Result() // Increment hash field count, err := rdb.HIncrBy(ctx, "business:123", "match_count", 1).Result() ``` ### 4. List Operations ```go // Push to list err := rdb.LPush(ctx, "matches:123", "match1").Err() // Pop from list match, err := rdb.RPop(ctx, "matches:123").Result() // Get list length length, err := rdb.LLen(ctx, "matches:123").Result() // Get list range matches, err := rdb.LRange(ctx, "matches:123", 0, 10).Result() ``` ### 5. Set Operations ```go // Add to set err := rdb.SAdd(ctx, "businesses", "business:123").Err() // Check if member exists exists, err := rdb.SIsMember(ctx, "businesses", "business:123").Result() // Get set members members, err := rdb.SMembers(ctx, "businesses").Result() // Remove from set err := rdb.SRem(ctx, "businesses", "business:123").Err() ``` ### 6. Sorted Sets (for ranking) ```go // Add to sorted set err := rdb.ZAdd(ctx, "match_scores", redis.Z{ Score: 95.5, Member: "match:123", }).Err() // Get top matches (by score) matches, err := rdb.ZRevRange(ctx, "match_scores", 0, 9).Result() // Get with scores matchesWithScores, err := rdb.ZRevRangeWithScores(ctx, "match_scores", 0, 9).Result() ``` ### 7. JSON Operations ```go import "github.com/redis/go-redis/v9" // Set JSON err := rdb.JSONSet(ctx, "business:123", "$", business).Err() // Get JSON var business Business err := rdb.JSONGet(ctx, "business:123", "$").Scan(&business) ``` --- ## MVP-Specific Patterns ### Match Results Caching ```go type MatchCache struct { client *redis.Client } // Cache match results with TTL func (c *MatchCache) CacheMatches(ctx context.Context, flowID string, matches []Match, ttl time.Duration) error { key := fmt.Sprintf("matches:%s", flowID) // Serialize matches data, err := json.Marshal(matches) if err != nil { return err } return c.client.Set(ctx, key, data, ttl).Err() } // Get cached matches func (c *MatchCache) GetMatches(ctx context.Context, flowID string) ([]Match, error) { key := fmt.Sprintf("matches:%s", flowID) data, err := c.client.Get(ctx, key).Bytes() if err == redis.Nil { return nil, ErrCacheMiss } if err != nil { return nil, err } var matches []Match if err := json.Unmarshal(data, &matches); err != nil { return nil, err } return matches, nil } // Invalidate cache func (c *MatchCache) Invalidate(ctx context.Context, flowID string) error { key := fmt.Sprintf("matches:%s", flowID) return c.client.Del(ctx, key).Err() } // Invalidate pattern (all matches for a business) func (c *MatchCache) InvalidatePattern(ctx context.Context, pattern string) error { keys, err := c.client.Keys(ctx, pattern).Result() if err != nil { return err } if len(keys) > 0 { return c.client.Del(ctx, keys...).Err() } return nil } ``` ### Session Management ```go type SessionManager struct { client *redis.Client } func (s *SessionManager) CreateSession(ctx context.Context, userID string) (string, error) { sessionID := uuid.New().String() key := fmt.Sprintf("session:%s", sessionID) err := s.client.Set(ctx, key, userID, 24*time.Hour).Err() if err != nil { return "", err } return sessionID, nil } func (s *SessionManager) GetSession(ctx context.Context, sessionID string) (string, error) { key := fmt.Sprintf("session:%s", sessionID) userID, err := s.client.Get(ctx, key).Result() if err == redis.Nil { return "", ErrSessionNotFound } return userID, err } func (s *SessionManager) DeleteSession(ctx context.Context, sessionID string) error { key := fmt.Sprintf("session:%s", sessionID) return s.client.Del(ctx, key).Err() } ``` ### Rate Limiting ```go type RateLimiter struct { client *redis.Client } func (r *RateLimiter) Allow(ctx context.Context, key string, limit int, window time.Duration) (bool, error) { // Sliding window rate limiting now := time.Now().Unix() windowStart := now - int64(window.Seconds()) pipe := r.client.Pipeline() // Remove old entries pipe.ZRemRangeByScore(ctx, key, "0", strconv.FormatInt(windowStart, 10)) // Count current requests pipe.ZCard(ctx, key) // Add current request pipe.ZAdd(ctx, key, redis.Z{ Score: float64(now), Member: uuid.New().String(), }) // Set expiry pipe.Expire(ctx, key, window) results, err := pipe.Exec(ctx) if err != nil { return false, err } count := results[1].(*redis.IntCmd).Val() return count < int64(limit), nil } // Token bucket implementation func (r *RateLimiter) TokenBucket(ctx context.Context, key string, capacity int, refillRate float64) (bool, error) { script := ` local key = KEYS[1] local capacity = tonumber(ARGV[1]) local tokens = tonumber(ARGV[2]) local refillTime = tonumber(ARGV[3]) local bucket = redis.call('HMGET', key, 'tokens', 'lastRefill') local currentTokens = tonumber(bucket[1]) or capacity local lastRefill = tonumber(bucket[2]) or 0 local now = redis.call('TIME') local currentTime = tonumber(now[1]) -- Refill tokens local elapsed = currentTime - lastRefill local newTokens = math.min(capacity, currentTokens + elapsed * tokens) if newTokens >= 1 then redis.call('HMSET', key, 'tokens', newTokens - 1, 'lastRefill', currentTime) redis.call('EXPIRE', key, refillTime) return 1 else return 0 end ` result, err := r.client.Eval(ctx, script, []string{key}, capacity, refillRate, int(window.Seconds())).Result() if err != nil { return false, err } return result.(int64) == 1, nil } ``` --- ## Pub/Sub (Future Use) ```go // Publisher func PublishMatchUpdate(ctx context.Context, client *redis.Client, userID string, match Match) error { channel := fmt.Sprintf("matches:%s", userID) data, err := json.Marshal(match) if err != nil { return err } return client.Publish(ctx, channel, data).Err() } // Subscriber func SubscribeMatchUpdates(ctx context.Context, client *redis.Client, userID string) (<-chan Match, error) { channel := fmt.Sprintf("matches:%s", userID) pubsub := client.Subscribe(ctx, channel) matches := make(chan Match) go func() { defer close(matches) for { msg, err := pubsub.ReceiveMessage(ctx) if err != nil { return } var match Match if err := json.Unmarshal([]byte(msg.Payload), &match); err != nil { continue } select { case matches <- match: case <-ctx.Done(): return } } }() return matches, nil } ``` --- ## Performance Tips 1. **Use pipelines** for multiple commands 2. **Use connection pooling** - configure MaxRetries, PoolSize 3. **Use pipelining** - batch operations 4. **Set appropriate TTL** - avoid memory leaks 5. **Use keys patterns carefully** - `KEYS` is blocking, use `SCAN` for production --- ## Error Handling ```go val, err := rdb.Get(ctx, "key").Result() if err == redis.Nil { // Key doesn't exist } else if err != nil { // Other error return err } ``` --- ## Tutorials & Resources - **Redis Commands**: https://redis.io/commands/ - **go-redis Examples**: https://github.com/redis/go-redis/tree/master/example - **Redis Patterns**: https://redis.io/docs/manual/patterns/