Das Tagged Storage-Muster löst das Problem veralteter Konfigurationen und Überlastung des Metadata-Dienstes in Multi-Tenant-Systemen. Wir analysieren, wie das auf AWS funktioniert und wo die Grenzen der Kompromisse liegen.
Das Problem zeigt sich nicht sofort — bis zu dem Moment, an dem die Anzahl der Mieter (Tenants) die Hundertergrenze überschreitet und die Konfigurationen sich schneller ändern, als der Cache lebt. Der klassische Ansatz mit TTL stößt auf einen Widerspruch: Entweder akzeptieren wir veraltete Daten und riskieren, die Isolation oder Feature-Flags zu verletzen, oder wir invalidieren den Cache aggressiv und belasten den Metadata-Dienst. In diesem Moment wird der Konfigurationsdienst selbst zum Engpass in Bezug auf den Durchsatz. Die Situation wird durch die Heterogenität der Daten kompliziert: Ein Teil der Konfigurationen erfordert eine hohe Lesefrequenz (DynamoDB eignet sich dafür), ein anderer — Hierarchien und Versionierung (Parameter Store). Ein universelles Speicherformat gibt es hier nicht, und der Versuch, eines zu verwenden, führt zu übermäßigen Kosten oder einer Verschlechterung der Latenz.
Die Lösung basiert auf dem Tagged Storage-Muster. Die Idee ist einfach: Konfigurationsschlüssel erhalten Präfixe (zum Beispiel tenant_config_ oder param_config_), die bestimmen, in welches Speicherformat die Anfrage geht. Dies beseitigt die Notwendigkeit, ein „bestes“ Speicherformat auszuwählen, und ermöglicht es, Daten basierend auf ihrem Zugriffsverhalten zu routen. Innerhalb des Dienstes wird das Strategy Pattern verwendet, das basierend auf dem Schlüssel das Backend auswählt. Dies ist eine kompromissbehaftete, aber pragmatische Lösung: Das Hinzufügen eines neuen Speichers erfordert kein Umschreiben der Logik, führt jedoch zu einer zusätzlichen Abstraktionsschicht und erschwert das Debugging.
Die Architektur stützt sich auf mehrere Schichten. Auf der Eingabeschicht durchlaufen die Anfragen Cognito, WAF und API Gateway, gelangen dann über den VPC-Link zum ALB und weiter zu den Diensten auf ECS Fargate. Innerhalb der Diensteschicht wird NestJS mit gRPC verwendet. Dies reduziert den Netzwerk-Overhead und verbessert die Latenz für Service-zu-Service-Interaktionen. Ein entscheidender Punkt ist die Tenant-Isolation: Der Dienst akzeptiert keine tenantId aus der Anfrage, sie wird aus dem JWT extrahiert. Dies beseitigt eine Klasse von Angriffen, bei denen der Client versucht, den Kontext des Mieters zu manipulieren.
Die Speicherschicht wird als Multi-Backend-Strategie implementiert. DynamoDB verwendet zusammengesetzte Schlüssel zur Isolation und für effiziente Abfragen. Der Parameter Store ist hierarchisch organisiert, was das Versionsmanagement vereinfacht. In komplexeren Szenarien wird eine zusätzliche Dimension im Schlüssel eingeführt (zum Beispiel Service-Level), was den Zugriff nicht nur auf der Ebene des Tenants, sondern auch auf der Ebene des Dienstes einschränkt. Dies ist wichtig für Systeme mit unterschiedlichen Verantwortungsbereichen innerhalb eines Mieters.
Ein separates Problem ist die Synchronisation von Konfigurationen. Polling erzeugt zusätzliche Last und Verzögerungen. Neustarts von Diensten führen zu Ausfallzeiten. Hier wird ein ereignisgesteuerter Ansatz verwendet: EventBridge überwacht Änderungen im Parameter Store und löst eine Lambda-Funktion aus, die den lokalen Cache aktualisiert. Dies beseitigt das Fenster der Veralterung und entfernt die Notwendigkeit des Pollings. Konfigurationen werden innerhalb von Sekunden aktualisiert, ohne die Benutzersitzungen zu unterbrechen.
Caching wird auf mehreren Ebenen implementiert. Im Speicher des Dienstes werden Metadaten mit Schlüsseln des Typs tenantId:serviceName:configKey gespeichert. Sensible Daten werden dabei nicht zwischengespeichert — sie verbleiben im Parameter Store mit Verschlüsselung (SecureString). Dies ist ein wichtiger Ausgleich zwischen Leistung und Sicherheit. Bei Bedarf kann der Cache in Redis oder Valkey ausgelagert werden, was jedoch eine Netzwerkverzögerung im Bereich von mehreren Millisekunden hinzufügt.
In Bezug auf die Sicherheit wird die Isolation auf mehreren Ebenen gewährleistet: JWT, DynamoDB-Schlüssel und Logik des Dienstes. Eine strengere Variante ist möglich — über STS und Token Vending Machine zur Ausgabe temporärer IAM-Anmeldeinformationen für den Tenant. Dies verstärkt die Kontrolle (einschließlich Audit über CloudTrail), fügt jedoch Latenz und operationale Komplexität hinzu.
Das Ergebnis dieses Ansatzes ist ein System, das sich ohne offensichtlichen Engpass im Metadata-Dienst skalieren lässt und nicht unter veralteten Konfigurationen leidet. Dabei werden keine genauen Metriken zur Verbesserung angegeben, aber architektonisch wird der grundlegende Konflikt zwischen Konsistenz und Leistung beseitigt. Der Preis — die Komplexität der Architektur und die Notwendigkeit, mehrere Storage-Backends gleichzeitig zu unterstützen.
Das Tagged Storage-Muster ist nicht universell. Es ist gerechtfertigt, wenn:
- es verschiedene Typen von Konfigurationen mit unterschiedlichem Zugriffsverhalten gibt
- die Anzahl der Tenants wächst
- strikte Isolation und schnelle Updates wichtig sind
In anderen Fällen kann dies übertrieben sein. Aber als evolutionäre Verbesserung für reife Multi-Tenant-Systeme ist es ein logischer Schritt.