Inhaltsverzeichnis14 Abschnitte
- TL;DR
- Die vier Phasen einer Daten-Migration
- Phase 1 — Inventory & Classification
- Phase 2 — Dual-Write & Shadow Mode
- Phase 3 — Cutover & Validation
- Phase 4 — Decommission & Observability
- Schema-Evolution-Strategie
- Nutze ein Schema-Registry
- Column-Mapping-Patterns
- Daten-Validation-Framework
- Rollback-Planung
- Observability-Stack für Migrations-Projekte
- FAQ
- Fazit
Daten-Strategie für Cloud-Migrationen: Das Platform-Engineer-Playbook
TL;DR
- Cloud-Migrationen scheitern öfter am Daten-Layer als anderswo. Vier Phasen: Inventory → Dual-Write/Shadow → Cutover → Decommission.
- Klassifizier Daten (Hot/Warm/Cold/Transient) vor Bewegung. Hot braucht Live-Replikation, Cold kann Off-Hours bulk-kopiert werden.
- Niemals Hard-Cutover für Hot-Data — Dual-Write mit Reconciliation, dann Feature-Flag-Switch.
- Audit-Trail, Rollback-Plan, 30 Tage Post-Cutover-Observability bevor Decommission.
Cloud-Migrations-Projekte scheitern öfter am Daten-Layer als anderswo. Networking, Compute und IAM kriegen thorough Attention — aber Daten werden oft als Nachher-Gedanke behandelt, in bulk die Nacht vor Cutover bewegt und gebetet. Dieser Guide existiert, um das Pattern zu ändern.
Ob du ein 50TB-Warehouse von On-Prem-Oracle zu BigQuery liftest, eine Kafka-Estate von Bare-Metal zu Amazon MSK re-platformst oder eine Fleet von Spark-ETL-Jobs zu Databricks auf Azure migrierst, die zugrundeliegenden Daten-Strategie-Fragen bleiben: Wann bewegst du was? Wie validierst du? Was ist dein Rollback?
Die vier Phasen einer Daten-Migration
Bevor du eine Zeile Terraform schreibst, mappe deine Migration auf vier diskrete Phasen. Phasen skippen ist, wie Projekte mit Phantom-Daten-Loss um 2 Uhr enden.
flowchart LR
A[Inventory & Classification] --> B[Dual-Write & Shadow Mode]
B --> C[Cutover & Validation]
C --> D[Decommission & Observability]
Phase 1 — Inventory & Classification
Jedes Byte deiner Daten fällt in eine von vier Kategorien:
| Klassifikation | Beschreibung | Migration-Risk | Beispiel |
|---|---|---|---|
| Hot | Aktiv read/written, latency-sensitive | Hoch | OLTP-Tables, Event-Streams |
| Warm | Häufig read, in Batch written | Mittel | Aggregated Reports, Feature-Stores |
| Cold | Archived, selten read | Niedrig | Compliance-Archive, Raw-Event-Logs |
| Transient | Cache, Temp-Tables, in-flight State | N/A (rebuild) | Redis-Caches, Kafka-Consumer-Offsets |
Klassifizier vor Bewegung. Hot-Data braucht Live-Replikation. Cold kann off-Hours bulk-kopiert. Transient wird auf Target rebuilt.
Nutz Kombination aus Query-Logs, Column-Lineage-Tools und manuellen Interviews mit Daten-Consumern, um Inventory zu produzieren. Harbinger Explorer kann das beschleunigen — scannt Metadata across Multi-Cloud-Estates und surfact Dependency-Graphs automatisch.
Phase 2 — Dual-Write & Shadow Mode
Für Hot-Data niemals Hard-Cutover. Stattdessen Dual-Write-Phase: Writes landen simultan auf Source und Target, Reads laufen weiter von Source.
# Beispiel: Debezium-CDC-Connector für Dual-Write-Shadow-Replikation
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaConnector
metadata:
name: orders-cdc-shadow
labels:
strimzi.io/cluster: migration-connect
spec:
class: io.debezium.connector.postgresql.PostgresConnector
tasksMax: 4
config:
database.hostname: source-postgres.internal
database.port: "5432"
database.user: debezium_reader
database.password: ${env:DEBEZIUM_PASSWORD}
database.dbname: orders
database.server.name: orders_shadow
table.include.list: public.orders,public.order_items,public.customers
slot.name: debezium_shadow_slot
publication.autocreate.mode: filtered
# Write zu Shadow-Topic für Target-Ingestion
topic.prefix: shadow.migration
transforms: Reroute
transforms.Reroute.type: io.debezium.transforms.ByLogicalTableRouter
transforms.Reroute.topic.regex: "shadow\.migration\.public\.(.*)"
transforms.Reroute.topic.replacement: "target.ingest.$1"
Während Shadow-Mode laufen Reconciliation-Jobs auf Schedule (stündlich minimum) — vergleichen Row-Counts, Checksums und Sample-Records zwischen Source und Target.
Phase 3 — Cutover & Validation
Cutover ist kein Moment — ein Window. Explizit im Runbook definieren:
#!/bin/bash
# migration-cutover.sh — execute in tmux-Session mit Logging
set -euo pipefail
LOG_FILE="/var/log/migration/cutover-$(date +%Y%m%d-%H%M%S).log"
echo "=== CUTOVER START: $(date -u) ===" | tee -a $LOG_FILE
# 1. Write-Traffic zu Source drainen
echo "Step 1: Enabling write-drain flag in feature flag service..." | tee -a $LOG_FILE
curl -X PATCH https://flags.internal/v1/flags/db_write_drain -H "Content-Type: application/json" -d '{"enabled": true}' | tee -a $LOG_FILE
# 2. Warten auf in-flight Transactions
echo "Step 2: Waiting 30s for in-flight writes..." | tee -a $LOG_FILE
sleep 30
# 3. Finaler Reconciliation-Check
echo "Step 3: Running final reconciliation..." | tee -a $LOG_FILE
python3 /opt/migration/reconcile.py --source postgres://source-db --target bigquery://project/dataset --fail-on-diff
# 4. DNS / Connection-Strings switchen
echo "Step 4: Updating connection string secret in Vault..." | tee -a $LOG_FILE
vault kv put secret/db/orders connection_string="postgresql://target-db.internal:5432/orders" migrated_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
# 5. Reads vom Target enablen
echo "Step 5: Flipping read flag..." | tee -a $LOG_FILE
curl -X PATCH https://flags.internal/v1/flags/db_read_source -H "Content-Type: application/json" -d '{"enabled": false}'
echo "=== CUTOVER COMPLETE: $(date -u) ===" | tee -a $LOG_FILE
Phase 4 — Decommission & Observability
Decommission Source nicht, bis du 30 Tage clean Production-Daten durch Target hast. Set Cross-System-Observability:
# Terraform: CloudWatch-Metric-Alarms für Post-Migration-Daten-Quality
resource "aws_cloudwatch_metric_alarm" "data_freshness" {
alarm_name = "migration-data-freshness-breach"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "MaxAgeMinutes"
namespace = "DataPlatform/Migration"
period = 300
statistic = "Maximum"
threshold = 15
alarm_description = "Data freshness degraded post-migration — possible pipeline stall"
alarm_actions = [aws_sns_topic.oncall.arn]
dimensions = {
Dataset = "orders"
Stage = "production"
}
}
Schema-Evolution-Strategie
Schema-Changes during Migration sind ein Multiplying-Complexity-Factor. Jede Schema-Migration wird zu drei Problemen: Source-Schema, Migration-Mapping, Target-Schema.
Nutze ein Schema-Registry
Ob auf Avro, Protobuf oder JSON-Schema — laufe ein Schema-Registry auf beiden Seiten der Migration und enforce Backward-Compatibility:
# confluent-schema-registry config snippet
schema.compatibility.level: BACKWARD_TRANSITIVE
BACKWARD_TRANSITIVE bedeutet, jede neue Schema-Version kann von allen alten Consumern read werden — kritisch wenn Source und Target Consumer während Shadow-Mode koexistieren.
Column-Mapping-Patterns
| Source-Pattern | Target-Pattern | Migration-Tool |
|---|---|---|
| Camel-Case Columns | Snake-Case | dbt-rename-Macro |
| Implicit Nullability | Explicit NOT NULL | Schema-Migration-Script |
| NUMERIC(18,4) | DECIMAL(18,4) | Type-Casting in Spark |
| Timestamp mit TZ | UTC-normalized TIMESTAMP | Spark-from_utc_timestamp |
| Composite-PK | Surrogate-Key + Composite-Index | dbt-Snapshot |
Daten-Validation-Framework
Der Gold-Standard ist ein Three-Tier-Validation-Approach:
- Structural — Schema matches, keine missing Columns, Typen kompatibel
- Statistical — Row-Counts, Null-Rates, Value-Distributions in Tolerance
- Semantic — Business-Rules halten (z.B. Order-Total = Sum von Line-Items)
# Lightweight-Reconciliation mit PySpark (Great-Expectations-Alternative)
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, count, sum as spark_sum, abs as spark_abs
spark = SparkSession.builder.appName("MigrationReconcile").getOrCreate()
source_df = spark.read.format("jdbc").options(
url="jdbc:postgresql://source-db:5432/orders",
dbtable="public.orders",
user="reader",
password=dbutils.secrets.get("migration", "source-db-password")
).load()
target_df = spark.read.format("bigquery").option("table", "project.dataset.orders").load()
# Structural-Check
assert set(source_df.columns) == set(target_df.columns), "Column mismatch!"
# Statistical-Check
source_stats = source_df.agg(
count("*").alias("row_count"),
spark_sum("total_amount").alias("total_amount_sum")
).collect()[0]
target_stats = target_df.agg(
count("*").alias("row_count"),
spark_sum("total_amount").alias("total_amount_sum")
).collect()[0]
tolerance = 0.001 # 0.1% Toleranz
row_diff_pct = abs(source_stats["row_count"] - target_stats["row_count"]) / source_stats["row_count"]
sum_diff_pct = abs(source_stats["total_amount_sum"] - target_stats["total_amount_sum"]) / source_stats["total_amount_sum"]
assert row_diff_pct < tolerance, f"Row count divergence: {row_diff_pct:.4%}"
assert sum_diff_pct < tolerance, f"Sum divergence: {sum_diff_pct:.4%}"
print("Reconciliation passed")
Rollback-Planung
Jede Migrations-Phase braucht eine Rollback-Procedure dokumentiert vor Cutover-Beginn. Ein Rollback, der nicht in Staging rehearsed wurde, ist kein Rollback-Plan — ein Wunsch.
| Phase | Rollback-Trigger | Rollback-Action | RTO |
|---|---|---|---|
| Shadow-Mode | Replication-Lag > 5 min | CDC disablen, Connector fixen | 10 min |
| Cutover | Error-Rate > 1% | Feature-Flags reverten | 2 min |
| Post-Cutover | Daten-Quality-Breach | Source re-enablen, Shadow re-open | 15 min |
Observability-Stack für Migrations-Projekte
Post-Migration sollte deine Observability antworten: "Liefert die neue Platform Daten mit selber Quality und Freshness wie die alte?"
Instrumentier drei Signal-Typen:
- Pipeline-Latency — p50/p95/p99 End-to-End-Job-Duration
- Daten-Freshness — Max-Age des neuesten Records in Critical-Tables
- Error-Rate — Failed Job-Runs als % der Total-Runs
Wenn du multiple migrated Workloads across Teams managst, wird Platform-Level-View essential. Tools wie Harbinger Explorer geben dir Unified-Operational-View across Cloud-Daten-Assets ohne Per-Team-Instrumentation-Overhead.
FAQ
Wie lange dauert eine typische Daten-Migration? Für 10-50TB Hot-Data mit Shadow-Mode: 3-6 Monate End-to-End. Cold-Data-Bulk-Copy mit klarer Tooling: 2-4 Wochen. Plan einen Monat Post-Cutover-Observability.
Welche Cloud-Region für DACH-Migrationen? eu-central-1 (Frankfurt) ist das pragmatische Default — DSGVO-konform, gute Latency. eu-west-1 (Dublin) für Cross-EU-Redundanz. eu-central-2 (Zürich) für CH-spezifische Compliance.
Kann ich CDC für Cold-Data nutzen? Overkill. Cold-Data: einmaliger Bulk-Copy mit pg_dump/COPY oder AWS-DMS-Full-Load. Spar CDC für Hot-Data.
Was kostet eine Cloud-Migration realistisch? Engineering-Aufwand: 1-3 Senior-Engineers für 3-6 Monate. Tools (Debezium-Hosting, DMS, etc.): 500-2.000 EUR/Monat. Cloud-Cost-Overlap während Shadow-Mode: ~30-50% Aufschlag auf Target-Cost.
Fazit
Eine Cloud-Migrations-Daten-Strategie ist kein One-Time-Document — eine lebende operative Praxis, die sich über Monate von careful, phased Execution spannt. Teams, die succeeden, behandeln Daten-Migration als Product-Delivery: Sie definieren Acceptance-Criteria, laufen Automated-Validation und planen für Failure.
Die Key-Takeaways:
- Klassifizier Daten vor Bewegung
- Nutze Dual-Write-Shadow-Mode für Hot-Data; niemals Hard-Cutover
- Automatisier Reconciliation — manuelle Spot-Checks skalieren nicht
- Definier Rollback-Procedures und rehearse sie
- Bleib in Observability-Mode 30 Tage Post-Cutover bevor Decommission
Harbinger Explorer 7 Tage kostenlos testen — kriege Unified-Visibility across deine Cloud-Daten-Estate, track Migrations-Progress across Teams und fang Daten-Quality-Issues bevor sie Production erreichen.
Stand: 14. Mai 2026.
Geschrieben von
Harbinger Team
Cloud-, Data- und AI-Engineer in DACH. Schreibt seit 2018 über infrastrukturkritische Tech-Entscheidungen — keine Marketing- Folien, sondern echte Trade-offs aus Production-Workloads.
Hat dir das geholfen?
Jede Woche ein neuer Artikel über DACH-Cloud, Data und AI — direkt in dein Postfach. Kein Spam, kein Marketing-Sprech.
Kein Spam. 1-Klick-Abmeldung. Datenschutz bei Loops.so.