Veröffentlicht am

Database per Service: Datenbank-Strategien auf Kubernetes

Teilen:
Authors

TL;DR

  • Database per Service gibt jedem Microservice seine eigene Datenbank -- volle Autonomie, unabhaengige Deployments und Technology Freedom, aber Datenkonsistenz wird zur Herausforderung
  • Shared Database ist einfacher zu starten, fuehrt aber zu enger Kopplung: Schema-Aenderungen betreffen alle Services, Deployments muessen koordiniert werden
  • Eventual Consistency ersetzt starke Konsistenz: Services kommunizieren Aenderungen ueber Events, Lesezugriffe koennen kurzzeitig veraltete Daten liefern
  • CQRS trennt Schreib- und Lese-Modelle -- optimierte Read-Stores fuer komplexe Abfragen ueber Service-Grenzen hinweg
  • StatefulSets sind das Kubernetes-Primitiv fuer Datenbanken: stabile Netzwerk-Identitaet, geordnetes Scaling und persistente Volumes

Kubernetes Database per Service: Datenbank-Strategien fuer Microservices

Die Entscheidung fuer eine Datenbank-Strategie ist eine der folgenreichsten Architektur-Entscheidungen in einer Microservices-Landschaft. Sie beeinflusst Team-Autonomie, Deployment-Geschwindigkeit, Datenkonsistenz und operativen Aufwand.

Dieser Artikel vergleicht Database per Service und Shared Database mit ihren Trade-offs, zeigt wie Eventual Consistency und CQRS die Herausforderungen loesen, und liefert produktionsreife Kubernetes-Manifeste fuer Datenbanken als StatefulSets.


Das Spektrum: Shared Database bis Database per Service

Tight Coupling                              Loose Coupling
     |                                           |
     v                                           v
+------------------+  +------------------+  +------------------+
| Shared Database  |  | Schema per       |  | Database per     |
| (eine DB, ein    |  | Service (eine    |  | Service (eigene  |
|  Schema)         |  |  DB, getrennte   |  |  DB-Instanz pro  |
|                  |  |  Schemas)        |  |  Service)        |
+------------------+  +------------------+  +------------------+

Die meisten Teams starten mit einer Shared Database und migrieren schrittweise Richtung Database per Service. Getrennte Schemas in derselben Instanz bieten einen pragmatischen Zwischenschritt.


Shared Database: Einfach, aber riskant

Vorteile und Nachteile

AspektBewertung
ACID-TransaktionenJOINs und Foreign Keys funktionieren ueber Service-Grenzen
SetupEine Datenbank deployen, fertig
Schema-KopplungAenderungen an Tabellen betreffen alle Services
Deployment-KopplungSchema-Migrationen erfordern koordinierte Releases
SkalierungAlle Services teilen sich CPU, Memory und IOPS
Technology Lock-inAlle Services nutzen dieselbe Datenbank-Technologie

Kubernetes Deployment: Shared PostgreSQL

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres-shared
  namespace: shop
spec:
  serviceName: postgres-shared
  replicas: 1
  selector:
    matchLabels:
      app: postgres-shared
  template:
    metadata:
      labels:
        app: postgres-shared
    spec:
      containers:
        - name: postgres
          image: postgres:16-alpine
          ports:
            - containerPort: 5432
          env:
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-credentials
                  key: password
            - name: POSTGRES_DB
              value: "shopdb"
          volumeMounts:
            - name: postgres-data
              mountPath: /var/lib/postgresql/data
          resources:
            requests:
              cpu: 500m
              memory: 1Gi
            limits:
              cpu: "2"
              memory: 4Gi
  volumeClaimTemplates:
    - metadata:
        name: postgres-data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 100Gi

Database per Service: Volle Autonomie

Jeder Service waehlt die optimale Datenbank-Technologie: PostgreSQL fuer relationale Daten, MongoDB fuer flexible Dokumente, Redis fuer Caches, TimescaleDB fuer Zeitreihen.

+------------------+     +------------------+     +------------------+
| Order Service    |     | Inventory Service|     | Customer Service |
+--------+---------+     +--------+---------+     +--------+---------+
         |                        |                        |
   +-----v------+          +-----v------+          +------v-----+
   | orders-db  |          | inventory  |          | customers  |
   | PostgreSQL |          | MongoDB    |          | PostgreSQL |
   +------------+          +------------+          +------------+

StatefulSet: Orders Database

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: orders-db
  namespace: shop
spec:
  serviceName: orders-db
  replicas: 3
  selector:
    matchLabels:
      app: orders-db
  template:
    metadata:
      labels:
        app: orders-db
        service: order-service
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - orders-db
              topologyKey: kubernetes.io/hostname
      containers:
        - name: postgres
          image: postgres:16-alpine
          ports:
            - containerPort: 5432
              name: postgres
          env:
            - name: POSTGRES_DB
              value: "orders"
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: orders-db-credentials
                  key: username
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: orders-db-credentials
                  key: password
          volumeMounts:
            - name: orders-data
              mountPath: /var/lib/postgresql/data
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: "1"
              memory: 2Gi
  volumeClaimTemplates:
    - metadata:
        name: orders-data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 50Gi
---
apiVersion: v1
kind: Service
metadata:
  name: orders-db
  namespace: shop
spec:
  selector:
    app: orders-db
  ports:
    - port: 5432
      targetPort: 5432
  clusterIP: None

