DBRaven
Architecture Graph

Multi-Tenant SaaS

Multi-tenant platform with PostgreSQL Row-Level Security for tenant data isolation, a dedicated control plane database for tenant metadata, Redis for per-tenant rate limiting and caching, and Kafka for tenant event streaming and audit trail.

Web API

Description

Multi-tenancy requires an explicit choice between three isolation models, each with different operational complexity and tenant density trade-offs:

1. Shared schema with Row-Level Security (RLS): all tenants share tables, RLS policies

enforce isolation. Maximum density (10k+ tenants per database), lowest operational

cost, hardest to debug isolation failures. Appropriate up to 1000–2000 active tenants.

2. Schema-per-tenant (PostgreSQL namespaces): one PostgreSQL schema per tenant within

a shared database. Good isolation, clean migration story per-tenant, but schema

migration across all tenants requires explicit orchestration. Practical up to 5000

tenants per database instance.

3. Database-per-tenant: maximum isolation, independent scaling, but highest operational

cost. One connection pool per tenant. Appropriate for large enterprise customers or

regulated data requirements.

This composition implements shared schema with RLS plus a control plane PostgreSQL for tenant metadata. The control plane is the authoritative registry of tenants, their configuration, and their provisioning state: separate from the tenant data plane to prevent noisy-neighbor effects.

Use Cases

  • ·B2B SaaS serving 10–5000 tenants
  • ·Developer platforms with isolated workspace per team or organization
  • ·Compliance-sensitive platforms where tenant data boundaries must be auditable
  • ·Platforms with shared infrastructure but per-tenant customization
  • ·Products transitioning from single-tenant to multi-tenant

Scale Profile

Entry Point

2+ tenants requiring data isolation

Sweet Spot

10–2000 active tenants, shared schema + RLS

Scaling Ceiling

Shared schema ceiling: ~2000 concurrently active tenants before query plan noise. Schema-per-tenant ceiling: ~5000 tenants. Database-per-tenant: theoretically unbounded but operationally expensive beyond 500 databases.

Typical RPS

100–20k RPS depending on tenant count and per-tenant load

Architecture Nodes (7)

2 SPOF: Control Plane: PostgreSQL, Tenant Data: PostgreSQL (RLS)3 stateful: Control Plane: PostgreSQL, Tenant Data: PostgreSQL (RLS), Apache Kafka
ClientsClient

Tenant-specific web and API clients. Each request carries a tenant identifier (JWT claim or subdomain header).

external
API GatewayAPI Gateway

Extracts tenant_id from JWT or subdomain. Validates tenant exists and is active via control plane lookup. Routes to application tier. Enforces per-tenant rate limits using Redis.

ingresstenant_routingauth
Application ServiceService

Stateless application servers. Sets PostgreSQL session parameter app.current_tenant_id before each query: RLS policies use this to filter rows. All ORM queries inherit the session-level tenant context.

statelessdeployablerls_aware
Control Plane: PostgreSQLDatabase
postgresql

Separate PostgreSQL instance for tenant registry. Stores: tenant_id, name, status, isolation_model, created_at, plan, configuration. Never co-located with tenant data. Read on every request: connection pooled aggressively.

statefulSPOFcontrol_planetenant_registryacid
Tenant Data: PostgreSQL (RLS)Database
postgresql

Data plane PostgreSQL with Row-Level Security. All tenant data tables have RLS policies on tenant_id column. BYPASSRLS role is strictly controlled. Migration tooling must apply policies to every new table.

statefulSPOFdata_planerlsacidtenant_data
RedisCache
redis

Per-tenant key namespace: tenant:{tenant_id}:{key}. Rate limiting per tenant (sliding window). Session cache, tenant feature flags, and short-lived computed results. Invalidation events keyed to tenant_id.

cacherate_limitingsession
Apache KafkaStream
kafka

Tenant event streaming. Events include tenant_id in header and payload. Used for audit trail, cross-tenant analytics, and webhook delivery. One consumer group per downstream consumer.

statefulevent_busauditwebhook_delivery

Dependencies (7)

4 critical path edges. Failure on these directly degrades user-facing requests.

ClientsAPI GatewaySynchronouscritical path

Tenant-scoped API requests

All requests. JWT contains tenant_id claim. Subdomain routing maps *.app.example.com to tenant_id. Gateway validates tenant on every request.

Timeout: 30s

API GatewayRedisSynchronous

Per-tenant rate limit check

Sliding window rate limit per tenant per endpoint. Key: ratelimit:{tenant_id}:{endpoint}. Blocks if limit exceeded. Returns 429 with Retry-After header.

