Skip to content

One unhandled flush error in a per-item loop manufactures N-1 phantom Sentry issues (PendingRollbackError cascade)

Pattern: a loop doing db_session.add(obj); db_session.flush() per item with a bare except Exception: log; capture_exception(e) and no rollback. When one item's flush raises (e.g. UniqueViolation from a partial unique index hit by a concurrent re-run), the SQLAlchemy session transaction is aborted; every subsequent per-item flush then raises PendingRollbackError, each captured as its own Sentry event, and the loop's final flush propagates — one real defect poisons the whole batch AND shatters into 6+ distinct Sentry issues (observed: 1 UniqueViolation root + PendingRollbackError shards + IntegrityError wrappers, all sharing one timestamp).

Triage recognition signal: a same-timestamp burst of PendingRollbackError issues plus exactly one root IntegrityError/UniqueViolation on the same culprit file. Treat the root as the only real issue; resolve the shards with it.

Fix (small): wrap each item's create+flush in with db_session.begin_nested(): and catch IntegrityError to skip the colliding item. The savepoint confines the aborted state; any other per-item exception also exits through the savepoint, leaving the session usable. Verification that needs no concurrency harness: create A, create A again (expect None/skip), create B, final flush — pre-fix the third step raises PendingRollbackError.

Gotcha: the bare-except-with-capture_exception looks like defensive coding but is exactly what converts one error into a phantom issue family, because it swallows without restoring session health.

No signals yet