Skip to content
esc close · switch
LYRA/ Tools/ Operations/ Data Sync Engine
Operations Scheduled · per-sourceETL

Your account, fresh — by sub-system.

Data Sync Engine is the plumbing layer behind every Lyra tool. Each data type has its own sync on its own cadence — recommendations hourly, RSA assets daily, negative keywords daily, Slack messages daily. The main campaign-data sync is on-demand. All sub-syncs UPSERT-keyed and idempotent — re-runs never duplicate.

Per-source cadences · Idempotent UPSERT · On-demand main sync
What runs · the sync surface

Five sub-syncs, five cadences.

Each data type has its own sync on its own cadence. Recommendations refresh hourly because they change fast; RSA assets refresh daily because they don't. The main campaign-data sync is on-demand because it's heavy. All five UPSERT-keyed by (customer_id, entity_id, date) — idempotent.

Workflow · Multi-brand agency · 60 connected accounts Live · last full pass 14:00 UTC
01 · Sub-syncs 5 Independent ingestion paths Recommendations · RSA assets · negative keywords · Slack messages · main campaign data. Each one its own cadence and own table set.
02 · Cadences Hourly → on-demand Per sub-sync schedule Recommendations every hour. RSA / negatives / Slack daily. Main campaign data on-demand via /sync/yesterday.
03 · Accounts 60 Per agency portfolio Each sub-sync iterates connected customers; large portfolios complete in a few minutes per sync.
04 · Strategy UPSERT Idempotent · keyed ON CONFLICT (customer_id, entity_id, date) UPDATE. Re-runs land on the same row; no duplicates, ever.
Data sources
  • Google Ads APIprimary
  • Slackchannel sync
  • Shopifyevery 6h
  • Merchant CenterPerformance Labelizer
Tables written
  • campaign_daily_datamain
  • ad_group_daily_datanon-PMax
  • asset_group_daily_dataPMax
  • search_terms_snapshots30d
Failure handling
  • One sub-sync failsothers run
  • Re-run is idempotentUPSERT
  • Stale-data warningfreshness rail
Sample · sync status

Fresh by sub-system , not all at once.

Top strip: per-source freshness across the 5 sub-syncs. Below: the last 5 sync runs across an agency portfolio of 60 accounts. Each sub-sync runs independently — recommendations refresh fast, RSA refreshes daily, main campaign data only when triggered.

SYNC STATUS · multi-brand agency · 60 accounts 5 sub-syncs · staggered cadence v.3.0 · sync-engine
Recommendations fresh · 14m hourly cron · last 14:00 UTC
RSA assets fresh · 6h daily 02:00 UTC
Negative kw fresh · 7h daily 01:00 UTC
Slack fresh · 5h daily 03:45 UTC
Main campaign data on-demand last triggered 00:00 UTC
#1801 Apr 26 · 14:00 UTC Recommendations sync cron · hourly Synced Google Ads recommendations across 60 accounts Pulled ~2,400 recommendations into the recommendations feed. UPSERT-keyed by (customer_id, recommendation_id, fetched_at). scheduled
#1800 Apr 26 · 03:45 UTC Slack sync cron · daily Captured 27 messages from 11 channels into Account Diary Per-channel cursor stored in diary_slack_sync_state; re-run is idempotent (Slack ts is the dedup key).
#1799 Apr 26 · 02:00 UTC RSA assets cron · daily Refreshed RSA performance for 60 accounts Pulled headline + description performance labels (Google's POOR / AVERAGE / GOOD / EXCELLENT) for every active RSA. Used by PMax Asset Diagnostics.
#1798 Apr 26 · 01:00 UTC Negative kw sync cron · daily Synced negative-keyword lists across 60 accounts Used to filter Search Terms Management candidate lists — terms already on a negative list are excluded from new candidates.
#1797 Apr 26 · 00:00 UTC Main sync manual · /sync/yesterday Triggered campaign-data sync for 60 accounts Synced campaign · ad-group · asset-group · search-term daily metrics for Apr 25. 58 successful, 2 failed (rate-limit). Failures retry on next trigger. on-demand
Last 5 sync runs · all UPSERT-keyed · re-runs idempotent v.3.0 · sync-engine
How the syncs work

Per source, per cadence , per table.

