Inhaltsverzeichnis10 Abschnitte
Das Dashboard zeigt 0 € Umsatz für die letzten 48 Stunden. Die Pipeline hat nicht failed. Kein Alert. Eine WHERE-Klausel droppt still alle Zeilen wegen NULL-Wert upstream. Data Observability existiert, um genau das zu fangen.
Observability in Software heißt: Du kannst den inneren Zustand eines Systems aus externen Outputs verstehen. Auf Daten angewandt: Sind meine Daten vollständig? Frisch? Korrekt? Hat sich unerwartet etwas geändert?
TL;DR
- Fünf Säulen: Freshness, Volume, Schema, Distribution, Lineage.
- Data-Testing (dbt-Tests, GE) prüft Assertions, die du vorab definierst — Observability detektiert kontinuierlich Anomalien, die du nicht vorhergesehen hast.
- Stack ohne Tool-Sprawl: dbt + Elementary deckt alle fünf Säulen ab.
- Schwerster Teil: Disziplin, auf Alerts zu reagieren. Alerts ohne Action = Noise.
Die fünf Säulen
| Säule | Frage | Beispiel-Failure |
|---|---|---|
| Freshness | Wurde Daten kürzlich aktualisiert? | Pipeline lief, lud 0 Zeilen; stiller Delay |
| Volume | Erwartete Datenmenge da? | Tabelle verliert über Nacht 30 % Rows |
| Schema | Hat sich die Struktur unerwartet geändert? | Spalte umbenannt, Typ geändert |
| Distribution | Statistisch normal? | Null-Rate springt von 0,1 % auf 40 % |
| Lineage | Woher kommt es, was hängt dran? | Upstream-Change bricht 12 Downstream-Models |
Freshness Monitoring
Einfachste Säule, oft die wertvollste.
-- PostgreSQL: Freshness-Check als geplanter Query
SELECT
CASE
WHEN MAX(updated_at) < NOW() - INTERVAL '2 hours'
THEN 'STALE' ELSE 'FRESH'
END AS freshness_status,
MAX(updated_at) AS last_update,
NOW() - MAX(updated_at) AS data_age
FROM warehouse.orders;
In dbt:
sources:
- name: raw
schema: raw_data
tables:
- name: orders
freshness:
warn_after: { count: 1, period: hour }
error_after: { count: 3, period: hour }
loaded_at_field: updated_at
Anti-Pattern: Freshness nur an der Quelle prüfen. Eine Pipeline kann erfolgreich laufen und am Target leere Daten produzieren, wenn Filter/Join alles killt. Auch am Ziel prüfen.
Volume Monitoring
Detektiert unerwartete Drops oder Spikes. Ein plötzlicher Zero-Row-Load bedeutet fast nie "die Quelle hatte keine Daten" — meist ist etwas kaputt.
import sqlalchemy as sa
engine = sa.create_engine("postgresql://...")
def check_volume_anomaly(table: str, date: str, threshold: float = 0.3) -> dict:
with engine.connect() as conn:
result = conn.execute(sa.text(
"WITH daily_counts AS ("
" SELECT DATE(loaded_at) AS load_date, COUNT(*) AS row_count"
f" FROM {table}"
" WHERE loaded_at >= NOW() - INTERVAL '8 days'"
" GROUP BY DATE(loaded_at)"
"),"
"baseline AS (SELECT AVG(row_count) AS avg_count FROM daily_counts WHERE load_date < :check_date),"
"today AS (SELECT row_count AS today_count FROM daily_counts WHERE load_date = :check_date)"
"SELECT today.today_count, baseline.avg_count,"
" ABS(today.today_count - baseline.avg_count) / NULLIF(baseline.avg_count, 0) AS deviation"
"FROM today, baseline"
), {"check_date": date}).fetchone()
return { "deviation_pct": round(float(result.deviation or 0) * 100, 1) }
Volume-Monitoring funktioniert als relativer Check (Abweichung vom Baseline), nicht als Absolut-Threshold. Black-Friday-Volumen ist 10× Dienstag. Fester Threshold feuert an jedem Peak.
Schema Monitoring
Schema-Changes sind ein stiller Killer. Spalte wird umbenannt, neue NOT-NULL-Constraint, Typ wechselt von VARCHAR auf INT. Keine Errors — nur korrumpierte Outputs downstream.
import json, sqlalchemy as sa
def snapshot_schema(engine, table: str) -> dict:
with engine.connect() as conn:
columns = conn.execute(sa.text(
"SELECT column_name, data_type, is_nullable, character_maximum_length"
" FROM information_schema.columns WHERE table_name = :table"
" ORDER BY ordinal_position"
), {"table": table}).fetchall()
return {row.column_name: dict(row._mapping) for row in columns}
def compare_schemas(old: dict, new: dict) -> list:
changes = []
for col in old:
if col not in new:
changes.append(f"DROPPED: '{col}'")
elif old[col]["data_type"] != new[col]["data_type"]:
changes.append(f"TYPE: '{col}' {old[col]['data_type']} → {new[col]['data_type']}")
for col in new:
if col not in old:
changes.append(f"ADDED: '{col}'")
return changes
Schema-Snapshots als Files oder Metadata-Table speichern. Vergleich auf jedem Pipeline-Run.
Distribution Monitoring
Tracked statistische Eigenschaften: Null-Raten, Distinct-Counts, Min/Max, Mean/Median — und alertet bei signifikanten Abweichungen.
SELECT
'orders' AS table_name,
'amount' AS column_name,
NOW() AS profiled_at,
COUNT(*) AS total_rows,
COUNT(amount) AS non_null_count,
ROUND(100.0 * (COUNT(*) - COUNT(amount)) / COUNT(*), 2) AS null_pct,
MIN(amount) AS min_val,
MAX(amount) AS max_val,
AVG(amount) AS mean_val,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount) AS median_val,
STDDEV(amount) AS stddev_val
FROM warehouse.orders
WHERE order_date = CURRENT_DATE;
Power liegt im Speichern der Profile über Zeit und Vergleich zur Baseline. Null-Rate-Sprung von 0,2 % auf 15 % ist starkes Signal, dass upstream was passiert ist.
Lineage Monitoring
Beantwortet: Woher? Was hängt dran?
raw.orders ────→ stg_orders ────→ fct_orders ────→ dim_revenue_daily
raw.customers ─→ stg_customers ──→ fct_orders ─────→ report_executive_kpis
↑
dim_revenue_daily ────────┘
report_executive_kpis hängt an drei Upstream-Models — das fragilste Endpoint.
dbt generiert Lineage automatisch aus ref() und source() Calls. Für Non-dbt-Pipelines: OpenLineage, Marquez oder DataHub via Instrumentierung.
Tool-Landschaft
| Tool | Freshness | Volume | Schema | Distribution | Lineage | SaaS |
|---|---|---|---|---|---|---|
| Monte Carlo | Ja | Ja | Ja | Ja | Ja | Ja |
| Soda Cloud | Ja | Ja | Ja | Ja | Teilweise | Ja |
| dbt + Elementary | Ja | Ja | Ja | Ja | Ja (via dbt) | Ja (Elementary) |
| Great Expectations | Teilweise | Ja | Teilweise | Ja | – | Self-host |
| OpenLineage/Marquez | – | – | – | – | Ja | Self-host |
| Re:data | Ja | Ja | Ja | Teilweise | Teilweise | Self-host |
Für die meisten Teams am Anfang: dbt mit Elementary deckt alle fünf Säulen ab. Für größere Multi-Engine-Stacks: Monte Carlo.
Minimal-Observability-Stack ohne Tool-Sprawl
- Freshness →
dbt source freshness(kostet nichts) - Volume →
dbt test row_count > 0+ Singular-Tests mit Deviation-Logik - Schema → dbt Schema-Tests (
not_null,accepted_values) + Snapshot-Skript - Distribution → Elementary (OSS dbt-Paket)
- Lineage →
dbt docs generate
Kostet nichts, braucht keine zusätzliche Infrastruktur, deckt die häufigsten Failures.
FAQ
Lohnt sich Monte Carlo für Mittelstand-DACH? Selten unter 50 Data-Praktiker:innen. Über das reicht dbt + Elementary plus ein paar Custom Scripts.
Wie DSGVO-konform Schema-Snapshots speichern? Schema-Snapshots enthalten keine personenbezogenen Daten. Trotzdem EU-Region (eu-central-1, West Europe).
Reicht Pipeline-Alerts für Observability? Nein. Pipeline kann erfolgreich laufen und stale, schemaverletzte oder unverteilte Daten liefern.
Was ist das Hauptproblem in der Praxis? Alert-Fatigue. 50 Alerts/Tag = alle ignoriert. Wenig, hochsignal-Alerts mit klarem Owner.
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.