Reliable event publishing with the transactional outbox pattern

Backend Live Coding November 13, 2026 python

With a transactional outbox.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# ======================================================================
# Q: How do you reliably send an event when something changes in the database
# (e.g. "new critical alert")?
#
# With a transactional outbox. If I commit and then publish, and the publish
# fails, the event is lost. If I publish before the commit and the commit fails,
# I send an event for something that did not happen. That is why I write the
# event to an outbox table in the same transaction, and a relay worker sends it
# to the broker.
# Delivery is at-least-once, so consumers must be idempotent (dedup by event
# id). SKIP LOCKED is the classic pattern for a job queue inside PostgreSQL.
# ======================================================================

with session.begin():
    alert = Alert(tenant_id=tid, severity='critical', asset_id=aid)
    session.add(alert)
    session.flush()
    session.add(Outbox(topic='alerts.created',
                       key=str(alert.id),
                       payload={'alert_id': alert.id, 'tenant_id': tid}))

# relay worker (Celery beat every few seconds or a separate process)
def relay_outbox(session, batch=500):
    with session.begin():
        rows = session.scalars(
            select(Outbox).where(Outbox.sent_at.is_(None))
            .order_by(Outbox.id).limit(batch)
            .with_for_update(skip_locked=True)).all()   # many workers without conflicts
        for row in rows:
            broker.publish(row.topic, key=row.key, value=row.payload)
            row.sent_at = func.now()