The approach
Data flows source → ingestion → raw/bronze → transformations → serving tables → semantic model → report. Start at the report and walk backwards until you find the first layer where today's data is present. The problem is in the layer just after that one.
Write the timeline in the incident channel as you go. It saves time later.
Step 1: confirm the symptom (5 minutes)
- Which report, which page, which filter? Is it missing, partial or wrong?
- Is it all users, or one region or role? Row-level security can make data look missing.
- When did it last look right?
Step 2: the report and semantic model
- Check the dataset refresh history. In Power BI, the semantic model's Refresh history shows failed or skipped refreshes.
- If the refresh succeeded, but before upstream data landed, the fix is simply to refresh again and change the schedule.
# Power BI: last refreshes for a dataset (MicrosoftPowerBIMgmt module)
Invoke-PowerBIRestMethod -Method Get -Url "groups/<workspace-id>/datasets/<dataset-id>/refreshes?`$top=5" |
ConvertFrom-Json | Select-Object -ExpandProperty value | Select status, startTime, endTime, serviceExceptionJson
Step 3: serving tables
Check the freshness of the tables the model reads:
SELECT max(load_ts) AS last_load, max(business_date) AS last_business_date, count(*) AS rows_today
FROM gold.sales_daily
WHERE business_date = current_date - 1;
Compare the row count with the same day last week. A count of zero, or a sharp drop, tells you whether the data is missing or partial.
On Delta tables you can see when the table was last written and by which job:
DESCRIBE HISTORY gold.sales_daily LIMIT 5;
Step 4: pipelines
In the orchestrator, find the jobs that build those tables:
| Finding | Meaning |
|---|---|
| Job failed | Read the error; fix it and rerun |
| Job succeeded but wrote 0 rows | Upstream was empty or late when it ran |
| Job didn't run | Schedule paused, trigger stopped, upstream dependency never completed |
| Job still running | Slow; check resources, skew and locks |
# Airflow
airflow dags list-runs -d sales_daily --state failed -o table
# Databricks
databricks jobs list-runs --job-id 123 --limit 5 --output json | jq '.runs[] | {run_id, state}'
Step 5: raw/bronze and the source
SELECT max(_ingested_at), count(*) FROM bronze.sales_orders WHERE _ingested_at >= current_date;
- Nothing in bronze means ingestion or the source is the problem. Check the ingestion job, landing folder, CDC connector lag or API errors.
- Check the source directly: did the source system send a file today? Was there an upstream outage, a schema change or a credential expiry?
Common root causes
| Cause | Typical clue |
|---|---|
| Upstream file late or missing | Landing folder empty; source team confirms delay |
| Expired credential or secret | 401/403 errors in ingestion |
| Schema change at source | Parse errors, NULL columns, rows rejected |
| Job ran before upstream finished | Success with 0 rows; time-based schedule instead of dependency |
| Capacity or quota | Jobs queued, warehouse suspended or throttled |
| Report refreshed too early | Data present in tables but not in the report |
| Filter or RLS change | Data present for some users only |
Step 6: fix, backfill, verify
- Fix the cause (rerun, update the credential, adjust the schema).
- Backfill the affected dates in dependency order, from bronze through gold to the semantic model.
- Verify counts and totals against the source or the same day last week.
- Refresh the report and check it as a user would.
Warning
Make sure reruns are idempotent (overwrite the partition or merge on keys). Appending a second load doubles the numbers, which is worse than missing data.
Step 7: communicate
Post a short update when you confirm the issue and when it's fixed:
Sales dashboard missing 25 Oct data. Cause: supplier file arrived 3 hours late. Data loaded and verified at 10:40; dashboard refreshed. Next step: switch the load to trigger on file arrival.
Prevent it next time
- Freshness checks on key tables (for example dbt
source freshnessor a scheduled query) that alert before users notice. - Volume checks comparing row counts with the recent average.
- Dependency-based triggers (file arrival, upstream job success) instead of fixed times.
- Show "data as of" on dashboards so users can see staleness themselves.
- An owner per dataset and an agreed delivery time (data SLA).
Checklist
- Symptom confirmed (missing, partial, wrong; who's affected)
- Walked back layer by layer to the first missing layer
- Root cause fixed
- Backfill in dependency order, idempotent
- Counts verified and report refreshed
- Users updated
- Freshness/volume alert added