From 73853eb9ed8ba0767a476bc1e2fd69d15212fb34 Mon Sep 17 00:00:00 2001 From: goon-foss Date: Fri, 18 Sep 2026 15:11:45 +0200 Subject: [PATCH] perf(api): kilka tagow od najnowszych idzie po indeksie najrzadszego z nich Z dwoma tagami (rough-sex + anal) lista szla stara droga po calym katalogu, 7,4 s na zimnym cache. Teraz dolaczamy najrzadszy tag po ix_scene_tags_tag_sort, a reszta zostaje jako EXISTS: 0,2 s. Dolaczona tabela ma alias, inaczej EXISTS po scene_tags korelowal sie z nia. Co-Authored-By: Claude Opus 5 --- app/api/scenes.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/app/api/scenes.py b/app/api/scenes.py index 8978236..3f0813a 100644 --- a/app/api/scenes.py +++ b/app/api/scenes.py @@ -466,22 +466,25 @@ def list_scenes( # ix_scenes_created_at_desc zamiast materializować wszystkie scene_tags. Slug-JOIN # ukrywał tag_id przed plannerem → używał średniej (8.4M/11541≈726) → zły plan # (4-12s). Z literałem: ~20ms. Zob. też _build... light mode. - # Jeden tag + sort od najnowszych (ekran tagu w apce): zamiast EXISTS dolaczamy wiersz - # scene_tags i sortujemy po jego `sort_date`, patrz galaz sort == "release_date". + # Sort od najnowszych z filtrem tagu: jeden tag zamiast EXISTS dolaczamy jako wiersz + # scene_tags i sortujemy po jego `sort_date` (galaz sort == "release_date"). Przy kilku + # tagach dolaczamy NAJRZADSZY, bo spacer po indeksie konczy sie najpozniej na jego + # ostatniej scenie; reszta zostaje jako EXISTS. rough-sex + anal: 7,4 s na zimnym cache + # po starej sciezce (2026-09-18). tag_sort_id: uuid.UUID | None = None if tag_slug_list: - id_by_slug = dict( - session.execute( - select(Tag.slug, Tag.id).where(Tag.slug.in_(tag_slug_list)) - ).all() - ) + tag_rows = session.execute( + select(Tag.slug, Tag.id, Tag.scene_count).where(Tag.slug.in_(tag_slug_list)) + ).all() + id_by_slug = {slug: tid for slug, tid, _ in tag_rows} + if sort == "release_date" and tag_rows: + tag_sort_id = min(tag_rows, key=lambda r: r[2] or 0)[1] for slug in tag_slug_list: tag_id = id_by_slug.get(slug) if tag_id is None: base = base.where(false()) # nieznany slug → brak wyników break - if len(tag_slug_list) == 1 and sort == "release_date": - tag_sort_id = tag_id + if tag_id == tag_sort_id: continue base = base.where( exists( @@ -651,16 +654,18 @@ def list_scenes( # od strefy sesji, stad jawne AT TIME ZONE 'UTC'. Indeks: # ix_scenes_effective_date_desc (migracja 0033). if tag_sort_id is not None: - # Z filtrem jednego tagu ta sama data siedzi w scene_tags.sort_date (wyzwalacz, + # Z filtrem tagu ta sama data siedzi w scene_tags.sort_date (wyzwalacz, # migracja 0042) i idzie po ix_scene_tags_tag_sort (0043): od najnowszej sceny # tagu, koniec po LIMIT. Po samym `scenes` Postgres sprawdzal tag scena po scenie # od najnowszej w calym katalogu i przy tagach rzedu 40-60 tys. scen (`rough-sex`) # przegladal 46 tys. scen na jedna strone: 7-15 s na zimnym cache (GOON-64). # W obrebie jednego dnia kolejnosc po id, nie po created_at: indeks jest # wezszy, a stronicowanie i tak stabilne. + # Alias, bo przy kilku tagach EXISTS po scene_tags skorelowalby sie z tym JOIN-em. + st = aliased(SceneTag) ordered = base.join( - SceneTag, and_(SceneTag.scene_id == Scene.id, SceneTag.tag_id == tag_sort_id) - ).order_by(SceneTag.sort_date.desc(), SceneTag.scene_id.desc()) + st, and_(st.scene_id == Scene.id, st.tag_id == tag_sort_id) + ).order_by(st.sort_date.desc(), st.scene_id.desc()) else: ordered = base.order_by( _effective_date_desc(), Scene.created_at.desc(), Scene.id.desc()