Quand votre SaaS B2B passe de 5 à 50 puis 500 clients, l'architecture multi-tenant détermine 80 % du coût opérationnel et de la sécurité. 3 patterns dominent en 2026 : schema-per-tenant, row-level (shared schema), database-per-tenant. Chacun a son sweet-spot.
TL;DR
- Row-level : 1 DB, 1 schema,
tenant_idpartout. Le plus simple, scaling jusqu'à 10K tenants.- Schema-per-tenant : 1 DB, N schémas. Isolation forte, complex migrations.
- DB-per-tenant : N DBs. Isolation max, coût opérationnel élevé.
Comparatif 3 patterns
| Critère | Row-level | Schema-per-tenant | DB-per-tenant |
|---|---|---|---|
| Isolation données | Logique (tenant_id) | Schema PG | Database PG |
| Coût hosting | Bas | Moyen | Élevé |
| Scaling clients | 1-10K | 100-1K | 10-100 |
| Migrations | Faciles | Lourdes (×N) | Très lourdes |
| Backup/restore par tenant | Difficile | Facile | Trivial |
| Risque fuite cross-tenant | Élevé (bug = leak) | Bas | Quasi nul |
| Setup complexity | Bas | Moyen | Élevé |
| Use case | SaaS B2B/B2C standard | SaaS regulated (santé, finance) | SaaS Enterprise (gov, banques) |
Pattern 1 — Row-level (recommandé 90 % cas)
`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 // <-- tenant_id
organization Organization @relation(fields: [organizationId], references: [id])
name String
// ... autres champs
@@index([organizationId])
}
model User {
id String @id @default(cuid())
organizationId String // <-- tenant_id
email String
// ...
@@unique([organizationId, email])
}
`
Sécurité critique : tous les queries DOIVENT filtrer organizationId. Implémenter via Prisma middleware :
`ts
// lib/prisma-tenant.ts
const SCOPED_MODELS = ['Project', 'User', 'Document', 'Comment']; // pas Organization
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);
// Inject WHERE
if (operation.startsWith('find') || operation === 'count' || operation === 'aggregate') {
args.where = { ...args.where, organizationId };
}
// Block updateMany / deleteMany sans where explicite
if (operation === 'updateMany' || operation === 'deleteMany') {
args.where = { ...args.where, organizationId };
}
// Inject pour CREATE
if (operation === 'create') {
args.data = { ...args.data, organizationId };
}
if (operation === 'createMany') {
args.data = (args.data as any[]).map(d => ({ ...d, organizationId }));
}
return query(args);
},
},
},
});
}
`
Tests cross-tenant obligatoires :
`ts
// tests/cross-tenant.spec.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([]); // scoping bloque l'accès
});
Besoin d'un site web professionnel ?
Kolonell crée des sites web qui attirent des clients, optimisés pour le marché sénégalais. Devis gratuit en 2 minutes.
`
Pattern 2 — Schema-per-tenant
Chaque tenant a son schema Postgres dédié :
`sql
-- Schemas
CREATE SCHEMA tenant_acme;
CREATE SCHEMA tenant_globex;
-- Mêmes tables dans chaque schema
CREATE TABLE tenant_acme.projects (...);
CREATE TABLE tenant_globex.projects (...);
`
`ts
// Connection routing par tenant
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} } },
});
`
Pour :
- Isolation forte
- Backup individual facile (
pg_dump --schema=tenant_acme) - RGPD : suppression compte = DROP SCHEMA
Contre :
- Migrations : doivent runner sur N schemas
- Limite ~1000-2000 schemas par DB Postgres
- Scaling au-delà = multi-DB
Pattern 3 — Database-per-tenant
Chaque tenant a sa DB Postgres dédiée :
`
[Tenant 1] → Postgres DB tenant_1
[Tenant 2] → Postgres DB tenant_2
[Tenant N] → Postgres DB tenant_N
`
Pour :
- Isolation maximale
- Performance dédiée par tenant
- Compliance (HIPAA, PCI Level 1, gouvernement)
Contre :
- Coût opérationnel élevé : N backups, N monitoring
- Migrations complexes (orchestration nécessaire)
- Pas adapté < 100 tenants enterprise
Quand migrer pattern 1 → pattern 2 ou 3 ?
| Signal | Action |
|---|---|
| 1 tenant prend 60-80 % requêtes | Schema-per-tenant pour isoler perf |
| Tenant Enterprise demande isolation données | Schema-per-tenant ou DB-per-tenant |
| Compliance HIPAA / PCI Level 1 | DB-per-tenant |
| Tenant > 1M lignes / table | Schema partitioning ou DB séparée |
Stratégie hybride recommandée
`
[Pool partagé : tenants Standard (row-level)] → cluster Postgres principal
[Pool premium : tenants Enterprise (schema-per-tenant)] → cluster Postgres dédié
[Pool gov : tenants ultra-sensibles (DB-per-tenant)] → Postgres isolé
`
Prisma + connection pooling permettent de servir les 3 patterns depuis la même app.
Considérations performance
Indexes composés tenant-first
`prisma
@@index([organizationId, createdAt])
@@index([organizationId, status])
@@index([organizationId, slug])
`
Le organizationId doit toujours être en premier dans les indexes composés.
Connection pooling
Pour 1000+ tenants : PgBouncer ou Neon serverless. Sans pooling = explosion connexions.
Caching Redis
Cache par tenant : cache:org_${organizationId}:projects:list. TTL court 5-15 min.
Cas réel — SaaS Dakar (CRM PME)
| Phase | Architecture | Tenants | Cost/mo |
|---|---|---|---|
| 0-50 tenants | Row-level Neon Free | 0-50 | $0-19 |
| 50-500 tenants | Row-level Neon Pro | 50-500 | $19-69 |
| 500-2000 | Row-level + read replicas | 500-2000 | $150-400 |
| 2000+ Enterprise | Hybrid : pool standard + schema-per-tenant Enterprise | 2K-10K | $400-1500 |
Migration row-level → schema-per-tenant à ~1500 tenants. Doable mais préparer 4-8 semaines.
Pièges fréquents
- Oublier filter tenant_id sur 1 query = catastrophe RGPD + commercial.
- Pas de tests cross-tenant CI = bug futur garanti.
- Migrer trop tôt vers schema-per-tenant = complexité prématurée.
- Pas de backup par tenant = perte impossible à restaurer sans casse.
- Pas d'isolation "noisy neighbor" : 1 tenant qui spam ralentit tous les autres.
FAQ
Q : Mongo ou Postgres ?
R : Postgres pour 95 % SaaS B2B. Mongo si schema vraiment dynamique par tenant.
Q : Multi-region nécessaire ?
R : Pour SaaS Africa-EU : Hetzner Frankfurt suffit. US : Neon multi-region disponible.
Q : Soft-delete vs hard-delete ?
R : Soft-delete (deletedAt) pour audit + RGPD (rectification facile). Hard-delete sur demande RGPD.
Conclusion
Multi-tenant Postgres en 2026 = row-level pour 95 % des SaaS B2B, schema-per-tenant pour cas regulated, DB-per-tenant pour gov/banques. Investissement initial 2-4 semaines pour isolation propre. ROI : sécurité et scaling jusqu'à 10K+ tenants.
Mohamed Bah
Fondateur, Kolonell
Passionné par le digital et l'entrepreneuriat en Afrique, Mohamed accompagne les entreprises sénégalaises dans leur transformation digitale depuis 2020. Fondateur de Kolonell, il croit que chaque PME mérite une présence en ligne professionnelle et accessible.