Skip to content

Benchmark

Measures the overhead added by the audit extension for create, update, and delete operations.

What We Measure

BenchmarkDescription
A) create — no auditBaseline Prisma create() without extension
B) create — with auditcreate() + audit log INSERT (after-only changes)
C) update — with audit + diffupdate() + before/after diff calculation + audit log INSERT
D) delete — with auditdelete() + before-only changes + audit log INSERT

The update benchmark (C) is the most expensive because the extension must:

  1. Fetch the existing record (before state)
  2. Execute the update
  3. Compute the diff between before and after
  4. INSERT the audit log entry

Test Setup

  • Database: PostgreSQL 16 (Docker, port 5433)
  • Data: Fresh rows per benchmark (300 iterations each)
  • Warmup: 30 iterations (discarded)
  • Tracked model: User with password as sensitive field

Running Locally

bash
# Start PostgreSQL
docker compose -f test/e2e/docker-compose.yml up -d

# Run benchmark
DATABASE_URL=postgresql://test:test@localhost:5433/audit_test \
  npx ts-node benchmarks/audit-overhead.ts

Results

Measured on Apple M-series, PostgreSQL 16, local Docker. Your results will vary.

BenchmarkAvgP50P95P99
A) create — no audit (baseline)0.40ms0.40ms0.52ms0.57ms
B) create — with audit1.44ms1.37ms1.84ms3.11ms
C) update — with audit + diff2.06ms2.01ms2.54ms2.85ms
D) delete — with audit1.71ms1.57ms2.09ms3.91ms

Create overhead: +1.04ms (+260%) Update is the slowest at 2.06ms due to the additional findFirst (before state) + diff computation.

Interpretation

The audit extension adds ~1ms per write operation. This is the cost of the additional INSERT INTO audit_logs plus (for updates) a findFirst to capture the before state and compute the diff.

In absolute terms, even the slowest operation (update with diff) completes in 2ms — well under the threshold for any API endpoint. For most CRUD APIs where writes are infrequent compared to reads, this is negligible.

For bulk operations (createMany with 100+ records), the extension logs each record individually, so consider using noAudit context for batch imports and adding a single manual audit log entry instead.

Methodology

  • performance.now() for millisecond-precision timing
  • AuditContext.run() wraps each operation with actor context (matches real usage)
  • Append-only rules are temporarily dropped for cleanup between benchmarks
  • Sensitive field masking (password[REDACTED]) is active during measurement

Released under the MIT License.