skip to content

You need a durable audit trail of every change to a handful of core tables. Compare building it with audit triggers, with log-based change data capture that reads the database transaction log, and with a transactional outbox table written by the application — and say when you would choose each.

level: principalimportance: should knowfreq 38%

answer

  1. triggers: atomic, every writer, taxes writes
  2. CDC: free write path, async, no app context
  3. outbox: typed intent, only cooperating writers
  4. replication slot lag can fill the disk
  5. TRUNCATE and bulk loads dodge row triggers

basics

~20 s

Triggers capture every writer atomically with the change but tax every write and roughly double log volume. Log-based capture reads the transaction log the engine already writes, so the write path pays nothing, but it is asynchronous and loses application context. An outbox gives typed business events in the same transaction, but only for writers that cooperate.

solid answer

~1 min

**Audit triggers.** Strongest completeness guarantee: they fire for every writer — services, ad-hoc SQL, ETL — and the audit row commits atomically with the change, so no gap is possible. They can capture application context such as the session user or an app-set request identifier. The price is synchronous write-path cost, roughly doubled transaction-log volume, and coupling to every schema change. **Log-based CDC.** Reads the write-ahead, redo, or binary log the engine writes anyway, so the write path is untouched and bulk loads cost nothing extra. It sees every change including out-of-band DML, and gives an ordered stream. Costs: it is eventually consistent, it needs streaming infrastructure and operational care (replication slots, log retention, schema evolution), and it records physical row changes without the application's intent or identity. **Transactional outbox.** The application writes a typed business event in the same transaction as the change, so it is atomic and semantically rich. But it captures only what the application chooses to emit and misses any writer that bypasses it. Rule of thumb: compliance-grade, must-never-miss auditing at modest write rates — triggers. High-volume auditing or a stream that also feeds downstream systems — log-based CDC. Business-event integration — outbox. Large systems often run CDC broadly and keep triggers on the few tables where atomicity is legally required.

go deeper

for a junior

Know the three mechanisms exist and that a trigger writes the history row in the same transaction as the change.

for a middle

Contrast synchronous trigger cost against asynchronous log-based capture, and note the outbox only covers application writes.

for a senior

Bring operational reality: log-volume amplification, replication lag, retention and slot monitoring, schema-evolution coupling.

for a principal

Decide by requirement — atomicity and actor context versus write-path budget and stream reuse — and defend a hybrid where the guarantee differs per table.