Timeout: 50ms

API GatewayControl Plane: PostgreSQLSynchronouscritical path

Tenant validation

Validates tenant status on each request. Cached in Redis with 60-second TTL to reduce control plane load. Critical path: if control plane is down, new requests cannot be validated.

Timeout: 200ms

API GatewayApplication ServiceSynchronouscritical path

Authenticated requests

Gateway forwards request with validated tenant_id header to application tier.

Timeout: 30s

Application ServiceTenant Data: PostgreSQL (RLS)Synchronouscritical path

Tenant data reads/writes

App sets SET LOCAL app.current_tenant_id = $tenant_id per session. RLS policies enforce row-level filtering. Missing or wrong tenant_id returns empty results, not an error: a configuration mistake is silent.

Timeout: 5s

Application ServiceRedisSynchronous

Tenant-scoped cache

All Redis keys prefixed with tenant:{tenant_id}:. Cache invalidation on write is per-tenant. No cross-tenant key pollution possible.

Timeout: 100ms

Application ServiceApache KafkaAsync Message

Tenant domain events

Publishes tenant-scoped domain events for audit trail and webhook delivery. Events include tenant_id for downstream fanout and filtering.

Failure Propagation

How a failure in one component cascades through the system. Each path documents the mechanism and the mitigation that breaks the chain.

Tenant Data: PostgreSQL (RLS)fails →
Application Service
noisy neighbor query contention

Mechanism

Large tenant running complex analytical queries holds shared locks or saturates autovacuum. Other tenants experience latency increases. In shared schema, one tenant's pg_stat_activity overwhelms the query planner's statistics.

Mitigation

Set statement_timeout per session. Implement per-tenant connection limits at PgBouncer. Alert when any single tenant's queries exceed 30% of total active connections.

Control Plane: PostgreSQLfails →
API Gateway
primary database failure

Mechanism

Control plane failure means tenant validation fails. New requests cannot be authenticated. Cached tenant state in Redis (60s TTL) provides brief degraded service. After TTL expiry, all requests fail.

Mitigation

Redis TTL acts as a buffer. Deploy control plane with HA. Implement gateway-level fallback: if control plane unreachable and tenant cached, allow request. Alert immediately on control plane degradation.

Tenant Data: PostgreSQL (RLS)fails →
Application Service
rls policy misconfiguration

Mechanism

Missing or incorrect RLS policy on a new table silently returns all rows to any tenant. BYPASSRLS role misuse or missing SET LOCAL call in app means no row-level filtering. Data leak with no error.

Mitigation

Automated schema migration test: assert RLS is enabled on all tenant data tables. CI test: query new table as tenant A and verify tenant B's rows are invisible. Audit BYPASSRLS role grants quarterly.

Scaling Transitions

Inflection points where this architecture begins to degrade and what the recommended evolution looks like.

~2000 concurrently active tenants in shared schemaTenant Data: PostgreSQL (RLS) bottleneck

Query planner statistics and autovacuum cannot keep pace with thousands of tenant-specific row distributions. Query plan quality degrades for tenants with unusual data distributions.

Recommended Action

Migrate to schema-per-tenant isolation model. Each tenant gets a dedicated PostgreSQL schema. Run migrations per-schema with orchestration tool.

Evolution path:shared schema to schema per tenant
~500 enterprise tenants requiring SLA guaranteesTenant Data: PostgreSQL (RLS) bottleneck

Shared database cannot provide per-tenant performance isolation. Enterprise SLA commitments require guaranteed IOPS and connection capacity.

Recommended Action

Introduce dedicated PostgreSQL instances for large enterprise tenants. Maintain shared-schema pool for smaller tenants. Hybrid isolation model.

Evolution path:schema per tenant to database per tenant

Patterns Applied

Architectural Notes

  • ·RLS policy must be tested in CI. A missing ENABLE ROW LEVEL SECURITY or FORCE ROW LEVEL SECURITY on a table is a data leak, not a bug: it must be caught before deployment.
  • ·Control plane and data plane must be separate databases. Putting tenant metadata in the same database as tenant data means a noisy tenant can affect tenant provisioning.
  • ·Per-tenant key namespacing in Redis must be enforced at the service layer. A missing prefix is a cross-tenant data leak with no error signal.
  • ·Schema migrations in multi-tenant systems take longer: a migration must run across all tenant schemas or all tenant rows. Plan for migration windows proportional to tenant count.

Confidence

Strong

Multi-tenant SaaS with PostgreSQL RLS is documented by Supabase, Citus, and multiple B2B SaaS engineering teams. Operational trade-offs are well-characterized.