Summary
The themes/static `ActiveResolver` (mounted in #501 / PR #523) calls `themeActiveStore.Get(context.Background())` synchronously on EVERY request to `/themes/active/style.css`. Today this is microsecond-cheap because the `options` table is single-row and autoloaded, but it scales linearly with public traffic.
Fix
Wrap the resolver with a TTL cache (60s default, configurable via env):
```go
type cachedResolver struct {
inner func() string
mu sync.RWMutex
value string
until time.Time
ttl time.Duration
}
func (c *cachedResolver) Get() string {
c.mu.RLock()
if time.Now().Before(c.until) {
v := c.value
c.mu.RUnlock()
return v
}
c.mu.RUnlock()
c.mu.Lock()
defer c.mu.Unlock()
c.value = c.inner()
c.until = time.Now().Add(c.ttl)
return c.value
}
```
Invalidate on theme-switch (the admin `activate` handler in `adminthemes` already writes the active slug — bus an invalidate signal through the cache).
Acceptance
- Active-theme resolver memoized for TTL seconds.
- Theme switch invalidates the cache immediately.
- Benchmark: 10k req/sec against `/themes/active/style.css` should hit Postgres ≤ 200 times in 10s, not 10k.
Priority
Lowest of the three follow-ups from PR #523. CSS assets are CDN-cacheable upstream so this is polish, not load-bearing.
Summary
The themes/static `ActiveResolver` (mounted in #501 / PR #523) calls `themeActiveStore.Get(context.Background())` synchronously on EVERY request to `/themes/active/style.css`. Today this is microsecond-cheap because the `options` table is single-row and autoloaded, but it scales linearly with public traffic.
Fix
Wrap the resolver with a TTL cache (60s default, configurable via env):
```go
type cachedResolver struct {
inner func() string
mu sync.RWMutex
value string
until time.Time
ttl time.Duration
}
func (c *cachedResolver) Get() string {
c.mu.RLock()
if time.Now().Before(c.until) {
v := c.value
c.mu.RUnlock()
return v
}
c.mu.RUnlock()
c.mu.Lock()
defer c.mu.Unlock()
c.value = c.inner()
c.until = time.Now().Add(c.ttl)
return c.value
}
```
Invalidate on theme-switch (the admin `activate` handler in `adminthemes` already writes the active slug — bus an invalidate signal through the cache).
Acceptance
Priority
Lowest of the three follow-ups from PR #523. CSS assets are CDN-cacheable upstream so this is polish, not load-bearing.