Architecture Review: Multi-Tenant SaaS Platform
A multi-tenant SaaS architecture where multiple customers are served from a shared deployment, with PostgreSQL row-level security providing logical tenant isolation, Redis delivering per-tenant caching, and connection pooling managing the aggregate connection demand across tenant workloads. Tenant isolation, resource fairness, and operational simplicity are the three competing forces this architecture must balance.
Evidence Confidence
Moderate
strong
Executive Summary
Multi-Tenant SaaS Platform: moderate operational readiness (81% evidence confidence). 4 architectural strengths identified, 4 operational risks to manage. Primary concern: Connection Pool Exhaustion. Requires Intermediate operational maturity.
Readiness Rationale
Overall moderate readiness across 8 dimensions. Weak: consistency. Strong: migration, observability, failure recovery.
Key Concerns
- !Connection Pool Exhaustion
- !Hot Partition
Key Strengths
- +Redis caching absorbs repeated read requests at the edge, reducing database load and latency for high read-to-write ratio workloads by orders of magnitude
- +A connection pool bounds the total database connections an application can open, preventing connection storms during traffic…
- +Read-heavy APIs benefit directly from Redis as a caching tier that absorbs repeated identical reads and provides sub-millisecond…
- +Read-heavy APIs generate large numbers of short-lived database connections
8
Assessments
3
Tradeoffs
6
Sections
11
Recommendations
Readiness Assessments
8Architectural Tradeoffs
3Recommendations
11Monitor: Hot Partition
risk_monitoringOne partition (a database shard, a Kafka topic partition, a Redis hash slot) receives traffic so far above its peers that it saturates while the others sit idle. Aggregate capacity looks healthy, but the hot partition throttles or lags, and everything routed to it degrades. The cause is skew in how keys map to partitions, and the fix depends on whether the skew is spread across many keys or concentrated in one.
Affects 1 node. (Sharding)
Monitor: Connection Pool Exhaustion
risk_monitoringAll database connections in the pool are in use; new requests queue and then time out, causing cascading latency and errors across all dependent services.
Affects 1 node. (Redis). 1 mitigation identified
Implement: Monitor read hotspot signals
observabilitySeed 'Read Hotspot Saturation' identifies 3 metrics relevant to hot_partition. Execution preview confirms this risk manifests under modelled load.
Metrics to instrument: partition_qps, p99_latency_ms, cache_hit_rate
Single-tenant PostgreSQL with per-user row filtering in application code → Multi-tenant PostgreSQL with row-level security policies
migration_planningTrigger: Adding a second paying customer; first audit or security review; team growing beyond sole developer who holds mental model of tenant data boundaries. Migrate from 'Single-tenant PostgreSQL with per-user row filtering in application code' to 'Multi-tenant PostgreSQL with row-level security policies'. Define a standard RLS policy template at database setup time. Use automated testing to verify cross-tenant data isolation for all major query paths before onboarding any paying customers.
RLS policies must be applied to all existing and future tables: missing a table creates a data exposure risk; RLS with complex policies has measurable query planning overhead; test policy performance on large tables before enabling
Shared schema multi-tenant with no caching → Shared schema + Redis with tenant-namespaced cache keys
migration_planningTrigger: Database read load growing disproportionately with tenant count; per-tenant read p99 degrading despite stable individual tenant query volumes. Migrate from 'Shared schema multi-tenant with no caching' to 'Shared schema + Redis with tenant-namespaced cache keys'. Namespace all cache keys with tenant_id as prefix. Set Redis maxmemory-policy to allkeys-lru and size the cache for the aggregate working set of the top 10 tenants rather than all tenants combined.
Cache key namespacing mistakes cause cross-tenant reads: must be validated in tests; Redis eviction policy must account for large tenants evicting small tenants' hot keys
Prepare runbook for: Burst Traffic Cold Cache Stampede
simulation_preparednessSimulation demonstrates critical degradation of redis, postgresql
Without a runbook, recovery from this failure mode will be ad-hoc
Prepare runbook for: Connection Pool Exhaustion with Horizontal User Scale
simulation_preparednessSimulation demonstrates critical degradation of postgresql
Without a runbook, recovery from this failure mode will be ad-hoc
Plan evolution: Single Cache Layer → Distributed Cache
evolution_planningEvolution from Single Redis Node / Sentinel Cluster → Distributed Redis Cluster (Consistent Hash Ring)
Migration complexity: medium. Rollback: complex.
Plan evolution: Direct DB Queries → CQRS Read Models
evolution_planningEvolution from Unified Read/Write Database → CQRS with Separate Read Projections
Migration complexity: high. Rollback: complex.
Monitor threshold: Tier 1: Connection Pool Exhaustion
scaling_monitoringSignal: PgBouncer pool wait queue > 0 during peak hours; application errors reporting "connection pool exhausted" or pool timeout; pool utilization > 90%
Bottleneck: Aggregate tenant connection demand exceeding PgBouncer pool_size. Evolution: Implement per-tenant connection quotas at the application layer before hitting the pool; increase pool_size incrementally; identify top-N connection consumers by tenant and implement connection reuse within tenant request handlers
Monitor threshold: Tier 2: Noisy Tenant I/O Saturation
scaling_monitoringSignal: pg_stat_activity shows one tenant's queries dominating query runtime; other tenants reporting p99 latency regression while their own query counts are stable; PostgreSQL shared_buffers cache eviction rate increasing
Bottleneck: Single large tenant displacing other tenants' working sets from shared buffer cache. Evolution: Implement pg_cgroups or connection-level resource groups if available; consider database-per-tenant for the top N largest tenants while keeping the shared schema for smaller tenants (hybrid isolation model)
Scaling Pressure Signals
8PgBouncer pool wait queue > 0 during peak hours; application errors reporting "connection pool exhausted" or pool timeout; pool utilization > 90%
Threshold
Tier 1: Connection Pool Exhaustion
Likely Bottleneck
Aggregate tenant connection demand exceeding PgBouncer pool_size
Recommended Evolution
Implement per-tenant connection quotas at the application layer before hitting the pool; increase pool_size incrementally; identify top-N connection consumers by tenant and implement connection reuse within tenant request handlers
pg_stat_activity shows one tenant's queries dominating query runtime; other tenants reporting p99 latency regression while their own query counts are stable; PostgreSQL shared_buffers cache eviction rate increasing
Threshold
Tier 2: Noisy Tenant I/O Saturation
Likely Bottleneck
Single large tenant displacing other tenants' working sets from shared buffer cache
Recommended Evolution
Implement pg_cgroups or connection-level resource groups if available; consider database-per-tenant for the top N largest tenants while keeping the shared schema for smaller tenants (hybrid isolation model)
DDL migration duration > 30s on any shared table; lock acquisition timeouts reported during migration windows; migration deployment requiring off-hours scheduling
Threshold
Tier 3: Schema Migration Pressure
Likely Bottleneck
Large shared tables requiring locks during DDL migrations affect all tenants
Recommended Evolution
Adopt zero-downtime migration patterns exclusively: pg_repack for table rewrites, column additions without constraints first, then constraint additions via NOT VALID; never use ALTER TABLE ... ADD COLUMN with DEFAULT in PostgreSQL < 11
Top 5 tenants account for > 50% of database I/O; largest tenants requesting SLA guarantees that cannot be met on shared infrastructure; compliance or data residency requirements incompatible with shared schema
Threshold
Tier 4: Tenant Growth Segmentation
Likely Bottleneck
Shared infrastructure unable to provide performance isolation guarantees for enterprise tenants
Recommended Evolution
Implement silo model for enterprise tenants (dedicated PostgreSQL instance per tenant) while retaining shared schema for SMB/startup tiers; implement a tenant-aware router to direct requests to the correct database tier
PgBouncer pool wait queue > 0 during peak hours; application errors reporting "connection pool exhausted" or pool timeout; pool utilization > 90%
Threshold
Escalation trigger: Aggregate tenant connection demand exceeding PgBouncer pool_size
Likely Bottleneck
Tier 1: Connection Pool Exhaustion
Recommended Evolution
Monitor: partition_qps, p99_latency_ms, cache_hit_rate
pg_stat_activity shows one tenant's queries dominating query runtime; other tenants reporting p99 latency regression while their own query counts are stable; PostgreSQL shared_buffers cache eviction rate increasing
Threshold
Escalation trigger: Single large tenant displacing other tenants' working sets from shared buffer cache
Likely Bottleneck
Tier 2: Noisy Tenant I/O Saturation
Recommended Evolution
Monitor: partition_qps, p99_latency_ms, cache_hit_rate
DDL migration duration > 30s on any shared table; lock acquisition timeouts reported during migration windows; migration deployment requiring off-hours scheduling
Threshold
Escalation trigger: Large shared tables requiring locks during DDL migrations affect all tenants
Likely Bottleneck
Tier 3: Schema Migration Pressure
Recommended Evolution
Monitor: partition_qps, p99_latency_ms, cache_hit_rate
Top 5 tenants account for > 50% of database I/O; largest tenants requesting SLA guarantees that cannot be met on shared infrastructure; compliance or data residency requirements incompatible with shared schema
Threshold
Escalation trigger: Shared infrastructure unable to provide performance isolation guarantees for enterprise tenants
Likely Bottleneck
Tier 4: Tenant Growth Segmentation
Recommended Evolution
Monitor: partition_qps, p99_latency_ms, cache_hit_rate
Migration Readiness
12Migration Stages
3Single-tenant PostgreSQL with per-user row filtering in application code → Multi-tenant PostgreSQL with row-level security policies
infoMigration trigger: Adding a second paying customer; first audit or security review; team growing beyond sole developer who holds mental model of tenant data boundaries
Shared schema multi-tenant with no caching → Shared schema + Redis with tenant-namespaced cache keys
infoMigration trigger: Database read load growing disproportionately with tenant count; per-tenant read p99 degrading despite stable individual tenant query volumes
Shared schema for all tenants → Hybrid: dedicated database per enterprise tenant + shared schema for standard tenants
infoMigration trigger: Enterprise customer requesting dedicated infrastructure in contract; top-tier tenant accounting for > 30% of database I/O; compliance requirement (GDPR data residency, SOC2 logical isolation)
Risks
9RLS policies must be applied to all existing and future tabl
warningRLS policies must be applied to all existing and future tables: missing a table creates a data exposure risk
RLS with complex policies has measurable query planning over
warningRLS with complex policies has measurable query planning overhead; test policy performance on large tables before enabling
Cache key namespacing mistakes cause cross-tenant reads: mus
warningCache key namespacing mistakes cause cross-tenant reads: must be validated in tests
Redis eviction policy must account for large tenants evictin
warningRedis eviction policy must account for large tenants evicting small tenants' hot keys
Tenant router adds a network hop and routing logic that must
warningTenant router adds a network hop and routing logic that must handle tenant → shard mapping correctly
Database-per-tenant multiplies the migration surface: each D
warningDatabase-per-tenant multiplies the migration surface: each DDL change must be applied to N databases
Projection lag creates a read-after-write window where users
criticalProjection lag creates a read-after-write window where users see stale data after their own writes. Mitigation: Route immediate post-write reads to the write store (session-scoped write token); accept eventual consistency only for non-user-initiated reads
↗ direct-db-to-cqrs
Projection rebuild after schema change can take hours or day
criticalProjection rebuild after schema change can take hours or days on large datasets. Mitigation: Design blue/green projection deployment: build new projection in parallel before switching traffic; test rebuild time in staging
↗ direct-db-to-cqrs
Missing partition for current time window causes all INSERTs
criticalMissing partition for current time window causes all INSERTs to fail with 'no partition of relation found'. Mitigation: Create partitions 7-30 days in advance; alert when next partition does not exist before its time window opens
↗ postgresql-to-partitioned
Review Sections
6Referenced Intelligence