
The Architecture Behind Our Multi-Tenant SaaS Platform
Multi-tenancy decisions made in week one of a SaaS product tend to haunt you for years. We evaluated three isolation strategies — database-per-tenant, schema-per-tenant, and shared-schema with row-level security — before settling on the third for our platform.
Postgres row-level security (RLS) lets us enforce tenant isolation at the database layer rather than trusting application code to always remember a WHERE clause. Every query runs under a session variable set at connection time, and RLS policies do the rest — even a bug in application logic can't leak another tenant's rows.
The tradeoff is operational complexity: connection pooling has to be tenant-aware, and long-running background jobs need to explicitly set and clear the session context. We solved this with a thin wrapper around our ORM that makes tenant scoping the default, not an opt-in.
At 50M+ daily events, the bottleneck stopped being the database and started being event ingestion. We moved to a queue-backed write path with batched inserts, which cut write amplification significantly and kept infrastructure costs flat as tenant count grew.
The lesson that generalizes: pick the isolation model that fails safely. RLS meant that when things did go wrong, they failed as an error, not a data leak.
