goon/alembic/versions/20260727_0027_scenes_title_trgm.py
goon-foss ef8c255e1f perf(scenes): indeks trigramowy na title + ranking kandydatow do przegladu
Kazde szukanie slowa w tytulach bylo sekwencyjnym skanem 3,46 mln wierszy. Blokowalo
to ranking over-attribution: metryka "ile scen ma te nazwe w tytule, ale NIE jest do
niej przypisanych" wymaga jednego skanu NA KANDYDATA, a kandydatow jest ~800. Proba
zostala ubita po 10 minutach.

GIN + gin_trgm_ops obsluguje LIKE, ILIKE i regexy, a pg_trgm sam normalizuje wielkosc
liter, wiec indeks na surowym title wystarcza. Efekt: 26 ms zamiast sekund na
zapytanie, ranking 800 kandydatow w 48 sekund zamiast godzin. Indeks 315 MB.

Zbudowany CONCURRENTLY w autocommit_block: zwykle CREATE INDEX trzymaloby lock
blokujacy zapisy na scenes przez cala budowe i zatrzymalo ingest.

Ranking od razu pokazuje dwie rozne rzeczy. Czyste smieci ("Pornhub" 105.9, "Monster"
92, "Pretty" 63, "Nice" 42) oraz SAME NAZWISKA (Devine, Starr, Dior, Cruz, West,
Knight, Johnson, James) - wpisy powstale z rozbicia imienia i nazwiska, ktore potem
lapia kazda scene z tym nazwiskiem, nalezaca do zupelnie innej osoby.

Usuniete przy okazji: "Pornhub" (108 scen, 0 z kanonu, wszystkie to "full video on
pornhub") i "Precious" (119 scen, 0 z kanonu, sam przymiotnik).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 15:59:52 +02:00

46 lines
1.9 KiB
Python

"""indeks trigramowy na scenes.title
Revision ID: 0027_scenes_title_trgm
Revises: 0026_scene_performer_source
Create Date: 2026-07-27
Bez tego każde szukanie słowa w tytułach to sekwencyjny skan 3,46 mln wierszy. Blokowało
to ranking kandydatów do przeglądu over-attribution
(`scripts/review_performer_attributions.py`): metryka „ile scen ma tę nazwę w tytule, ale
NIE jest do niej przypisanych" wymaga jednego skanu NA KANDYDATA, a kandydatów jest ~800.
Przy skanie sekwencyjnym to godziny obciążania bazy — próba została ubita po 10 minutach.
GIN + `gin_trgm_ops` obsługuje `LIKE`, `ILIKE` i regexy (`~`, `~*`), a pg_trgm normalizuje
wielkość liter sam, więc indeks na surowym `title` wystarcza.
**CONCURRENTLY i `autocommit_block`**: zwykłe `CREATE INDEX` bierze lock blokujący zapisy
na `scenes` na cały czas budowy (kilka minut przy tej wielkości), czyli zatrzymałoby
ingest. `CONCURRENTLY` nie może biec w transakcji, a Alembic domyślnie owija migrację w
transakcję — stąd `autocommit_block()`.
Uwaga operacyjna: nieudane `CREATE INDEX CONCURRENTLY` zostawia indeks w stanie INVALID.
Sprawdzenie: `SELECT indexrelid::regclass FROM pg_index WHERE NOT indisvalid;` — taki
trzeba dropnąć ręcznie i powtórzyć.
"""
from collections.abc import Sequence
from alembic import op
revision: str = "0027_scenes_title_trgm"
down_revision: str | None = "0026_scene_performer_source"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
with op.get_context().autocommit_block():
op.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm")
op.execute(
"CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_scenes_title_trgm "
"ON scenes USING gin (title gin_trgm_ops)"
)
def downgrade() -> None:
with op.get_context().autocommit_block():
op.execute("DROP INDEX CONCURRENTLY IF EXISTS ix_scenes_title_trgm")