Inhaltsverzeichnis18 Abschnitte
- Warum Delta-Tabellen mit der Zeit degradieren
- 1. OPTIMIZE — File-Compaction
- 2. Z-Ordering — Daten-Co-Location für schnelleres Filtering
- 3. Liquid Clustering — die moderne Alternative
- 4. Partitions-Strategie
- 5. Auto-Optimize (Auto-Compaction + Optimized-Writes)
- 6. Bloom-Filter-Indizes
- 7. VACUUM — Storage zurückgewinnen
- 8. Data-Skipping-Statistiken
- 9. Tabellen-Health monitoren
- 10. Optimization-Checkliste
- FAQ
- Soll ich Liquid Clustering oder Z-Order wählen?
- Wie tracke ich, ob meine Optimierungen wirken?
- Wirkt VACUUM auf DSGVO-Löschungen?
- Kann ich OPTIMIZE auf Streaming-Tabellen laufen lassen?
- Wie verhält sich Photon mit OPTIMIZE?
- Schluss
TL;DR: Delta-Tabellen brauchen kontinuierliche Pflege. Auto-Compaction + Optimized-Writes als Foundation, wöchentlich OPTIMIZE+ZORDER auf den meistabgefragten Tabellen, Liquid Clustering für Tabellen mit ändernden Access-Patterns, regelmäßig VACUUM. Gut optimierte Tabellen sind 5–50× schneller als unoptimierte auf derselben Hardware.
Delta Lake ist die Storage-Schicht, die den Databricks-Lakehouse zum Laufen bringt. Aber out-of-the-box, ohne Tuning, akkumulieren Delta-Tabellen Tausende kleiner Files, leiden unter schlechtem Data-Skipping und liefern Query-Resultate Größenordnungen langsamer als sie sollten.
Dieser Guide deckt jeden verfügbaren Optimization-Hebel ab — von Basis-File-Compaction bis zum neueren Liquid-Clustering — mit konkreten SQL- und PySpark-Beispielen, die du heute laufen lassen kannst.
Warum Delta-Tabellen mit der Zeit degradieren
Wenn du Daten in Delta streamst oder häufige kleine Batch-Writes machst, erzeugst du viele kleine Parquet-Files. Query-Engines müssen jedes File öffnen und scannen — Scans werden teuer. Das ist das Small-Files-Problem und der #1-Grund, warum Delta-Tabellen langsamer werden.
Eine gesunde Delta-Tabelle hat typischerweise Files im 128 MB–1 GB-Bereich. So prüfst du den Stand:
DESCRIBE DETAIL prod.gold.transactions;
-- Look at: numFiles, sizeInBytes, avgFileSizeBytes
from delta.tables import DeltaTable
dt = DeltaTable.forName(spark, "prod.gold.transactions")
detail = dt.detail()
detail.select("numFiles", "sizeInBytes").show()
1. OPTIMIZE — File-Compaction
OPTIMIZE schreibt kleine Files in größere um (Ziel: ~1 GB by default). Auf Tabellen laufen lassen, die häufige Streaming- oder Micro-Batch-Writes bekommen.
-- Basic optimize
OPTIMIZE prod.gold.transactions;
-- Optimize a specific partition (faster for large tables)
OPTIMIZE prod.gold.transactions
WHERE date >= '2024-01-01';
Wann OPTIMIZE laufen lassen:
- Nach Streaming-Jobs, die häufig schreiben
- Als Nightly-Maintenance-Job
- Vor teuren analytischen Queries
# Schedule via Databricks Workflows
from pyspark.sql import SparkSession
spark = SparkSession.builder.getOrCreate()
tables_to_optimize = [
"prod.gold.transactions",
"prod.gold.user_events",
"prod.silver.raw_orders"
]
for table in tables_to_optimize:
print(f"Optimizing {table}...")
spark.sql(f"OPTIMIZE {table}")
print(f"Done: {table}")
2. Z-Ordering — Daten-Co-Location für schnelleres Filtering
Z-Ordering co-lokalisiert physisch verwandte Daten in denselben Files. Mit Delta-Data-Skipping kombiniert kann die Engine ganze Files überspringen, wenn auf Z-geordnete Spalten gefiltert wird.
-- Z-order by commonly filtered columns
OPTIMIZE prod.gold.transactions
ZORDER BY (customer_id, transaction_date);
Regeln für gute Z-Order-Spalten-Wahl:
| Kriterium | Erklärung |
|---|---|
| Hohe Cardinality | Mehr distinct Values = besseres Skipping (z. B. customer_id) |
| Häufig gefiltert | Spalten in WHERE-Klauseln profitieren am meisten |
| Auf 1–4 Spalten begrenzen | Effektivität degradiert mit mehr Spalten |
| Keine Partition-Spalten Z-Ordern | Redundant — Partitionierung co-lokalisiert schon |
Checken, ob Z-Order wirkt:
-- Look at numFilesSkipped in query plan
EXPLAIN COST
SELECT * FROM prod.gold.transactions
WHERE customer_id = 'CUST_12345'
AND transaction_date = '2024-03-15';
3. Liquid Clustering — die moderne Alternative
Eingeführt in Databricks Runtime 13.3+ ersetzt Liquid Clustering sowohl Partitionierung als auch Z-Ordering durch einen inkrementellen, schreibfreundlichen Ansatz. Files werden träge während OPTIMIZE-Läufen geclustert, statt das Datenlayout zum Write-Zeitpunkt zu erzwingen.
-- Create a table with liquid clustering
CREATE TABLE prod.gold.events_clustered
CLUSTER BY (user_id, event_type)
AS SELECT * FROM prod.silver.raw_events;
-- Or add clustering to an existing table
ALTER TABLE prod.gold.transactions
CLUSTER BY (customer_id, transaction_date);
-- Run OPTIMIZE to trigger clustering (incremental — only rewrites what changed)
OPTIMIZE prod.gold.transactions;
Liquid Clustering vs Z-Order vs Partitionierung:
| Feature | Partitionierung | Z-Order | Liquid Clustering |
|---|---|---|---|
| Write-Performance | Schnell (statisches Layout) | Langsam (Full-Rewrite) | Schnell (inkrementell) |
| Spalten-Flexibilität | Fest bei Creation | Änderbar | Änderbar |
| High-Cardinality-Support | Schlecht | Gut | Exzellent |
| Automatische Wartung | Nein | Nein | Ja (mit OPTIMIZE) |
| DBR-Anforderung | Beliebig | Beliebig | 13.3+ |
Liquid Clustering nutzen wenn:
- Du Clustering-Spalten über die Zeit ändern willst
- Die Tabelle kontinuierliche Writes bekommt (Streaming)
- Cardinality zu hoch für Partitionierung
4. Partitions-Strategie
Partitionierung erzeugt physische Directory-Trennung. Mächtig, kann aber nach hinten losgehen.
-- Good partition: low cardinality, frequently filtered
CREATE TABLE prod.gold.sales
PARTITIONED BY (year, month)
AS SELECT *, year(sale_date) AS year, month(sale_date) AS month
FROM prod.silver.raw_sales;
-- Query that benefits from partition pruning
SELECT SUM(amount) FROM prod.gold.sales
WHERE year = 2024 AND month = 3;
-- -> Only reads 2024/03 partition directory
Partitionierungs-Anti-Patterns:
-- BAD: High cardinality partitioning -> Millionen winziger Directories
CREATE TABLE prod.gold.events
PARTITIONED BY (user_id) -- NEVER do this for high-cardinality columns
...
-- BAD: Over-partitioning
CREATE TABLE prod.gold.transactions
PARTITIONED BY (year, month, day, hour) -- Too granular for most batch workloads
Faustregel: Nach Spalten mit < 10.000 distinct Values partitionieren, auf die fast jede Query filtert. Für alles andere: Z-Order oder Liquid Clustering.
5. Auto-Optimize (Auto-Compaction + Optimized-Writes)
Auf Tabellen- oder Session-Ebene aktivieren, um das Small-Files-Problem schon beim Write zu reduzieren:
# Enable for a specific table
spark.sql('''
ALTER TABLE prod.silver.raw_events
SET TBLPROPERTIES (
'delta.autoOptimize.optimizeWrite' = 'true',
'delta.autoOptimize.autoCompact' = 'true'
)
''')
# Or at the session level for all writes
spark.conf.set("spark.databricks.delta.optimizeWrite.enabled", "true")
spark.conf.set("spark.databricks.delta.autoCompact.enabled", "true")
- Optimized Writes: Coalesce vor dem Write, um richtig dimensionierte Files zu produzieren
- Auto Compaction: Nach jedem Write läuft im Hintergrund eine leichtgewichtige Compaction
Das ersetzt keine periodischen OPTIMIZE-Läufe, reduziert aber die Degradation zwischen Läufen deutlich.
6. Bloom-Filter-Indizes
Für High-Cardinality-String-Spalten (UUIDs, E-Mails, Identifier), auf die du mit Equality-Conditions filterst, liefern Bloom-Filter Sub-File-Skipping:
CREATE BLOOMFILTER INDEX ON TABLE prod.gold.users
FOR COLUMNS (user_uuid OPTIONS (fpp=0.01), email OPTIONS (fpp=0.01));
fpp ist die False-Positive-Probability. Niedriger = genauer, aber größerer Index. 0,01 (1 %) ist ein guter Default.
-- This query now benefits from bloom filter skipping
SELECT * FROM prod.gold.users
WHERE user_uuid = '550e8400-e29b-41d4-a716-446655440000';
7. VACUUM — Storage zurückgewinnen
Jeder DELETE, UPDATE oder OPTIMIZE hinterlässt alte Parquet-Files fürs Time-Travel. VACUUM entfernt sie:
-- Default: delete files older than 7 days (168 hours)
VACUUM prod.gold.transactions;
-- Custom retention (NEVER go below 7 days if you use streaming readers)
VACUUM prod.gold.transactions RETAIN 240 HOURS;
-- Dry run to see what would be deleted
VACUUM prod.gold.transactions DRY RUN;
Produktive VACUUM-Strategie:
- Wöchentlich VACUUM auf allen großen Tabellen
- Mindestens 7 Tage Retention behalten
- VACUUM nach OPTIMIZE (damit kompaktierte Files nicht sofort gevacuumt werden)
8. Data-Skipping-Statistiken
Delta sammelt automatisch Min/Max-Statistiken für die ersten 32 Spalten in einer Tabelle. Du kannst Stats-Collection erzwingen oder ändern, welche Spalten getrackt werden:
-- Increase the number of columns with stats (careful: increases write overhead)
ALTER TABLE prod.gold.transactions
SET TBLPROPERTIES ('delta.dataSkippingNumIndexedCols' = 10);
-- Force stats recomputation
ANALYZE TABLE prod.gold.transactions COMPUTE STATISTICS FOR ALL COLUMNS;
9. Tabellen-Health monitoren
Einfache Health-Check-Query, um Tabellen zu monitoren:
def check_table_health(table_name: str) -> dict:
detail = spark.sql(f"DESCRIBE DETAIL {table_name}").collect()[0]
num_files = detail["numFiles"]
size_bytes = detail["sizeInBytes"]
avg_file_size_mb = (size_bytes / num_files / 1_000_000) if num_files > 0 else 0
return {
"table": table_name,
"num_files": num_files,
"total_size_gb": round(size_bytes / 1_000_000_000, 2),
"avg_file_size_mb": round(avg_file_size_mb, 2),
"needs_optimize": avg_file_size_mb < 64, # Flag if avg file < 64MB
}
for table in ["prod.gold.transactions", "prod.gold.users"]:
health = check_table_health(table)
print(health)
Mit Harbinger Explorer zur Verwaltung mehrerer Databricks-Workspaces machst du diese Tabellen-Health-Metadaten in einem einheitlichen Dashboard über alle Umgebungen sichtbar — und identifizierst, welche Tabellen OPTIMIZE-Läufe brauchen, ohne in jeden Workspace einzeln zu schauen.
10. Optimization-Checkliste
| Aufgabe | Frequenz | Command |
|---|---|---|
| OPTIMIZE (Compaction) | Täglich/Wöchentlich | OPTIMIZE <table> |
| ZORDER (falls anwendbar) | Mit OPTIMIZE | OPTIMIZE <table> ZORDER BY (col) |
| VACUUM | Wöchentlich | VACUUM <table> |
| Bloom-Filter-Rebuild | Nach Schema-Änderungen | CREATE BLOOMFILTER INDEX ... |
| ANALYZE-Statistiken | Monatlich | ANALYZE TABLE ... COMPUTE STATISTICS |
FAQ
Soll ich Liquid Clustering oder Z-Order wählen?
Für neue Tabellen auf DBR 13.3+: Liquid Clustering. Es ist inkrementell, ändert sich später leichter, und der Write-Overhead ist niedriger. Z-Order bleibt für ältere Runtimes oder strikt statische Layouts sinnvoll.
Wie tracke ich, ob meine Optimierungen wirken?
DESCRIBE DETAIL für File-Count und Avg-File-Size, EXPLAIN COST für numFilesSkipped in Query-Plans. Vor/nach Wartung vergleichen.
Wirkt VACUUM auf DSGVO-Löschungen?
Direkt nein — DELETE ist der Trigger. Aber: ohne VACUUM bleiben gelöschte Records via Time-Travel rekonstruierbar. Daher: nach DSGVO-DELETE immer VACUUM mit kurzer Retention.
Kann ich OPTIMIZE auf Streaming-Tabellen laufen lassen?
Ja — Auto-Compaction + Optimized-Writes als Default lassen, periodisches OPTIMIZE für tieferen Cleanup. Auf laufende Streams achten (sie könnten alte Files referenzieren).
Wie verhält sich Photon mit OPTIMIZE?
Photon beschleunigt OPTIMIZE und folgende Reads massiv. Auf Photon-fähigen Clustern: OPTIMIZE läuft 2–5× schneller, Liquid-Clustering-Operationen profitieren ähnlich.
Schluss
Delta-Lake-Optimierung ist keine einmalige Aufgabe — es ist eine laufende Praxis. Mit Auto-Compaction und Optimized-Writes anfangen, um Degradation an der Quelle zu verhindern. Wöchentliche OPTIMIZE+ZORDER-Jobs auf den meist-abgefragten Tabellen. Liquid Clustering für Tabellen mit sich entwickelnden Access-Patterns. Und immer VACUUM, um Storage-Kosten in Schach zu halten.
Eine gut optimierte Delta-Tabelle sollte 5–50× schnellere Resultate liefern als eine unoptimierte auf derselben Hardware.
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.