0.2.79 - Slow api fixes
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
package api
|
||||
|
||||
import "sync"
|
||||
|
||||
// consideredFollows bounds the memory below. A household has a handful of episodes in
|
||||
// flight; this is generous enough that a whole evening's viewing fits and small enough
|
||||
// that nothing a client can do grows it into a leak.
|
||||
const consideredFollows = 512
|
||||
|
||||
// followChecks remembers which (viewer, episode) pairs have already been put through
|
||||
// the automatic My Shows check.
|
||||
//
|
||||
// The check is hung off playback reports, and a report arrives every ten seconds for the
|
||||
// length of an episode. Its own guard — half watched — is true for every one of those
|
||||
// after the halfway point, so on a forty-minute episode the second half of the viewing
|
||||
// ran the whole check a hundred times: two serial Emby lookups, the Sonarr catalogue and
|
||||
// a Postgres write, all of it on the response path of a request the television makes
|
||||
// while somebody is watching something. Every run after the first reached the same
|
||||
// `inserted == false` and discarded its own work.
|
||||
//
|
||||
// Deduplicating on the durable insert is correct and was never the problem; what it
|
||||
// could not do is prevent the work leading up to it. This is deliberately in memory and
|
||||
// deliberately lossy, the playbackTitles arrangement: a restarted gateway does the check
|
||||
// once more per episode, which is the cost of one lookup rather than a schema change.
|
||||
type followChecks struct {
|
||||
mu sync.Mutex
|
||||
seen map[string]struct{}
|
||||
order []string
|
||||
}
|
||||
|
||||
// claim reports whether this is the first time the pair has been offered, and records it.
|
||||
// One call does both because two would be a race between the check and the record, and
|
||||
// the thing being protected is precisely a burst of concurrent reports.
|
||||
func (f *followChecks) claim(userID, itemID string) bool {
|
||||
if userID == "" || itemID == "" {
|
||||
return false
|
||||
}
|
||||
key := userID + "|" + itemID
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.seen == nil {
|
||||
f.seen = make(map[string]struct{}, consideredFollows)
|
||||
}
|
||||
if _, known := f.seen[key]; known {
|
||||
return false
|
||||
}
|
||||
f.seen[key] = struct{}{}
|
||||
f.order = append(f.order, key)
|
||||
if len(f.order) > consideredFollows {
|
||||
delete(f.seen, f.order[0])
|
||||
f.order = f.order[1:]
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user