Scaling Multi-Tenant SaaS: Horizontal vs Vertical Patterns and When to Apply Each
A practical deep dive into scaling strategies for multi-tenant SaaS — horizontal scaling, vertical scaling, tenant sharding, and connection pool design for high tenant counts.
Your multi-tenant SaaS is growing. New tenants are signing up faster than expected, existing tenants are generating more data and more traffic, and the infrastructure that felt generous at launch is starting to feel constrained. You have two categories of scaling levers — vertical and horizontal — and the multi-tenant context determines which ones to reach for first and when to reach for more disruptive options.
This is the scaling decision map for multi-tenant SaaS, covering both the application tier and the data tier.
The Multi-Tenant Scaling Challenge Is Different
In a single-tenant application, scaling is primarily about total load: how much traffic, how much data, how many concurrent users. You scale one system up or out to handle more.
In a multi-tenant SaaS, you have an additional dimension: tenant distribution. A system that handles ten million requests per day comfortably might struggle if eighty percent of those requests come from a single tenant. The aggregate capacity exists; the per-tenant isolation does not. This means multi-tenant scaling must address both total capacity and per-tenant fairness simultaneously.
Application Tier: Horizontal Scaling Is the Default
Application servers in a multi-tenant SaaS are stateless by design — the tenant context is in the request (derived from the JWT), not in server-side state. This makes horizontal scaling straightforward: add more application server instances behind a load balancer.
Tenant-aware routing at the load balancer layer is optional for most products but becomes valuable when you have enterprise tenants with dedicated application tiers. Route requests by the X-Tenant-ID header or by the subdomain to a dedicated set of instances. Standard tenants go to the shared pool; enterprise tenants go to their dedicated pool.
Connection pool sizing requires care in a multi-tenant context. A pool sized for total expected concurrency works at the aggregate level, but if one tenant holds many connections simultaneously, others experience pool exhaustion. PgBouncer and similar poolers can be configured with per-pool (per-tenant-database) limits in database-per-tenant models.
For shared-schema models, a single connection pool serves all tenants. Size it for the expected concurrent connection count with headroom. Monitor pool wait time — persistent pool wait time indicates the pool is undersized.
Database Tier: Vertical First, Then Horizontal
Vertical Scaling
Vertical scaling — upgrading the database instance to more CPU, RAM, and faster storage — is often the right first move and the most underrated one. A larger instance handles more concurrent queries and holds more of the working data set in the buffer cache. For many multi-tenant SaaS products, vertical scaling provides headroom through several growth stages before horizontal approaches are necessary.
Before scaling vertically, confirm the bottleneck is actually instance capacity rather than query inefficiency. pg_stat_statements shows which queries consume the most total time. Slow query optimization (adding missing indexes, rewriting inefficient queries, fixing N+1 patterns) often provides more headroom than a vertical scale at a lower cost.
Read Replicas
For read-heavy workloads, adding read replicas distributes query load without changing the primary database. Route read queries (reports, dashboard queries, search) to replicas and write queries to the primary.
In a multi-tenant context, tenant context propagation to replicas is the same as to the primary — RLS policies apply equally. The main operational concern is replication lag: if a tenant writes data and immediately reads it from a replica, they may see stale data. Design the application to read from the primary for a short window after a write (read-after-write consistency), or route to the primary for queries where freshness is critical.
Connection Pooling at Scale
As tenant count grows, the number of unique connection pools (in schema-per-tenant or database-per-tenant models) grows with it. PgBouncer handles this by acting as a proxy with a configurable number of backend connections. But PgBouncer itself eventually becomes a bottleneck when the number of client connections and databases it proxies is very large.
At this scale, consider a hierarchical pooling topology: a fleet of PgBouncer instances, each responsible for a subset of tenant databases, with a routing layer that directs connections to the correct PgBouncer instance.
Tenant Sharding: Distributing Tenants Across Database Clusters
When a single database instance cannot handle the total tenant load — even after vertical scaling and read replicas — horizontal scaling of the database tier means sharding: distributing tenants across multiple database clusters.
Shard Assignment Strategies
Static shard assignment: Each tenant is assigned to a shard at provisioning time based on a hash of their tenant ID or simple round-robin. The assignment is permanent.
- Simple to implement and reason about.
- Produces uneven shard load over time if tenant data volume varies significantly.
Dynamic shard assignment with a routing registry: A central registry maps tenant ID to shard. Tenants can be moved between shards as load dictates.
- More operationally complex, but provides the ability to rebalance load.
- Requires a tenant migration tool to move data between shards safely.
- The routing registry becomes a critical dependency — if it is unavailable, the application cannot determine which shard to query.
The Routing Layer as Infrastructure
Whether you use static or dynamic shard assignment, the routing layer — the code or service that resolves a tenant's shard — becomes critical infrastructure. It must be:
- Fast: Ideally in-memory resolution with a short TTL, backed by a database or cache.
- Highly available: A routing failure means the application cannot serve any tenant. Cache the routing table aggressively.
- Versioned: Routing changes (tenant migrations between shards) must be deployed atomically with the data migration, not before or after.
Multi-Tenant SaaS Scaling Sequence in Practice
For most growing multi-tenant SaaS products, the scaling sequence looks like this:
- Optimize slow queries with indexes and query rewrites.
- Vertically scale the database instance.
- Add read replicas for read-heavy dashboards and reports.
- Implement per-tenant rate limiting and background job queuing to prevent noisy neighbor effects.
- Move high-volume or enterprise tenants to dedicated database instances.
- Implement tenant sharding for remaining shared-tenant load.
Each step provides headroom. Steps 5 and 6 are architectural changes that require significant engineering investment; exhaust the earlier steps before reaching for them.
Scaling is not a one-time event — it is an ongoing operational practice. The teams that handle growth without crises are the ones that watch their per-tenant metrics, address bottlenecks proactively, and keep the routing layer clean enough to adapt as the tenant mix changes.
Clixo builds and scales multi-tenant SaaS architectures for product teams who need the system to grow without requiring rewrites. Start a build.