Sahojit← All posts
Data Engineering

Silent Data Loss: The ETL Bug That Doesn't Crash Anything

May 2025·5 min read

Your Airflow DAG ran green. No failed tasks, no error logs, no Slack alerts. But downstream, a dashboard that should show 50,000 records is showing 43,000. The missing 7,000 rows didn't cause an error — they just quietly disappeared somewhere between source and destination.

This is silent data loss, and it's one of the most dangerous failure modes in data engineering because your pipeline is technically healthy. The problem is in the data, not the code.

How it happens

Silent data loss has a dozen causes, none of which trigger exceptions:

In each case, the pipeline completes successfully. If you're only monitoring for task failures, you'll never catch it.

Why traditional monitoring misses it

Most data pipeline monitoring tracks task status (success/failure), latency, and maybe total row count as a spot check. The problem with a raw row count threshold — 'alert if count < 1000' — is that legitimate variance will trigger false positives constantly. Traffic drops on weekends. Marketing campaigns spike volume mid-week. Your threshold is always wrong for some period.

What you need is a statistical model of what 'normal' looks like for this pipeline at this time of week, and an alert that fires when observed counts deviate significantly from that model. That's what statistical process control gives you.

CUSUM: catching slow drift

CUSUM (Cumulative Sum Control Chart) accumulates small deviations over time and fires when they add up to something significant. It's designed for exactly this problem — detecting a persistent downward shift in row counts that's too gradual to trigger a threshold alert on any single run.

python
def cusum(observations: list[float], target: float, threshold: float, slack: float = 0.5):
    """
    target: expected mean (e.g. rolling 14-day average row count)
    threshold: cumulative deviation that triggers an alert
    slack: tolerance — deviations smaller than this are ignored
    """
    cusum_neg = 0.0
    alerts = []
    for i, obs in enumerate(observations):
        deviation = target - obs  # positive = below target (data loss direction)
        cusum_neg = max(0, cusum_neg + deviation - slack)
        if cusum_neg > threshold:
            alerts.append(i)
            cusum_neg = 0  # reset after alert
    return alerts

The slack parameter is important — it lets you absorb normal variance without accumulating a false signal. I set it to 0.5 standard deviations of the 14-day rolling distribution, tuned on 30 days of historical data to achieve zero false positives.

EWMA: smoothing out weekend dips

CUSUM is great for persistent drift but less sensitive to sudden single-run drops. EWMA (Exponentially Weighted Moving Average) control charts catch sudden deviations while smoothing out the cyclical patterns (weekends, holidays) that make raw thresholds useless.

python
def ewma_control_chart(observations: list[float], alpha: float = 0.3, k: float = 3.0):
    """
    alpha: smoothing factor (higher = more sensitive to recent observations)
    k: number of standard deviations for control limits
    """
    ewma = observations[0]
    sigma = float.__pow__(sum((o - ewma) ** 2 for o in observations[:5]) / 5, 0.5)
    alerts = []
    for i, obs in enumerate(observations[1:], 1):
        ewma = alpha * obs + (1 - alpha) * ewma
        control_limit = ewma - k * sigma * (alpha / (2 - alpha)) ** 0.5
        if obs < control_limit:
            alerts.append(i)
    return alerts

The ML classifier layer

Running both CUSUM and EWMA reduces false positives, but legitimate volume drops (a marketing campaign ending, a weekend) can still look like data loss. The final layer is a lightweight classifier trained on historical data that takes the raw signal plus features like day-of-week, hour-of-day, and recent trend direction, and predicts 'genuine data loss' vs 'expected variance'.

I trained an XGBoost binary classifier on 6 months of labelled pipeline runs (labelled by manually reviewing each alert in the first month). After training, false positive rate dropped to zero on the hold-out set while maintaining 100% recall on the 12 injected silent loss scenarios.

Results in practice

The biggest shift in mindset: stop monitoring pipeline health and start monitoring data health. A green Airflow DAG is not the same as correct data in your tables.

The full system runs as a post-pipeline Airflow task — it reads the metadata table the main pipeline writes to, computes the control chart signals, and either passes or fires a PagerDuty alert with the exact table, column, and suspected loss window. Total overhead: under 30 seconds per pipeline run.

← Back to Writingsahojit-portfolio.vercel.app ↗