Websites12 min read

SaaS multi-tenant Postgres: 3 architectures compared (2026)

Mohamed Bah·Fondateur, Kolonell
May 20, 2026
Share:
SaaS multi-tenant Postgres: 3 architectures compared (2026)

SaaS multi-tenant Postgres: 3 architectures compared (2026)

Websites

When your B2B SaaS goes from 5 to 50 to 500 clients, multi-tenant architecture determines 80% of operational cost and security. 3 patterns dominate in 2026: schema-per-tenant, row-level (shared schema), database-per-tenant. Each has its sweet spot.

TL;DR

- Row-level: 1 DB, 1 schema, tenant_id everywhere. Simplest, scales to 10K tenants.

- Schema-per-tenant: 1 DB, N schemas. Strong isolation, complex migrations.

- DB-per-tenant: N DBs. Max isolation, high operational cost.

3-pattern comparison

CriterionRow-levelSchema-per-tenantDB-per-tenant
Data isolationLogical (tenant_id)PG schemaPG database
Hosting costLowMediumHigh
Client scaling1-10K100-1K10-100
MigrationsEasyHeavy (×N)Very heavy
Per-tenant backup/restoreHardEasyTrivial
Cross-tenant leak riskHigh (bug = leak)LowNear-zero
Setup complexityLowMediumHigh
Use caseStandard B2B/B2C SaaSRegulated SaaS (health, finance)Enterprise SaaS (gov, banks)

`prisma

model Organization {

id String @id @default(cuid())

name String

plan Plan

users User[]

projects Project[]

}

model Project {

id String @id @default(cuid())

organizationId String

organization Organization @relation(fields: [organizationId], references: [id])

name String

@@index([organizationId])

}

model User {

id String @id @default(cuid())

organizationId String

email String

@@unique([organizationId, email])

}

`

Critical security: every query MUST filter organizationId. Implement via Prisma middleware:

`ts

const SCOPED_MODELS = ['Project', 'User', 'Document', 'Comment'];

export function tenantScopedPrisma(organizationId: string) {

return new PrismaClient().$extends({

query: {

$allModels: {

async $allOperations({ args, model, operation, query }) {

if (!SCOPED_MODELS.includes(model ?? '')) return query(args);

if (operation.startsWith('find') || operation === 'count' || operation === 'aggregate') {

args.where = { ...args.where, organizationId };

}

if (operation === 'updateMany' || operation === 'deleteMany') {

args.where = { ...args.where, organizationId };

}

if (operation === 'create') {

args.data = { ...args.data, organizationId };

}

if (operation === 'createMany') {

args.data = (args.data as any[]).map(d => ({ ...d, organizationId }));

}

return query(args);

},

},

},

});

}

`

Mandatory cross-tenant tests:

`ts

test('User from org A cannot access org B data', async () => {

const orgA = await createOrg();

const orgB = await createOrg();

const projectB = await createProject(orgB.id);

const prismaA = tenantScopedPrisma(orgA.id);

const found = await prismaA.project.findMany({ where: { id: projectB.id } });

expect(found).toEqual([]);

});

`

Pattern 2 — Schema-per-tenant

Need a professional website?

Kolonell builds websites that attract clients, optimized for the Sénégalese market. Free quote in 2 minutes.

Each tenant has its dedicated Postgres schema:

`sql

CREATE SCHEMA tenant_acme;

CREATE SCHEMA tenant_globex;

CREATE TABLE tenant_acme.projects (...);

CREATE TABLE tenant_globex.projects (...);

`

`ts

const pool = new Pool({

connectionString: process.env.DATABASE_URL,

searchPath: tenant_${tenantId},

});

const prisma = new PrismaClient({

datasources: { db: { url: ${process.env.DATABASE_URL}?schema=tenant_${tenantId} } },

});

`

For:

  • Strong isolation
  • Easy individual backup (pg_dump --schema=tenant_acme)
  • GDPR: account deletion = DROP SCHEMA

Against:

  • Migrations: must run on N schemas
  • Limit ~1000-2000 schemas per Postgres DB
  • Scaling beyond = multi-DB

Pattern 3 — Database-per-tenant

Each tenant has its dedicated Postgres DB:

`

[Tenant 1] → Postgres DB tenant_1

[Tenant 2] → Postgres DB tenant_2

[Tenant N] → Postgres DB tenant_N

`

For:

  • Maximum isolation
  • Dedicated performance per tenant
  • Compliance (HIPAA, PCI Level 1, government)

Against:

  • High operational cost: N backups, N monitoring
  • Complex migrations (orchestration needed)
  • Not suitable <100 enterprise tenants

When to migrate pattern 1 → 2 or 3?

SignalAction
1 tenant takes 60-80% queriesSchema-per-tenant to isolate perf
Enterprise tenant requests data isolationSchema-per-tenant or DB-per-tenant
HIPAA / PCI Level 1 complianceDB-per-tenant
Tenant >1M rows/tableSchema partitioning or separate DB

`

[Shared pool: Standard tenants (row-level)] → main Postgres cluster

[Premium pool: Enterprise tenants (schema-per-tenant)] → dedicated Postgres cluster

[Gov pool: ultra-sensitive tenants (DB-per-tenant)] → isolated Postgres

`

Prisma + connection pooling lets you serve 3 patterns from the same app.

Performance considerations

Tenant-first composite indexes

`prisma

@@index([organizationId, createdAt])

@@index([organizationId, status])

@@index([organizationId, slug])

`

organizationId must always be first in composite indexes.

Connection pooling

For 1000+ tenants: PgBouncer or Neon serverless. Without pooling = connection explosion.

Redis caching

Per-tenant cache: cache:org_${organizationId}:projects:list. Short 5-15 min TTL.

Real case — Dakar SaaS (SME CRM)

PhaseArchitectureTenantsCost/mo
0-50 tenantsRow-level Neon Free0-50$0-19
50-500 tenantsRow-level Neon Pro50-500$19-69
500-2000Row-level + read replicas500-2000$150-400
2000+ EnterpriseHybrid: standard pool + schema-per-tenant Enterprise2K-10K$400-1500

Row-level → schema-per-tenant migration at ~1500 tenants. Doable but plan 4-8 weeks.

Common pitfalls

  • Forgetting tenant_id filter on 1 query = GDPR + commercial catastrophe.
  • No CI cross-tenant tests = guaranteed future bug.
  • Migrating too early to schema-per-tenant = premature complexity.
  • No per-tenant backup = unrecoverable loss.
  • No "noisy neighbor" isolation — 1 spamming tenant slows all others.

FAQ

Q: Mongo or Postgres?

A: Postgres for 95% B2B SaaS. Mongo if truly dynamic schema per tenant.

Q: Multi-region needed?

A: For Africa-EU SaaS: Hetzner Frankfurt enough. US: Neon multi-region available.

Q: Soft-delete vs hard-delete?

A: Soft-delete (deletedAt) for audit + GDPR (easy rectification). Hard-delete on GDPR request.

Conclusion

Multi-tenant Postgres in 2026 = row-level for 95% of B2B SaaS, schema-per-tenant for regulated cases, DB-per-tenant for gov/banks. 2-4 weeks initial investment for clean isolation. ROI: security and scaling to 10K+ tenants.

Tags:#SaaS#Multi-Tenant#Postgres#Architecture#B2B#Prisma
Share:

Mohamed Bah

Fondateur, Kolonell

Passionate about digital and entrepreneurship in Africa, Mohamed has been helping Sénégalese businesses with their digital transformation since 2020. Founder of Kolonell, he believes every SME deserves a professional and accessible online présence.