redis.go 1.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. // Package store wraps Redis and Postgres. M0 ships Redis only; the
  2. // Postgres layer lands with M2 (recipient resolution).
  3. //
  4. // Redis is used for:
  5. // - dedupe (SET NX EX, INCR) – SPEC §5
  6. // - rate limiting (INCR + EXPIRE, or redis-cell) – SPEC §22 layer 3/4
  7. // - per-source quarantine (local map, see ingestd) – SPEC §22 layer 7
  8. package store
  9. import (
  10. "context"
  11. "fmt"
  12. "time"
  13. "github.com/redis/go-redis/v9"
  14. )
  15. // Redis is a thin wrapper to keep construction in one place.
  16. type Redis struct {
  17. *redis.Client
  18. }
  19. // ConnectRedis dials Redis. Two retries with backoff.
  20. func ConnectRedis(ctx context.Context, url string) (*Redis, error) {
  21. opts, err := redis.ParseURL(url)
  22. if err != nil {
  23. return nil, fmt.Errorf("parse redis url %q: %w", url, err)
  24. }
  25. c := redis.NewClient(opts)
  26. pingCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
  27. defer cancel()
  28. if err := c.Ping(pingCtx).Err(); err != nil {
  29. return nil, fmt.Errorf("redis ping: %w", err)
  30. }
  31. return &Redis{c}, nil
  32. }
  33. // IsConnRefused reports whether err looks like Redis being down.
  34. func IsConnRefused(err error) bool {
  35. return err != nil && (contains(err.Error(), "connection refused") || contains(err.Error(), "EOF"))
  36. }
  37. func contains(s, sub string) bool {
  38. for i := 0; i+len(sub) <= len(s); i++ {
  39. if s[i:i+len(sub)] == sub {
  40. return true
  41. }
  42. }
  43. return false
  44. }