Architecture
Architecture SaaS multi-tenant : ce que les CTO doivent savoir
Utilisez des tables partagées avec sécurité au niveau des lignes (RLS) pour plus de 5 000 locataires ou produits en phase de démarrage ; cela coûte entre 15 et 200 $/mois au total. Utilisez une base de données par locataire pour les secteurs réglementés (fintech, soins de santé) comptant moins de 100 locataires. Utilisez la séparation de schéma pour 50 à 5 000 locataires nécessitant un isolement modéré. Cette décision façonne votre structure de coûts, votre historique de conformité et votre pipeline de déploiement pendant des années.
Vous créez un produit SaaS. Plusieurs clients l'utiliseront. Chaque client s'attend à ce que ses données restent privées, ses configurations restent séparées et son expérience ressemble à sa propre plateforme. La question n'est pas de savoir si vous avez besoin d'une architecture multi-tenant. C'est le modèle à choisir.
Cette décision façonne votre schéma de base de données, votre pipeline de déploiement, votre histoire de conformité et votre structure de coûts pendant des années. Si vous vous trompez, vous passerez six mois à migrer vers un autre modèle pendant que votre feuille de route stagne.
Il existe trois modèles à considérer. Chacun d’entre eux compromet le coût, l’isolement et la complexité opérationnelle de différentes manières.
Les trois modèles d'architecture multi-tenant
1. Base de données par locataire
Chaque locataire dispose de sa propre base de données. La couche d'application achemine les requêtes vers la base de données appropriée en fonction d'un identifiant de locataire, généralement résolu à partir du sous-domaine, de la clé API ou de la revendication JWT.
Il s’agit du modèle d’isolement le plus puissant. Les données du locataire A se trouvent dans une base de données distincte de celles du locataire B. Il n'y a aucun moyen pour un bug de requête de divulguer des lignes entre les locataires, car les lignes existent dans différentes bases de données sur différentes connexions.
Avantages :
- Respectueux de la conformité. Les auditeurs adorent entendre « chaque client possède sa propre base de données ». Les exigences SOC 2, HIPAA et de résidence des données deviennent des conversations simples.
- Sauvegarde et restauration par locataire. Lorsqu'un locataire vous demande de revenir à l'état d'hier, vous restaurez une base de données. Pas d'extraction chirurgicale des tables partagées.
- Aucun risque de voisinage bruyant. Un locataire exécutant des requêtes d’analyse coûteuses ne peut pas dégrader les performances des autres locataires.
- Mise à l’échelle par locataire. Vous pouvez placer des locataires de grande valeur sur des instances de base de données plus grandes.
Inconvénients :
- Cher. Chaque base de données a un coût de base : calcul, stockage, sauvegardes, surveillance. À 10 locataires, c'est gérable. À 500, c'est un élément de campagne qui met votre directeur financier mal à l'aise.
- L’enfer des migrations. Les modifications de schéma doivent être appliquées à des centaines de bases de données. Vous avez besoin d'outils pour orchestrer les migrations, suivre les bases de données qui sont à jour et gérer les échecs au cours d'un déploiement.
- Le pooling de connexions devient compliqué. Votre serveur d'applications a besoin de pools de connexions à des centaines de bases de données. Les limites de connexion deviennent une contrainte avant le processeur ou la mémoire.
- Les requêtes entre locataires sont pénibles. Les rapports agrégés, les analyses à l'échelle de la plateforme ou les tableaux de bord d'administration qui affichent les données entre les locataires nécessitent des requêtes de fédération ou un pipeline d'analyse distinct.
Idéal pour :Fintech, soins de santé et SaaS d'entreprise avec des exigences strictes en matière de résidence des données. Si votre contrat indique que « les données client doivent résider dans une région AWS spécifique » ou « les données doivent être supprimables dans les 24 heures suivant la résiliation du contrat », la base de données par locataire rend les deux réalisables de manière triviale.
2. Base de données partagée, séparation des schémas
Une base de données, mais chaque locataire obtient son propre schéma (espace de noms). Dans PostgreSQL, cela signifie que chaque locataire dispose d'un ensemble distinct de tables sous son propre nom de schéma : tenant_abc.users, tenant_xyz.users. L'application définit le search_path sur chaque connexion pour acheminer les requêtes vers le schéma correct.
C’est le juste milieu. Vous bénéficiez d'une meilleure isolation que les tables partagées, à moindre coût que les bases de données séparées.
Avantages :
- Moins cher que la base de données par locataire. Une instance de base de données, un pool de connexions, une pile de surveillance.
- Un isolement décent. Les schémas fournissent une limite d'espace de noms. Une requête mal configurée dans un schéma ne peut pas accéder aux tables d'un autre schéma (en supposant que vous définissiez correctement
search_path). - La sauvegarde par locataire est possible via
pg_dump --schema. - Les migrations sont plus faciles que la base de données par locataire. Vous exécutez la migration une fois par schéma, mais tous les schémas résident dans la même base de données, les outils sont donc plus simples.
Inconvénients :
- Dérive du schéma. Lorsque les migrations échouent sur certains schémas mais réussissent sur d'autres, vous vous retrouvez avec des locataires exécutant différentes versions de votre schéma. Le débogage est misérable.
- PostgreSQL ne gère pas bien des milliers de schémas. Au-delà de 5 000 à 10 000 schémas, vous rencontrerez une dégradation des performances dans les recherches
pg_catalog, despg_dumpfois plus lentes et des conflits de vide automatique. - Même risque de voisin bruyant qu’une base de données partagée. La requête coûteuse d'un locataire est toujours en concurrence pour le même processeur et les mêmes E/S.
- La prise en charge des outils est incohérente. Les ORM et les frameworks de migration ont différents niveaux de prise en charge des modèles de schéma par locataire.
Idéal pour :SaaS de milieu de gamme avec 50 à 5 000 locataires pour lesquels vous avez besoin d'une meilleure isolation que la sécurité au niveau des lignes, mais ne pouvez pas justifier le coût de bases de données séparées.
3. Tout partagé avec une sécurité au niveau des lignes
Tous les locataires partagent les mêmes tables. Une colonne tenant_id sur chaque table identifie le locataire propriétaire de chaque ligne. Les politiques de sécurité au niveau des lignes (RLS) dans PostgreSQL imposent que les requêtes ne puissent voir que les lignes appartenant au locataire actuel.
Il s'agit du modèle le plus courant pour les produits SaaS B2B qui ne sont pas vendus aux entreprises réglementées.
Avantages :
- Coût d'infrastructure le moins cher. Une base de données, un ensemble de tables, un pool de connexions. L’ajout d’un locataire ne coûte aucune infrastructure supplémentaire.
- Migrations les plus simples. Un schéma, une migration. Vous n’orchestrez rien sur plusieurs bases de données ou schémas.
- Un chemin de base de code. Aucune logique conditionnelle pour « à quelle base de données je parle » ou « quel schéma dois-je utiliser ». La colonne
tenant_idfait partie du schéma et RLS gère le reste. - Les analyses multi-locataires sont triviales. Les tableaux de bord d'administration et les rapports à l'échelle de la plateforme interrogent les mêmes tables avec des autorisations élevées.
Inconvénients :
- Les bogues de requête peuvent divulguer des données. Si un développeur écrit une requête qui contourne RLS (en utilisant une connexion de superutilisateur ou en oubliant de définir la variable de session), les données du locataire fuient. Il s’agit d’un risque au niveau du code, et non d’une garantie au niveau de l’infrastructure.
- Les conversations sur la conformité sont plus difficiles. « Toutes les données client se trouvent dans les mêmes tables, séparées par une colonne » est une solution plus difficile à vendre aux équipes de sécurité de l'entreprise que « chaque client possède sa propre base de données ».
- Le risque de voisinage bruyant est ici le plus élevé. Un locataire important 10 millions de lignes verrouille les tables qui affectent tous les locataires.
- La sauvegarde et la restauration par locataire nécessitent une extraction chirurgicale. Vous ne pouvez pas restaurer un seul locataire sans écrire des outils personnalisés.
Idéal pour :SaaS B2B en dessous du niveau entreprise. Produits avec des centaines ou des milliers de locataires pour lesquels le coût de l'infrastructure compte plus que les certifications de conformité.
Tableau comparatif
| Facteur | Base de données par locataire | Séparation de schéma | Partagé + RLS |
|---|---|---|---|
| Coût par locataire | Élevé (calcul dédié + stockage) | Moyen (calcul partagé, schémas séparés) | Faible (une table, une ligne) |
| Isolement des données | Le plus puissant (bases de données séparées) | Modéré (limites du schéma) | Le plus faible (colonne + politique) |
| Complexité des requêtes | Faible par locataire, élevé entre locataires | Faible par locataire, modéré entre locataires | Faible (mêmes tables, RLS s'en charge) |
| Difficulté de migration | Difficile (N bases de données à migrer) | Modéré (N schémas, une base de données) | Facile (un schéma, une migration) |
| Préparation à la conformité | Excellent (les auditeurs adorent ça) | Bon (limite défendable) | Adéquat (nécessite une piste d'audit RLS) |
| Risque voisin bruyant | Aucune | Présente | Le plus haut |
| Coût d'intégration du locataire | Approvisionnement requis | Création de schéma + migration | Insérer une ligne |
Comment nous avons construit DropTaxi sur une multilocation de bases de données partagées
Quand nous avons construitDropTaxi, un SaaS de réservation de taxi multi-tenant pour les opérateurs indiens, nous avons choisi le modèle de base de données partagée avec des colonnes tenant_id.
Le raisonnement était simple. Les exploitants de taxis sont de petites entreprises. Ils n'ont pas d'exigences de conformité exigeant une isolation au niveau de la base de données. Le nombre de locataires devait passer de 5 à plus de 500 sans coûts d'infrastructure par locataire. Et l'intégration devait être instantanée : inscrivez-vous, configurez la marque, pointez un domaine, lancez le direct. Pas de déploiement, pas de provisionnement, pas d'attente.
Voici à quoi ressemble l'architecture en pratique :
Intégration des locataires sans déploiement.Ajouter un nouveau locataire signifie insérer une ligne dans la table tenants avec son nom de marque, ses couleurs, l'URL du logo, le domaine, les tarifs et le jeton du bot Telegram. Aucun pipeline CI ne s'exécute. Aucun conteneur ne redémarre. La requête suivante adressée à ce domaine résout le nouveau locataire et affiche son site de marque.
Sous-domaines de marque via middleware.Chaque requête HTTP entrante atteint une couche middleware Hono qui lit l'en-tête Host. Le middleware interroge la base de données (via Drizzle ORM sur Turso) pour trouver le locataire correspondant à ce domaine. S'il trouve une correspondance, la configuration complète du locataire se charge dans le contexte de la demande. Si ce n'est pas le cas, la demande obtient un 404. La couche Astro SSR restitue ensuite les pages en utilisant la marque du locataire, afin que les visiteurs voient un site de réservation de taxi autonome plutôt qu'une plateforme générique.
Déploiement unique pour tous les locataires.Une machine Fly.io gère l’ensemble de la plateforme. Une base de données. Une base de code. Le seul coût par locataire est la configuration DNS et les lignes de la base de données qui stockent leurs paramètres.
Ce modèle fonctionne parce que le produit ne dessert pas les secteurs réglementés, que la sensibilité des données est faible (détails de réservation, pas de dossiers financiers) et que le principal problème de mise à l'échelle concerne le nombre de locataires plutôt que le volume de données par locataire.
Modèles pratiques pour les systèmes multi-locataires
Quel que soit le modèle que vous choisissez, ces modèles apparaissent dans les systèmes de production multi-locataires.
Middleware de résolution de locataires
La résolution du locataire doit avoir lieu une seule fois, à la limite de votre pipeline de demandes, et le locataire résolu doit se propager tout au long du cycle de vie de la demande. Stratégies de résolution courantes :
- Sous-domaine :
acme.yourapp.comest résolu en locataireacme. Analyser à partir de l'en-têteHost. - Domaine personnalisé :
app.acme.comcorrespond à un locataire via une table de recherche de domaine. - Préfixe du chemin :
yourapp.com/acme/dashboardextrait le locataire de l'URL. Moins courant en production, mais utile pendant le développement. - Clé JWT/API :Pour les produits API-first, l'identifiant du locataire réside dans le jeton d'authentification. Le middleware valide le jeton et extrait la réclamation du locataire.
Stockez le locataire résolu dans un contexte de requête (c.set() de Hono, req.tenant d'Express ou une variable locale de thread dans Go). Aucun code en aval ne devrait avoir besoin de résoudre à nouveau le locataire.
Regroupement de connexions
Dans le modèle base de données par locataire, le regroupement de connexions devient le premier goulot d'étranglement que vous rencontrerez. Chaque base de données de locataires a besoin de son propre pool et votre serveur d'applications dispose d'un nombre limité de connexions qu'il peut maintenir ouvertes.
Des solutions qui fonctionnent en production :
- PgBouncer par instance de base de donnéesavec pooling au niveau des transactions. Cela multiplexe de nombreuses connexions d'application sur un plus petit nombre de connexions de base de données.
- Initialisation paresseuse du pool.Ne créez pas de pools de connexions pour les locataires qui n’ont pas reçu de demande au cours de la dernière heure. Faites tourner les pools à la demande et expulsez les pools inactifs avec une durée de vie.
- Services de mutualisation géréscomme le pooler de connexions de Supabase ou le pilote sans serveur de Neon, qui gèrent la gestion du pool en dehors de votre processus de candidature.
Pour les modèles de base de données partagée, un seul pool de connexions fonctionne correctement. Le contexte du locataire est défini au niveau de la session (SET app.current_tenant pour RLS, SET search_path pour la séparation de schéma) à chaque extraction de connexion.
Mise en cache à l'échelle du locataire
Vos clés de cache nécessitent un préfixe de locataire. Si vous mettez en cache une clé « données du tableau de bord » sans la limiter à un locataire, vous transmettrez les données du tableau de bord du locataire A au locataire B. Cela semble évident, mais il s'agit du bogue multi-location le plus courant en production.
Utilisez un format de clé tel que tenant:{tenant_id}:resource:{resource_type}:{resource_id}. Si vous utilisez Redis, envisagez des bases de données Redis distinctes par locataire (0 à 15 sont disponibles par défaut) pour les déploiements plus petits, ou un préfixe de clé pour les plus grands.
Isolement du travail en arrière-plan
Les tâches en arrière-plan (envois d'e-mails, génération de rapports, importations de données) nécessitent que le contexte du locataire soit propagé du site de mise en file d'attente au travailleur. Lorsque vous mettez une tâche en file d'attente, incluez le tenant_id dans la charge utile de la tâche. Le travailleur doit configurer le même contexte de locataire que celui fourni par votre middleware HTTP avant de traiter la tâche.
Pour protéger les voisins bruyants dans les files d’attente de tâches, utilisez des files d’attente distinctes ou des priorités de file d’attente par locataire. Un locataire important 100 000 enregistrements ne devrait pas bloquer les e-mails de bienvenue d’un autre locataire. BullMQ, Sidekiq et Celery prennent tous en charge les files d'attente nommées qui vous permettent d'acheminer les locataires à volume élevé vers des travailleurs dédiés.
Multi-portail comme variante multi-tenant
La multilocation ne signifie pas seulement « même application, différents clients ». Cela peut également signifier « même plate-forme, différents rôles d'utilisateur avec des portails distincts ». Quand nous avons construitZestAMC, une plate-forme multiportail basée sur les rôles, l'architecture partageait la même base de données et la même base de code, mais exposait différentes interfaces à différents types d'utilisateurs : administrateurs, gestionnaires et agents de terrain. Chaque portail avait son propre routage, ses autorisations et sa propre interface utilisateur, mais tous s'appuyaient sur la même couche de données avec une portée au niveau des lignes.
Il s'agit d'un modèle utile lorsque vos « locataires » ne sont pas des organisations distinctes mais des rôles distincts au sein d'une même organisation. Les primitives multi-tenant (accès aux données limité, middleware par rôle, propagation du contexte) restent les mêmes.
Cadre décisionnel : comment choisir votre modèle
Le bon modèle dépend de trois variables : les exigences de conformité, le nombre de locataires attendu et le budget d'infrastructure.
Commencez par la conformité.Si vos clients appartiennent à des secteurs réglementés (finance, soins de santé, gouvernement) ou si vos contrats incluent des clauses de résidence des données, la base de données par locataire est le choix le plus sûr. La prime de coût est un élément des contrats d'entreprise pour lequel vos clients s'attendent à payer.
Tenez compte du nombre de locataires.Si vous prévoyez moins de 100 locataires et que chaque locataire génère des revenus significatifs, la base de données par locataire est viable. Entre 100 et 5 000, la séparation des schémas fonctionne si vous utilisez PostgreSQL et pouvez investir dans des outils de migration. Au-dessus de 5 000, les tables partagées avec RLS constituent le choix pragmatique. Les aspects économiques des infrastructures des autres modèles s’effondrent lorsque le nombre de locataires est élevé.
Vérifiez votre budget.Si vous êtes en phase de pré-revenu ou de démarrage, les tables partagées avec RLS vous permettent d'expédier plus rapidement et de dépenser moins. Vous pourrez migrer vers un modèle d’isolation plus solide ultérieurement lorsque des entreprises clientes le demanderont (et paieront pour cela). La plupart des produits SaaS n'ont jamais besoin d'effectuer cette migration, car la plupart des produits SaaS sont vendus à des PME soucieuses des fonctionnalités et non des modèles d'isolation de bases de données.
Considérez l’approche hybride.Certains produits exécutent des tables partagées pour leur niveau standard et une base de données par locataire pour les entreprises clientes. Il s'agit d'une plus grande complexité opérationnelle, mais cela vous permet de servir les deux marchés sans imposer un modèle unique. Stripe et Notion utilisent tous deux des variantes de ce modèle.
Erreurs courantes à éviter
- Choisir une base de données par locataire pour un produit qui comptera des milliers de locataires.Le coût opérationnel de la gestion de milliers de bases de données dépasse les avantages de l'isolation. Si vos locataires sont des PME payant 50 $/mois, vous ne pouvez pas vous permettre une infrastructure dédiée par locataire.
- Oublier de définir la portée de votre suite de tests.Vos tests d'intégration doivent s'exécuter avec le contexte du locataire. Si vos tests réussissent sans définir de locataire, ils testent un chemin de code que vos utilisateurs de production n'utiliseront pas.
- Ne pas tester les politiques RLS avec des requêtes contradictoires.Écrivez des tests qui tentent d'accéder aux données du locataire B tout en étant authentifié en tant que locataire A. Exécutez-les dans CI. Une suite de tests réussie sans tests d’accès entre locataires donne une fausse confiance.
- La gestion des locataires du bâtiment après coup.Le provisionnement, la configuration et le déprovisionnement des locataires sont des fonctionnalités de produit de premier ordre. Créez les outils d'administration parallèlement au produit, et non après le lancement.
- Ignorer la journalisation tenant compte du locataire.Chaque ligne de journal doit inclure l'identifiant du locataire. Lorsque vous déboguez un problème de production à 2 heures du matin, « quelque chose de cassé » est inutile. "Le locataire acme_corp a rencontré une contrainte de clé étrangère sur la table des commandes" est exploitable.
La version courte
Choisissez une base de données par locataire si la conformité détermine votre architecture. Choisissez la séparation de schéma si vous avez besoin d’un isolement modéré à une échelle modérée. Choisissez des tables partagées avec RLS si vous optimisez la vitesse et le coût. Créez un middleware de résolution de locataires dès le premier jour. Étendez vos caches, vos journaux, vos tâches en arrière-plan et vos tests au contexte du locataire. Et ne sur-concevez pas le modèle d’isolation pour votre étape actuelle ; vous pouvez renforcer l’isolement lorsque vos clients l’exigent et que vos revenus le soutiennent.
Questions fréquemment posées
Quelle est la meilleure architecture de base de données multi-tenant pour SaaS ?
Cela dépend de trois facteurs. Utilisez une base de données par locataire pour les secteurs réglementés (fintech, soins de santé) comptant moins de 100 locataires. Utilisez la séparation de schéma pour 50 à 5 000 locataires nécessitant un isolement modéré. Utilisez des tables partagées avec une sécurité au niveau des lignes pour plus de 5 000 locataires ou des produits en phase de démarrage optimisant la vitesse et les coûts.
Qu'est-ce que la sécurité au niveau des lignes dans un SaaS multi-tenant ?
La sécurité au niveau des lignes (RLS) utilise les politiques PostgreSQL pour limiter les requêtes de chaque locataire à leurs propres lignes. Une colonne tenant_id sur chaque table identifie la propriété. RLS est le modèle le moins cher (une base de données, aucun coût d'infrastructure par locataire) et gère plus de 5 000 locataires. Le risque : une requête mal configurée contournant RLS peut entraîner une fuite de données entre les locataires.
Combien coûte une base de données par locataire par rapport à une base de données partagée ?
La base de données par locataire ajoute entre 15 et 100 $ par locataire et par mois en termes de calcul, de stockage et de sauvegardes. Pour 500 locataires, cela représente entre 7 500 $ et 50 000 $/mois rien qu'en coûts de base de données. Les tables partagées avec RLS exécutent une base de données pour un total de 15 à 200 $/mois. La séparation des schémas se situe entre les deux au niveau d'une instance de base de données, mais avec une surcharge de migration par schéma.
Comment gérez-vous la résolution des locataires dans les applications multi-locataires ?
Résolvez le locataire une fois à la limite de votre pipeline de requêtes à l'aide de l'analyse de sous-domaine, de la recherche de domaine personnalisée, du préfixe de chemin ou des revendications JWT. Stockez le locataire résolu dans un contexte de requête (Hono c.set(), Express req.tenant). Aucun code en aval ne doit résoudre à nouveau le locataire. Ce modèle fonctionne sur les trois modèles d'isolation.
Puis-je mélanger des modèles d’isolation multi-locataires pour différents niveaux de clients ?
Oui. Exécutez des tables partagées avec RLS pour votre niveau standard et une base de données par locataire pour les entreprises clientes qui nécessitent des certifications de conformité. Stripe et Notion utilisent des variantes de cette approche hybride. Cela ajoute de la complexité opérationnelle mais vous permet de servir les PME à faible coût tout en répondant aux exigences d'isolation des données de l'entreprise.
Lectures connexes
Quand migrer d’un monolithe vers des microservices (et quand ne pas le faire)
La plupart des startups adoptent les microservices trop tôt. La plupart des entreprises attendent trop longtemps. Voici comment savoir quand votre monolithe est devenu trop grand et comment migrer sans réécriture.
Supabase vs Firebase vs backend personnalisé : lequel pour votre startup
Supabase vous offre Postgres gratuitement jusqu'à 500 Mo. Firebase s'étend à des millions mais vous enferme dans l'écosystème de Google. Un backend personnalisé coûte entre 3 000 et 8 000 $ d’avance, mais vous donne un contrôle total.
Serverless vs conteneurs : quelle architecture convient à votre SaaS ?
Le sans serveur coûte 0 $ au lancement, mais devient coûteux à grande échelle. Les conteneurs coûtent plus cher au départ mais restent prévisibles. Voici comment choisir la bonne architecture pour votre produit SaaS.