Obsidian·RegTech / Reporting·April 2025

Multi-Tenant Reporting-Plattform mit Row-Level-Security

Jurisdiktions-genaue Reports für 180+ Tenants in DACH, mit garantierter Datenisolation auf DB-Ebene.

180+
Tenants
12k
Reports/Monat
<200ms
Query-P95
DB-Level
Isolation
ReactNode.jsTimescaleDBAWSRLS
Engineers
Sarah Chen · Christopher Schmitt
Dauer
11 Wochen · Schema-zu-Scale
Format
2 Audit-Wochen + 3 Build-Sprints
Kontext
Frankfurt · RegTech

Das Problem

Obsidian lieferte regulatorische Reports an 180+ Finanzinstitute in DACH — Banken, Vermögensverwalter, Versicherungen. Jeder Tenant hatte unterschiedliche Jurisdiktionen (DE, AT, CH, LI, LU), unterschiedliche Reporting-Standards (BaFin, FMA, FINMA), unterschiedliche Datentiefen.

Die bestehende Plattform war Single-Tenant repliziert: jeder neue Kunde = separate DB-Instance, separate Deployment-Pipeline, separates Monitoring. Onboarding-Zeit: drei Wochen. Kosten pro Tenant: vierstellig monatlich, nur für Infrastruktur.

Die Geschäftsführung hatte zwei Schmerzen: Skalierung (jeder neue Kunde produzierte lineare Kosten) und Compliance-Angst (wenn irgendwo in dem Geflecht ein Datenleck passierte, wäre es das Ende). Die angebotenen Lösungen: entweder eine teure Enterprise-Multi-Tenant-Suite für sechsstellig pro Jahr, oder ein komplettes Custom-Rewrite mit Beratungsarmada.

Wir haben einen dritten Weg vorgeschlagen: Postgres-native Row-Level-Security.

Discovery: Warum RLS und nicht Application-Layer?

In Woche 1 haben wir die bestehende Plattform analysiert. Jede Query hatte schon einen WHERE tenant_id = ? Filter — gut gemeint, aber in der Application-Layer durchgesetzt. Ein vergessener Filter in einem neuen Endpoint = Datenleak. Ein gehacktes Session-Token = Daten aus anderen Tenants lesbar.

Wir haben drei Optionen evaluiert:

  1. Physische Isolation — wie bisher, eine DB pro Tenant. Sicher, aber teuer und langsam
  2. Application-Layer-Tenancy — das bisherige Modell, aufgeräumt. Billig, aber fragil
  3. DB-Level RLS — Postgres-native Row-Level-Security. Sicher wie Option 1, billig wie Option 2

Option 3 hatte ein Risiko: die meisten Teams haben keine Erfahrung mit RLS. Debug-Sessions können verwirrend werden („warum sehe ich keine Daten?" — „weil der Tenant-Context fehlt"). Aber wenn es einmal sauber aufgesetzt ist, ist die Isolation physikalisch unmöglich zu umgehen, nicht nur konventionell.

Architektur

180+ Tenants
  ↓
React Dashboard (white-label pro Tenant)
  ↓
Node.js API
  ↓ SET app.tenant_id = <from JWT>
  ↓
Postgres mit RLS Policies pro Table
  ↓ USING (tenant_id = current_setting('app.tenant_id')::uuid)
  ↓
TimescaleDB Hypertables (Zeitreihen)
  ↓
EXCLUDE Constraints (keine überlappenden Perioden)

RLS Policy Beispiel:

CREATE POLICY tenant_isolation ON jurisdiction_reports
  USING (tenant_id = current_setting('app.tenant_id')::uuid);

Jede Query vom Backend setzt den Tenant-Context am Anfang der Connection. Postgres filtert dann automatisch jede WHERE-Clause — vergessen unmöglich. Wenn jemand direkten DB-Access bekommt (was er nicht sollte), kann er immer noch nur den Scope des gesetzten Tenants sehen.

"BaFin-Audit im ersten Anlauf durch. Das war vorher die Angst Nummer eins — dass wir nach zwei Monaten Prüfung zurückgeschickt werden. Jetzt haben wir einen Mechanismus der nicht nur compliance-konform ist, sondern compliance-default." — CTO, Obsidian

Build: elf Wochen, fokussiert auf Migration-Sicherheit

Woche 1–2 — Schema-Audit Jede Tabelle analysieren: gibt es schon ein tenant_id? Wo nicht, wie ergänzen wir's sauber? Welche Tabellen müssen definitiv RLS haben (Reports, Kundendaten) vs. welche können shared bleiben (Stammdaten, Locales)?

Woche 3–5 — RLS-Rollout auf neue DB Schema-Aufbau mit RLS from day one. TimescaleDB-Extensions für Zeitreihen-Hypertables. EXCLUDE Constraints für jurisdiction + period (keine doppelten Reports pro Mandant+Periode). Testing mit synthetischen Daten aus mehreren Tenants — konnte ein Mandant Daten eines anderen sehen? Antwort: nein, egal wie wir's versucht haben.

Woche 6–8 — Migration pro Tenant Parallel-Run: neuer + alter Stack, Daten in beiden Systemen. Tenant-für-Tenant Migration mit Read-Comparison. Wenn die Reports 100% identisch rauskamen: Cut-Over. Bei Abweichungen: Delta analysieren (meistens Null-Handling bei alten Daten).

Woche 9–11 — White-Label Dashboards + Monitoring React Dashboard mit per-Tenant Themes (Logo, Farben, Accent). Admin-Console für internen Support. Monitoring: RLS-Violations als kritische Alerts (sollte nie passieren, aber falls doch → sofortige Eskalation).

Ergebnis

  • 180+ Tenants auf einer DB — vorher 180+ separate Instanzen
  • Onboarding 3 Wochen → 15 Minuten — ein neues Tenant = INSERT in einer Tabelle
  • 12.000 Reports/Monat — P95 Query-Latenz unter 200ms
  • Zero Data-Leak-Incidents in 9 Monaten Production
  • BaFin-Audit im ersten Anlauf bestanden

Was wir gelernt haben

RLS ist unterschätzt. Viele Teams schreiben ihre eigene Tenant-Isolation in der Application-Layer — fragil, error-prone, und schwer auditierbar. Postgres-native RLS verschiebt die Isolation auf die Ebene wo sie hingehört: in die Engine, die die Daten hält. Audit-Erklärung wird trivial: „Postgres RLS Policies werden vom DB-Engine enforced, nicht vom Application-Code."

Performance war kein Problem — entgegen der Vorurteile. RLS hat einen Ruf als „slow" weil viele Benchmarks Edge-Cases testen. Bei korrektem Indexing (immer tenant_id im Composite-Index ganz vorne) ist der Overhead < 5%. Bei unseren 12k Reports/Monat: unmerklich.

EXCLUDE Constraints sind Gold. Das war unsere Entdeckung während des Builds: Postgres kann mit EXCLUDE USING gist garantieren, dass zwei Reports für denselben Tenant + dieselbe Jurisdiction + überlappende Perioden nicht nebeneinander existieren können. Das hat vorher die Application mit komplexer Logik gemacht — jetzt macht es die DB atomar.

Monitoring vor Migration. Der wichtigste Tipp: bevor du RLS in Production schaltest, richte Alerts ein die jeden Query ohne tenant_context loggen. Wir hatten in Woche 2 der Migration einen Test-Endpoint, der ohne Context lief. Alert hat sofort gepingt. Fix in 10 Minuten. Ohne Alert hätten wir's nicht gefunden.

Ähnliches Projekt?

Lass uns darüber sprechen.

Projekt anfragen