db_pool.go 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. // Package repository contains persistence infrastructure helpers.
  2. //
  3. // DB pool lifetimes are clamped here because lib/pq starts watchCancel
  4. // goroutines for context-aware queries. If a cloud proxy silently drops idle
  5. // TCP without RST/FIN, those goroutines can block in Read until database/sql
  6. // retires the connection. This is a short-term mitigation; the long-term
  7. // follow-up is migrating PostgreSQL access to jackc/pgx/v5/stdlib.
  8. package repository
  9. import (
  10. "database/sql"
  11. "log/slog"
  12. "time"
  13. "github.com/Wei-Shaw/sub2api/internal/config"
  14. )
  15. const (
  16. defaultConnMaxLifetime = 30 * time.Minute
  17. defaultConnMaxIdleTime = 5 * time.Minute
  18. maxConfiguredConnAge = 24 * time.Hour
  19. )
  20. type dbPoolSettings struct {
  21. MaxOpenConns int
  22. MaxIdleConns int
  23. ConnMaxLifetime time.Duration
  24. ConnMaxIdleTime time.Duration
  25. }
  26. func clampDBPoolSettings(cfg *config.Config) dbPoolSettings {
  27. return dbPoolSettings{
  28. MaxOpenConns: cfg.Database.MaxOpenConns,
  29. MaxIdleConns: cfg.Database.MaxIdleConns,
  30. ConnMaxLifetime: clampDBPoolDuration("database.conn_max_lifetime_minutes", cfg.Database.ConnMaxLifetimeMinutes, defaultConnMaxLifetime),
  31. ConnMaxIdleTime: clampDBPoolDuration("database.conn_max_idle_time_minutes", cfg.Database.ConnMaxIdleTimeMinutes, defaultConnMaxIdleTime),
  32. }
  33. }
  34. func clampDBPoolDuration(key string, minutes int, fallback time.Duration) time.Duration {
  35. if minutes <= 0 || minutes > int(maxConfiguredConnAge/time.Minute) {
  36. slog.Warn("database connection pool duration clamped",
  37. "key", key,
  38. "before", minutes,
  39. "after", int(fallback/time.Minute),
  40. )
  41. return fallback
  42. }
  43. return time.Duration(minutes) * time.Minute
  44. }
  45. func applyDBPoolSettings(db *sql.DB, cfg *config.Config) {
  46. settings := clampDBPoolSettings(cfg)
  47. db.SetMaxOpenConns(settings.MaxOpenConns)
  48. db.SetMaxIdleConns(settings.MaxIdleConns)
  49. db.SetConnMaxLifetime(settings.ConnMaxLifetime)
  50. db.SetConnMaxIdleTime(settings.ConnMaxIdleTime)
  51. slog.Info("database connection pool configured",
  52. slog.Group("effective",
  53. slog.Int("max_open", settings.MaxOpenConns),
  54. slog.Int("max_idle", settings.MaxIdleConns),
  55. slog.Duration("max_lifetime", settings.ConnMaxLifetime),
  56. slog.Duration("max_idle_time", settings.ConnMaxIdleTime),
  57. ),
  58. )
  59. }