Data Quality Monitoring for Marketing Pipelines
Schema validation catches malformed rows; quality monitoring catches everything else
A row can be perfectly well-formed (every field present, correctly typed, passing every schema rule) and still be wrong in ways schema validation can't detect: a connector silently stopped syncing three days ago and nothing downstream has fresh data, a tracking tag got removed in a site redesign and event volume quietly dropped 40%, or a currency conversion bug started reporting all European spend in the wrong currency. Data quality monitoring is the discipline of continuously checking the data's statistical properties and business-logic consistency, not just its structural shape.
The core categories of automated checks
Freshness — has this table been updated as recently as expected (e.g. MAX(event_date) should never be more than 25 hours old for a daily pipeline). Volume — does today's row count fall within a statistically expected range relative to recent history, rather than spiking or collapsing unexpectedly. Distribution — have the proportions of key categorical values shifted abnormally (e.g. the share of traffic attributed to 'direct' suddenly jumping, often a symptom of broken UTM tracking or tag misconfiguration elsewhere). Referential integrity — do foreign keys in a fact table actually resolve to real rows in their dimension tables, rather than orphaned references.
-- Freshness check: alert if the latest partition is more than 25 hours old
SELECT
MAX(event_date) AS latest_date,
TIMESTAMP_DIFF(CURRENT_TIMESTAMP(), MAX(event_timestamp), HOUR) AS hours_stale
FROM `project.analytics.events_*`
HAVING hours_stale > 25;
-- Volume anomaly check: compare today's row count against a 14-day trailing average
WITH daily_counts AS (
SELECT event_date, COUNT(*) AS row_count
FROM `project.analytics.events_*`
WHERE event_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 14 DAY)
GROUP BY event_date
)
SELECT
event_date,
row_count,
AVG(row_count) OVER (ORDER BY event_date ROWS BETWEEN 13 PRECEDING AND 1 PRECEDING) AS trailing_avg,
SAFE_DIVIDE(row_count, AVG(row_count) OVER (ORDER BY event_date ROWS BETWEEN 13 PRECEDING AND 1 PRECEDING)) AS ratio_to_avg
FROM daily_counts
ORDER BY event_date DESCAnomaly thresholds should be relative, not fixed
dbt tests as the lightweight entry point
If your transformation layer is dbt, you don't need a separate dedicated data-quality platform to start: dbt_utils and dbt_expectations packages add generic tests for freshness, row-count anomaly detection, and distribution checks directly as YAML configuration on existing models, run as part of every dbt build. This is usually the right first step before investing in a dedicated data observability platform (Monte Carlo, Metaplane, Elementary) — those add automatic anomaly detection and lineage-aware alerting across sources dbt tests alone don't cover, but they're worth adopting once hand-written tests become the bottleneck, not before.
Alerting without alert fatigue
A data quality monitoring setup that pages someone for every minor fluctuation trains the team to ignore alerts within weeks. Separate severity tiers explicitly: a hard pipeline failure or complete freshness breach should page immediately; a mild statistical anomaly in a non-critical table should land in a daily digest, not an urgent page. Route alerts to the channel and audience that can actually act on them — a dbt test failure on a staging model belongs with the data engineering team, not in a general marketing Slack channel where no one has the context to respond.
What's Next?
Quality checks tell you something's wrong — observability is the broader practice of being able to quickly understand why, by tracing a bad number back through the full lineage of transformations and sources that produced it.
I build these systems professionally.
Whether it's a RAG pipeline, analytics migration, or AI workflow — let's talk.