goon/alembic/versions/20260916_0039_scenes_title_created_id_idx.py
goon-foss b6a075b28a perf(db): kasuje ix_scenes_title_normalized, zbedny prefiks indeksu z 0039
ix_scenes_title_created_id_desc zaczyna sie od tej samej kolumny, wiec obsluguje wszystko,
co obslugiwal stary indeks: lookupy `title_normalized = X` z resolvera i skan zakresu przy
sortowaniu po tytule. Trzymanie obu to 367 MB na dysku i jeden indeks wiecej do utrzymania
przy kazdym zapisie do scenes, a ingest pisze tam caly czas.

Sprawdzone na produkcji przez BEGIN; DROP INDEX; EXPLAIN ANALYZE; ROLLBACK, zeby zobaczyc
wybor planera bez starego indeksu, a potem jeszcze raz po samym dropie:

  exact title lookup        1,76 ms -> 0,07 ms, Index Scan po nowym indeksie
  OR studio + exact title   ten sam ksztalt planu (BitmapOr), 3,7 ms
  OR data + exact title     Seq Scan przy szerokim oknie dat, ale to decyzja o release_date:
                            z enable_seqscan=off BitmapOr siega po nowy indeks
  trgm ILIKE                bez zmian, swoj indeks GIN

W wariancie z OR planer i tak siegal juz po nowy indeks, jeszcze zanim stary znikl.
Indeksy scenes: 3544 MB -> 3177 MB. Listy scen po dropie bez zmian (tytul 22-54 ms,
studio 38-73 ms na stronach 1-5).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 10:47:24 +02:00

49 lines
1.9 KiB
Python

"""Indeks pod cale ORDER BY listy scen sortowanej po tytule.
`sort=title` konczy sie na `title_normalized ASC, created_at DESC, id DESC` (PK jako
ostateczny tie-break, zeby strony sie nie nakladaly). Stary `ix_scenes_title_normalized`
pokrywal tylko pierwszy czlon, wiec Postgres dokladal Incremental Sort, a ten przed
oddaniem PIERWSZEGO wiersza musi zebrac caly kubelek scen o tym samym tytule. Na stronie 1
glownego feedu ciagnal przez to 1084 wiersze zamiast 51 (709 ms zimno), a z filtrem taga
„blowjob" nie zmiescil sie w 90 s.
Pomiar na produkcji 2026-09-16, ten sam zestaw filtrow, po rozgrzewce:
strona 1 709 ms -> 12 ms (skan czyta 81 wpisow indeksu zamiast 3064)
strona 5 27 ms -> 9 ms
+ tag >90 s -> 1,1 s
Kierunki w indeksie musza byc dokladnie takie jak w ORDER BY, bo sort jest mieszany
(ASC, DESC, DESC) i inaczej skan nie przejdzie po kolei.
Ten indeks zaczyna sie od `title_normalized`, wiec obsluguje tez zwykle lookupy po tytule
(2,2 mln skanow), przez ktore stary `ix_scenes_title_normalized` staje sie zbedny. Usuwa go
migracja 0040, osobno, zeby dalo sie cofnac samo usuniecie.
CONCURRENTLY w autocommit_block, jak w 0035 i 0038. Nieudany CONCURRENTLY zostawia indeks
INVALID; wtedy DROP INDEX i ponownie.
Revision ID: 0039_scenes_title_created_id_idx
Revises: 0038_scenes_studio_effdate_idx
Create Date: 2026-09-16
"""
from __future__ import annotations
from alembic import op
revision = "0039_scenes_title_created_id_idx"
down_revision: str | None = "0038_scenes_studio_effdate_idx"
branch_labels = None
depends_on = None
def upgrade() -> None:
with op.get_context().autocommit_block():
op.execute(
"CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_scenes_title_created_id_desc "
"ON scenes (title_normalized, created_at DESC, id DESC)"
)
def downgrade() -> None:
with op.get_context().autocommit_block():
op.execute("DROP INDEX CONCURRENTLY IF EXISTS ix_scenes_title_created_id_desc")