Data Freshness: Why Your Numbers Disagree
Two people ask the same question in two tools and get two answers. The CRM says one revenue figure, the finance spreadsheet says another, and the dashboard says a third. Nobody is lying and nothing is obviously broken. Most of the time the gap is data freshness: each system is giving a truthful answer as of a different moment, and nobody wrote down which moment that was. Until you can say when each number was last true, the argument about which one is right cannot be settled. It just gets louder.
This article covers the three concepts people collapse into one word, the specific mechanics of sync schedules that produce disagreement, how to measure freshness with numbers instead of vibes, and how to report it so people stop escalating and start trusting.
Data freshness, latency, and completeness are three different things
Teams use "the data is stale" to mean at least three unrelated failures. Separating them is the single highest-leverage move, because each one has a different fix.
Freshness
Freshness is the age of the newest data in a dataset relative to now. If the most recent order in your reporting table closed at 09:12 and it is now 09:47, the orders dataset has 35 minutes of lag. Freshness is a property of the dataset as a whole, and it is measured in time, not in percentages.
Latency
Latency is how long one individual record takes to travel from where it happened to where you read it. It is a distribution, not a single number. A pipeline can have a median latency of 4 minutes and a 95th percentile of 3 hours, because a small slice of records go through a retry path or a slower connector. Freshness reports the front edge of the dataset. Latency reports the journey of each row. A dataset can look fresh because a handful of fast records arrived while most of yesterday's rows are still in flight.
Completeness
Completeness is whether every record that should exist for a period actually exists. A dataset can be perfectly fresh and badly incomplete: new rows keep landing every minute, but one of six regions failed its extract and contributed nothing. It can also be complete and stale: last month closed cleanly and has not moved since.
| Concept | Question it answers | Unit | Typical failure | Who notices first |
|---|---|---|---|---|
| Freshness | How old is the newest data? | Minutes or hours of lag | A scheduled job did not run | Anyone watching a live number |
| Latency | How long does one record take to arrive? | Distribution: p50, p95, p99 | Retry queues, rate limits, slow connectors | Ops teams chasing one missing record |
| Completeness | Is everything for this period here? | Count or percentage of expected rows | A partial extract that still "succeeded" | Finance at month end |
Say which of the three you mean before you open a ticket. "The dashboard is wrong" is not actionable. "Orders lag is 6 hours against a 30 minute target, and row counts look normal" is.
How sync schedules turn one truth into several numbers
Almost every disagreement between two tools has a mechanical explanation. Here are the ones that account for most of them.
Schedule phase. Tool A syncs at :00 and tool B at :20. For 20 minutes of every hour they hold different pictures of the same source. Nothing is broken. Both are correct as of their own moment. This alone causes an enormous share of "why do these not match" messages.
Incremental windows and watermarks. Most syncs are incremental: pull everything where updated_at is greater than the last watermark. If a record is updated during the extract itself, or if the source clock is slightly ahead, that record can fall in the gap between two runs and never get picked up. The fix is an overlap window, for example re-reading the last 15 minutes on every run and upserting, which trades a little duplicate work for not silently losing rows.
Backdated and late-arriving records. A refund posted today may carry last week's transaction date. An invoice gets its date changed. A support ticket is created retroactively. These land after you already reported the period they belong to, which means an old number legitimately changes. A dataset can be perfectly fresh and still restate yesterday.
Mutable source rows. CRM deals move stages. Subscription statuses flip. If your pipeline snapshots rather than tracking history, "deals closed in June" answers differently depending on when you ask. This is a modeling decision as much as a pipeline one, and it is worth resolving before you argue about sync frequency. Our guide to data modelling tools covers how teams handle slowly changing dimensions and history tables.
Deletes and soft deletes. Incremental syncs based on updated_at usually cannot see a hard delete, because the row is simply gone and nothing signals it. The record lives on in your copy forever. Soft deletes work only if the pipeline knows to filter the flag, and a filter that lives in one tool's query but not another's produces two different totals from identical data.
Rate limits and pagination. A connector that hits an API limit halfway through often finishes with a partial result and reports success. You get a green run and an incomplete table.
Caches and extracts downstream. The warehouse table can be fresh while the materialized view, the BI extract, and the browser cache are each an hour behind it. Every hop adds staleness. The chain is only as fresh as its slowest link, and users see the end of the chain, not your pipeline monitor.
Timezones and calendar boundaries. One tool cuts the day at UTC midnight, another at the account's local timezone, a third on a fiscal calendar with 4-4-5 periods. All three can be internally consistent and mutually contradictory. Any time zone offset shifts a slice of records across the boundary, and the daily totals will never match.
When data freshness is not actually the culprit
Before you rebuild a pipeline, rule out the cheaper explanations. Two numbers that disagree may both be current.
- Definition drift. "Active user" means logged in this week to one team and performed a key action to another. "Revenue" may include or exclude tax, refunds, discounts, and partial periods.
- Filter drift. One query excludes internal accounts and test orders. The other does not. This is the most common cause of small, stubborn gaps.
- Grain mismatch. One number counts orders, the other counts line items, and a multi-item order breaks the tie.
- Silent bucketing logic. Conditional bucketing written inline in a query is easy to get subtly wrong, and two analysts almost never write the same rules. If your definitions live in scattered
CASEexpressions, read our breakdown of SQL CASE WHEN patterns and better alternatives before you standardize.
A useful triage question: if both numbers were computed from data frozen at the same instant, would they still disagree? If yes, it is a definition problem. If no, it is freshness.
How to measure data freshness
You cannot manage freshness you have not instrumented. The good news is that meaningful measurement takes a day, not a quarter.
Track three clocks per record
- Event time. When the thing happened in the real world.
- Ingest time. When your system first received the row. Store it as a column such as
_loaded_at, set by the loader, never by the source. - Publish time. When the transformed, user-facing table was last successfully rebuilt.
With those three columns you can compute every metric below. Without them you are guessing.
The metrics worth having
| Metric | How to compute | What it catches |
|---|---|---|
| Source lag | now() - max(event_time) | Quiet sources, dead upstream systems, misconfigured extracts |
| Ingest lag | now() - max(_loaded_at) | Failed or skipped pipeline runs |
| End-to-end latency | _loaded_at - event_time per row, reported at p50 and p95 | Slow connectors, retry backlogs, rate limiting |
| Time since last success | now() - last successful run timestamp | Jobs that fail silently or never start |
| Volume against baseline | Today's row count versus the trailing 4-week median for the same weekday and hour | Partial extracts that still report success |
| Restatement rate | Share of rows whose values changed after first load | Backdating, mutable sources, whether "final" is really final |
Two rules make these numbers honest. First, measure at the layer users actually read, not at the first landing table. A fresh raw table behind a stale view helps nobody. Second, report percentiles rather than averages. Average latency hides exactly the tail that generates complaints.
The cheapest possible starting query
For most teams, one query per critical table gets you 80 percent of the value:
select
'orders' as dataset,
max(event_time) as newest_event,
max(loaded_at) as newest_load,
extract(epoch from (now() - max(loaded_at)))/60 as ingest_lag_minutes,
count(*) filter (where loaded_at > now() - interval '1 hour') as rows_last_hour
from analytics.orders;
Run it on a schedule, store the results in a small freshness table, and you now have a history you can chart and alert on. Beware of a subtle trap: source lag naturally rises overnight and on weekends because real activity drops. Compare against the same weekday and hour, not against a flat threshold, or you will generate alerts that everyone learns to ignore.
Setting a data freshness target people can actually meet
A freshness target is a promise, so write it like one. A workable format has four parts: the dataset, the measurement point, the threshold, and the compliance window.
Orders (reporting layer) will be less than 30 minutes behind source in 95 percent of business hours, measured Monday to Friday, 08:00 to 20:00 local.
Set the threshold from the decision cadence, not from what the pipeline currently does. If a number drives a weekly meeting, hourly freshness is plenty and every dollar spent going faster is wasted. If it drives an on-call page, minutes matter.
| Tier | Example datasets | Decision cadence | Target lag | Alert at |
|---|---|---|---|---|
| Operational | Support queue, live orders, on-call signals | Continuous | Under 15 minutes | 2x target, immediate page |
| Daily management | Pipeline, marketing spend, product usage | Once or twice a day | Under 4 hours | Missed morning window |
| Reporting | Finance summaries, board metrics | Weekly or monthly | Under 24 hours, plus a stated close date | Missed close date |
| Archive | Historical snapshots, audit tables | Rarely | Days is fine | Volume anomaly only |
Two practices keep this from becoming shelfware. Alert on lag, not on job success, because a job that finishes in three seconds with zero rows is a green check mark and a broken dataset. And give every tier an owner by name, because a freshness target nobody owns is a wish.
How to communicate data freshness so people trust the number
This is the part most teams skip, and it is where trust is actually won or lost. Users do not need your pipeline architecture. They need to know how much to believe the number in front of them.
Stamp everything with "as of". Every report, export, alert, and chat answer should carry the moment the underlying data was last refreshed, in a stated timezone. "Revenue: 412,300 (as of 14:05 UTC, orders lag 12 minutes)" ends more arguments than any dashboard redesign.
Put the stamp next to the number, not in a footer. People screenshot the number. If the timestamp is 400 pixels away it does not travel with it, and a stale screenshot becomes next quarter's disputed fact.
Publish a restatement policy. Say plainly that yesterday's figures may move for up to three days because of refunds and backdated entries, and that a period is final after close. When a number changes and people were warned, it reads as rigor. When it changes silently, it reads as unreliability, no matter how correct the new value is.
Write the reconciliation note before anyone asks. For any two systems that habitually disagree, publish a short standing explanation: the CRM counts a deal at signature, finance counts it at invoice, the gap is typically a few days of pipeline, and here is the query that ties them out. This single document retires a recurring meeting.
Mark degraded data instead of hiding it. When a source is behind, show the number with an explicit staleness label rather than blanking the view. Blank panels generate escalations. Labeled numbers generate informed judgment.
Design for the glance. Freshness is metadata, so it should sit quietly beside the value rather than compete with it, and a small trend line of lag over the past week says more than a status light. If you are deciding how to show that, our guides to examples of charts and different types of charts cover which forms carry small supporting signals without stealing attention.
Where Skopx fits, and where it honestly does not
Skopx is not a BI tool, a warehouse, or an ETL platform. It does not build dashboards or move data on a schedule between systems. If you need modeled tables and visual exploration, use a warehouse and a BI tool. That is the right job for the right tool.
What Skopx does well here is the part that usually falls between those tools: asking one question across systems that each have their own sync schedule, and getting an answer that shows its sources. Skopx connects to nearly 1,000 integrations and queries PostgreSQL, MySQL, and MongoDB directly in chat, so a freshness comparison across a database, a CRM, and a spreadsheet becomes one sentence:
Show me the newest created_at in our Postgres orders table, the newest closed deal date in HubSpot, and the last row date in the Q3 finance sheet, with how many minutes behind now each one is.
It returns the three timestamps side by side with the lag for each, and cites which connected source each value came from, so you can see immediately whether the disagreement is a sync gap or a definition gap.
From there, Workflows can watch it for you. You describe the automation in chat rather than dragging boxes: run every hour, check the newest load timestamp, and if the lag crosses your threshold, post the current lag and the last loaded record to a Slack channel. Triggers can be manual, scheduled at a 15 minute minimum, or fired by webhook, and each run is inspectable step by step so you can see exactly which check tripped. The limits are real and worth knowing up front: workflows are acyclic, capped at 20 steps, have no human-approval step and no custom code step, and any AI step runs on your own provider key. The daily morning brief covers the softer half of the same problem by surfacing what changed and what is slipping across connected tools, which is often how a quietly dead sync gets caught.
Skopx is paid from day one. Solo is $5 per month and Team is $16 per seat per month with no seat cap. There is no free tier and no trial, so evaluate it against a specific job. Current details are on the pricing page. AI runs on your own provider key with no markup from us, and your data is encrypted at rest with AES-256, moves over TLS 1.3, is isolated per organization at the row level, and never trains a model.
A two-week plan to stop the disagreements
- Day 1. List the five numbers people argue about most. For each, write the owning system, the query or view behind it, and the sync schedule that feeds it.
- Day 2. Add
loaded_atto any table missing it, and start recording the freshness query results into a small history table. - Days 3 to 5. Chart lag for those five datasets. Most teams find at least one source that has been quietly behind for weeks.
- Days 6 to 8. Write definitions for the five numbers, including filters, grain, and timezone. Settle the disagreements that turn out to be definitional.
- Days 9 to 11. Set a target per dataset using the tier table above. Alert on lag, not job success.
- Days 12 to 14. Add "as of" stamps everywhere the numbers appear, publish the restatement policy, and write the reconciliation note for the worst-offending pair of systems.
None of this requires new infrastructure. It requires deciding what fresh means, measuring it, and telling people.
Frequently asked questions
What is a good data freshness target?
There is no universal answer, only a rule: match the decision cadence. A number reviewed weekly does not benefit from minute-level freshness. A number that triggers an on-call response needs minutes. Set the target from how fast someone would act on a change, then check whether your current pipeline meets it before you promise it.
How is data freshness different from data quality?
Freshness is one dimension of quality, alongside accuracy, completeness, consistency, and validity. Data can be perfectly accurate and useless because it is a day old, or perfectly fresh and wrong because a join duplicates rows. Treat them as separate checks with separate alerts, since a single "data health" score usually hides which one failed.
Why do two dashboards show different numbers if both refreshed today?
Most often because they refreshed at different moments, applied different filters, or cut the day on different timezone boundaries. Test it by freezing both to the same cutoff timestamp and re-running. If they still disagree, the cause is definitional rather than freshness, and no amount of faster syncing will fix it.
How do I measure data freshness without building an observability stack?
Run one query per critical table that records the newest event timestamp, the newest load timestamp, and the row count for the last hour. Store the results, chart the lag, and alert when it exceeds your target. That covers most real incidents. Dedicated observability tooling is worth adding once you have more datasets than you can enumerate, not before.
Should I switch to streaming to fix freshness problems?
Usually not. Streaming reduces latency but adds real operational complexity, and it does nothing for late-arriving records, restatements, definition drift, or downstream caches. Tighten your schedules, add an overlap window to incremental extracts, and instrument lag first. Move to streaming only when a specific decision genuinely needs sub-minute data.
Does Skopx solve data freshness problems?
Partly, and only in specific ways. Skopx does not move or transform your data, so it will not make a slow pipeline faster. It does let you check freshness across databases and connected tools in one question with cited sources, run scheduled workflows that alert when lag crosses a threshold, and surface what changed each morning. Skopx catches what falls between your tools. For the pipeline itself, you still need your warehouse and orchestration stack.
Skopx Team
The Skopx engineering and product team