perf: rate limiter cleanup goroutine, DB pool constraints
- ipLimiter evicts stale entries after 10 min of inactivity via 5 min sweep - DB pool capped at 25 max open, 5 idle, 15 min lifetime
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.dustin.coffee/hobokenchicken/dumpsterChat/internal/config"
|
||||
|
||||
@@ -24,6 +25,11 @@ func New(cfg *config.Config) (*DB, error) {
|
||||
return nil, fmt.Errorf("ping database: %w", err)
|
||||
}
|
||||
|
||||
// ponytail: prevent unbounded connection growth under burst load
|
||||
db.SetMaxOpenConns(25)
|
||||
db.SetMaxIdleConns(5)
|
||||
db.SetConnMaxLifetime(15 * time.Minute)
|
||||
|
||||
return &DB{db}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
@@ -12,16 +13,21 @@ import (
|
||||
type ipLimiter struct {
|
||||
mu sync.Mutex
|
||||
limiters map[string]*rate.Limiter
|
||||
lastUsed map[string]time.Time
|
||||
rate rate.Limit
|
||||
burst int
|
||||
}
|
||||
|
||||
func newIPLimiter(r rate.Limit, burst int) *ipLimiter {
|
||||
return &ipLimiter{
|
||||
l := &ipLimiter{
|
||||
limiters: make(map[string]*rate.Limiter),
|
||||
lastUsed: make(map[string]time.Time),
|
||||
rate: r,
|
||||
burst: burst,
|
||||
}
|
||||
// ponytail: evict inactive limiters every 5 minutes to prevent unbounded map growth
|
||||
go l.cleanup()
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *ipLimiter) getLimiter(ip string) *rate.Limiter {
|
||||
@@ -33,9 +39,27 @@ func (l *ipLimiter) getLimiter(ip string) *rate.Limiter {
|
||||
lim = rate.NewLimiter(l.rate, l.burst)
|
||||
l.limiters[ip] = lim
|
||||
}
|
||||
l.lastUsed[ip] = time.Now()
|
||||
return lim
|
||||
}
|
||||
|
||||
// cleanup runs in a background goroutine and removes limiters that haven't
|
||||
// been accessed in 10 minutes.
|
||||
func (l *ipLimiter) cleanup() {
|
||||
for {
|
||||
time.Sleep(5 * time.Minute)
|
||||
l.mu.Lock()
|
||||
now := time.Now()
|
||||
for ip, last := range l.lastUsed {
|
||||
if now.Sub(last) > 10*time.Minute {
|
||||
delete(l.limiters, ip)
|
||||
delete(l.lastUsed, ip)
|
||||
}
|
||||
}
|
||||
l.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// RateLimit returns middleware that limits requests per IP.
|
||||
// requestsPerSecond is the sustained rate, burst is the max burst size.
|
||||
func RateLimit(requestsPerSecond float64, burst int) func(http.Handler) http.Handler {
|
||||
|
||||
Reference in New Issue
Block a user