Skip to content

Benchmark

Measures the overhead of automatic WHERE deleted_at IS NULL filtering and the cost of soft-delete operations compared to hard deletes.

What We Measure

BenchmarkDescription
A) findMany — no extensionBaseline query returning all rows (including soft-deleted)
B) findMany — with soft-delete filterSame query with automatic deleted_at IS NULL injection
C) delete — hard deleteBaseline DELETE FROM
D) delete — soft deleteUPDATE SET deleted_at = now()
E) cascade soft-deleteUser + 3 Posts + 6 Comments deleted in cascade

Test Setup

  • Database: PostgreSQL 15 (Docker, port 5432)
  • Data: 500 users (half soft-deleted) for read benchmarks, fresh rows for delete benchmarks
  • Warmup: 30 iterations (discarded)
  • Measured: 300 iterations per benchmark (50 for cascade)

Running Locally

bash
# Start PostgreSQL
docker compose up -d

# Generate the Prisma 7 client and run the adapter-backed benchmark
npm run bench

Results

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

BenchmarkAvgP50P95P99
A) findMany — no extension3.11ms2.43ms5.78ms11.40ms
B) findMany — with soft-delete filter2.01ms1.61ms4.44ms7.48ms
C) delete — hard delete0.53ms0.52ms0.68ms0.77ms
D) delete — soft delete0.54ms0.53ms0.69ms0.77ms
E) cascade (User + 3 Posts + 6 Comments)0.56ms0.56ms0.72ms0.76ms

Observed findMany difference: -1.10ms (-35%) with half as many returned rows

Observed soft vs hard delete: 0.54ms vs 0.53ms in this small workload

Interpretation

The read scenarios return different row counts. The WHERE deleted_at IS NULL query returns 250 live rows while the baseline returns 500 rows. Its lower latency therefore reflects less data returned and should not be interpreted as negative extension overhead.

Soft delete and hard delete were close at roughly 0.53ms in this single-row benchmark. Measure the extension with your indexes, row width, triggers, and database workload before making capacity decisions.

The measured cascade case completed in 0.56ms for a User with 3 Posts and 6 Comments. Deeper trees and production contention can behave differently, so benchmark the relationship shapes your application actually uses.

Methodology

  • performance.now() for millisecond-precision timing
  • Raw SQL table creation (no Prisma migration) — matches e2e test pattern
  • Cascade benchmark uses createPrismaSoftDeleteExtension with explicit cascade config
  • Each delete benchmark seeds fresh rows to avoid measuring empty-set operations

Released under the MIT License.