Rejoice Software logo

Laravel · · 4 Min. Lesezeit

Multi-Tenant-Laravel: eine oder mehrere Datenbanken im Produktivbetrieb

Wir haben beides umgesetzt. Hier sind die typischen Fehlerquellen, Abwägungen und Migrationswege, die wir gerne von Anfang an gekannt hätten.

Zwei Wege, Kunden zu trennen

In einer mandantenfähigen Anwendung teilen sich alle Kunden (Mandanten) eine Codebasis, dürfen aber immer nur ihre eigenen Daten sehen. In Laravel gibt es dafür zwei Hauptwege.

Im Single-Database-Modell hat jede Tabelle mit Kundendaten eine Spalte tenant_id, und jede Abfrage wird auf den aktuellen Mandanten eingeschränkt. Im Multi-Database-Modell bekommt jeder Mandant eine eigene Datenbank (oder ein eigenes Schema), und die Anwendung wechselt die Verbindung, sobald sie weiß, welcher Mandant aktiv ist. Beides funktioniert in Produktion gut; die Fehlerbilder sind verschieden.

Eine Datenbank: Stärken und Risiken

Eine einzige Datenbank ist einfach zu betreiben: ein Satz Migrationen, ein Backup, ein Connection-Pool und einfache Auswertungen über alle Kunden. Sie ist außerdem günstig: tausend kleine Mandanten kosten so viel wie einer.

Das Hauptrisiko ist ein Datenleck. Eine Abfrage, die den Mandantenfilter vergisst – eine Raw-Query, ein Report, ein Job außerhalb eines Requests –, kann Daten eines anderen Kunden liefern. Weitere Risiken sind laute Nachbarn (ein großer Mandant bremst alle) und Wiederherstellungen: Die Daten eines einzelnen Mandanten aus einem gemeinsamen Backup zurückzuholen ist mühsam.

Mehrere Datenbanken: Stärken und Risiken

Getrennte Datenbanken bieten starke Isolation. Ein Bug zeigt einem Mandanten viel seltener die Daten eines anderen, du kannst einen einzelnen Kunden sichern und wiederherstellen, seine Daten in einer bestimmten Region ablegen und einen großen Mandanten auf einen eigenen Server verschieben.

Die Kosten liegen im Betrieb. Jede Migration läuft einmal pro Mandant, Deployments dauern also länger, und ein Fehler mittendrin hinterlässt Mandanten mit unterschiedlichen Schemaversionen. Verbindungslimits werden zu einer echten Grenze, und mandantenübergreifende Auswertungen brauchen eine zusätzliche Pipeline in ein Data Warehouse.

Single-Database-Mandanten in Laravel richtig umsetzen

  • Ermittle den Mandanten einmal und früh – in einer Middleware, anhand von Domain, Subdomain oder angemeldetem Nutzer – und speichere ihn an einer Stelle.
  • Nutze einen Global Scope und setze tenant_id beim Anlegen von Models automatisch über einen gemeinsamen Trait.
  • Beginne zusammengesetzte Indizes mit tenant_id und nimm tenant_id in Unique-Constraints auf (zum Beispiel tenant_id + email).
  • Gib die Mandanten-ID an jeden Queue-Job weiter und stelle sie vor der Ausführung wieder her.
  • Versieh Cache-Schlüssel und Dateipfade mit dem Mandanten als Präfix.
  • Schreibe Tests, die zwei Mandanten anlegen und sicherstellen, dass einer nie die Datensätze des anderen lesen kann.
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);
    }
}

Multi-Database-Mandanten richtig umsetzen

Halte eine zentrale Datenbank für Mandanten, Domains, Tarife und Abrechnung sowie eine eigene Datenbank pro Mandant für dessen Daten. Wechsle die Standardverbindung in der Middleware und in Queue-Jobs und setze sie danach zurück, damit ein lange laufender Worker nie den falschen Mandanten behält.

Führe Mandanten-Migrationen mit einem Befehl aus, der über alle Mandanten läuft, den Fortschritt protokolliert und nach einem Fehler fortsetzen kann. Behalte das Verbindungslimit des Datenbankservers im Blick und nutze einen Connection-Pooler, wenn du viele Worker betreibst. Pakete wie stancl/tenancy und spatie/laravel-multitenancy übernehmen vieles davon und unterstützen beide Modelle.

Das Hybridmodell

Viele reife Produkte landen dazwischen: Die meisten Mandanten teilen sich eine Datenbank, während Enterprise-Kunden, die Isolation, Datenresidenz oder dedizierte Leistung brauchen, eine eigene bekommen. Wenn jede Tabelle bereits eine tenant_id trägt, ist das Verschieben eines Mandanten in eine eigene Datenbank ein Export und Import – kein Neubau. Ein weiterer Grund, tenant_id vom ersten Tag an einzuführen, auch wenn du später vielleicht auf mehrere Datenbanken wechselst.

So entscheidest du

  • Viele kleine Kunden und ein kleines Team: eine Datenbank.
  • Verträge mit Anforderungen an Isolation, Wiederherstellung pro Kunde oder Datenstandort: mehrere Datenbanken oder hybrid.
  • Einige sehr große Kunden mit hoher Last: hybrid, mit dedizierten Datenbanken für sie.
  • Unsicher: eine Datenbank mit tenant_id überall und starken Isolationstests – so bleiben alle Optionen offen.

Sollen wir uns deine Website ansehen?

Starte den kostenlosen Website-Check für einen sofortigen Bericht oder erstelle ein Ticket – ein Engineer antwortet innerhalb eines Werktags.

#Laravel #SaaS #Multi-tenancy

Sprechen wir

Bauen wir etwas Bemerkenswertes.

30 Min. kostenlose Strategiesession. Kein Pitch, keine Verpflichtung — nur ein Plan.

Projekt starten