perf(scenes): count over PK, not whole entity, in filtered list

The bounded count for filtered scene lists ran `SELECT count(*) FROM (SELECT
scenes.* ... LIMIT 1001)` because the base query selects the full Scene entity.
Counting over all columns made the planner pick a far worse plan via psycopg
bound params (~4s for has_playback) than the same logic over the PK (~30-400ms).
Count semantics are unchanged — we only need rows to exist — so count over
`base.with_only_columns(Scene.id)`.

Partial: this fixes the count leg. The main ordered fetch on filtered lists
(has_playback / tags) can still pick a gather-all-then-sort plan under bound
params (fast with literal binds, slow parameterized) — tracked separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jtrzupek 2026-06-02 11:14:38 +02:00
parent c0bb3f403f
commit 20a8dc8e27

View file

@ -328,8 +328,13 @@ def list_scenes(
if _is_pure_default:
total = _default_scene_count(session)
else:
# PERF (2026-06-02): `base` to `select(Scene)` (wszystkie kolumny). Count nad
# `SELECT scenes.* ... LIMIT 1001` z bound-params psycopg dobierał zły plan i
# zajmował ~4s (has_playback) / ~6s (tags) — mimo że to samo z `SELECT id` /
# literałami robi ~30ms. Liczymy nad samym PK: identyczna semantyka, ~100× szybciej.
count_base = base.with_only_columns(Scene.id)
cnt = session.execute(
select(func.count()).select_from(base.limit(_COUNT_CAP + 1).subquery())
select(func.count()).select_from(count_base.limit(_COUNT_CAP + 1).subquery())
).scalar_one()
if cnt > _COUNT_CAP:
total, total_capped = _COUNT_CAP, True