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
|
import uuid
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.dialects.postgresql import insert as pg_insert
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.models.tag import Tag
|
from app.models.tag import Tag
|
||||||
|
|
@ -27,22 +27,18 @@ def resolve_tag(session: Session, *, norm: NormalizedTag) -> Tag | None:
|
||||||
name = (norm.name or "").strip() or slug.replace("-", " ").title()
|
name = (norm.name or "").strip() or slug.replace("-", " ").title()
|
||||||
if len(name) > 120:
|
if len(name) > 120:
|
||||||
name = name[:120]
|
name = name[:120]
|
||||||
tag = Tag(name=name, slug=slug)
|
# Concurrent insert race: worker scraper + API enrich_tags_from_tube oba
|
||||||
# SAVEPOINT — chroni outer transaction przed concurrent insert race:
|
# robią lookup→insert na tym samym slug ('hardcore-sex'), oba widzą NULL,
|
||||||
# gdy worker scrapuje sceny + API endpoint enrich_tags_from_tube robią
|
# oba próbują INSERT, drugi pada UniqueViolation uq_tags_slug (Sentry GOON-H,
|
||||||
# `resolve_tag('hardcore-sex')` jednocześnie, jeden INSERT się uda,
|
# 5 events od 2026-05-12). ON CONFLICT DO NOTHING + re-SELECT po slug
|
||||||
# drugi → UniqueViolation slug. Bez savepoint cała transakcja API
|
# załatwia atomowo — Postgres serializuje na unique index.
|
||||||
# rzucała 500 (Sentry GOON-H, 5x od 2026-05-12). Z savepoint rollback
|
stmt = (
|
||||||
# na savepoint + re-SELECT zwraca już-istniejący tag.
|
pg_insert(Tag.__table__)
|
||||||
sp = session.begin_nested()
|
.values(name=name, slug=slug)
|
||||||
try:
|
.on_conflict_do_nothing(index_elements=["slug"])
|
||||||
session.add(tag)
|
)
|
||||||
session.flush()
|
session.execute(stmt)
|
||||||
sp.commit()
|
return session.execute(select(Tag).where(Tag.slug == slug)).scalar_one_or_none()
|
||||||
return tag
|
|
||||||
except IntegrityError:
|
|
||||||
sp.rollback()
|
|
||||||
return session.execute(select(Tag).where(Tag.slug == slug)).scalar_one_or_none()
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_tag_by_id(session: Session, tag_id: uuid.UUID) -> Tag | None:
|
def resolve_tag_by_id(session: Session, tag_id: uuid.UUID) -> Tag | None:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue