perf(plex): the facets ask the episode rollup once per call, not once per show group

A watch-state scope (watched / in progress / unwatched) joined the per-user episode rollup into
each show facet query: five passes over the user's episodes per call, half of a filtered facets
request. The shows in scope are now resolved once and every group filters on their ids; the
grid-parity test holds the counts. Localdev: 99 -> 61 ms.
This commit is contained in:
2026-10-02 21:32:49 +02:00
parent fb2e3d8490
commit e6de18b8c0
2 changed files with 45 additions and 8 deletions
+20 -8
View File
@@ -358,6 +358,24 @@ def facets(
uid = user.id
# The shows that pass the watch-state scope, asked ONCE. The per-user episode rollup behind it is a
# full pass over the user's episodes (~10 ms on a prod-sized library), and each of the show groups
# (genres, ratings, studios, years, rating max) asked it again: half of a filtered facets call
# (E31 S4e). The same filter on the same rows, so the counts do not change
# (test_plex_facets_grid_parity).
shows_in_scope: list[int] | None = None
if show_ids and show in ("watched", "in_progress", "unwatched"):
agg = rollup.agg_subquery(db, uid).subquery()
in_scope = select(PlexShow.id).outerjoin(agg, agg.c.show_id == PlexShow.id)
in_scope = apply_show_state_filter(
in_scope,
func.coalesce(agg.c.total, 0),
func.coalesce(agg.c.watched, 0),
func.coalesce(agg.c.inprog, 0),
show,
)
shows_in_scope = list(db.scalars(in_scope.where(PlexShow.library_id.in_(show_ids))))
def wsq(query, model, ids, pp, kind_movie, with_dur):
"""Apply the (self-excluded) metadata filters + library scope + the per-user watch-state to a
facet aggregation query, so the facets reflect exactly the visible result set."""
@@ -376,14 +394,8 @@ def facets(
st = aliased(PlexState)
query = query.outerjoin(st, and_(st.item_id == model.id, st.user_id == uid))
query = apply_movie_state_filter(query, st, show)
else:
if show in ("watched", "in_progress", "unwatched"):
agg = rollup.agg_subquery(db, uid).subquery()
query = query.outerjoin(agg, agg.c.show_id == model.id)
tot = func.coalesce(agg.c.total, 0)
wat = func.coalesce(agg.c.watched, 0)
inp = func.coalesce(agg.c.inprog, 0)
query = apply_show_state_filter(query, tot, wat, inp, show)
elif shows_in_scope is not None:
query = query.where(model.id.in_(shows_in_scope))
return query
genre_counts: dict[str, int] = {}
@@ -214,3 +214,28 @@ def test_hidden_movie_absent_from_grid_and_facets(db, as_user):
facets = client.get("/api/plex/facets?scope=movie&show=all")
crs = {c["value"]: c["count"] for c in facets.json()["content_ratings"]}
assert crs.get("R") == 1, crs
def test_a_watch_state_scope_rolls_the_episodes_up_once(db, as_user):
"""The per-user episode rollup behind a show's watch state is a pass over every episode; each show
facet group asked it again (E31 S4e: half of a filtered facets call). It is asked once now."""
from sqlalchemy import event
from app.db import engine
seed = _seed(db)
client = as_user(seed["user"])
rollups: list[str] = []
def count(conn, cursor, statement, *_):
if "GROUP BY plex_items.show_id" in statement:
rollups.append(statement)
event.listen(engine, "before_cursor_execute", count)
try:
for show in ("watched", "in_progress", "unwatched"):
rollups.clear()
assert client.get(f"/api/plex/facets?scope=both&show={show}").status_code == 200
assert len(rollups) == 1, show
finally:
event.remove(engine, "before_cursor_execute", count)