0.2.79 - Slow api fixes

This commit is contained in:
ponzischeme89
2026-08-19 18:08:00 +12:00
parent 590e069366
commit 0782545013
41 changed files with 1820 additions and 317 deletions
+31
View File
@@ -40,6 +40,7 @@ import (
"github.com/ponzischeme89/memby/server/internal/scheduler"
"github.com/ponzischeme89/memby/server/internal/sonarr"
"github.com/ponzischeme89/memby/server/internal/store"
"github.com/ponzischeme89/memby/server/internal/timing"
)
type Server struct {
@@ -107,6 +108,19 @@ type Server struct {
// playbackTitles lets a progress or stop report, which carries only an item id, be
// logged by name.
playbackTitles playbackTitles
// featurePolicy keeps the operator's feature switches out of the request path. See
// features_cache.go.
featurePolicy featurePolicyCache
// upstream deduplicates concurrent cache misses for the same key, so two televisions
// asking for the same expensive answer at the same moment cost one upstream call
// rather than two. See coalesce.go.
upstream upstreamGroup
// household caches the completion scores every viewer's ranking reads and none of
// them can get a different answer to. See ranking.go.
household householdScores
// followChecks stops the automatic My Shows check repeating for the length of an
// episode. See playback_follow.go.
followChecks followChecks
// ingestRuns collapses a season pack's worth of finished scans into one banner.
ingestRuns ingestRuns
@@ -505,6 +519,13 @@ func (s *Server) withLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
r, identity := withRequestIdentity(r)
// Every layer under this one — the Emby client, the *arr clients, Postgres,
// Redis — records against this. It is installed for every request rather than
// only for ones that turn out to be slow, because nothing knows a request is
// slow until it has finished and the evidence has to have been collected on the
// way through. What it costs when the request is fast is a map nobody reads.
ctx, trace := timing.New(r.Context())
r = r.WithContext(ctx)
w.Header().Set("X-Memby-Correlation", identity.correlation)
s.loggerFor(r.Context()).Log(r.Context(), serverlogging.LevelTrace, "request started",
"method", r.Method, "path", r.URL.Path, "query_keys", queryKeys(r))
@@ -539,6 +560,16 @@ func (s *Server) withLogging(next http.Handler) http.Handler {
if cached := rec.Header().Get("X-Memby-Cache"); cached != "" {
fields = append(fields, "cache", cached)
}
// A duration on its own says something is slow and nothing about why, which is
// the state every latency investigation here started from. The breakdown is
// attached only past the threshold: on a fast request it would be a second
// column of noise on every line, and on a slow one it is the answer.
if elapsed := time.Since(start); elapsed >= s.slowRequestThreshold() && !trace.Empty() {
fields = append(fields, "breakdown", trace.Breakdown())
if rest := trace.Unattributed(elapsed); rest > 0 {
fields = append(fields, "elsewhere", rest.Round(time.Millisecond))
}
}
s.log.Log(r.Context(), level, "request", fields...)
})
}