Pod Anti-Affinity stellt sicher, dass Datenbank-Replicas auf unterschiedlichen Nodes laufen. Das schuetzt vor Node-Ausfaellen. Fuer weitergehende Backup-Strategien: Kubernetes Backup und Disaster Recovery.


Eventual Consistency: Der Preis der Entkopplung

Wenn jeder Service seine eigene Datenbank hat, gibt es keine JOINs ueber Service-Grenzen. Datenkonsistenz wird durch Events hergestellt -- mit einer kurzen Verzoegerung.

1. Order Service: INSERT INTO orders (customer, items) ...
2. Order Service: PUBLISH Event "OrderCreated" an Kafka
3. Inventory Service: CONSUME "OrderCreated"
4. Inventory Service: UPDATE inventory SET stock = stock - 1

Zwischen Schritt 2 und 4: Eventual Consistency
(Order existiert, aber Lagerbestand noch nicht aktualisiert)

Konsistenz-Fenster minimieren mit KEDA

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: inventory-consumer-scaler
  namespace: shop
spec:
  scaleTargetRef:
    name: inventory-service
  minReplicaCount: 2
  maxReplicaCount: 10
  triggers:
    - type: kafka
      metadata:
        bootstrapServers: "kafka-0.kafka.shop.svc.cluster.local:9092"
        consumerGroup: "inventory-service"
        topic: "order-events"
        lagThreshold: "10"

Mit KEDA skaliert der Inventory Service automatisch, wenn Events sich stauen. Mehr dazu: Kubernetes KEDA Autoscaling.


CQRS: Getrennte Lese- und Schreib-Modelle

CQRS loest das Problem von Abfragen, die Daten aus mehreren Services benoetigen. Statt JOINs ueber Service-Grenzen baut man spezialisierte Read-Stores.

Schreib-Seite (Commands)         Lese-Seite (Queries)
+------------------+             +------------------+
| Order Service    |--Events*/}  | Order Query      |
| (PostgreSQL)     |             | (Elasticsearch)  |
+------------------+             +------------------+
+------------------+             +------------------+
| Customer Service |--Events*/}  | Dashboard Query  |
| (PostgreSQL)     |             | (Redis)          |
+------------------+             +------------------+

Event-Projektion Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-projector
  namespace: shop
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-projector
  template:
    metadata:
      labels:
        app: order-projector
    spec:
      containers:
        - name: projector
          image: registry.example.com/order-projector:v1.2.0
          env:
            - name: KAFKA_BROKERS
              value: "kafka-0.kafka.shop.svc.cluster.local:9092"
            - name: KAFKA_TOPICS
              value: "order-events,customer-events,inventory-events"
            - name: KAFKA_CONSUMER_GROUP
              value: "order-projector"
            - name: ELASTICSEARCH_URL
              value: "http://order-query-store.shop.svc.cluster.local:9200"
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi

Der Projector konsumiert Events aus mehreren Topics und baut eine denormalisierte Sicht im Elasticsearch-Cluster. Abfragen wie "Alle Bestellungen von Kunde X mit Lagerbestand-Status" werden ohne Cross-Service-Calls beantwortet.


Vergleich: Shared Database vs. Database per Service

KriteriumShared DatabaseDatabase per Service
KonsistenzStrong (ACID)Eventual
Team-AutonomieNiedrigHoch
Deployment-UnabhaengigkeitNeinJa
Technology FreedomNeinJa (Polyglot Persistence)
Operativer AufwandNiedrig (eine DB)Hoch (viele DBs)
SkalierbarkeitVertikal (eine Instanz)Horizontal (pro Service)
Cross-Service-QueriesSQL JOINsCQRS oder API Composition

Migration: Von Shared Database zu Database per Service

  1. Schema-Ownership definieren: Welcher Service besitzt welche Tabellen?
  2. APIs statt JOINs: Direct-Table-Zugriffe durch API-Calls ersetzen
  3. Separate Schemas: Tabellen in Service-eigene Schemas verschieben (gleiche DB)
  4. Event-Publishing: Services publizieren Aenderungen als Events
  5. Separate Instanzen: Schemas in eigene Datenbank-Instanzen migrieren

Fuer die richtige Deployment-Strategie waehrend der Migration: Kubernetes Deployment Strategien.


Best Practices

  1. Klein starten: Beginnen Sie mit getrennten Schemas in einer Datenbank, bevor Sie separate Instanzen einfuehren.
  2. Backup-Strategie pro Datenbank: Jede Instanz braucht eigene Backup-Schedules und Retention Policies.
  3. Connection Pooling: PgBouncer oder ProxySQL als Sidecar reduzieren Connection-Overhead.
  4. Resource Limits setzen: Datenbanken ohne Limits koennen einen gesamten Node destabilisieren.
  5. Monitoring pro Instanz: Jede Datenbank braucht eigene Alerts fuer Disk Usage, Connection Count und Replication Lag.

Fuer Service-Kommunikation zwischen den Datenbank-besitzenden Services: Kubernetes Dapr fuer Microservices.


Verwandte Artikel


Sie planen die Datenbank-Strategie fuer Ihre Microservices-Architektur oder migrieren von einer Shared Database? Wir unterstuetzen Sie bei der Architektur-Entscheidung, StatefulSet-Konfiguration und dem produktiven Betrieb auf Kubernetes. Kontaktieren Sie uns unter /kontakt.

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