Ebook · Hoofdstuk 9 van 10
Monitoring en Onderhoud
Een datawarehouse dat niemand bewaakt is een datawarehouse dat morgen kapot is. Operations als discipline.
Observability voor data
Software-engineering heeft observability volwassen gemaakt: logs, metrics, traces. Data engineering loopt achter, maar haalt in. Een modern monitoring-stack voor een DWH dekt vier pijlers: freshness, volume, schema en distributie. Plus klassieke pipeline-orchestratie monitoring.
Disaster recovery: RPO en RTO voor data
Hoe lang mag je DWH stilliggen voordat het pijn doet? En hoeveel data mag je verliezen? Twee getallen die je expliciet moet hebben:
- RPO (Recovery Point Objective) — hoeveel data mag je verliezen in een ramp? RPO = 1 uur betekent dat je in het ergste geval het laatste uur opnieuw moet ingest, geen meer.
- RTO (Recovery Time Objective) — hoe snel moet je weer draaien na een storing? RTO = 4 uur betekent dat het systeem binnen 4 uur volledig hersteld moet zijn.
Cloud-warehouses bieden hier ingebouwde antwoorden. Snowflake's time travel (tot 90 dagen) en fail-safe (7 extra dagen) ondervangen de meeste data-corruptie scenario's. BigQuery heeft 7 dagen time travel. Microsoft Fabric en Delta Lake gebruiken Delta time travel. Dit alles dekt vooral logische rampen (verkeerde DELETE, foute deploy); voor regio-uitval heb je cross-region replicatie nodig — beschikbaar maar duur, alleen activeren als je RTO/RPO het vragen.
De vier pijlers van data observability
- Freshness — is de data recent genoeg? Wanneer is de laatste rij geladen?
- Volume — krijgen we het verwachte aantal rijen? Plotseling 0 rijen of 10x meer is verdacht.
- Schema — zijn er kolommen toegevoegd, verdwenen, of van type veranderd?
- Distributie — verandert het percentage NULL's, gemiddeldes, of categorieverdelingen plotseling?
Pillar 1: freshness monitoring
Het meest voorkomende incident is "data is oud" — een failed pipeline die niemand opmerkte. dbt heeft hier ingebouwde support:
# sources.yml
sources:
- name: app
schema: raw_app
tables:
- name: orders
loaded_at_field: updated_at
freshness:
warn_after: {count: 6, period: hour}
error_after: {count: 12, period: hour}
- name: customers
loaded_at_field: updated_at
freshness:
warn_after: {count: 24, period: hour}
error_after: {count: 48, period: hour}
-- Run als onderdeel van CI/CD of orkestrator
-- $ dbt source freshness
-- Faalt als data te oud is, met een nette samenvatting
Voor non-dbt projecten een eenvoudig SQL-statement:
-- Geeft > 0 als de data te oud is
SELECT
'orders' AS table_name,
MAX(updated_at) AS last_loaded,
DATEDIFF('hour', MAX(updated_at), CURRENT_TIMESTAMP) AS hours_old
FROM raw_app.orders
HAVING hours_old > 6;
Pillar 2: volume monitoring
Verwacht aantal nieuwe rijen per dag verschilt per tabel — maar drift uit een baseline is altijd verdacht.
-- Vergelijking met 7-daags rolling gemiddelde
WITH daily_loads AS (
SELECT DATE_TRUNC('day', load_ts) AS load_date,
COUNT(*) AS rows_loaded
FROM stg_orders
GROUP BY 1
),
baseline AS (
SELECT load_date, rows_loaded,
AVG(rows_loaded) OVER (
ORDER BY load_date
ROWS BETWEEN 7 PRECEDING AND 1 PRECEDING
) AS avg_7d
FROM daily_loads
)
SELECT *
FROM baseline
WHERE load_date = CURRENT_DATE - 1
AND ABS(rows_loaded - avg_7d) / NULLIF(avg_7d, 0) > 0.5; -- > 50% drift
Pillar 3: schema drift
Een producer zet stilletjes order_total om naar order_total_eur en je dashboard wijst nullen. Schema-drift detectie kan zo simpel als een snapshot zijn:
-- Bewaar dagelijks de huidige schema's
INSERT INTO meta.schema_snapshots
SELECT CURRENT_DATE,
table_schema,
table_name,
LISTAGG(column_name || ':' || data_type, ',') WITHIN GROUP (ORDER BY ordinal_position)
FROM information_schema.columns
WHERE table_schema NOT IN ('INFORMATION_SCHEMA','PG_CATALOG')
GROUP BY 1, 2, 3;
-- Detecteer veranderingen ten opzichte van gisteren
SELECT today.table_schema, today.table_name,
today.cols AS today_cols,
yest.cols AS yest_cols
FROM meta.schema_snapshots today
JOIN meta.schema_snapshots yest
ON today.table_schema = yest.table_schema
AND today.table_name = yest.table_name
AND yest.snapshot_date = today.snapshot_date - 1
WHERE today.cols <> yest.cols;
Pillar 4: distribution drift
Stel: 95% van je orders heeft normaal status='completed'. Vandaag is het 60%. Je dataset is technisch in orde, maar er zit een upstream probleem.
SELECT
order_status,
COUNT(*) * 1.0 / SUM(COUNT(*)) OVER () AS pct_today,
LAG(COUNT(*) * 1.0 / SUM(COUNT(*)) OVER ()) OVER (ORDER BY snapshot_date) AS pct_yesterday
FROM fact_orders
WHERE load_date IN (CURRENT_DATE, CURRENT_DATE - 1)
GROUP BY snapshot_date, order_status;
Tools die dit automatiseren met ML-baselining: Monte Carlo, Bigeye, Soda, Anomalo. Voor lichtere setups: Elementary (open source dbt-package).
Pipeline / orchestrator monitoring
Buiten data zelf moeten ook je pipelines bewaakt worden:
- Run-status (success / failed / overdue)
- Run-duur drift (gisteren 10 min, vandaag 90 min?)
- Retry-aantallen
- Compute-kosten per run
Airflow biedt een eenvoudige Slack-callback bij fouten:
from airflow.operators.python import PythonOperator
from urllib import request
import json, os
def slack_on_failure(context):
payload = {
"text": f":x: *{context['task_instance'].dag_id}* — task `{context['task_instance'].task_id}` failed.\n"
f"Run: {context['run_id']}\n"
f"Log: {context['task_instance'].log_url}"
}
req = request.Request(
os.environ["SLACK_WEBHOOK"],
data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"})
request.urlopen(req)
# DAG default args
default_args = {
"on_failure_callback": slack_on_failure,
"retries": 2,
"retry_delay": timedelta(minutes=5),
}
SLI's, SLO's en SLA's voor data
Net als webservices kunnen data-producten doelen krijgen:
- SLI (Indicator) — meetbaar getal: bijvoorbeeld "% queries onder 5 seconden".
- SLO (Objective) — interne doelstelling: bijvoorbeeld 99% < 5 sec.
- SLA (Agreement) — formele afspraak met afnemer met consequenties.
Voor een datawarehouse zijn typische SLI's:
- Freshness — % runs op tijd
- Completeness — % verwachte rijen geladen
- Query latency P95
- Dashboard beschikbaarheid
Alert design — niet schreeuwen
De grootste valkuil is alert fatigue: te veel meldingen, niemand kijkt nog. Regels:
- Alert op symptoom, niet op oorzaak — "data is > 12 uur oud" is bruikbaar; "Airflow task X failed" is meestal ruis.
- Severity-tiers — INFO (e-mail), WARN (Slack-channel), CRIT (PagerDuty).
- Snooze / auto-resolve — een alert die zichzelf herstelt moet zichzelf sluiten.
- Eigenaarschap — elke alert routeert naar een team, niet naar "@channel".
Test je alerts
Een alert die nog nooit gevuurd heeft, weet je niet of hij werkt. Disable af en toe een pipeline op de staging environment om te zien of de juiste mensen ge-paged worden. Geen surprise op zaterdagochtend.
Onderhoud: de stille killer
Een DWH dat niet onderhouden wordt rot weg. Plan periodiek:
- Storage cleanup — drop temp tabellen, ongebruikte tabellen, oude development schemas.
- Statistics refresh — Redshift
ANALYZE, BigQuery automatisch, Snowflake automatisch. - Vacuum — Redshift
VACUUM SORTop grote tabellen. - Re-cluster — Snowflake auto-clustering kostmonitoring.
- Permission audits — kwartaalreview wie nog toegang heeft.
- Documentation review — tabellen die niemand meer kent.
On-call rotatie en runbooks
Wanneer een DWH bedrijfskritisch wordt, hoort er een on-call rotatie bij. Twee elementen die het verschil maken tussen rustige en woelige weken:
- Runbooks per veelvoorkomende alert — een korte stap-voor-stap die de on-call door de eerste vijf minuten loodst. "Pipeline Y is failed: 1) check de log, 2) vergelijk met laatste succesvolle run, 3) ofwel rerun, ofwel pagina-wijzig de eigenaar." Geen lange architectuurstukken — operationele acties.
- Heldere escalatiepaden — wie wordt gewekt om 3:00 uur en bij welke severity? Documenteer dit in PagerDuty/Opsgenie zelf, niet alleen in Confluence. De on-call moet binnen 60 seconden weten wat te doen.
Capacity planning en groei-monitoring
Een DWH groeit. Wat vandaag past in een Medium-warehouse vraagt over een jaar een Large. Monitor minimaal:
- Storage-groei per schema, week-over-week — onverwachte spikes wijzen op een loop, log-tabel die explodeert of een ongepaste full-load.
- Query-volume en concurrency over tijd — als BI-gebruikers ineens drie keer zoveel queries draaien, plan capacity-uitbreiding voordat queues groeien.
- Top-N gebruikers en pipelines op kosten — de meeste kosten komen typisch van een handvol bronnen. Kennen wie ze zijn maakt fairness en optimalisatie mogelijk.
- Onbenutte capacity — overgedimensioneerde resources zijn verspilling. Snowflake auto-suspend, BigQuery flex slots, Redshift Serverless RPU-tuning kunnen vrijwel direct geld besparen.
Postmortems
Na elk significant incident: een blame-free postmortem met:
- Tijdlijn (wanneer ontdekt, wanneer hersteld, hoe lang impact?)
- Root cause analysis (de "5 whys")
- Wat ging goed — herhaalbare reflexen
- Wat ging slecht — verbeterpunten
- Concrete actiepunten met eigenaar en deadline
Niet om te wijzen, maar om te leren. Een postmortem die voor de derde keer dezelfde root cause noteert verdient een actie op systeemniveau, niet weer een training.
Documentatie als onderhoud
Documentatie hoort bij onderhoud, niet bij features. Tools die het meegroeien afdwingen: dbt docs (auto-generated), DataHub, Atlan. Minimaal vereist per tabel:
- Owner
- Beschrijving in business-taal
- Bron(nen)
- Refresh-frequentie en SLA
- Bekende beperkingen
Key takeaways
- Vier pijlers: freshness, volume, schema, distributie. Dek ze allemaal.
- Alert op user-impact (data oud, dashboard kapot), niet op interne oorzaak.
- SLI's en SLO's geven data-producten dezelfde discipline als web-producten.
- Pipeline-monitoring zit naast data-monitoring — beide nodig.
- Postmortems zijn blame-free en eindigen met actiepunten met eigenaar.
- Documentatie en cleanup zijn onderdeel van onderhoud, niet "later".
- Capacity planning vóór de pijngrens, niet erna.
Veelgestelde vragen
Wat zijn de vier pijlers van data observability?
Freshness (recent genoeg?), volume (juiste aantal rijen?), schema (kolommen ongewijzigd?) en distributie (verandert NULL-percentage of categorieverdeling?). Plus klassieke pipeline-orchestratie monitoring.
Hoe monitor je data freshness?
dbt source freshness checks via loaded_at_field met warn_after en error_after thresholds. Voor non-dbt projecten: MAX(updated_at) tegen CURRENT_TIMESTAMP. Tools als Monte Carlo, Bigeye en Soda automatiseren met ML-baselines.
Wat is het verschil tussen SLI, SLO en SLA?
SLI is een meetbaar getal (% queries onder 5 sec), SLO is je interne doelstelling (99%), SLA is een formele afspraak met afnemers met consequenties. Voor data: freshness, completeness, query latency, dashboard beschikbaarheid.
Hoe voorkom je alert fatigue?
Alert op symptoom, niet op oorzaak. Severity-tiers: INFO/WARN/CRIT. Routeer naar specifieke teams. Test alerts periodiek. Een alert die nooit gevuurd heeft is onbekend kwantiteit.
Wat hoort bij periodiek onderhoud?
Storage cleanup, statistics refresh, VACUUM op Redshift, re-clustering monitoring, permission audits, documentation review. Op lakehouses ook compaction en vacuum/expire snapshots op Delta- of Iceberg-tabellen.
Wat is een postmortem?
Blame-free analyse na incident: tijdlijn, root cause (5 whys), wat ging goed, wat ging slecht, actiepunten met eigenaar. Doel: leren, niet wijzen. Terugkerende root cause vraagt systeemverbetering.