## What the three mechanisms actually are **Audit triggers**: AFTER INSERT/UPDATE/DELETE triggers on the audited tables that write a row into a history table containing the old and/or new image plus metadata. They execute inside the transaction that made the change. **Log-based change data capture**: an external process tails the durable log the engine already produces for recovery and replication — write-ahead log, redo log, or binary log — decodes it into logical row changes, and publishes them. Nothing is added to the write path. **Transactional outbox**: the application, inside the same transaction as its business write, inserts a row describing what happened into an outbox table. A separate process reads and publishes those rows. Atomicity comes from being in the same transaction; delivery is asynchronous. ## Completeness — the first axis An audit trail is worth only what its weakest guarantee is. Triggers and CDC both capture changes made by *any* writer, including a DBA running an UPDATE by hand at 2am. That is the property auditors care about. An outbox does not: it captures only what the application code emits, so any path around the application is a hole. That single fact usually decides whether the outbox is even a candidate for auditing, as opposed to for integration. Within "any writer", triggers and CDC still differ. Triggers can be bypassed by disabling them, by TRUNCATE (which typically fires no row-level triggers), and by direct bulk-load paths that skip trigger firing on some engines. Log-based capture sees the physical effects of nearly everything, though TRUNCATE and some minimally-logged bulk operations are represented differently and need explicit handling. ## Atomicity and timing A trigger's audit row commits or rolls back with the change: at any instant the audit table is exactly consistent with the base table. That is uniquely valuable when a regulator asks whether a change can exist without a record, and the answer must be structurally no. CDC is eventually consistent by construction. The change commits, and the audit record appears milliseconds to seconds later. It is durable — the log is the source — so nothing is lost, but there is a window in which the two disagree, and consumers must be idempotent because delivery is typically at-least-once. The outbox splits the difference: the *record* is atomic with the change; only its *publication* is asynchronous. ## Cost on the write path Triggers are paid for on every write, by the transaction doing it. A per-row audit trigger roughly doubles rows written, adds index maintenance on the history table, and roughly doubles transaction-log volume — which propagates outward as replication lag and larger backups. Under bulk DML the effect is severe, and lock-hold time grows with it. Statement-level triggers over transition tables reduce but do not remove this. CDC's marginal cost on the write path is essentially zero, because the log is written for durability regardless. Its costs move elsewhere: log retention must cover consumer downtime (an unconsumed replication slot can fill the disk and stop the database — the classic CDC outage), and you now operate a streaming pipeline. The outbox costs one small insert per business operation — cheap, and it batches naturally. ## Semantics: physical rows versus business intent Triggers and CDC both record *what the rows looked like*. Neither knows that a particular UPDATE was "customer requested a refund"; downstream consumers must reconstruct intent from column deltas, and that reconstruction breaks whenever the schema changes. Triggers can at least capture some context — the database session user, or an application-set session variable carrying a request or actor id — which CDC generally cannot see at all, because that context never reaches the log. If "who did this, on whose behalf, in which request" is part of the audit requirement, that pushes toward triggers or outbox. The outbox is the only one that records intent directly, as a typed, versioned event authored deliberately. ## Coupling and evolution Triggers are DDL coupled to the audited table: every column addition is a change to the trigger and the history table, enforced by deployment ordering. CDC decouples the write path but couples consumers to the physical schema, so schema evolution becomes a registry-and-compatibility problem. The outbox is the most decoupled — the event contract is explicit and versionable independent of the tables. ## Choosing - **Triggers**: few tables, moderate write volume, hard atomicity or regulatory requirement, need for actor context, no appetite for new infrastructure. - **Log-based CDC**: high write volume, many tables, or the audit stream also feeds search indexes, warehouses and caches; you can operate streaming infrastructure and monitor log retention. - **Outbox**: you need business events for integration, and all writes go through one application you control. These are not exclusive, and the mature answer usually says so: CDC as the general capture mechanism, with triggers retained on the small set of tables where an atomic, context-bearing record is legally load-bearing.

  • What is the classic operational failure of log-based CDC?
    A stalled or removed consumer stops acknowledging progress, so the database must retain transaction-log segments it would otherwise recycle. Disk fills and the primary can stop accepting writes — an availability incident caused by an auditing component. Mitigation is monitoring consumer lag and retained log size with alerts well before the disk limit, plus a documented policy for dropping a dead slot.
  • Can an audit trigger record which application user made a change, given the database connection uses a shared service account?
    Only if the application explicitly passes it — typically by setting a session-scoped variable or context value at the start of the transaction, which the trigger reads. Otherwise the trigger sees just the pooled service account. This is a real advantage over log-based capture, which cannot see that context at all, but it depends on the application cooperating on every path.
  • Why is an outbox usually the wrong primary mechanism for a compliance audit trail?
    Because its completeness depends on the application choosing to emit an event. Any writer that bypasses the application — a manual fix, a migration script, a second service — leaves no record, and the gap is invisible. Compliance auditing wants a mechanism that cannot be bypassed by the code path taken.

saying these in an interview costs you the question

  • Claiming log-based CDC is synchronous and atomic with the transaction
  • Assuming a trigger sees the end-user identity when connections come from a shared pool
  • Ignoring that audit triggers roughly double transaction-log volume and replication load
  • Treating an outbox as a complete audit trail despite non-application writers
  • Forgetting that TRUNCATE and some bulk-load paths do not fire row-level triggers

context