fix(api): cap list_scenes filter sizes to prevent DB OOM (Fixes GOON-1M)
Some checks are pending
Backend tests / test (push) Waiting to run
Some checks are pending
Backend tests / test (push) Waiting to run
A single request with 194 studio_slugs + 23 tag filters (each tag = a correlated EXISTS) plus an ILIKE search built a query heavy enough that the OOM killer killed the Postgres backend, triggering a full crash-recovery (~1s prod-wide outage, all in-flight connections dropped). Any user could do this with a big enough filter. Cap studios to 50, tags to 15, performers to 15 (far above any real UI usage) and return 422 instead of executing — bounding query complexity regardless of the planner's choice. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
813bf741b9
commit
05a35955ad
1 changed files with 13 additions and 0 deletions
|
|
@ -167,9 +167,18 @@ def list_scenes(
|
|||
if q:
|
||||
base = base.where(Scene.title_normalized.ilike(f"%{q.lower()}%"))
|
||||
|
||||
# Cap rozmiarów filtrów. Bez tego pojedynczy request z setkami studio_slugs +
|
||||
# dziesiątkami tagów (każdy tag = osobny correlated EXISTS) + ILIKE budował zapytanie,
|
||||
# które OOM-killer ubijał → PG crash-recovery = ~1s globalnej przerwy (GOON-1M,
|
||||
# 2026-06-26: 194 studios + 23 tagi). Realny UI nigdy nie wysyła tylu. 422 zamiast
|
||||
# wywalania bazy. Limity hojne (>> normalne użycie), ale ograniczają złożoność query.
|
||||
_MAX_STUDIOS, _MAX_TAGS, _MAX_PERFORMERS = 50, 15, 15
|
||||
|
||||
studio_slug_list = _split_csv(studio_slugs)
|
||||
if studio_slug:
|
||||
studio_slug_list.append(studio_slug)
|
||||
if len(studio_slug_list) > _MAX_STUDIOS:
|
||||
raise HTTPException(status_code=422, detail=f"too many studio filters (max {_MAX_STUDIOS})")
|
||||
if studio_slug_list:
|
||||
base = base.where(
|
||||
Scene.studio_id.in_(
|
||||
|
|
@ -178,6 +187,8 @@ def list_scenes(
|
|||
)
|
||||
|
||||
tag_slug_list = _split_csv(tags)
|
||||
if len(tag_slug_list) > _MAX_TAGS:
|
||||
raise HTTPException(status_code=422, detail=f"too many tag filters (max {_MAX_TAGS})")
|
||||
# AND między tagami: scena musi mieć WSZYSTKIE zaznaczone tagi. Każdy slug → osobny
|
||||
# exists() — zaznaczanie kolejnych filtrów zawęża wyniki, jak intuicja użytkownika.
|
||||
#
|
||||
|
|
@ -207,6 +218,8 @@ def list_scenes(
|
|||
)
|
||||
|
||||
perf_id_strings = _split_csv(performer_ids)
|
||||
if len(perf_id_strings) > _MAX_PERFORMERS:
|
||||
raise HTTPException(status_code=422, detail=f"too many performer filters (max {_MAX_PERFORMERS})")
|
||||
if perf_id_strings:
|
||||
try:
|
||||
perf_ids = [uuid.UUID(s) for s in perf_id_strings]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue