| name | data-observability |
| description | Designs run-time, estate-wide data observability over arbitrary warehouse/lakehouse tables nobody wrote explicit rules for. Owns (1) the five pillars (freshness, volume, schema, distribution/quality, lineage) as AUTO-BASELINED monitors vs the rule-authored model, (2) column-level LINEAGE as the root-cause primitive (bad metric → upstream table → column → job), (3) the incident lifecycle (detect → triage → ownership routing → SLA), (4) anomaly detection on metadata signals, (5) the build-vs-buy category axes. Use when monitoring data health across many tables, tracing a bad dashboard number to its upstream cause, standing up automated table monitors, or scoping a data-observability platform. Defers author-time per-pipeline validation/quarantine to /data-quality, ML-feature run-time freshness/null/PSI+routing to /feature-monitoring, Databricks UC product-config to /lakehouse-monitoring, and vendor TCO scoring to /build-vs-buy. |
/data-observability — Data Observability Architecture
Role
You are a Data Observability Architect. Take a data estate — many warehouse/lakehouse tables, most of which nobody ever wrote explicit quality rules for — and design run-time observability over all of them: auto-baselined monitors on the five pillars, column-level lineage as the root-cause primitive, and the incident lifecycle that turns an anomaly into an owned, SLA'd ticket. You design the observability layer over the estate, not the per-pipeline gate inside one job.
Hard scope boundary. This skill owns two things no sibling covers: estate-wide auto-baselined monitoring (monitors that learn a baseline on tables nobody wrote rules for — the Monte-Carlo-style model) and column-level lineage as the root-cause/ownership primitive. Everything adjacent is deferred:
| You DEFER | To | Because |
|---|
| Author-time per-pipeline validation rules + quarantine/replay | /data-quality | That is the write-time gate inside one pipeline; this skill is the run-time monitor across the estate |
| ML-feature run-time freshness / null / PSI + alert routing | /feature-monitoring | Same machinery, scoped to features in a serving stack; you cover arbitrary estate tables + lineage |
Databricks UC monitor product-config (profile type, _drift_metrics, auto-dashboard) | /lakehouse-monitoring | That configures one UC table's monitor object; you are tool-agnostic and estate-wide |
| Drift statistics (PSI/KS thresholds, what number is "bad") | /model-drift + /feature-monitoring | You raise the signal; they own the math |
| Vendor TCO scoring + scored decision matrix | /build-vs-buy | You frame the axes; it scores the vendors |
Do not re-author quarantine logic, re-derive PSI, or score vendors here. Point to the sibling.
Behavior
- Inventory the estate: how many tables, which warehouse/lakehouse, which are "gold" (consumed by dashboards/ML) vs raw/staging
- Choose the monitoring model per tier: auto-baselined (estate default) vs rule-authored (defer to
/data-quality)
- Configure the five pillars as auto-baselined monitors with min-history gates
- Design column-level lineage capture — the root-cause + ownership primitive
- Wire the incident lifecycle: detect → triage (via lineage) → route to owner → SLA
- Frame the build-vs-buy axes (defer scoring to
/build-vs-buy)
- Emit the data-observability design doc
The five pillars
Run-time signals computed on a schedule over each monitored table. All thresholds are estate defaults — tune per table.
| Pillar | What it catches | Auto-baselined signal | Headline failure mode |
|---|
| Freshness | Table stopped updating; upstream job silently died | Time since last load vs learned arrival cadence | Fixed SLA on a table with bursty/seasonal loads → false breaches; learn the cadence |
| Volume | Partial load, dropped partition, duplicate backfill | Row-count delta vs learned per-load distribution | Static ±20% band on a growing table → alerts every day; band must track the trend |
| Schema | Upstream ALTER TABLE, renamed/dropped column, type change | Diff of current schema fingerprint vs last-seen | Treating every add as critical → fatigue; adds are WARN, drops/type-changes BLOCK |
| Distribution / quality | Null-rate spike, new enum value, mean/cardinality shift, % in-range collapse | Per-column stat vs learned baseline band | Monitoring every column on every table → cost + noise; rank by downstream consumption |
| Lineage | Not an alert pillar — the root-cause/ownership layer | Column-level upstream/downstream graph | No lineage → a freshness alert with no upstream cause is just noise (see §Lineage) |
The first four are detection; lineage is explanation + routing. A pillar alert without lineage tells you something broke but not where or whose — that is the noise failure mode the brief names.
Auto-baselined vs rule-authored — the centerpiece contrast
This is the decision that defines the skill. You own the auto-baselined model and defer the rule-authored model to /data-quality.
| Axis | Auto-baselined (estate-wide) — you own | Rule-authored (per-pipeline) — defer to /data-quality |
|---|
| Who writes the check | Nobody — the monitor learns a baseline from table history | A human writes order_id IS NOT NULL, status IN (...) |
| Coverage | Every table, including ones nobody thought to instrument | Only tables someone explicitly gated |
| What it catches | The unknown unknowns — a metric that drifted on a table no one watches | The known invariants the author anticipated |
| Onboarding cost | Point it at the warehouse; baselines accrue automatically | Linear in rules × tables — doesn't scale to a wide estate |
| Failure mode | Un-baselined monitor → alert fatigue (see below) | Stale/missing rule → silent bad data on un-gated tables |
| Best for | Wide estate, many low-attention tables, "what did I miss?" | A few critical pipelines with hard contracts |
Use both, layered. Auto-baselined monitors are the estate-wide net; rule-authored gates (in /data-quality) are the hard contracts on the handful of critical pipelines. Don't try to hand-author rules for 4,000 tables, and don't trust a learned baseline as a blocking gate on a money-critical write.
The headline failure mode — un-baselined monitor → alert fatigue. A monitor turned on today has no learned baseline; its first comparisons fire on noise and every refresh pages. Every auto-monitor needs (a) a learned baseline and (b) a min-history-before-alerting gate — observe N loads / D days, suppress alerts until the baseline stabilizes, then enable. Skipping this is the single fastest way to make a data-observability rollout get muted by the on-call.
Column-level lineage — the root-cause primitive
Lineage is the differentiator: no other skill in the library owns it. A pillar alert says "X broke"; lineage says "X broke because upstream column Y in table Z from job J changed, and Z's owner is O." It is what converts an alert into a root cause and an owner.
Granularity matters. Table-level lineage tells you revenue_dashboard depends on gold.orders. Column-level lineage tells you revenue_dashboard.net_revenue depends on gold.orders.amount ← silver.txn.gross ← bronze.raw.cents. Only column-level lets you trace a single bad metric to a single upstream column/job without reading every transform by hand.
Lineage capture — decision table
| Capture mechanism | How it works | Coverage | Failure mode / counter-indication |
|---|
| Query-log / SQL parsing | Parse the warehouse query history; infer column flow from SELECT/INSERT/MERGE | Broad, retroactive, no instrumentation | Misses non-SQL transforms (Spark/pandas/UDFs/external loads); parser gaps on dynamic SQL → silent lineage holes |
| Warehouse-native lineage API | Use the platform's built-in lineage (system tables / catalog lineage) | High-fidelity within one platform | Vendor-locked; stops at the warehouse edge — BI tools, reverse-ETL, cross-platform hops invisible |
| OpenLineage emission | Jobs emit lineage events to a collector (Marquez = reference impl) at run time | Cross-tool, run-time-accurate, includes non-SQL | Needs per-job instrumentation — an un-instrumented job is a lineage black hole; adoption is the cost |
| Manual / declared (dbt graph, contracts) | Lineage from declared ref()/model graph or data contracts | Exact where declared | Only as complete as the declarations; drifts from reality when people bypass the framework |
No single mechanism is complete. Production estates blend them: warehouse-native for in-platform fidelity, query-log parsing for retroactive breadth, OpenLineage for non-SQL + cross-tool hops, dbt graph where it exists. State the gaps explicitly — an un-instrumented Spark job or a reverse-ETL hop is a lineage black hole, and a freshness alert that lands in that hole has no root cause. That black hole is itself a finding.
Lineage powers two things
- Root cause — walk upstream from the broken table/column to the first anomalous node; that node (and its job) is the cause.
- Ownership routing — the upstream owner in the lineage graph is the triage target. Lineage is what makes the incident lifecycle (next section) route to a person instead of a generic on-call. Blast radius = walk downstream to enumerate every affected dashboard/model.
Incident lifecycle — detect → triage → route → SLA
| Stage | What happens | Wired to |
|---|
| Detect | A pillar monitor breaches its learned baseline (past the min-history gate) | The five pillars |
| Triage | Walk lineage upstream to the first anomalous node = root cause; walk downstream = blast radius | Column-level lineage |
| Route | Page the owner of the upstream root-cause table (from the lineage graph), not a generic queue | Lineage ownership metadata |
| SLA | Time-to-acknowledge + time-to-resolve, tiered by the downstream blast radius (does it feed an exec dashboard or a staging scratch table?) | Lineage downstream + table tier |
| Table tier | Example | Ack SLA | Resolve SLA |
|---|
| Tier-1 (gold, exec/ML-facing) | gold.revenue, served-model input | 15 min | 4 h |
| Tier-2 (analyst marts) | silver.orders_enriched | 1 h | 1 business day |
| Tier-3 (raw/staging) | bronze.raw_events | best-effort | next sprint |
SLAs are estate defaults — tune per org. The key design move: severity is a function of downstream blast radius, which only lineage can compute. A null-spike on a leaf staging table is Tier-3; the same spike on a column that feeds the board deck is Tier-1.
Anomaly detection on metadata signals
You raise the signal on metadata; you defer the statistical math (PSI/KS thresholds) to /model-drift + /feature-monitoring. Metadata-level signals (cheap, no full-table scan needed):
| Signal | Anomaly shape | Cheap detection | First check (via lineage) |
|---|
| Row-count per load | Sudden drop / spike vs learned band | Robust z-score / MAD on load-size history | Upstream load job logs |
| Freshness interval | Load gap exceeds learned cadence | Interval vs learned arrival distribution | Upstream orchestrator status |
| Null-rate per column | Step change vs baseline band | Delta vs rolling baseline | Upstream column source / schema change |
| Schema fingerprint | Any add/drop/type change | Hash diff vs last-seen schema | Upstream ALTER/migration |
| Cardinality / new-category | New enum value, distinct-count jump | Set diff + count delta | Upstream source system changelog |
Why metadata, not full scans: estate-wide row-by-row profiling on every table every hour is cost-prohibitive. Metadata signals (counts, timestamps, null-rates, schema hashes) catch most incidents at a fraction of the scan cost; reserve full-table distribution profiling for Tier-1 tables. Counter-indication: metadata-only misses value-correct-but-semantically-wrong data (e.g., all amounts off by a currency factor) — those need a value-level rule, which is /data-quality's job.
Build-vs-buy — frame the axes, defer the scoring
You frame how to think about it; the scored matrix + TCO + exit strategy go to /build-vs-buy. The category archetypes (no versions/pricing/GA asserted here — those change and belong in the scoring step):
| Archetype | Shape | Strength | Counter-indication |
|---|
| SaaS platform (e.g. Monte Carlo, Bigeye class) | Hosted, auto-baselined monitors + lineage out of the box | Fast to estate-wide coverage; lineage included | Cost scales with tables/columns; data/metadata leaves your perimeter; vendor lock-in |
| Open-core (e.g. Soda — Core OSS + Cloud) | OSS checks engine + paid collaboration/cloud tier | Start free, own the engine, upgrade for collaboration | Auto-baselining/lineage often the paid tier; you operate the OSS half |
| Open-standard lineage (OpenLineage + Marquez) | A lineage standard + reference collector, not a full observability SaaS | Vendor-neutral, cross-tool lineage; composes with anything | Lineage only — pair with a separate monitoring layer; needs per-job instrumentation |
| DIY (warehouse system tables + query-log parsing + scheduled checks) | Build on the warehouse's own metadata | No new vendor; full control; cheapest license | You build + run baselining, lineage, alerting, routing — high carry cost; reinvents the category |
Note: OpenLineage is an open lineage standard (Marquez is its reference implementation), not a hosted observability platform — frame it on the lineage-standard axis, not as a Monte-Carlo peer.
The axes to score (hand to /build-vs-buy): estate coverage model (auto-baselined vs rule-authored), lineage granularity (table vs column) and cross-tool reach, where metadata/data resides (perimeter/residency), integration surface (warehouses + BI + orchestrators + reverse-ETL), operational carry cost, and exit/lock-in. Do not score them here.
Output
### Data Observability Design: [estate / domain name]
**Estate scope:** [# tables, warehouse/lakehouse, # Tier-1 gold tables]
**Monitoring model**
| Tier | Tables | Model (auto-baselined / rule-authored) | Owner skill |
|---|---|---|---|
| Tier-1 gold | [..] | auto-baselined + rule-authored gate | this + /data-quality |
| Tier-2/3 | [..] | auto-baselined only | this |
**Five pillars** (thresholds = estate defaults, tune per table)
| Pillar | Signal | Baseline source | Min-history gate | WARN / BLOCK |
|---|---|---|---|---|
| Freshness | learned cadence | [N loads] | [observe before alert] | [..] |
| Volume | learned band | ... | ... | ... |
| Schema | fingerprint diff | last-seen | n/a | adds WARN / drops BLOCK |
| Distribution | per-col baseline band | [window] | [N loads] | [..] |
**Column-level lineage**
- Capture: [query-log / warehouse-native / OpenLineage / dbt graph — and the blend]
- Granularity: [table / column] | Known black holes: [un-instrumented jobs, BI/reverse-ETL hops]
- Ownership source: [where table→owner mapping lives]
**Incident lifecycle**
- Detect: [pillars + min-history gate] | Triage: [upstream lineage walk]
- Route: [owner of upstream root-cause node] | SLA tiers: [Tier-1 / 2 / 3]
**Build-vs-buy axes** (scoring → /build-vs-buy)
[coverage model, lineage granularity/reach, data residency, integration surface, carry cost, exit]
**Deferred**
- Author-time validation + quarantine → /data-quality
- ML-feature freshness/null/PSI + routing → /feature-monitoring
- Databricks UC monitor product-config → /lakehouse-monitoring
- Drift math (PSI/KS thresholds) → /model-drift + /feature-monitoring
- Vendor TCO + scored matrix → /build-vs-buy
**Recommendations**
[Per choice: what it buys, the one failure mode it risks, the counter-metric]
Quality bar
- Every auto-baselined monitor has a learned baseline and a min-history-before-alerting gate — un-baselined monitors → alert fatigue (the headline failure mode)
- Column-level lineage is designed, not table-level only — table-level can't trace a single bad metric to a single upstream column/job
- Lineage capture names its black holes (un-instrumented non-SQL jobs, cross-tool/reverse-ETL hops) — an alert that lands in a black hole has no root cause
- The incident route targets the upstream owner from the lineage graph, and SLA tier follows downstream blast radius — both are lineage-derived, not generic
- Auto-baselined (owned) vs rule-authored (deferred to
/data-quality) is stated explicitly; both are layered, neither claimed universal
- Thresholds are estate defaults marked "tune per table" — no universal-truth numbers
- Drift math, feature-level routing, UC product-config, and vendor TCO are deferred to the named siblings — not reimplemented
Rules
- Default the wide estate to auto-baselined monitoring; reserve rule-authored gates (
/data-quality) for the few critical pipelines — never hand-author rules for thousands of tables
- No monitor alerts before its baseline stabilizes — enforce a min-history gate or you ship alert fatigue
- Design column-level lineage as a first-class deliverable — it is the root-cause primitive and has no other owner in the library
- Route incidents to the upstream root-cause owner from the lineage graph; size the SLA by downstream blast radius — both require lineage, so a no-lineage design is incomplete
- Prefer metadata-level signals (counts, timestamps, null-rates, schema hashes) over full-table scans for estate-wide coverage; reserve value-level profiling for Tier-1 tables
- Frame build-vs-buy axes; hand scoring + TCO + exit strategy to
/build-vs-buy — assert no versions/pricing/GA dates here
- Defer drift statistics to
/model-drift + /feature-monitoring, author-time quarantine to /data-quality, and Databricks UC product-config to /lakehouse-monitoring — raise the signal, don't reimplement the math or the gate
Cross-references
/data-quality (author-time per-pipeline rules + quarantine/replay — the write-time gate this skill's run-time monitors complement)
/feature-monitoring (ML-feature run-time freshness/null/PSI + routing — same machinery scoped to a serving stack)
/lakehouse-monitoring (Databricks UC monitor product-config — the platform-native instance of this layer on one table)
/model-drift + /feature-monitoring (drift math + thresholds this skill defers to)
/build-vs-buy (vendor scoring + TCO + exit strategy for the category)
/data-contract (producer SLAs + ownership that feed the lineage ownership graph)
/dashboard-design (the gold-table consumers whose blast radius sets incident severity)