// Package store wraps Redis and Postgres. M0 ships Redis only; the // Postgres layer lands with M2 (recipient resolution). // // Redis is used for: // - dedupe (SET NX EX, INCR) – SPEC §5 // - rate limiting (INCR + EXPIRE, or redis-cell) – SPEC §22 layer 3/4 // - per-source quarantine (local map, see ingestd) – SPEC §22 layer 7 package store import ( "context" "fmt" "time" "github.com/redis/go-redis/v9" ) // Redis is a thin wrapper to keep construction in one place. type Redis struct { *redis.Client } // ConnectRedis dials Redis. Two retries with backoff. func ConnectRedis(ctx context.Context, url string) (*Redis, error) { opts, err := redis.ParseURL(url) if err != nil { return nil, fmt.Errorf("parse redis url %q: %w", url, err) } c := redis.NewClient(opts) pingCtx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() if err := c.Ping(pingCtx).Err(); err != nil { return nil, fmt.Errorf("redis ping: %w", err) } return &Redis{c}, nil } // IsConnRefused reports whether err looks like Redis being down. func IsConnRefused(err error) bool { return err != nil && (contains(err.Error(), "connection refused") || contains(err.Error(), "EOF")) } func contains(s, sub string) bool { for i := 0; i+len(sub) <= len(s); i++ { if s[i:i+len(sub)] == sub { return true } } return false }