Use Multi-Tenant SaaS Platform as the Foundational Architecture Pattern
Deterministic ADR derived from topology, simulation, and advisor intelligence for Multi-Tenant SaaS Platform. Traceable to YAML knowledge entities.
Context
Serving multiple customers from a shared infrastructure creates a fundamental tension: tenants must be isolated from each other's data and partially from each other's resource usage, while the operator must maintain a single deployable unit and avoid per-tenant operational overhead. A noisy tenant that exhausts database connections, saturates the cache, or generates hot partition keys can degrade all other tenants. Row-level security enforces data isolation at the database layer; connection pooling bounds aggregate connection usage; cache namespacing prevents cross-tenant cache pollution. Primary operational risks include: Noisy tenant connection exhaustion: a single tenant with runaway queries or misconfigured connection pools can consume the entire PgBouncer pool, blocking all other tenants; N+1 query amplification across tenants: ORM-generated queries that are innocuous for small tenants become table-scan-scale queries for large tenants sharing the same schema; Row-level security policy bypass risk: missing RLS policies on new tables or direct superuser connections inadvertently expose cross-tenant data.
Decision
We will adopt the **Multi-Tenant SaaS Platform** architecture pattern. This is a moderate-complexity architecture appropriate for teams at small product team level or above. The advisor rates this pattern as 'intermediate' operational maturity.
Rationale
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. The primary architectural strength is: 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. 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. Key trade-off: Introduces eventual consistency: stale reads possible within TTL window. Operational note: Cache-aside (lazy population) is the dominant integration pattern. Evidence: Cache hit rates of 80–95% observed in production read-heavy APIs. Core technology stack: postgresql, redis.
Architectural 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
Accepted Tradeoffs
- ⚠Shared schema (RLS) is simpler to operate than database-per-tenant but provides weaker isolation; a database misconfiguration can expose cross-tenant data
- ⚠Connection pooling provides aggregate connection efficiency but cannot prevent a single misbehaving tenant from consuming the entire pool without application-level tenant quotas
- ⚠Redis caching reduces per-tenant database load but requires explicit cache invalidation on tenant-scoped writes; cache miss storms on tenant data load degrade the database
- ⚠Single shared schema simplifies migrations but means every DDL change affects all tenants simultaneously: zero-downtime migrations become mandatory
- ⚠Vertical scaling the shared PostgreSQL primary scales all tenants together; there is no mechanism to scale compute for one tenant without scaling for all
Risks
One 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.
All database connections in the pool are in use; new requests queue and then time out, causing cascading latency and errors across all dependent services.
In a multi-tenant system, one tenant's high resource consumption: query load, connection count, write rate, or storage I/O: degrades database or service performance for all other tenants sharing the same infrastructure, violating the implicit isolation guarantee that a shared-infrastructure SaaS product implies.
Application code issues one query to fetch N parent records, then issues N individual queries to fetch each child record, executing N+1 round trips to the database instead of 1–2, multiplying database load proportionally to the result set size.
Alternatives Considered
AI Retrieval-Augmented Generation Platform was not selected because its high operational complexity exceeds the current team's readiness relative to Multi-Tenant SaaS Platform's moderate complexity.
Analytics Data Platform was not selected because its high operational complexity exceeds the current team's readiness relative to Multi-Tenant SaaS Platform's moderate complexity.
API Gateway Platform was not selected because its high operational complexity exceeds the current team's readiness relative to Multi-Tenant SaaS Platform's moderate complexity.
Audit and Compliance Platform was not selected because its high operational complexity exceeds the current team's readiness relative to Multi-Tenant SaaS Platform's moderate complexity.
Scaling Thresholds
Signals indicating the architecture is approaching its scaling limits:
Tier 1: Connection Pool Exhaustion
Signal: PgBouncer pool wait queue > 0 during peak hours; application errors reporting "connection pool exhausted" or pool timeout; pool utilization > 90%
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
Tier 2: Noisy Tenant I/O Saturation
Signal: 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
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)
Tier 3: Schema Migration Pressure
Signal: DDL migration duration > 30s on any shared table; lock acquisition timeouts reported during migration windows; migration deployment requiring off-hours scheduling
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
Tier 4: Tenant Growth Segmentation
Signal: 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
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
Migration Path
Single-tenant PostgreSQL with per-user row filtering in application code → Multi-tenant PostgreSQL with row-level security policies
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
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
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)
Operational Requirements
- Minimum team maturity: Small Product Team: This scenario has moderate operational complexity. It is recommended for Small Product Team teams or higher.
- Runbooks and alerting for high-severity risks: 3 high-severity risks identified. Each requires a documented runbook, alerting threshold, and on-call response procedure before running in production.
- Cache sizing and eviction policy configuration: Redis or equivalent cache requires correct maxmemory configuration, eviction policy selection (allkeys-lru is common), and cold-start warming strategy after restarts.
- PostgreSQL: scenario includes high_write_throughput or write_heavy workload: Deploy PgBouncer in transaction-mode pooling before relying on vertical scaling