Skip to content

Benchmark

Measures the overhead added by the Prisma tenancy extension compared to direct queries without RLS.

What We Measure

BenchmarkDescription
A) Direct query (no extension)Baseline — superuser Prisma query, no RLS
B) findMany with tenancy extensionApp user query with set_config() + RLS active
C) findFirst with tenancy extensionSingle-row lookup with RLS

The extension wraps every query in a $transaction that calls SET LOCAL app.current_tenant = ? before executing the actual query. Benchmark B measures this full round-trip.

Test Setup

  • Database: PostgreSQL 16 (Docker)
  • Data: 1,000 rows across 3 tenants
  • Warmup: 50 iterations (discarded)
  • Measured: 500 iterations per benchmark

Running Locally

bash
# Start PostgreSQL
docker compose up -d --wait

# Run benchmark
DATABASE_URL=postgresql://tenancy:tenancy@localhost:5433/tenancy_test \
  npx ts-node benchmarks/rls-overhead.ts

Results

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

BenchmarkAvgP50P95P99
A) Direct query (no RLS)4.11ms3.32ms6.28ms9.96ms
B) findMany with extension3.12ms2.63ms5.63ms8.94ms
C) findFirst with extension1.27ms1.23ms1.58ms2.00ms

Extension overhead (avg): -0.99ms (-24%) — faster with RLSExtension overhead (P95): -0.65ms

Interpretation

The extension with RLS is actually faster than the baseline because RLS filters rows at the database level — the app_user client only sees its own tenant's rows, returning fewer results than the superuser baseline that returns all 1,000 rows across 3 tenants.

The SET LOCAL overhead (~0.5ms) is more than offset by scanning fewer rows. For single-row lookups (findFirst), the difference is minimal at 1.27ms.

For most API endpoints (10-50ms total), the tenancy extension adds negligible latency. The key takeaway: RLS is not just a security feature, it's a performance optimization when your tables contain data from multiple tenants.

Methodology

  • performance.now() measures wall-clock time per query
  • Warmup iterations ensure connection pool and query plan caches are hot
  • Percentiles computed from sorted timing arrays (no outlier removal)
  • Both clients connect to the same PostgreSQL instance; the baseline uses a superuser (no RLS), the extension client uses app_user (RLS enforced)

Released under the MIT License.