Skip to content

Cache active-theme resolver in /themes static handler #526

@tayebmokni

Description

@tayebmokni

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions