feat: add settings cache to reduce database queries in MediaScanner

- Add SettingsCache with TTL-based invalidation (30 seconds)
- Cache scan_poll_interval_seconds and auto_scan_enabled settings
- Reduce database queries from every poll/check to once per TTL period
- Improve error handling with proper fallback values
- Simplify boolean parsing with strings.ToLower for consistency

This optimization reduces database load when checking scan settings,
which occurs frequently during media scanning operations.
This commit is contained in:
2026-03-05 19:35:02 -05:00
parent ab11eade68
commit b3263b2611
2 changed files with 85 additions and 12 deletions
+57
View File
@@ -0,0 +1,57 @@
package services
import (
"sync"
"time"
)
type SettingsCache struct {
data map[string]string
mu sync.RWMutex
ttl time.Duration
lastUpdate time.Time
}
func NewSettingsCache(ttl time.Duration) *SettingsCache {
return &SettingsCache{
data: make(map[string]string),
ttl: ttl,
lastUpdate: time.Now(),
}
}
func (c *SettingsCache) Get(key string) (string, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
// Check if cache is expired
if time.Since(c.lastUpdate) > c.ttl {
return "", false
}
val, ok := c.data[key]
return val, ok
}
func (c *SettingsCache) Set(key, value string) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = value
c.lastUpdate = time.Now()
}
func (c *SettingsCache) Invalidate() {
c.mu.Lock()
defer c.mu.Unlock()
c.data = make(map[string]string)
c.lastUpdate = time.Time{}
}
func (c *SettingsCache) InvalidateKey(key string) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.data, key)
}
+28 -12
View File
@@ -89,6 +89,7 @@ type MediaScanner struct {
scan_mutex sync.Mutex
pollInterval time.Duration
watching atomic.Bool
settingsCache *SettingsCache
totalFiles int
newItems int
@@ -106,6 +107,7 @@ func NewMediaScanner(db *database.Queries) *MediaScanner {
return &MediaScanner{
db: db,
watcher: watcher,
settingsCache: NewSettingsCache(30 * time.Second),
dirtyDirs: make(map[string]time.Time),
fileStability: make(map[string]*atomic.Bool),
pollInterval: 60 * time.Second,
@@ -119,38 +121,52 @@ func NewMediaScanner(db *database.Queries) *MediaScanner {
}
func (s *MediaScanner) GetPollInterval() time.Duration {
if s.db == nil {
return 30 * time.Second
// Check cache first
if cached, ok := s.settingsCache.Get("scan_poll_interval_seconds"); ok {
if seconds, err := strconv.Atoi(cached); err == nil {
return time.Duration(seconds) * time.Second
}
}
// Cache miss - query database
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
setting, err := s.db.GetSystemSetting(ctx, "scan_poll_interval_seconds")
if err != nil || setting == "" {
return 30 * time.Second
return 60 * time.Second
}
// Store in cache
s.settingsCache.Set("scan_poll_interval_seconds", setting)
// Convert to duration
seconds, err := strconv.Atoi(setting)
if err != nil || seconds < 1 {
return 30 * time.Second
if err != nil {
return 60 * time.Second
}
return time.Duration(seconds) * time.Second
}
func (s *MediaScanner) GetAutoScanEnabled() bool {
if s.db == nil {
return true // default to enabled
// Check cache first
if cached, ok := s.settingsCache.Get("auto_scan_enabled"); ok {
return strings.ToLower(cached) == "true"
}
// Cache miss - query database
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
setting, err := s.db.GetSystemSetting(ctx, "auto_scan_enabled")
if err != nil || setting == "" {
return true
}
enabled, err := strconv.ParseBool(setting)
if err != nil {
return true
}
return enabled
// Store in cache
s.settingsCache.Set("auto_scan_enabled", setting)
return strings.ToLower(setting) == "true"
}
func (s *MediaScanner) SetAdminID(adminID pgtype.UUID) {