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:
@@ -358,6 +358,24 @@ def facets(
|
|||||||
|
|
||||||
uid = user.id
|
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):
|
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
|
"""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."""
|
facet aggregation query, so the facets reflect exactly the visible result set."""
|
||||||
@@ -376,14 +394,8 @@ def facets(
|
|||||||
st = aliased(PlexState)
|
st = aliased(PlexState)
|
||||||
query = query.outerjoin(st, and_(st.item_id == model.id, st.user_id == uid))
|
query = query.outerjoin(st, and_(st.item_id == model.id, st.user_id == uid))
|
||||||
query = apply_movie_state_filter(query, st, show)
|
query = apply_movie_state_filter(query, st, show)
|
||||||
else:
|
elif shows_in_scope is not None:
|
||||||
if show in ("watched", "in_progress", "unwatched"):
|
query = query.where(model.id.in_(shows_in_scope))
|
||||||
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)
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
genre_counts: dict[str, int] = {}
|
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")
|
facets = client.get("/api/plex/facets?scope=movie&show=all")
|
||||||
crs = {c["value"]: c["count"] for c in facets.json()["content_ratings"]}
|
crs = {c["value"]: c["count"] for c in facets.json()["content_ratings"]}
|
||||||
assert crs.get("R") == 1, crs
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user