From 276fb4c24d6c2e716c2439c58a8f5e7d8c3a7485 Mon Sep 17 00:00:00 2001 From: goon-foss Date: Thu, 13 Aug 2026 14:50:46 +0200 Subject: [PATCH] fix(dedup): samozlaczenie po phashu odsiewa zdegenerowane wartosci `_pairs_exact_phash` laczylo scene_fingerprints po wartosci BEZ zadnego ograniczenia. Do tej pory uchodzilo, bo zdegenerowanych wartosci bylo malo i blacklista je kasowala. Zmienia to backfill: dokladamy ~240 tys. odciskow z xvideos/xnxx/epornera, a te serwuja zaslepki przy usunietych filmach. Dwa problemy naraz: 1. Blacklista odswieza sie RAZ NA DOBE, wiec swiezo powstala zaslepka ma pelna dobe okna na wpuszczenie do potoku tysiecy niepowiazanych scen. 2. Samozlaczenie jest KWADRATOWE po liczbie scen dzielacych wartosc: zaslepka przy 5 tys. scen to 12,5 mln par i job pada na pamieci, zanim cokolwiek zdazy ja zablacklistowac. Teraz zapytanie samo odsiewa wartosci dzielone przez >=10 scen (ten sam prog co phash_blacklist) ORAZ te juz zablacklistowane, wiec dziala natychmiast, nie po dobie. NIE jest to atrapa: 4% w backfill jedna taka wartosc juz istnieje (12 scen) i blacklista jej jeszcze nie widziala. Po zmianie samozlaczenie daje 23 555 par w 8,3 s. --- app/scheduler/bulk_dedup.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/app/scheduler/bulk_dedup.py b/app/scheduler/bulk_dedup.py index bf7f3c9..7ea3b34 100644 --- a/app/scheduler/bulk_dedup.py +++ b/app/scheduler/bulk_dedup.py @@ -24,6 +24,7 @@ from sqlalchemy.orm import Session from app.config import get_settings from app.db import session_scope +from app.scheduler.phash_blacklist import DEFAULT_MIN_SCENES from app.models.merge_candidate import MergeCandidate, MergeKind, MergeStatus from app.models.scene import Scene, SceneFingerprint, ScenePerformer from app.resolve.scene_merge import merge_scenes @@ -187,13 +188,37 @@ def _pairs_exact_phash(session: Session) -> Iterable[tuple[uuid.UUID, uuid.UUID] tylko ≥auto_t, więc przypadkowe kolizje z innym performerem/duration wpadną w review). Skalowalne dla setek tys. phashy gdzie Hamming-fuzzy O(N²) jest nierealne.""" from sqlalchemy import text + + # Wartości ZDEGENEROWANE (zaślepka, czarna klatka, intro studia) odsiewamy TUTAJ, + # nie tylko przez `phash_blacklist`. Dwa powody: + # + # 1. Blacklista jest odświeżana raz na dobę, więc świeżo wpisana zaślepka ma + # całą dobę okna. Do tej pory to nie bolało, ale backfill z sierpnia 2026 + # dokłada ~240 tys. odcisków z xvideos/xnxx/epornera, a te serwują zaślepki + # przy usuniętych filmach. Wystarczy jedna taka wartość, żeby wpuścić do + # potoku tysiące scen, których nic nie łączy poza obrazkiem. + # 2. To samozłączenie jest KWADRATOWE po liczbie scen dzielących wartość: + # zaślepka przy 5 tys. scen to 12,5 mln par, czyli job pada na pamięci, + # zanim cokolwiek zdąży ją zablacklistować. + # + # Próg jest ten sam, co w `phash_blacklist` (≥10 scen = nie identyfikuje sceny), + # więc obie ścieżki mówią to samo, tylko ta działa natychmiast. rows = session.execute(text(""" + WITH degenerate AS ( + SELECT value + FROM scene_fingerprints + WHERE kind = 'phash' + GROUP BY value + HAVING count(DISTINCT scene_id) >= :min_scenes + ) SELECT DISTINCT a.scene_id AS sa, b.scene_id AS sb FROM scene_fingerprints a JOIN scene_fingerprints b ON a.value = b.value AND a.scene_id < b.scene_id WHERE a.kind = 'phash' AND b.kind = 'phash' - """)) + AND NOT EXISTS (SELECT 1 FROM degenerate d WHERE d.value = a.value) + AND NOT EXISTS (SELECT 1 FROM phash_blacklist b2 WHERE b2.value = a.value) + """), {"min_scenes": DEFAULT_MIN_SCENES}) for sa, sb in rows: yield (sa, sb)