The sync model
How the warehouse stays fresh — nightly full pulls, 6-hourly incrementals, and no webhooks.
Google Ads has no webhooks, so freshness comes from scheduled pulls. A background worker keeps a normalized
copy of every account in the gads_* tables (all money in micros), and reads never hit the Google Ads API
live.
Three sync modes
- Backfill — 90 days of metrics, all dimensions, and 30 days of change history. Enqueued once, when you import an account.
- Full — a nightly
LAST_14_DAYSrefresh plus all dimensions and change history since the last full run. - Incremental — dimensions, today's and yesterday's campaign metrics, and recent change history.
Scheduling
A cron runs every 6 hours and, for each active account, enqueues a full sync when the last one is stale (older than ~20 hours) or an incremental otherwise. It never clobbers an in-flight sync. A separate worker drains the queue every 5 minutes. Audits run on their own daily schedule.
Freshness is surfaced, not hidden
gads_get_sync_status reports the latest run, and strategic reads return a dataFreshness warning. When data
is stale or history is thin, act on the warning first — trigger a sync and wait — rather than trusting the
numbers.
In local development there's no cron. Trigger the worker manually, or use Trigger sync in the account /
gads_trigger_sync over MCP. A sync that's already running is refused rather than duplicated.