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:
@@ -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)
|
||||||
|
}
|
||||||
@@ -89,6 +89,7 @@ type MediaScanner struct {
|
|||||||
scan_mutex sync.Mutex
|
scan_mutex sync.Mutex
|
||||||
pollInterval time.Duration
|
pollInterval time.Duration
|
||||||
watching atomic.Bool
|
watching atomic.Bool
|
||||||
|
settingsCache *SettingsCache
|
||||||
|
|
||||||
totalFiles int
|
totalFiles int
|
||||||
newItems int
|
newItems int
|
||||||
@@ -106,6 +107,7 @@ func NewMediaScanner(db *database.Queries) *MediaScanner {
|
|||||||
return &MediaScanner{
|
return &MediaScanner{
|
||||||
db: db,
|
db: db,
|
||||||
watcher: watcher,
|
watcher: watcher,
|
||||||
|
settingsCache: NewSettingsCache(30 * time.Second),
|
||||||
dirtyDirs: make(map[string]time.Time),
|
dirtyDirs: make(map[string]time.Time),
|
||||||
fileStability: make(map[string]*atomic.Bool),
|
fileStability: make(map[string]*atomic.Bool),
|
||||||
pollInterval: 60 * time.Second,
|
pollInterval: 60 * time.Second,
|
||||||
@@ -119,38 +121,52 @@ func NewMediaScanner(db *database.Queries) *MediaScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MediaScanner) GetPollInterval() time.Duration {
|
func (s *MediaScanner) GetPollInterval() time.Duration {
|
||||||
if s.db == nil {
|
// Check cache first
|
||||||
return 30 * time.Second
|
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)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setting, err := s.db.GetSystemSetting(ctx, "scan_poll_interval_seconds")
|
setting, err := s.db.GetSystemSetting(ctx, "scan_poll_interval_seconds")
|
||||||
if err != nil || setting == "" {
|
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)
|
seconds, err := strconv.Atoi(setting)
|
||||||
if err != nil || seconds < 1 {
|
if err != nil {
|
||||||
return 30 * time.Second
|
return 60 * time.Second
|
||||||
}
|
}
|
||||||
return time.Duration(seconds) * time.Second
|
return time.Duration(seconds) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MediaScanner) GetAutoScanEnabled() bool {
|
func (s *MediaScanner) GetAutoScanEnabled() bool {
|
||||||
if s.db == nil {
|
// Check cache first
|
||||||
return true // default to enabled
|
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)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
setting, err := s.db.GetSystemSetting(ctx, "auto_scan_enabled")
|
setting, err := s.db.GetSystemSetting(ctx, "auto_scan_enabled")
|
||||||
if err != nil || setting == "" {
|
if err != nil || setting == "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
enabled, err := strconv.ParseBool(setting)
|
|
||||||
if err != nil {
|
// Store in cache
|
||||||
return true
|
s.settingsCache.Set("auto_scan_enabled", setting)
|
||||||
}
|
|
||||||
return enabled
|
return strings.ToLower(setting) == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MediaScanner) SetAdminID(adminID pgtype.UUID) {
|
func (s *MediaScanner) SetAdminID(adminID pgtype.UUID) {
|
||||||
|
|||||||
Reference in New Issue
Block a user