Skip to content
SP StackPractices
advanced By StackPractices

Multi-Tenancy Architecture

Design multi-tenant applications with shared or isolated databases, tenant-aware routing, and data isolation strategies.

Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.

Overview

Multi-tenancy is an architecture where a single software instance serves multiple customers (tenants) while keeping their data and configuration isolated. The trade-off is between operational simplicity (shared everything) and data isolation (separate everything). Choosing the right model affects scalability, security, and compliance.

When to Use

Use this resource when:

  • Building SaaS applications serving multiple organizations
  • Meeting compliance requirements (SOC 2, HIPAA) that mandate data segregation
  • Optimizing infrastructure costs by sharing compute across tenants
  • Scaling from hundreds to thousands of tenants with predictable performance

Solution

Shared Database with Tenant ID (PostgreSQL)

-- Row-Level Security ensures tenant isolation
CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,
    user_id UUID NOT NULL,
    amount DECIMAL(10,2) NOT NULL
);

-- Enable RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

-- Policy: tenants can only see their own data
CREATE POLICY tenant_isolation ON orders
  USING (tenant_id = current_setting('app.current_tenant')::UUID);

Tenant-Aware Middleware (Node.js)

function tenantMiddleware(req, res, next) {
  const tenantId = req.headers['x-tenant-id'] || req.subdomain;
  
  if (!tenantId) {
    return res.status(400).json({ error: 'Tenant ID required' });
  }
  
  // Set tenant context for this request
  req.tenantId = tenantId;
  
  // Apply to database connection
  db.query("SET app.current_tenant = $1", [tenantId]);
  
  next();
}

Schema-Per-Tenant Migration

from sqlalchemy import create_engine, MetaData

def migrate_tenant_schema(tenant_id: str):
    engine = create_engine("postgresql://user:pass@localhost/db")
    with engine.begin() as conn:
        conn.execute("CREATE SCHEMA IF NOT EXISTS tenant_{}".format(tenant_id))
        # Run migrations within tenant schema
        metadata = MetaData(schema="tenant_{}".format(tenant_id))
        metadata.create_all(conn)

Explanation

Three multi-tenancy models:

ModelIsolationCostComplexity
Shared DB + Tenant IDLow (RLS needed)LowestLow
Schema-per-tenantMediumMediumMedium
Database-per-tenantHighHighestHigh

Tenant resolution strategies:

  • Subdomain: tenant1.app.com, tenant2.app.com
  • Path: app.com/tenant1/, app.com/tenant2/
  • Header: X-Tenant-ID in API requests
  • JWT claim: tenant embedded in auth token

Variants

ApproachBest ForTrade-off
Shared everythingEarly-stage SaaSSimplest; weakest isolation
Shared compute, isolated storageMid-market SaaSBalance of cost and compliance
Fully isolatedEnterprise/regulatedHighest cost; strongest isolation
Cell-basedGlobal scaleShards tenants across regions

Best Practices

  • Never trust tenant ID from user input: Always resolve from authenticated context
  • Index tenant_id first: Every query filters by tenant; make it the leading column
  • Use connection pooling carefully: Schema-per-tenant requires dynamic schema switching
  • Backup per tenant: Schema-per-tenant makes pg_dump per-schema trivial
  • Resource quotas: Limit CPU, storage, and API rate per tenant to prevent noisy neighbors

Common Mistakes

  1. Missing tenant filter: One forgotten WHERE tenant_id = $1 exposes all customer data
  2. Caching without tenant scoping: Shared cache keys leak data across tenants
  3. Background jobs without tenant context: Scheduled tasks must run for each tenant separately
  4. Hard-coded schemas: Mixing tenant data in application code creates security holes
  5. No tenant-aware logging: Debugging production issues requires filtering logs by tenant

Frequently Asked Questions

Q: Can I migrate from shared DB to schema-per-tenant later? A: Yes, but it requires a significant migration. Start with tenant_id columns and RLS even if you plan to split later.

Q: How do I handle tenant-specific customizations? A: Use feature flags per tenant, white-label configuration, or metadata-driven UI. Avoid separate code branches.

Q: Does GDPR affect multi-tenancy design? A: Yes. Right to erasure is simpler with schema-per-tenant (drop schema) than with shared tables (delete rows across many tables).