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
+44 -49
View File
@@ -1,6 +1,7 @@
package api
import (
"context"
"encoding/json"
"net/http"
"net/url"
@@ -15,6 +16,12 @@ type personFilmographyResponse struct {
// handlePerson returns Emby's person item. PremiereDate and EndDate are the person's
// birth and death dates; Overview is their biography.
//
// It is cached for the household rather than per viewer, and outside the namespace a
// playback stop invalidates. Nothing about who Cillian Murphy is depends on who is
// asking or on what they finished watching ten minutes ago, and filing it under the
// viewer meant a cast page paid Emby again after every episode anybody in the house
// completed.
func (s *Server) handlePerson(w http.ResponseWriter, r *http.Request, sess store.Session) {
ctx := r.Context()
personID := r.PathValue("id")
@@ -22,31 +29,26 @@ func (s *Server) handlePerson(w http.ResponseWriter, r *http.Request, sess store
writeError(w, http.StatusBadRequest, "person id is required")
return
}
key := cache.UserKey(sess.EmbyUserID, "person:v1:"+personID)
if raw, err := s.cache.Get(ctx, key); err == nil {
w.Header().Set("X-Memby-Cache", "hit")
writeRaw(w, http.StatusOK, raw)
return
}
person, err := s.emby.Item(
ctx,
credentials(sess),
personID,
"Overview,Genres,PrimaryImageAspectRatio",
)
person, hit, err := s.cachedRead(ctx, cache.MetadataKey("person:v2:"+personID), s.cfg.ItemTTL,
func(ctx context.Context) (json.RawMessage, error) {
return s.emby.Item(ctx, credentials(sess), personID,
"Overview,Genres,PrimaryImageAspectRatio")
})
if err != nil {
s.writeUpstreamError(ctx, w, err, "could not load the person")
return
}
if err := s.cache.Set(ctx, key, person, s.cfg.ItemTTL); err != nil {
s.loggerFor(ctx).Warn("person cache write failed", "error", err)
}
w.Header().Set("X-Memby-Cache", "miss")
writeRaw(w, http.StatusOK, person)
writeCached(w, hit, person)
}
// Filmography is kept separate from the biography so life dates can decorate the cast
// row without also loading every cast member's credits.
//
// Unlike the biography this one is genuinely per viewer — it asks Emby for user data, so
// the grid can mark what has been watched — and therefore stays where invalidation can
// reach it. What it gains instead is coalescing: this is the most expensive query the
// gateway makes of Emby, a recursive scan of the whole library filtered by person, and
// three televisions opening the same cast card used to mean three of them.
func (s *Server) handlePersonFilmography(w http.ResponseWriter, r *http.Request, sess store.Session) {
ctx := r.Context()
personID := r.PathValue("id")
@@ -55,40 +57,33 @@ func (s *Server) handlePersonFilmography(w http.ResponseWriter, r *http.Request,
return
}
key := cache.UserKey(sess.EmbyUserID, "person-filmography:v1:"+personID)
if raw, err := s.cache.Get(ctx, key); err == nil {
w.Header().Set("X-Memby-Cache", "hit")
writeRaw(w, http.StatusOK, raw)
return
}
result, err := s.emby.Items(ctx, credentials(sess), url.Values{
"PersonIds": {personID},
"IncludeItemTypes": {"Movie,Series"},
"Recursive": {"true"},
"SortBy": {"ProductionYear,SortName"},
"SortOrder": {"Descending"},
"Limit": {"60"},
"Fields": {"Overview,ProductionYear,PrimaryImageAspectRatio"},
"EnableImages": {"true"},
"EnableImageTypes": {"Primary"},
"ImageTypeLimit": {"1"},
"EnableUserData": {"true"},
})
body, hit, err := s.cachedRead(ctx, key, s.cfg.ItemTTL,
func(ctx context.Context) (json.RawMessage, error) {
result, err := s.emby.Items(ctx, credentials(sess), url.Values{
"PersonIds": {personID},
"IncludeItemTypes": {"Movie,Series"},
"Recursive": {"true"},
"SortBy": {"ProductionYear,SortName"},
"SortOrder": {"Descending"},
"Limit": {"60"},
"Fields": {"Overview,ProductionYear,PrimaryImageAspectRatio"},
"EnableImages": {"true"},
"EnableImageTypes": {"Primary"},
"ImageTypeLimit": {"1"},
"EnableUserData": {"true"},
})
if err != nil {
return nil, err
}
items := result.Items
if items == nil {
items = []json.RawMessage{}
}
return json.Marshal(personFilmographyResponse{Items: items})
})
if err != nil {
s.writeUpstreamError(ctx, w, err, "could not load the person's filmography")
return
}
items := result.Items
if items == nil {
items = []json.RawMessage{}
}
body, err := json.Marshal(personFilmographyResponse{Items: items})
if err != nil {
writeError(w, http.StatusInternalServerError, "could not encode the filmography")
return
}
if err := s.cache.Set(ctx, key, body, s.cfg.ItemTTL); err != nil {
s.loggerFor(ctx).Warn("person filmography cache write failed", "error", err)
}
w.Header().Set("X-Memby-Cache", "miss")
writeRaw(w, http.StatusOK, body)
writeCached(w, hit, body)
}