Add in-memory result cache with 1-hour TTL
DB is now queried at most once per hour per user; subsequent requests within the window are served from memory. TTL matches the existing Cache-Control header. Restart clears the cache immediately. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -64,10 +64,10 @@ Set `OP_TYPES=5,6,7,9,11,18,24` for a more inclusive count.
|
||||
- **Template drift.** The harder part. Gitea's `profile.tmpl` does change
|
||||
between versions — diff the new upstream against your override on each
|
||||
upgrade and re-merge the snippet.
|
||||
- **Cache.** 1-hour `Cache-Control` header keeps load trivial. Drop to 5
|
||||
minutes for snappier updates if needed; the underlying query is cheap
|
||||
thanks to the existing index on `(user_id, act_user_id, created_unix)`
|
||||
added in Gitea 1.24.
|
||||
- **Cache.** Results are cached in memory for 1 hour (matching the
|
||||
`Cache-Control: max-age=3600` response header), so the DB is only hit
|
||||
once per hour per user regardless of traffic. Restart the service to
|
||||
clear the cache immediately.
|
||||
|
||||
## Local dev
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -83,12 +84,43 @@ func getEnv(key, fallback string) string {
|
||||
return fallback
|
||||
}
|
||||
|
||||
// ---- cache ----------------------------------------------------------------
|
||||
|
||||
const cacheTTL = time.Hour
|
||||
|
||||
type cacheEntry struct {
|
||||
data []dayCount
|
||||
expires time.Time
|
||||
}
|
||||
|
||||
type resultCache struct {
|
||||
mu sync.Mutex
|
||||
entries map[string]cacheEntry
|
||||
}
|
||||
|
||||
func (c *resultCache) get(username string) ([]dayCount, bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
e, ok := c.entries[username]
|
||||
if !ok || time.Now().After(e.expires) {
|
||||
return nil, false
|
||||
}
|
||||
return e.data, true
|
||||
}
|
||||
|
||||
func (c *resultCache) set(username string, data []dayCount) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.entries[username] = cacheEntry{data: data, expires: time.Now().Add(cacheTTL)}
|
||||
}
|
||||
|
||||
// ---- handler --------------------------------------------------------------
|
||||
|
||||
type heatmapHandler struct {
|
||||
db *sql.DB
|
||||
cfg *config
|
||||
log *slog.Logger
|
||||
db *sql.DB
|
||||
cfg *config
|
||||
log *slog.Logger
|
||||
cache *resultCache
|
||||
}
|
||||
|
||||
type dayCount struct {
|
||||
@@ -118,17 +150,22 @@ func (h *heatmapHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
|
||||
defer cancel()
|
||||
counts, ok := h.cache.get(username)
|
||||
if !ok {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
counts, err := h.queryHeatmap(ctx, username)
|
||||
if err != nil {
|
||||
h.log.Error("heatmap query failed", "user", username, "err", err)
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if counts == nil {
|
||||
counts = []dayCount{}
|
||||
var err error
|
||||
counts, err = h.queryHeatmap(ctx, username)
|
||||
if err != nil {
|
||||
h.log.Error("heatmap query failed", "user", username, "err", err)
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if counts == nil {
|
||||
counts = []dayCount{}
|
||||
}
|
||||
h.cache.set(username, counts)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -216,7 +253,12 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
h := &heatmapHandler{db: db, cfg: cfg, log: logger}
|
||||
h := &heatmapHandler{
|
||||
db: db,
|
||||
cfg: cfg,
|
||||
log: logger,
|
||||
cache: &resultCache{entries: make(map[string]cacheEntry)},
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/heatmap/", h)
|
||||
|
||||
Reference in New Issue
Block a user