Limits and operations

Copy Markdown View Source

Explicit events are incomplete by design

AuditLog records only events that the application writes. It cannot recover a missing event from a changed row. SQL consoles, migrations, ETL jobs, replication workers, and forgotten code paths can change data without producing an event.

This is a semantic audit log, not exhaustive database provenance.

Atomicity stops at the database boundary

A domain write and event are atomic when the caller places both in one transaction on the same database. Writes to another database or external service cannot join that transaction.

AuditLog does not publish events. If an application must deliver messages, write a separate outbox row in the same transaction and let an outbox worker handle delivery and retries.

Time and order have narrow meanings

PostgreSQL sets occurred_at with transaction_timestamp(). It is the transaction start time, not insertion time, commit time, or the exact time a person acted.

All events written in one transaction share that timestamp. Queries use the event UUID as a deterministic display tie-breaker, but UUID order does not show insertion or causal order. Use causation_id to record a direct cause.

A correlation ID groups work into one request or business operation. It does not prove that one event caused another.

Snapshots can become sensitive or stale

Actor labels and reasons preserve what an operator supplied at the time. They can become stale after a rename. They can also contain personal or sensitive data and may need their own access and retention policy.

JSON metadata weakens schema guarantees. Keep it small, bounded, and limited to queryable facts. Do not store complete row images, credentials, access tokens, or large payloads.

Every event maintains four indexes

The table maintains subject, actor, correlation, and global timeline indexes. Each index serves a documented query path, but all four amplify event writes and consume storage. Measure the cost at the application's actual event rate.

Do not add broad JSON, action, reason, or causation indexes without a measured query that needs them.

Append-only means ordinary DML only

An ENABLE ALWAYS trigger rejects UPDATE and DELETE, including DML from a logical-replication apply worker running with session_replication_role = replica.

This is not tamper evidence. A table owner or superuser can disable or replace the trigger, use TRUNCATE, alter or drop the table, or edit physical backups. Database administration remains more powerful than the trigger.

The trigger also blocks ordinary cleanup. Retention must use conspicuous, reviewed maintenance DDL. AuditLog exposes no runtime deletion function and no hidden bypass.

Calling AuditLog.Migration.down/1 drops the event table and permanently removes its contents. Use it for migration rollback only when that data loss is acceptable.

Retention, restore, and maintenance need runbooks

Before production use, define:

  • who may read actor labels, reasons, and metadata;
  • how long each class of event must remain;
  • how backups and restores preserve the table and ALWAYS trigger state;
  • how destructive retention work is reviewed, tested, and recorded;
  • how to verify the trigger after replication or restore; and
  • how application traffic is stopped during destructive maintenance.

Removing events changes the audit record permanently. Test retention and restore procedures against a production-shaped copy before relying on them.