"""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")