fix(scenes): use ON CONFLICT for tag slug upsert in enrich_tags_from_tube
Replace SAVEPOINT + IntegrityError fallback in resolve_tag with postgres INSERT ... ON CONFLICT (slug) DO NOTHING + re-SELECT. Postgres serializes on the unique index, so concurrent inserts of the same slug no longer race on lookup→insert and the second caller no longer raises uq_tags_slug. Mirrors the on_conflict pattern already used for SceneTag/MovieTag inserts.
This commit is contained in:
parent
aac6b10d77
commit
49bb65d707
1 changed files with 13 additions and 17 deletions
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||
import uuid
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.dialects.postgresql import insert as pg_insert
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.tag import Tag
|
||||
|
|
@ -27,21 +27,17 @@ def resolve_tag(session: Session, *, norm: NormalizedTag) -> Tag | None:
|
|||
name = (norm.name or "").strip() or slug.replace("-", " ").title()
|
||||
if len(name) > 120:
|
||||
name = name[:120]
|
||||
tag = Tag(name=name, slug=slug)
|
||||
# SAVEPOINT — chroni outer transaction przed concurrent insert race:
|
||||
# gdy worker scrapuje sceny + API endpoint enrich_tags_from_tube robią
|
||||
# `resolve_tag('hardcore-sex')` jednocześnie, jeden INSERT się uda,
|
||||
# drugi → UniqueViolation slug. Bez savepoint cała transakcja API
|
||||
# rzucała 500 (Sentry GOON-H, 5x od 2026-05-12). Z savepoint rollback
|
||||
# na savepoint + re-SELECT zwraca już-istniejący tag.
|
||||
sp = session.begin_nested()
|
||||
try:
|
||||
session.add(tag)
|
||||
session.flush()
|
||||
sp.commit()
|
||||
return tag
|
||||
except IntegrityError:
|
||||
sp.rollback()
|
||||
# Concurrent insert race: worker scraper + API enrich_tags_from_tube oba
|
||||
# robią lookup→insert na tym samym slug ('hardcore-sex'), oba widzą NULL,
|
||||
# oba próbują INSERT, drugi pada UniqueViolation uq_tags_slug (Sentry GOON-H,
|
||||
# 5 events od 2026-05-12). ON CONFLICT DO NOTHING + re-SELECT po slug
|
||||
# załatwia atomowo — Postgres serializuje na unique index.
|
||||
stmt = (
|
||||
pg_insert(Tag.__table__)
|
||||
.values(name=name, slug=slug)
|
||||
.on_conflict_do_nothing(index_elements=["slug"])
|
||||
)
|
||||
session.execute(stmt)
|
||||
return session.execute(select(Tag).where(Tag.slug == slug)).scalar_one_or_none()
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue