"""taxonomy scene_count denormalization — tags / performers / studios Revision ID: 0019_taxonomy_scene_counts Revises: 0018_movie_play_progress Create Date: 2026-05-31 Perf fix (user-report 2026-05-31 "wolne ładowanie scen/favorites/tags"): baza urosła do 1.69M scen / 6.3M scene_tags, a /tags?order=popular liczył scene_count dla KAŻDEGO tagu na żywo (agregacja 6.3M scene_tags + EXISTS playback, external-merge sort 22MB) — ~4.3s, i to razy 2 (total + items). Analogicznie performers/studios + favorites. Denormalizujemy `scene_count` na tags/performers/studios. Worker przelicza je w tle (`_job_refresh_taxonomy_counts`, co `GOON_SCHED_TAXONOMY_COUNTS_HOURS`=3h jednym UPDATE...FROM). Endpointy czytają gotową kolumnę + ORDER BY indexed DESC → <20ms. scene_count = liczba scen z danym tagiem/performerem/studiem mających ≥1 ŻYWY playback_source (dead_at IS NULL) — dokładnie ta sama definicja co dotychczasowe live-aggregaty (has_live_playback filter w taxonomies.py / favorites.py). Counts są do ~3h nieświeże — dla "(123)" przy filtrze i sortu "popular" bez znaczenia. """ from collections.abc import Sequence import sqlalchemy as sa from alembic import op revision: str = "0019_taxonomy_scene_counts" down_revision: str | None = "0018_movie_play_progress" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None _TABLES = ("tags", "performers", "studios") def upgrade() -> None: for tbl in _TABLES: op.add_column( tbl, sa.Column( "scene_count", sa.Integer(), nullable=False, server_default="0" ), ) # DESC index — ORDER BY scene_count DESC (sortowanie "popular"). op.create_index( f"ix_{tbl}_scene_count", tbl, [sa.text("scene_count DESC")], ) def downgrade() -> None: for tbl in _TABLES: op.drop_index(f"ix_{tbl}_scene_count", table_name=tbl) op.drop_column(tbl, "scene_count")