Was macht Mehrinstanzenfähigkeit (Multi-Tenancy)?
Regeln
- Zeilenbasierte Isolierung (Standard): Füge
tenantId-Spalte zu jeder mandantenspezifischen Tabelle hinzu – Durchsetzung via Middleware oder RLS (Row-Level Security)
- Schema-pro-Mandant: Verwende, wenn Mandanten benutzerdefinierte Schemas oder strenge Datenisolierung benötigen – komplexere Operationen, bessere Isolierung
- Subdomain-Routing: Extrahiere Mandanten aus
{tenant}.app.com – löse in Middleware auf, setze tenantId im Request-Kontext
- Benutzerdefinierte Domains: CNAME zu deiner App, suche Mandanten über
Host-Header – verwende eine domains-Tabelle, die benutzerdefinierte Domains auf Mandanten-IDs abbildet
- Mandantenspezifische Konfiguration: Speichere Theme (Logo, Farben), Feature-Flags und Limits in einer
tenant_settings-Tabelle – cachespeichere im Speicher mit TTL
- RLS-Muster (Postgres):
ALTER TABLE posts ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON posts USING (tenant_id = current_setting('app.tenant_id'))
- Middleware: Extrahiere Mandanten aus Subdomain/Header, setze im Kontext, wende auf alle Datenbankabfragen an – lasse niemals eine Abfrage ohne Mandantenbereich laufen
- Datenpartitionierung: Bei großen Tabellen partitioniere nach
tenantId – verbessert die Abfrageleistung und ermöglicht mandantenweise Sicherung/Wiederherstellung
Zeilenbasiertes Isolierungsmuster
function getTenantFromHost(host: string): string {
const subdomain = host.split(".")[0];
return subdomain;
}
prisma.$use(async (params, next) => {
if (TENANT_MODELS.includes(params.model)) {
params.args.where = { ...params.args.where, tenantId: context.tenantId };
}
return next(params);
});
Subdomain-Routing (Next.js Middleware)
export function middleware(request: NextRequest) {
const host = request.headers.get("host") ?? "";
const tenant = host.split(".")[0];
const response = NextResponse.next();
response.headers.set("x-tenant-id", tenant);
return response;
}
Vermeiden
- Abfragen ohne Mandantenbereich – eine fehlende
WHERE tenantId = ? lässt Daten zwischen Mandaten durchsickern
- Hardcodieren des Mandanten im Client – löse serverseitig aus Subdomain/Domain auf
- Kein Index auf
tenantId – jede mandantenspezifische Tabelle benötigt einen zusammengesetzten Index, der mit tenantId beginnt
- Vermischen von Mandantendaten in gemeinsamen Caches – Namensraum-Cache-Schlüssel mit
{tenantId}:{key}