Yesterday's totals change once the daily export replaces the intraday table
In short
The intraday table is a provisional stream, not an early copy of the daily one: rows arrive late, get replaced, and are re-partitioned when the settled table for that date lands. A number is safe to publish once the daily partition exists, its row count sits within a couple of points of the trailing 4 same-weekday partitions, and its maximum event timestamp reaches the end of the reporting day.
Key takeaways
- Intraday and daily are different objects with different guarantees, so reading one as a preview of the other guarantees drift.
- The audit is 4 columns per partition: date, row count, maximum event timestamp, and whether the settled table exists.
- A partition within roughly 2% of the trailing 4 same-weekday partitions is publishable; outside that, hold it.
- Timezone is the cause nobody suspects, because the export date and the reporting date are set by different clocks.
- Choose one policy per consumer — wait, rebuild on arrival, or freeze and restate — and write the restatement rule down before anyone needs it.
A model built at 08:00 and the same model rebuilt at 14:00 disagreeing about yesterday is usually correct behaviour, badly framed. The intraday table is a provisional stream: rows land as they arrive, some arrive hours after the event they describe, and when the settled daily table for that date is written it replaces rather than extends what you already read. Anything that queried the provisional object and published the answer as final has published an estimate without labelling it.
That does not mean every movement is benign. Five things produce the same symptom, and only one of them is the export behaving as designed. This page is about the raw event export and the warehouse tables built from it. Restatement inside an ad platform's own reporting is a different mechanism with a different fix, and it belongs to the reporting cluster.
Audit the partitions before you audit the model
Almost every investigation here starts in the wrong place — in the transformation code — because that is where the number is computed. The evidence is upstream, and it is 4 columns wide.
- List every partition for the last 21 days with its row count, its minimum and maximum event timestamp in UTC, and a flag for whether the settled daily table for that date exists at all.
- Compare each row count against the trailing 4 partitions for the same weekday, not against yesterday. Traffic has a weekly shape, so a Sunday judged against a Saturday will look broken every week.
- Read the maximum event timestamp. A settled partition should reach the end of its reporting day; one that stops at 19:40 is missing the evening, whatever its row count says.
- Flag any date where the settled table is absent but the intraday one is not. That is the pair that produces the 08:00-versus-14:00 disagreement, and it is visible before anyone opens a dashboard.
- Record the audit as a table your pipeline writes on every run. Done once by hand it answers today's question; run on a schedule it becomes the freshness contract everything downstream can check.
| Event date | Rows | Max event timestamp (UTC) | Settled table | Verdict |
|---|---|---|---|---|
| 2026-07-21 | 1,182,004 | 23:59:58 | Yes | Publishable |
| 2026-07-22 | 1,166,431 | 23:59:51 | Yes | Publishable |
| 2026-07-23 | 1,171,908 | 23:59:44 | Yes | Publishable |
| 2026-07-24 | 913,522 | 19:41:02 | Yes | Hold — 22% short and the evening is missing |
| 2026-07-25 | 704,118 | 23:58:12 | No | Hold — intraday only, will be replaced |
Five reasons a number you already published moves
| Cause | What the audit shows | What to do |
|---|---|---|
| Intraday read as final | The settled table for that date did not exist at the time the model ran | Gate the model on partition existence, not on a clock |
| Late-arriving events for a prior date | A settled partition's row count grows after it was first written; event timestamps precede the arrival time by hours | Rebuild a trailing window of 3 days rather than only yesterday |
| Missing or delayed settled partition | A gap in the date sequence, or a settled table that appeared many hours after its usual arrival | Backfill from the retained intraday data and re-run downstream, alerting on absence |
| Timezone boundary | Row counts are complete but the day's shape is shifted; the first and last hours look wrong | Fix the reporting-date definition once, in one place, and document which clock it uses |
| A schema change altering a derived field | Counts unchanged, a downstream metric moved; a new column or a new event parameter appeared on the same date | Diff the schema between partitions and pin the fields the model depends on |
The first three are freshness problems and share a family of fixes. The last two are correctness problems that will not resolve themselves no matter how long you wait, which is why separating them early is worth the audit.
The boundary that puts an event in the wrong day
Export partitions are usually named for a date derived from one clock, while the reporting day your business talks about is derived from another — the property's configured timezone, the finance calendar, or the local time where the campaign ran. When those differ, the totals are not wrong so much as answering a question nobody asked. The tell is a day whose row count looks healthy and whose hourly shape is displaced: the morning peak lands in the previous partition and the last hours of trading fall into the next.
- Define the reporting date once, as a derived column in the staging layer, and never let a query recompute it from a raw timestamp inline.
- Store event timestamps in UTC and derive local dates from them, rather than storing local times and trying to recover the offset later.
- Handle the offset shifts. Two days a year are 23 or 25 hours long in many markets, and a job that assumes 24 will silently under- or over-count on exactly those dates.
- Keep the export's own partition date as a separate column. You need both — one to find the data, one to report it — and collapsing them is how this bug becomes permanent. The three-layer model that keeps derived definitions out of the raw layer is set out in turning raw event rows into a model people can query.
When the counts are identical and the metric still moved
This is the cause that survives every freshness fix, because nothing about the volume changed. Someone added an event parameter, renamed one, or started sending a field with a different type, and a derived metric that reads that field now resolves differently for rows written after the change. Nested parameter structures make it worse: a query reading a value by key returns nothing rather than failing when the key is absent, so the metric drops without an error anywhere.
Diff the field list between two partitions either side of the movement. It is a cheap query and it either produces a suspect immediately or rules the whole category out. The same class of silent absence explains conversions that arrive with no click identifier attached — the mechanism traced in the click identifier missing by the time the form submits, where an empty field and a never-populated field are indistinguishable downstream.
A number that changes because more data arrived is a pipeline working. A number that changes because a field started arriving under a different name is a pipeline that has stopped telling you the truth.
Wait, rebuild on arrival, or freeze and restate
There are only three defensible policies, and the mistake is running all three by accident in different models.
- Wait. Downstream models refuse to run for a date until its partition passes the completeness rule. Right for finance-facing and client-facing numbers, where being late is cheaper than being wrong. The cost is that a delayed export delays the whole chain, so the alert on absence matters more than the alert on lateness.
- Rebuild on arrival. Models run early on provisional data and re-run over a trailing window when the settled partition lands. Right for operational dashboards that need same-day signal. Every rebuild re-reads the same rows, so the query cost multiplies exactly the way fan-out multiplies a tagging bill — see the tagging server bill climbing faster than traffic.
- Freeze and restate. Snapshot the figure at a stated cut-off, publish it as final, and record any later movement as an explicit restatement with a version and a reason. Right for anything an external party has already acted on, because silently changing a number somebody quoted is worse than restating it.
The four checks worth wiring in permanently
- Partition existence, as a gate rather than a warning. A model that runs against a missing date should fail loudly instead of producing a small number.
- Row-count deviation against the trailing same-weekday median, with the tolerance you chose written into the check rather than held in somebody's head.
- Maximum event timestamp per partition, which catches the truncated day that a row-count check on its own will pass.
- Schema drift between consecutive partitions, so a new or renamed field is announced by the pipeline rather than discovered in a client meeting.
None of this is sophisticated, and all of it is the kind of work that gets deferred until the first restatement. Building the freshness contract at the same time as the model — rather than adding it after a bad month — is part of what we mean by measurement being in the definition of done on MVP and product builds. The rest of this silo sits under tracking, consent and event pipelines, part of our marketing and advertising practice.
Frequently asked questions
Short answers to the follow-ups this page tends to raise.
Why does the daily export table differ from the intraday table?
Because they are different objects with different guarantees. The intraday table is a provisional stream that accumulates rows as they arrive and is replaced when the settled table for that date is written; the daily table is the complete, deduplicated version of the same day. Rows arriving late, events re-partitioned onto their correct date, and any provisional row that was superseded all show up as a difference between the two.
How do I know whether yesterday's number is safe to publish?
Check three things: the settled partition for that date exists, its row count is within your stated tolerance of the trailing 4 same-weekday partitions, and its maximum event timestamp reaches the end of the reporting day. All three have to pass. A partition with a healthy row count but a maximum timestamp of 19:41 is missing the evening and will move again.
How far back should a rebuild go?
Far enough to cover your observed late-arrival distribution, which for most consumer traffic means a trailing window of about 3 days. Measure it rather than copying a number: compare the arrival time and the event time on a settled partition and find the point where the additional rows stop being material. Rebuilding 30 days nightly is not more correct, it is just more expensive.
A partition is missing entirely. Can I backfill it?
Usually yes, from the intraday data for that date if your retention still covers it, and that is exactly why the retention window is worth setting deliberately. Backfill into the same partition the settled table would have occupied, mark it as reconstructed so nobody mistakes it for a native export, and re-run every downstream model that already read the gap as a real low day.
- event export
- warehouse
- data freshness
- diagnostics
The work behind this page
Builds from our portfolio that this page draws on.
ShipSight
A supply-chain control tower that tracks every shipment across ocean, air and ground, predicts each ETA with a confidence score, and flags at-risk shipments before they slip.
LogisticsPipelineIQ
An AI SDR platform that scores every lead for fit, runs multichannel sequences across email, LinkedIn and call, drafts the replies, and books the meeting.
Sales AIRead next
- The exported event row: nested parameters and no session columnOne row per event, a repeated parameter struct with four typed slots, and a pseudonymous identifier that can be missing. Every figure a report shows is derived from this.definition
- The click identifier is missing on half the conversions that need oneAn import rejected for a missing click identifier is a capture problem, not a platform problem. Three counts per entry path tell you which hop is eating the parameter.diagnostic
- The ad account reports a third more conversions than the warehouse, every single monthFour causes, ranked. Two are defects you can fix and two are definitional differences you can only document — and an exact match is not one of the available outcomes.diagnostic
- A slice of events lands before the visitor has answered the bannerEvents arriving with an absent consent field are not a compliance abstraction. They are a race between two scripts, and the race has a rate you can measure this afternoon.diagnostic
- Click identifiers: the URL parameters that let a server-sent conversion find the ad that caused itA campaign tag describes where traffic came from. A click identifier is the key that joins a sale back to a specific click — and only one of the two is load-bearing.definition
- Consent state: a typed field on each event, not a switch on the pageThe pageview before the banner answer and the purchase after it are both correct, and they carry different consent values. That only works if consent travels per event.definition
Working on something in this space?
Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team, and what a sensible first slice of the work looks like.
Start the conversation