Diese Lektion zeigt dir, wie du deine Domäne — Parks, Turbinen, Wetter-Zeitreihen, Strom-Zeitreihen — als Delta-Tabellen modellierst. Am Ende kannst du Tabellenschemata skizzieren, Partitionierungsstrategien auswählen und erklären, wie dieselben Tabellen sowohl deine Produktions-Pipeline als auch deine Analysten bedienen.
Du hast drei miteinander verwobene Anforderungen, die in Delta-Tabellen koexistieren müssen:
Das Tabellenlayout richtig hinzubekommen ist deine wirkungsvollste Architekturentscheidung überhaupt. Ein schlechtes Schema bedeutet, dass du Pipelines später neu schreiben musst. Ein gutes bedeutet, dass dein Team die Plattform kaum bemerkt.
Trenne was Dinge sind von was mit ihnen passiert ist. Das Asset-Register ist eine langsam veränderliche Dimension — es beschreibt Parks und Turbinen, wächst aber nicht mit jedem Zeitstempel.[1]
-- Silver-Schicht: dim_assets
CREATE TABLE silver.dim_assets (
asset_id STRING NOT NULL, -- z. B. "wp-nordsee-1"
asset_type STRING NOT NULL, -- "wind_park" | "turbine"
parent_asset_id STRING, -- NULL bei Parks, Park-ID bei Turbinen
name STRING,
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL,
hub_height_m DOUBLE, -- nur für Turbinen
capacity_kw DOUBLE,
commissioned DATE,
metadata MAP<STRING, STRING> -- flexible Attribute
)
USING DELTA
TBLPROPERTIES ('delta.columnMapping.mode' = 'name');
parent_asset_id erzeugt die Hierarchie. Das ist einfacher als getrennte Park-/Turbinen-Tabellen und erlaubt dir, beide Ebenen mit einem einzigen Join abzufragen. Die Geo-Position einer Turbine ist ihre eigene (nicht die des Parks) — wichtig für die präzise Wetterzuordnung.
Du könntest Turbinen als ARRAY<STRUCT> innerhalb einer Park-Zeile modellieren. Tu es nicht. Es macht Zeitreihen-Joins schmerzhaft, bricht Abfragen je Turbine und verkompliziert die geo-räumliche Wetterzuordnung. Halte es flach und joine, wenn du die Hierarchie brauchst.
Zeitreihen sind deine Faktentabellen — sie wachsen mit jedem Messzyklus. Das Designziel: schnelle Schreibvorgänge aus der Pipeline, schnelle Lesevorgänge für „gib mir den letzten Wert" ebenso wie für „gib mir einen Zeitraum".
-- Silver-Schicht: Wetter-Zeitreihen, auf Asset-Standorte abgebildet
CREATE TABLE silver.weather_ts (
asset_id STRING NOT NULL,
timestamp TIMESTAMP NOT NULL,
forecast_ref_time TIMESTAMP, -- wann die Prognose erstellt wurde
wind_speed_ms DOUBLE,
wind_direction_deg DOUBLE,
temperature_c DOUBLE,
irradiance_wm2 DOUBLE,
pressure_hpa DOUBLE,
humidity_pct DOUBLE,
source_model STRING -- "ecmwf_ifs", "gfs", "icon"
)
USING DELTA
PARTITIONED BY (asset_id)
TBLPROPERTIES (
'delta.autoOptimize.optimizeWrite' = 'true',
'delta.autoOptimize.autoCompact' = 'true'
);
-- Silver-Schicht: Stromerzeugung und Telemetrie je Turbine
CREATE TABLE silver.power_ts (
asset_id STRING NOT NULL, -- auf Turbinenebene
timestamp TIMESTAMP NOT NULL,
power_kw DOUBLE,
rotor_speed_rpm DOUBLE,
nacelle_temp_c DOUBLE,
availability DOUBLE, -- 0,0–1,0
status_code STRING
)
USING DELTA
PARTITIONED BY (asset_id)
TBLPROPERTIES (
'delta.autoOptimize.optimizeWrite' = 'true',
'delta.autoOptimize.autoCompact' = 'true'
);
Die Partitionierung bestimmt, welche Dateien Spark beim Lesen überspringen kann. Machst du sie falsch, scannt jede Abfrage alles. Die Frage bei Zeitreihen lautet: nach Asset, nach Datum oder nach beidem partitionieren?
| Strategie | Abfragemuster Pipeline | Abfragemuster Analyse | Fazit |
|---|---|---|---|
PARTITIONED BY (asset_id) |
✅ Schnell: „neuestes Wetter für Asset X" | ⚠️ Scannt die gesamte Zeit dieses Assets | Gut, wenn Assets moderate Datenmengen haben |
PARTITIONED BY (date) |
⚠️ Scannt alle Assets des heutigen Tages | ✅ Schnell: „letzte 7 Tage über alle Assets" | Gut für flottenweite Auswertungen |
PARTITIONED BY (asset_id, date) |
✅ In beide Richtungen schnell | ✅ In beide Richtungen schnell | ⚠️ Risiko: zu viele winzige Partitionen |
PARTITIONED BY (asset_id) + Z-ORDER BY timestamp |
✅ Schnell: Asset + Zeitraum | ✅ Schnell: Asset + Zeitraum | ✅ Beste Balance für die meisten Fälle |
Für eine Flotte von ca. 10–500 Windparks (mit je 5–100 Turbinen) liefert die Partitionierung nach asset_id eine handhabbare Anzahl Partitionen. Das Z-Ordering nach timestamp legt aktuelle Daten innerhalb jeder Partition zusammen — so werden sowohl „neuester Wert für die Pipeline" als auch „Zeitraum für die Analyse" schnell, ohne Tausende winziger Partitionen zu erzeugen.
Z-Ordering ist die Data-Skipping-Optimierung von Delta Lake. Sie ordnet Daten innerhalb von Dateien physisch nach den angegebenen Spalten um, sodass Abfragen, die auf diese Spalten filtern, ganze Dateien überspringen können.[2]
-- Regelmäßig ausführen (oder per Auto-Optimize), damit Daten beieinander liegen
OPTIMIZE silver.weather_ts ZORDER BY (timestamp);
OPTIMIZE silver.power_ts ZORDER BY (timestamp);
Nach dem Z-Ordering überspringt eine Abfrage wie WHERE asset_id = 'turbine-47' AND timestamp > '2026-07-10' die Partitionen anderer Assets (Partition Pruning) und zusätzlich Dateien mit älteren Zeitstempeln innerhalb dieser Partition (Data Skipping). Beide Zugriffspfade — Pipeline und Analyse — profitieren.
Dieselben Silver-Tabellen bedienen zwei Zielgruppen. So greift jede darauf zu:
| Anliegen | Wie Delta damit umgeht |
|---|---|
| Pipeline schreibt, während Analyst liest | MVCC: Jeder Leser sieht einen konsistenten Snapshot. Keine Sperren. |
| Analyst braucht reproduzierbare Ergebnisse | SELECT * FROM silver.weather_ts VERSION AS OF 42 — auf eine bestimmte Version festlegen. |
| Pipeline braucht „nur neue Daten seit dem letzten Lauf" | Change Data Feed (CDF) oder Watermark-basierte Lesevorgänge auf timestamp. |
| Analyst aggregiert Parkebene aus Turbinen | power_ts mit dim_assets über parent_asset_id joinen, GROUP BY Park. |
| Analyst entdeckt veraltete Daten | DESCRIBE HISTORY silver.weather_ts — zeigt letzten Schreibzeitpunkt und Version. |
Wettermodelle liefern Daten auf einem Gitter (z. B. ECMWF mit 0,25° ≈ 28 km Auflösung). Deine Assets sind Punktstandorte. Die Transformation in der Silver-Schicht muss Gitterzellen auf nahegelegene Assets abbilden.
-- Vereinfacht: nächstgelegener Gitterpunkt je Asset
-- In der Praxis für höhere Genauigkeit bilineare Interpolation verwenden
SELECT
a.asset_id,
w.timestamp,
w.wind_speed_ms,
w.temperature_c,
w.irradiance_wm2
FROM bronze.weather_raw w
JOIN silver.dim_assets a
ON ABS(w.grid_lat - a.latitude) < 0.125 -- halbe Gitterzelle
AND ABS(w.grid_lon - a.longitude) < 0.125
WHERE a.asset_type IN ('wind_park', 'turbine')
Der geo-räumliche Join gehört in die Transformation Bronze → Silver. Ab diesem Punkt sind alle Wetterdaten über asset_id geschlüsselt, nicht mehr über Gitterkoordinaten. Damit müssen Silver und Gold nie etwas über Gittergeometrie wissen — das vereinfacht die gesamte nachgelagerte Logik.
Bei einem kompakten Windpark (Turbinen innerhalb von ca. 2 km) fallen womöglich alle Turbinen in dieselbe Gitterzelle — sie teilen sich also die Wetterdaten. Bei großen Offshore-Clustern, die mehrere Gitterzellen überspannen, erhalten unterschiedliche Turbinen unterschiedliches Wetter. Dein Schema deckt beides ab: Wetter wird je asset_id zugeordnet, und der Join bestimmt die Granularität.
| Anti-Muster | Warum es schadet | Stattdessen |
|---|---|---|
| Eine breite Tabelle mit allen Variablen | Schemaänderungen (neuer Sensor) erfordern vollständiges Neuschreiben; dünn besetzte Spalten verschwenden Speicher | Getrennte Faktentabellen je Domäne (Wetter, Strom, Telemetrie) |
Partitionierung nach (asset_id, date) bei 500 Turbinen |
500 × 365 = 182.500 Partitionen pro Jahr. Das Auflisten der Dateien wird zum Flaschenhals | Nur nach asset_id partitionieren, Z-ORDER nach timestamp |
| Silver-Tabellen für Analysten duplizieren | Datendrift zwischen den Kopien, doppelte Speicherkosten, Governance-Albtraum | Eine Tabelle + MVCC. Analysten per Unity Catalog Leserechte geben |
| Turbinen-Arrays in Park-Zeilen einbetten | Zeitreihen lassen sich ohne Explode/Unnest nicht mit einzelnen Turbinen joinen | Flaches dim_assets mit Parent-Zeiger |
Warum eine flache Tabelle mit einer parent_asset_id-Spalte statt Turbinen als Arrays in Park-Zeilen zu verschachteln?
Welche Partitionierungsstrategie bedient bei 200 Turbinen mit 15-Minuten-Daten Pipeline und Analysten am besten?
Wo in der Medaillon-Architektur gehört der geo-räumliche Join (Wettergitter → Asset-Standort) hin?
Wie aggregiert ein Analyst Strom auf Turbinenebene zur Parkebene, ohne eine separate Park-Tabelle?
📖 Empfohlene Lektüre
Delta Lake best practices — Databricks-Dokumentation. Behandelt Partitionierung, Z-Ordering, Auto-Optimize und Tabellendesign. Konzentriere dich auf die Abschnitte „Choose the right partition column" und „Compact files with optimize" — sie unterfüttern die Entscheidungen dieser Lektion direkt.
Du weißt jetzt, wie du Assets und Zeitreihen in Delta modellierst. Die nächste Lektion behandelt Ingestion-Pipelines — wie Wetterdaten tatsächlich ankommen (GRIB2-Dateien, API-Abrufe), geparst werden und in Bronze landen. Wir schauen uns Zeitpläne, inkrementelle Ladevorgänge und das Werkzeug (cfgrib, xarray) an, das Wetterdaten für Spark zugänglich macht.
Fragen? Willst du tiefer in Partitionierung, Z-Ordering oder den geo-räumlichen Join einsteigen? Frag deinen Lehrer — ich kann dir detailliertere Beispiele zeigen oder Abwägungen für deine konkrete Flottengröße besprechen.
Rechtliches: Impressum