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.

AuditLog provides semantic audit logging, 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 are snapshots, not live references. Later changes do not update them. They can contain personal or sensitive data and may need their own access and retention policy.

JSON metadata weakens schema guarantees. AuditLog does not enforce a metadata size limit. The application must set and enforce that limit and store only queryable facts. Do not store complete row images, credentials, access tokens, or unbounded payloads.

AuditLog canonicalizes metadata through JSON before insertion. Returned metadata therefore uses string keys and JSON primitive values. Application code must not rely on atom keys or Elixir-specific nested values surviving the boundary.

Index maintenance adds write and storage cost

Every insert updates the indexes created by AuditLog.Migration and consumes storage. Measure that cost at the application's actual event rate. Inspect the migration SQL when an exact index inventory matters.

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;
  • who must review, test, and record destructive retention work;
  • who must verify the trigger after replication or restore, and how; and
  • who must stop application traffic during destructive maintenance, and how.

Removing events changes the audit record permanently. Test retention and restore procedures against a copy that reproduces the production data volume and PostgreSQL configuration before relying on them.