- Authors

- Name
- Phillip Pham
- @ddppham
TL;DR
- Das Schema-per-Service Pattern bietet logische Trennung bei physisch geteilter Datenbank -- ein pragmatischer Mittelweg fuer Teams mit begrenztem DBA-Kapazitaet.
- PostgreSQL als StatefulSet mit Persistent Volumes laeuft stabil auf Kubernetes, erfordert aber durchdachtes Backup und Failover.
- Flyway oder Liquibase gehoeren in die CI/CD-Pipeline -- Schema-Migrationen muessen vor dem Service-Deployment laufen.
- Strikte Zugriffskontrolle pro Schema ueber dedizierte DB-User und Network Policies ist Pflicht.
- Ab ca. 10+ Services oder stark divergierenden Lastprofilen lohnt sich der Wechsel auf Database-per-Service.
Das Problem: Zu viele Datenbanken, zu wenig Admins
Microservices-Architektur sagt: jeder Service bekommt seine eigene Datenbank. In der Theorie sauber, in der Praxis oft ein Albtraum. Zehn Services bedeuten zehn Datenbankinstanzen mit eigenen Backups, Patches, Monitoring-Dashboards und Failover-Konfigurationen.
Fuer Teams mit ein bis zwei Ops-Leuten ist das nicht tragbar. Die Alternative: eine gemeinsame Datenbankinstanz, in der jeder Service sein eigenes Schema bekommt. Man verliert etwas Autonomie, gewinnt aber drastisch an Betreibbarkeit.
Dieses Pattern heisst "Shared Database with Schema-per-Service" und ist kein Anti-Pattern -- solange man es bewusst einsetzt und die Grenzen kennt.
Schema-per-Service vs. Database-per-Service
| Kriterium | Schema-per-Service | Database-per-Service |
|---|---|---|
| Isolation | Logisch (Schema-Ebene) | Physisch (eigene Instanz) |
| Betriebsaufwand | Niedrig (1 DB verwalten) | Hoch (N Instanzen verwalten) |
| Unabhaengige Skalierung | Eingeschraenkt | Voll moeglich |
| Technologie-Mix | Nein (gleiche Engine) | Ja (PostgreSQL, MongoDB, etc.) |
| Backup-Komplexitaet | Einfach (1 Backup) | Hoch (N Backups koordinieren) |
| Cross-Service Queries | Technisch moeglich (aber vermeiden) | Nicht moeglich |
| Kosten | Niedrig | Hoch |
| Team-Autonomie | Eingeschraenkt | Maximal |
Die Entscheidung ist kontextabhaengig. Fuer Teams unter 20 Entwicklern mit weniger als 10 Services ist Schema-per-Service fast immer der bessere Startpunkt.
PostgreSQL StatefulSet auf Kubernetes
Eine PostgreSQL-Instanz laeuft auf Kubernetes als StatefulSet mit Persistent Volume Claims. Hier ein produktionsnahes Setup:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: database
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-credentials
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
livenessProbe:
exec:
command: ["pg_isready", "-U", "postgres"]
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command: ["pg_isready", "-U", "postgres"]
initialDelaySeconds: 5
periodSeconds: 5
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 50Gi
Dazu der Service fuer die interne DNS-Aufloesung:
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: database
spec:
type: ClusterIP
ports:
- port: 5432
targetPort: 5432
selector:
app: postgres
Jeder Microservice verbindet sich ueber postgres.database.svc.cluster.local:5432 mit der Datenbank. Einfach, stabil, kein Service-Mesh noetig.
Zugriffskontrolle: Ein User pro Service
Der kritischste Punkt bei Shared Databases: kein Service darf auf fremde Schemas zugreifen. Das setzt man auf Datenbankebene durch:
#!/bin/bash
# Schema und User fuer einen neuen Service anlegen
SERVICE_NAME=$1
DB_NAME="shared_app"
# Schema erstellen
psql -h postgres.database.svc.cluster.local -U postgres -d $DB_NAME -c "
CREATE SCHEMA IF NOT EXISTS ${SERVICE_NAME};
"
# Dedizierter User mit Zugriff nur auf sein Schema
psql -h postgres.database.svc.cluster.local -U postgres -d $DB_NAME -c "
CREATE USER ${SERVICE_NAME}_user WITH PASSWORD '$(openssl rand -base64 24)';
GRANT USAGE ON SCHEMA ${SERVICE_NAME} TO ${SERVICE_NAME}_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA ${SERVICE_NAME} TO ${SERVICE_NAME}_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA ${SERVICE_NAME}
GRANT ALL PRIVILEGES ON TABLES TO ${SERVICE_NAME}_user;
ALTER ROLE ${SERVICE_NAME}_user SET search_path TO ${SERVICE_NAME};
"
Zusaetzlich sollte man den public Schema-Zugriff entfernen:
psql -h postgres.database.svc.cluster.local -U postgres -d $DB_NAME -c "
REVOKE ALL ON SCHEMA public FROM PUBLIC;
"
Die Credentials kommen als Kubernetes Secret in den jeweiligen Namespace des Service:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: order-service
type: Opaque
stringData:
DB_HOST: "postgres.database.svc.cluster.local"
DB_PORT: "5432"
DB_USER: "orders_user"
DB_PASSWORD: "generated-password-here"
DB_NAME: "shared_app"
DB_SCHEMA: "orders"
Schema-Migrationen mit Flyway
Schema-Migrationen muessen automatisiert und versioniert sein. Flyway ist hier der Standard. Jeder Service verwaltet seine eigenen Migrations-Skripte:
order-service/
src/
db/migration/
V1__create_orders_table.sql
V2__add_shipping_address.sql
V3__add_order_status_index.sql
In der CI/CD-Pipeline laeuft die Migration als Init-Container oder als separater Job vor dem Deployment:
apiVersion: batch/v1
kind: Job
metadata:
name: order-service-migration
namespace: order-service
spec:
template:
spec:
containers:
- name: flyway
image: flyway/flyway:10
args: ["migrate"]
env:
- name: FLYWAY_URL
value: "jdbc:postgresql://postgres.database.svc.cluster.local:5432/shared_app"
- name: FLYWAY_SCHEMAS
value: "orders"
- name: FLYWAY_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_USER
- name: FLYWAY_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_PASSWORD
volumeMounts:
- name: migrations
mountPath: /flyway/sql
volumes:
- name: migrations
configMap:
name: order-service-migrations
restartPolicy: Never
backoffLimit: 3
Der entscheidende Punkt: Migrationen laufen immer vor dem Service-Deployment. Nie gleichzeitig, nie danach. ArgoCD oder Flux koennen das ueber Sync-Waves bzw. Dependencies abbilden.
Network Policies: Zugriff einschraenken
Nicht jeder Pod im Cluster sollte die Datenbank erreichen koennen. Network Policies schraenken den Zugriff auf die Services ein, die tatsaechlich berechtigt sind:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: postgres-access
namespace: database
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
db-access: "true"
podSelector:
matchLabels:
requires-db: "true"
ports:
- port: 5432
protocol: TCP
Nur Pods mit dem Label requires-db: "true" aus Namespaces mit db-access: "true" koennen Port 5432 erreichen. Alles andere wird geblockt.
Monitoring: Was man beobachten muss
Eine Shared Database wird zum Single Point of Failure, wenn man sie nicht im Blick behaelt. Diese Metriken sind Pflicht:
| Metrik | Warnung bei | Kritisch bei |
|---|---|---|
| Aktive Verbindungen | 70% des Limits | 90% des Limits |
| Query-Laufzeit (p95) | > 200ms | > 1s |
| Disk Usage | 70% | 85% |
| Replication Lag | > 5s | > 30s |
| Dead Tuples Ratio | > 5% | > 15% |
| Cache Hit Ratio | < 95% | < 90% |
Der postgres_exporter liefert diese Metriken direkt an Prometheus. Mehr zum Thema Monitoring-Setup gibt es unter Kubernetes Observability Stack.
Wann man auf Database-per-Service wechseln sollte
Das Shared-Database Pattern hat klare Grenzen. Der Wechsel ist faellig, wenn:
- Einzelne Services extreme Last erzeugen und die anderen Services beeintraechtigen. Connection Pooling und Read Replicas helfen nur begrenzt.
- Services unterschiedliche Datenmodelle brauchen. Ein Service will eine Graph-Datenbank, ein anderer Timeseries -- dann ist PostgreSQL fuer alle der falsche Kompromiss.
- Team-Autonomie wichtiger wird als Betriebseffizienz. Ab 5+ Teams, die unabhaengig deployen wollen, wird die zentrale Datenbank zum Bottleneck im Deployment-Prozess.
- Schema-Aenderungen sich gegenseitig blockieren. Wenn Migration A auf ein Lock wartet, das Migration B haelt, hat man ein echtes Problem.
Der Uebergang muss nicht auf einen Schlag passieren. Man kann einzelne Services schrittweise auf eigene Instanzen migrieren, waehrend der Rest auf der Shared Database bleibt.
Backup und Disaster Recovery
Bei einer Shared Database schuetzt ein einziges Backup alle Services -- aber ein fehlgeschlagenes Restore betrifft auch alle Services. Das erhoet die Anforderungen:
#!/bin/bash
# Taegliches Backup mit pg_dump (logisch, pro Schema moeglich)
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups/${TIMESTAMP}"
mkdir -p $BACKUP_DIR
# Vollstaendiges Backup
pg_dump -h postgres.database.svc.cluster.local \
-U postgres \
-d shared_app \
-Fc \
-f "${BACKUP_DIR}/shared_app_full.dump"
# Einzelne Schemas fuer granulares Restore
for SCHEMA in orders products customers; do
pg_dump -h postgres.database.svc.cluster.local \
-U postgres \
-d shared_app \
-n $SCHEMA \
-Fc \
-f "${BACKUP_DIR}/${SCHEMA}.dump"
done
Fuer Point-in-Time Recovery (PITR) ist WAL-Archivierung Pflicht. Tools wie pgBackRest oder Barman automatisieren das zuverlaessig. Mehr zu Storage-Strategien fuer persistente Daten unter Kubernetes Storage Performance.
DSGVO-Aspekte bei Shared Databases
Eine gemeinsame Datenbank mit personenbezogenen Daten erfordert besondere Sorgfalt:
- Zugriffstrennung: Jeder Service-User darf nur sein Schema sehen. Das ist technisch durchsetzbar (siehe oben) und muss regelmaessig auditiert werden.
- Audit Logging: PostgreSQL
pgauditExtension aktivieren, um alle Zugriffe auf personenbezogene Daten zu protokollieren. - Verschluesselung: TLS fuer alle Verbindungen zwischen Services und Datenbank. Encryption at Rest ueber die Storage-Ebene.
- Loeschkonzept: Jeder Service muss seine eigenen Daten loeschen koennen, ohne andere Schemas zu beruehren. Das Schema-per-Service Pattern unterstuetzt das nativ.
Wer sich tiefer mit Compliance-Themen befassen will, findet im Artikel zu DSGVO-Compliance fuer Kubernetes weiterfuehrende Details.
Weiter gedacht
Das Shared-Database Pattern ist ein Startpunkt, kein Endzustand. Es erlaubt Teams, schnell produktiv zu werden, ohne sich in Datenbank-Ops zu verlieren. Aber es erfordert Disziplin: strikte Schema-Trennung, automatisierte Migrationen, saubere Zugriffskontrolle.
Wer mehr ueber die Gesamtarchitektur von Microservices auf Kubernetes erfahren will, findet passende Einstiegspunkte unter Kubernetes Integration Patterns und Monolith zu Microservices Migration.
Falls ihr Unterstuetzung bei der Architektur oder Umsetzung braucht -- meldet euch unter /kontakt. Wir helfen Teams dabei, ihre Kubernetes-Plattform sauber aufzubauen.
Kubernetes-Beratung gesucht?
Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.
📖 Verwandte Artikel
Weitere interessante Beiträge zu ähnlichen Themen
Microservices Decomposition: Monolith aufteilen
Monolith in Microservices aufteilen mit Domain-Driven Design und Strangler Fig Pattern. Praktischer Kubernetes-Guide mit Deployments und Datenbank-Strategien.
Strangler Fig Pattern: Monolith zu Microservices migrieren
Schrittweise Migration vom Monolithen zu Microservices mit dem Strangler Fig Pattern auf Kubernetes inklusive Ingress-Routing und Rollback-Strategie.
Kubernetes pgvector: PostgreSQL als performanten Vector Store implementieren
Erfahren Sie, wie Sie pgvector nutzen, um PostgreSQL auf Kubernetes als performanten und datenschutzkonformen Vector Store für KI-Anwendungen im deutschen Mittelstand zu etablieren. Profitieren Sie von einer zukunftssicheren hybriden Datenarchitektur, die lokalen Anforderungen gerecht wird.
Kubernetes CQRS Pattern Microservices in Deutschland optimal nutzen
Optimieren Sie Skalierbarkeit und Performance Ihrer komplexen Microservices auf Kubernetes in Deutschland mit dem CQRS Pattern. Erfahren Sie, wie diese zukunftsweisende Architektur Compliance-Anforderungen erfüllt und digitale Souveränität für deutsche Unternehmen sichert.
Datenbanken auf Kubernetes: StatefulSets richtig nutzen
PostgreSQL mit StatefulSets auf Kubernetes betreiben: Stabile Netzwerk-Identitäten, PersistentVolumeClaims und automatisierte Backups per CronJob.