goon/alembic/versions/20260818_0030_scenes_quality_threshold_idx.py
goon-foss 0fed952c5c feat(filtr): minimalna jakosc w filtrze scen (720p/1080p/4K)
Prosba uzytkownika (zgloszenie 17376139). Backend mial juz parametr `min_quality_p`,
ale w praktyce byl NIEUZYWALNY i to zajelo wiekszosc pracy.

Wydajnosc, mierzona na produkcji przez EXPLAIN ANALYZE, prog 1080p:

  EXISTS na playback_sources (stan zastany)   32 s   3 556 612 odwiedzonych scen
  + indeks funkcyjny per-scena                24 s
  JOIN z DISTINCT po jakosci                   1,1 s W IZOLACJI
  ten sam JOIN + reszta filtrow feedu         52 s   planer wraca do skanu po dacie
  kolumna scenes.max_quality_p (0029)          2,0-2,6 s
  + indeksy czesciowe pod progi (0030)         0,09-0,11 s

Wniosek, ktory kosztowal najwiecej: ksztaltem zapytania sie tego NIE dalo zalatwic.
Koszt to LICZBA odwiedzonych scen, nie pojedynczy test, a przy `ORDER BY created_at
DESC LIMIT 24` planer i tak wracal do przechodzenia scen po dacie, gdy tylko doszly
pozostale filtry (bramka wertykalow, heurystyka szkieletow, prog dlugosci). Stad
denormalizacja + indeksy czesciowe z predykatem progu, ktore daja gotowe sortowanie.

Semantyka SUROWA, swiadomie: `quality` zapisujemy oportunistycznie, zna ja 6,5% scen
(210 302 z 3 236 368), najlepsze zrodlo ma 26% pokrycia. Wariant lagodny, przepuszczajacy
sceny o nieznanej jakosci, odrzucony po zmierzeniu: odsiewalby 88 647 z 3,2 mln, czyli
nie realizowalby intencji "nie chce ogladac ziemniaka". Filtr pokazuje wiec tylko jakosc
POTWIERDZONA, a UI mowi to wprost podpisem pod przelacznikiem, bo inaczej gwaltowny
spadek liczby wynikow wyglada na zepsuta aplikacje.

Warianty tekstowe 4K/UHD usuniete z warunku: 0 wystapien na 238 452 zywe zrodla,
a `OR ilike` blokowal uzycie indeksu.

Odswiezanie w taxonomy_counts (co 3h), z czyszczeniem do NULL gdy scena traci zrodlo
z jakoscia. Backfill: 210 302 sceny w 2m56s.

Zweryfikowane na emulatorze: sekcja MIN QUALITY renderuje sie z podpisem ostrzegawczym.
Lista wyszla pusta, ale to build debugowy dostajacy 403 (podpis debug spoza allowlisty),
nie blad filtra: ten sam URL z poprawnym podpisem oddaje 50 wynikow w 0,12 s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 11:06:34 +02:00

50 lines
1.8 KiB
Python

"""Indeksy czesciowe pod progi filtra jakosci (720/1080/2160)
Dlaczego osobne indeksy na prog, a nie jeden na (max_quality_p, created_at):
tamten nie da sie uzyc do `ORDER BY created_at DESC LIMIT n`, bo wynik nie jest
posortowany po created_at w obrebie ZAKRESU wartosci max_quality_p. Planer go
ignorowal i wracal do przejscia `scenes` po dacie z filtrem.
Indeks czesciowy z predykatem `max_quality_p >= N`, posortowany po created_at DESC,
jest dla progu N dokladnie tym, czego zapytanie potrzebuje: Postgres uzywa go, gdy
warunek zapytania implikuje predykat indeksu. Zmierzone na produkcji 2026-08-18:
0029 (sama kolumna) 2,0-2,6 s planer szedl ix_scenes_created_at_desc
0030 (te indeksy) 0,09-0,11 s Index Scan using ix_scenes_created_q1080
Progi sa ZWIAZANE Z UI: `QUALITY_OPTIONS` w mobile/src/screens/ScenesFilterModal.tsx
oferuje dokladnie 720/1080/2160. Inna wartosc w `?min_quality_p=` nadal dziala, tylko
wolniej (fallback do skanu po dacie). Dodajac opcje w UI, dodaj tu indeks.
Rozmiary sa pomijalne (4,3 MB / 2,7 MB / 0,3 MB), bo jakosc zna tylko 6,5% katalogu.
Revision ID: 0030_scenes_quality_idx
Revises: 0029_scenes_max_quality
Create Date: 2026-08-18
"""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
revision: str = "0030_scenes_quality_idx"
down_revision: str | None = "0029_scenes_max_quality"
branch_labels = None
depends_on = None
_THRESHOLDS = (720, 1080, 2160)
def upgrade() -> None:
for q in _THRESHOLDS:
op.create_index(
f"ix_scenes_created_q{q}",
"scenes",
[sa.text("created_at DESC")],
postgresql_where=sa.text(f"max_quality_p >= {q}"),
)
def downgrade() -> None:
for q in _THRESHOLDS:
op.drop_index(f"ix_scenes_created_q{q}", table_name="scenes")