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
+32 -22
View File
@@ -1,6 +1,7 @@
package api
import (
"context"
"encoding/json"
"errors"
"net/http"
@@ -10,6 +11,7 @@ import (
"github.com/ponzischeme89/memby/server/internal/emby"
"github.com/ponzischeme89/memby/server/internal/recommend"
"github.com/ponzischeme89/memby/server/internal/store"
"github.com/ponzischeme89/memby/server/internal/timing"
)
// fieldsRelated is the detail set. It once added Studios on its own account — the
@@ -50,7 +52,24 @@ func (s *Server) handleRelated(w http.ResponseWriter, r *http.Request, sess stor
return
}
reasons, related, err := s.recommender.RelatedTo(ctx, credentials(sess), item, relatedRowSize)
// Coalesced, because this route is asked for twice for the same title as a matter of
// course: the launcher warms it while a card is focused and the detail page asks for
// it again the moment somebody presses. Those two requests used to build the answer
// twice, and building it is the same Emby fan-out the home rows pay for.
body, err := s.buildCached(ctx, key, relatedTTL(s.cfg.ItemTTL),
func(ctx context.Context) (json.RawMessage, error) {
reasons, related, err := s.recommender.RelatedTo(
timing.WithLabel(ctx, "emby.related"), credentials(sess), item, relatedRowSize)
if err != nil {
return nil, err
}
items := nonNilRaws(recommend.Raws(related))
s.decorateItemRatings(ctx, items)
return json.Marshal(relatedResponse{
Reasons: nonNilStrings(reasons),
Items: items,
})
})
if err != nil {
// RelatedTo degrades rather than failing, so the only error it returns is the
// viewer having navigated on. That is the television saving work, not a fault.
@@ -61,29 +80,20 @@ func (s *Server) handleRelated(w http.ResponseWriter, r *http.Request, sess stor
s.writeUpstreamError(ctx, w, err, "could not load related titles")
return
}
writeCached(w, false, body)
}
items := nonNilRaws(recommend.Raws(related))
s.decorateItemRatings(ctx, items)
body, err := json.Marshal(relatedResponse{
Reasons: nonNilStrings(reasons),
Items: items,
})
if err != nil {
writeError(w, http.StatusInternalServerError, "could not encode related titles")
return
// relatedTTL keeps an empty carousel briefly rather than for the item lifetime: it
// usually means something upstream was unwell, and the ten-minute answer would otherwise
// outlive the minute of trouble that produced it.
func relatedTTL(itemTTL time.Duration) func(json.RawMessage) time.Duration {
return func(body json.RawMessage) time.Duration {
var decoded relatedResponse
if json.Unmarshal(body, &decoded) == nil && len(decoded.Items) == 0 {
return relatedEmptyTTL
}
return itemTTL
}
// An empty carousel is cached briefly rather than for the item lifetime: it usually
// means something upstream was unwell, and the ten-minute answer would otherwise
// outlive the minute of trouble that produced it.
ttl := s.cfg.ItemTTL
if len(items) == 0 {
ttl = relatedEmptyTTL
}
if err := s.cache.Set(ctx, key, body, ttl); err != nil {
s.loggerFor(ctx).Warn("related cache write failed", "error", err)
}
w.Header().Set("X-Memby-Cache", "miss")
writeRaw(w, http.StatusOK, body)
}
// relatedRowSize is a carousel's worth. The strip scrolls, but a viewer who reaches the