perf(api): historia ogladania wsadowo, wyszukiwanie w obrebie strony po rownosci originu

- /watch/recent: _build_scenes_out_batch(light) zamiast 5-7 zapytan na scene (N+1 w Sentry)
- /scenes?origin=tube❌ rownosc zamiast ILIKE '%...%', 4,6 s -> 1,1 s na hdporngg z q

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
goon-foss 2026-09-15 14:06:01 +02:00
parent 8462ab50df
commit 747d0cec3b
2 changed files with 22 additions and 5 deletions

View file

@ -383,13 +383,21 @@ def list_scenes(
base = base.where(~live_playback_exists())
if origin:
# Substring match na origin, 'hqporner' złapie 'tube:hqpornercom'.
# Pelny origin z ekranu strony ('tube:hdporngg') porownujemy wprost: rownosc idzie
# po ix_playback_sources_origin_live, a ILIKE '%...%' przeszukiwal kazde zrodlo
# kazdej kandydujacej sceny (wyszukiwanie w obrebie strony 4,6 s, Sentry Slow DB
# Query 2026-09-15). Krotka forma z filtra ('hqporner') zostaje substringiem.
origin_clause = (
PlaybackSource.origin == origin
if ":" in origin
else PlaybackSource.origin.ilike(f"%{origin}%")
)
base = base.where(
exists(
select(1).where(
PlaybackSource.scene_id == Scene.id,
PlaybackSource.dead_at.is_(None),
PlaybackSource.origin.ilike(f"%{origin}%"),
origin_clause,
)
)
)

View file

@ -20,7 +20,7 @@ from sqlalchemy import desc, select
from sqlalchemy.orm import Session
from app.api.device import get_device_id
from app.api.scenes import _build_scene_out
from app.api.scenes import _build_scenes_out_batch
from app.api.schemas import SceneOut
from app.auth import require_api_key
from app.db import get_session
@ -237,11 +237,20 @@ def list_recent(
if not include_finished:
stmt = stmt.where(ScenePlayProgress.finished.is_(False))
# Serializacja wsadowa jak w liscie scen: rail „Continue watching" leci przy kazdym
# starcie apki, a pelny `_build_scene_out` robil 5-7 zapytan na scene (N+1 w Sentry,
# 2026-09-14). Rail uzywa tytulu, miniatury i dlugosci, wiec wersja lekka wystarcza.
rows = session.execute(stmt).all()
outs = _build_scenes_out_batch(session, [scene for _, scene in rows], light=True, device_id=device_id)
out_by_id = {o.id: o for o in outs}
items: list[WatchEntry] = []
for prog, scene in session.execute(stmt).all():
for prog, scene in rows:
scene_out = out_by_id.get(scene.id)
if scene_out is None:
continue
items.append(
WatchEntry(
scene=_build_scene_out(session, scene, device_id=device_id),
scene=scene_out,
position_sec=prog.position_sec,
duration_sec=prog.duration_sec,
finished=prog.finished,