Laravel · · 4 min de lecture
Laravel multi-tenant : une base ou plusieurs en production
Nous avons livré les deux. Voici les pannes typiques, les compromis et les chemins de migration que nous aurions aimé connaître dès le premier jour.
Deux façons de séparer les clients
Dans une application multi-tenant, tous les clients (tenants) partagent le même code, mais chacun ne doit jamais voir que ses propres données. Avec Laravel, il existe deux grandes façons d’y parvenir.
Dans le modèle à base unique, chaque table contenant des données client possède une colonne tenant_id, et chaque requête est limitée au tenant courant. Dans le modèle multi-bases, chaque tenant a sa propre base (ou son schéma), et l’application change de connexion dès qu’elle sait quel tenant est actif. Les deux fonctionnent bien en production ; ils échouent de manières différentes.
Base unique : forces et risques
Une base unique est simple à exploiter : un seul jeu de migrations, une sauvegarde, un pool de connexions et des rapports faciles sur l’ensemble des clients. Elle est aussi économique : mille petits tenants coûtent autant qu’un seul.
Le risque principal est la fuite de données. Une requête qui oublie le filtre de tenant — une requête brute, un rapport, un job exécuté hors requête HTTP — peut renvoyer les données d’un autre client. Les autres risques sont les voisins bruyants (un gros tenant qui ralentit tout le monde) et les restaurations : récupérer les données d’un seul tenant depuis une sauvegarde partagée est laborieux.
Multi-bases : forces et risques
Des bases séparées offrent un isolement fort. Il est bien moins probable qu’un bug montre à un tenant les données d’un autre ; vous pouvez sauvegarder et restaurer un seul client, placer ses données dans une région précise et déplacer un gros tenant sur son propre serveur.
Le coût est opérationnel. Chaque migration s’exécute une fois par tenant : les déploiements sont plus longs et un échec en cours de route laisse les tenants sur des versions de schéma différentes. Les limites de connexions deviennent une vraie contrainte, et l’analytique transverse exige un pipeline supplémentaire vers un entrepôt de données.
Réussir la base unique avec Laravel
- Identifiez le tenant une seule fois, tôt — dans un middleware, à partir du domaine, du sous-domaine ou de l’utilisateur connecté — et stockez-le à un seul endroit.
- Appliquez un global scope et renseignez tenant_id automatiquement à la création des modèles, via un trait partagé.
- Commencez les index composites par tenant_id et incluez tenant_id dans les contraintes d’unicité (par exemple tenant_id + email).
- Transmettez l’identifiant du tenant à chaque job en file d’attente et restaurez-le avant son exécution.
- Préfixez les clés de cache et les chemins de stockage de fichiers par le tenant.
- Écrivez des tests qui créent deux tenants et vérifient que l’un ne peut jamais lire les enregistrements de l’autre.
trait BelongsToTenant
{
protected static function bootBelongsToTenant(): void
{
static::addGlobalScope('tenant', fn ($query) => $query->where('tenant_id', tenant()->id));
static::creating(fn ($model) => $model->tenant_id ??= tenant()->id);
}
}
Réussir le multi-bases
Gardez une base centrale pour les tenants, domaines, formules et la facturation, et une base par tenant pour ses données. Changez la connexion par défaut dans le middleware et dans les jobs, puis réinitialisez-la pour qu’un worker de longue durée ne conserve jamais le mauvais tenant.
Lancez les migrations des tenants avec une commande qui parcourt les tenants, journalise la progression et peut reprendre après un échec. Surveillez la limite de connexions du serveur de base de données et utilisez un pooler si vous avez beaucoup de workers. Des paquets comme stancl/tenancy et spatie/laravel-multitenancy gèrent une grande partie de ce travail et prennent en charge les deux modèles.
Le modèle hybride
Beaucoup de produits matures finissent entre les deux : la plupart des tenants partagent une base, tandis que les grands comptes qui exigent isolement, localisation des données ou performances dédiées ont la leur. Si chaque table porte déjà un tenant_id, déplacer un tenant vers une base dédiée se résume à un export puis un import, pas à une réécriture. Raison de plus pour ajouter tenant_id dès le premier jour, même si vous passez peut-être au multi-bases plus tard.
Comment choisir
- Beaucoup de petits clients et une petite équipe : base unique.
- Des contrats exigeant isolement, restaurations par client ou localisation des données : multi-bases, ou hybride.
- Quelques très gros clients aux charges importantes : hybride, avec des bases dédiées pour eux.
- En cas de doute : base unique avec tenant_id partout et des tests d’isolement solides — toutes les options restent ouvertes.
Vous voulez que nous regardions votre site ?
Lancez l’analyse de site gratuite pour obtenir un rapport immédiat, ou créez un ticket : un ingénieur vous répond sous un jour ouvré.