diff --git a/app/api/scenes.py b/app/api/scenes.py index f746a1d..1578025 100644 --- a/app/api/scenes.py +++ b/app/api/scenes.py @@ -251,6 +251,16 @@ _ORIGIN_DRIVEN_MAX_SCENES = 60_000 # a przy sort=studio przejscie po studiach szuka trafien w kazdym z 33 tys. studiow # ("sunny leone" 15 min), podczas gdy trigramy + sort trafien to 0,37 s. _RARE_PHRASE_HITS = 4 +# Lista jednego serwisu bez frazy (ekran serwisu w apce). Planer idzie od najnowszych scen +# calego katalogu i sprawdza, czy scena jest z tego serwisu. Dla serwisu, ktory stoi, +# jego sceny sa gleboko: superporn (37 tys., ostatnia 20.08) 92 s na strone 1, bo +# przejscie mijalo 1,1 mln nowszych scen; yespornvip 23 s (pomiar 2026-09-23). Dlatego +# przejscie dostaje limit czasu, a po nim lista zaczyna od scen serwisu. Limit 1 s, bo +# u zywych srednich serwisow przejscie trwa ok. 1 s (hdporngg), a start od scen serwisu +# kosztuje wtedy wiecej (5 s na zimno). Powyzej 150 tys. scen (porndoe 279 tys.) start +# od serwisu jest wolniejszy niz przejscie, wiec zostaje zwykly plan. +_ORIGIN_LIST_WALK_BUDGET_MS = 1000 +_ORIGIN_LIST_MAX_SCENES = 150_000 _TRGM_RUN_RE = re.compile(r"[^\W_]{3}") _TITLE_SAMPLE_TTL = 3600.0 _TITLE_SAMPLE: dict = {"ts": 0.0, "vals": ()} @@ -334,10 +344,7 @@ def _search_walk_rows( if ids is None: # Bez trigramu zwykly plan czyta caly indeks (44 s), wiec lepiej pusto. return None if budget == _SEARCH_WALK_BUDGET_MS else [] - if not ids: - return [] - by_id = {s.id: s for s in session.execute(select(Scene).where(Scene.id.in_(ids))).scalars()} - return [by_id[i] for i in ids if i in by_id] + return _scenes_in_order(session, ids) def _rare_title_phrase(q: str, sample) -> bool: @@ -356,7 +363,9 @@ def _origin_driven_worth(q: str, sample) -> bool: return not _rare_title_phrase(q, sample) -def _origin_scene_ids(session: Session, origin: str) -> list[uuid.UUID] | None: +def _origin_scene_ids( + session: Session, origin: str, max_scenes: int = _ORIGIN_DRIVEN_MAX_SCENES +) -> list[uuid.UUID] | None: """Id scen z zywym zrodlem serwisu albo None, gdy serwis jest za duzy na te sciezke. Wielkosc bierzemy z source_stats (liczone co 6 h), zeby dla xvideos nie ciagnac @@ -365,17 +374,25 @@ def _origin_scene_ids(session: Session, origin: str) -> list[uuid.UUID] | None: from app.models.source_stats import SourceStats size = session.execute(select(SourceStats.scenes).where(SourceStats.origin == origin)).scalar() - if size is not None and size > _ORIGIN_DRIVEN_MAX_SCENES: + if size is not None and size > max_scenes: return None ids = list( session.execute( select(PlaybackSource.scene_id) .where(PlaybackSource.origin == origin, PlaybackSource.dead_at.is_(None)) .distinct() - .limit(_ORIGIN_DRIVEN_MAX_SCENES + 1) + .limit(max_scenes + 1) ).scalars() ) - return ids if len(ids) <= _ORIGIN_DRIVEN_MAX_SCENES else None + return ids if len(ids) <= max_scenes else None + + +def _scenes_in_order(session: Session, ids: list[uuid.UUID]) -> list[Scene]: + """Pelne wiersze scen dla listy id, w kolejnosci tej listy.""" + if not ids: + return [] + by_id = {s.id: s for s in session.execute(select(Scene).where(Scene.id.in_(ids))).scalars()} + return [by_id[i] for i in ids if i in by_id] def _split_csv(raw: str | None) -> list[str]: @@ -1037,6 +1054,17 @@ def list_scenes( Scene.id == any_(bindparam("origin_scene_ids", origin_ids, type_=ARRAY(PG_UUID(as_uuid=True)))) ) + if rows is None and origin and not q and sort in ("created_at", "release_date"): + walked = _search_walk_ids(session, ordered, _off, _want, _ORIGIN_LIST_WALK_BUDGET_MS) + if walked is not None: + rows = _scenes_in_order(session, walked) + else: + origin_ids = _origin_scene_ids(session, origin, _ORIGIN_LIST_MAX_SCENES) + if origin_ids is not None: + ordered = ordered.where( + Scene.id + == any_(bindparam("origin_scene_ids", origin_ids, type_=ARRAY(PG_UUID(as_uuid=True)))) + ) if rows is None and origin_ids is None and q and ( sort == "created_at" or (sort == "release_date" and tag_sort_id is None) ): diff --git a/app/db.py b/app/db.py index 591b134..45c8527 100644 --- a/app/db.py +++ b/app/db.py @@ -8,10 +8,16 @@ from app.config import get_settings _settings = get_settings() +# prepare_threshold=None: psycopg domyslnie przygotowuje zapytanie po 5 wykonaniach na +# polaczeniu, a Postgres po kolejnych 5 potrafi przejsc na plan ogolny, niezalezny od +# parametrow. Dla list tagu to katastrofa: plan dobry dla malego tagu na duzym ("pov") +# trwal 44 s zamiast 0,46 s (pomiar 2026-09-23), wiec lista ladowala sie raz szybko, raz +# minute, zaleznie od tego, ktore polaczenie z puli trafilo. Planowanie od nowa to kilka ms. engine = create_engine( _settings.database_url, pool_pre_ping=True, future=True, + connect_args={"prepare_threshold": None}, ) SessionLocal = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False, future=True)