There isn't one big sync. There are five, each tuned to its data type. The pattern is the same for all five: read from source, normalise, UPSERT into the destination table on a composite key. Idempotent by design.

01 · RecommendationsHourly · cron

The fastest-changing surface.

Google Ads recommendations refresh hourly because they're the surface most actively used by automation (Auto-Apply Guard, the recommendations feed UI). Pulls every active rec across every connected account; UPSERT-keyed by (customer_id, recommendation_id, fetched_at).

Cadence 0 * * * *
Source Google Ads API
Volume ~40/account/h
02 · RSA assetsDaily 02:00 UTC

Performance labels for ads.

Per-RSA headline + description performance labels — Google's POOR / AVERAGE / GOOD / EXCELLENT. Used by PMax Asset Diagnostics. Daily is sufficient because labels don't shift hour-to-hour.

Cadence 0 2 * * *
Source Google Ads API
Drives PMax diagnostics
03 · Negative kwDaily 01:00 UTC

Existing negatives, mirrored.

Pulls every account's existing negative-keyword lists (campaign + ad-group level). Used to filter Search Terms Management — terms already negated don't appear as new candidates. Daily is fine because negative lists don't churn.

Cadence 0 1 * * *
Source Google Ads API
Drives STM candidate filter
04 · SlackDaily 03:45 UTC

Channel messages → diary.

Configured Slack channels per client; each day's run captures messages with `source_user_name` set to the Slack author. Per-channel cursor in `diary_slack_sync_state` makes re-runs idempotent (Slack ts is the dedup key).

Cadence 45 3 * * *
Source Slack API
Sink account_diary_entries
05 · MainOn-demand

Heavy, so you trigger it.

The main campaign-data sync (`/sync/yesterday`) is heavy — it pulls daily metrics for every campaign, ad group, and asset group across every connected account. Triggered on-demand (typically nightly via an external orchestrator) rather than scheduled inside Lyra's own cron list.

Endpoint /sync/yesterday
Volume 60+ accts/run
Tables campaign_daily_data + 3 more
06 · UPSERTIdempotent

Same key, same row.

Every sub-sync writes via INSERT … ON CONFLICT (customer_id, entity_id, date) DO UPDATE. Re-running a failed sync lands on the same rows — no duplicates, no orphans, no drift between runs. The sync's correctness contract.

Strategy ON CONFLICT UPDATE
Re-run Safe always
Drift None by design
Frequently asked

The honest answers about syncs.

What's on a cron and what's on-demand?

Recommendations sync runs hourly. RSA assets daily at 02:00 UTC. Negative keywords daily at 01:00 UTC. Slack messages daily at 03:45 UTC. The main campaign-data sync (`/sync/yesterday`) is on-demand — usually triggered nightly by an external orchestrator, not by Lyra's own internal cron today. The freshness dashboard surfaces which sub-syncs are stale at any point.

Why isn't the main sync on a cron?

Historical reason: the main sync is heavy (60+ accounts, four tables, daily metric per entity), and the orchestrator that triggers it lives upstream of Lyra in this deployment. Adding an internal cron for it is on the deferred backlog. Sub-syncs are on internal crons because they're scoped, fast, and tied to specific in-product features.

What if a sync run fails?

Each sub-sync is independent — one sub-sync's failure doesn't block the others. Because every sync UPSERTs on a composite key, re-running a failed sub-sync is safe (no duplicates). Failed sub-syncs surface as a 'stale' status in the freshness dashboard until the next successful run lands.

Is there an LLM in the sync path?

No. Pure ETL. The sync engine reads from sources (Google Ads API, Slack, Shopify, Merchant Center), normalises the response, and UPSERTs into the destination table. Lyra Agent and the rest of the AI layer read from the synced tables; they don't participate in the sync itself.

Can I trigger a manual sync?

Yes. `POST /sync/yesterday` triggers the main campaign-data sync for one or all connected accounts. Sub-syncs can be triggered manually too (each has its own endpoint), though the cron usually keeps them fresh enough that you rarely need to.

14-day free trial · No credit card · Cancel in one click

Run the audit.
Keep the findings.

Connect your account and let Lyra run its 18 tools for 14 days. If the projected waste recovery isn't worth at least 10× the $49, don't pay. You keep every insight either way.

Read-only connect · write access opt-in per tool · SOC 2 in progress · GDPR + CCPA compliant