0.2.79 - Slow api fixes
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/ponzischeme89/memby/server/internal/cache"
|
||||
"github.com/ponzischeme89/memby/server/internal/emby"
|
||||
"github.com/ponzischeme89/memby/server/internal/store"
|
||||
"github.com/ponzischeme89/memby/server/internal/timing"
|
||||
)
|
||||
|
||||
// extrasResponse is the Extras tab: featurettes, deleted scenes, interviews and the local
|
||||
@@ -31,48 +35,64 @@ func (s *Server) handleExtras(w http.ResponseWriter, r *http.Request, sess store
|
||||
writeError(w, http.StatusBadRequest, "item id is required")
|
||||
return
|
||||
}
|
||||
key := cache.UserKey(sess.EmbyUserID, "extras:v1:"+itemID)
|
||||
if raw, err := s.cache.Get(ctx, key); err == nil {
|
||||
w.Header().Set("X-Memby-Cache", "hit")
|
||||
writeRaw(w, http.StatusOK, raw)
|
||||
return
|
||||
}
|
||||
|
||||
cred := credentials(sess)
|
||||
// Neither list failing is fatal, and only both failing is a failure worth reporting: a
|
||||
// title can perfectly well have a trailer and no special features, or the reverse, and
|
||||
// an Extras tab withheld because one of two lookups was unwell is the worse outcome.
|
||||
var items []json.RawMessage
|
||||
trailerErr := error(nil)
|
||||
if result, err := s.emby.LocalTrailers(ctx, cred, itemID); err == nil {
|
||||
items = append(items, result.Items...)
|
||||
} else {
|
||||
trailerErr = err
|
||||
}
|
||||
featureErr := error(nil)
|
||||
if result, err := s.emby.SpecialFeatures(ctx, cred, itemID); err == nil {
|
||||
items = append(items, result.Items...)
|
||||
} else {
|
||||
featureErr = err
|
||||
}
|
||||
if trailerErr != nil && featureErr != nil {
|
||||
s.writeUpstreamError(ctx, w, featureErr, "could not load extras")
|
||||
return
|
||||
}
|
||||
|
||||
items = dedupeExtras(items)
|
||||
body, err := json.Marshal(extrasResponse{Items: nonNilRaws(items)})
|
||||
// Cached for the household rather than per viewer, and outside the namespace a
|
||||
// playback stop invalidates: what a film ships with is a fact about the film, and
|
||||
// nothing in this response carries user data. Empty answers included — most of a
|
||||
// library has no extras at all and every detail page asks, so the "no" is the entry
|
||||
// worth keeping.
|
||||
key := cache.MetadataKey("extras:v2:" + itemID)
|
||||
body, hit, err := s.cachedRead(ctx, key, s.cfg.ItemTTL,
|
||||
func(ctx context.Context) (json.RawMessage, error) {
|
||||
return s.extras(ctx, credentials(sess), itemID)
|
||||
})
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "could not encode extras")
|
||||
s.writeUpstreamError(ctx, w, err, "could not load extras")
|
||||
return
|
||||
}
|
||||
// Cached for the ordinary item lifetime, empty answers included. Most of a library has
|
||||
// no extras at all and every detail page asks, so the "no" is the entry worth keeping.
|
||||
if err := s.cache.Set(ctx, key, body, s.cfg.ItemTTL); err != nil {
|
||||
s.loggerFor(ctx).Warn("extras cache write failed", "error", err)
|
||||
writeCached(w, hit, body)
|
||||
}
|
||||
|
||||
// extras joins the two places Emby keeps them.
|
||||
//
|
||||
// The two lookups are independent and are made together. They used to run in turn, which
|
||||
// on a detail page open — where this is one of five requests the television makes at once
|
||||
// — was one round trip of pure waiting for no reason at all.
|
||||
//
|
||||
// Neither list failing is fatal, and only both failing is a failure worth reporting: a
|
||||
// title can perfectly well have a trailer and no special features, or the reverse, and an
|
||||
// Extras tab withheld because one of two lookups was unwell is the worse outcome.
|
||||
func (s *Server) extras(
|
||||
ctx context.Context, cred emby.Credentials, itemID string,
|
||||
) (json.RawMessage, error) {
|
||||
var (
|
||||
trailers *emby.ItemsResult
|
||||
trailerErr error
|
||||
features *emby.ItemsResult
|
||||
featureErr error
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
trailers, trailerErr = s.emby.LocalTrailers(timing.WithLabel(ctx, "emby.trailers"), cred, itemID)
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
features, featureErr = s.emby.SpecialFeatures(timing.WithLabel(ctx, "emby.features"), cred, itemID)
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
if trailerErr != nil && featureErr != nil {
|
||||
return nil, featureErr
|
||||
}
|
||||
w.Header().Set("X-Memby-Cache", "miss")
|
||||
writeRaw(w, http.StatusOK, body)
|
||||
var items []json.RawMessage
|
||||
if trailerErr == nil && trailers != nil {
|
||||
items = append(items, trailers.Items...)
|
||||
}
|
||||
if featureErr == nil && features != nil {
|
||||
items = append(items, features.Items...)
|
||||
}
|
||||
return json.Marshal(extrasResponse{Items: nonNilRaws(dedupeExtras(items))})
|
||||
}
|
||||
|
||||
// dedupeExtras drops the same file appearing in both lists, in first-seen order.
|
||||
|
||||
Reference in New Issue
Block a user