TemporalStore for Ads Serving

Ads serving needs temporal features, not just profile lookup.

Ads systems depend on fast-changing user intent, campaign state, retrieval signals, ranking features, budget pacing, frequency caps, and policy windows. TemporalStore can serve the sequence and aggregate state behind targeting, retrieval, and ranking without forcing every new signal into a separate cache, stream job, or offline materialized table.

Beyond regular feature serving

Regular feature serving can read a user profile or a campaign attribute. Ads serving often needs the temporal shape: recent ad exposures, conversion paths, interest changes, advertiser/category velocity, campaign spend windows, and per-policy counters. Those signals are naturally sequence and aggregate workloads.

Ads is especially sensitive because the serving decision has many clocks at once: user intent changes in seconds, budgets and pacing move in minutes, conversions feed back over hours, and policy windows may last days. TemporalStore gives those clocks a shared request-time model instead of scattering them across caches and jobs.

Targeting sequences

Recent views, searches, clicks, ad impressions, conversions, skips, reports, and negative actions.

Retrieval signals

User-category, user-advertiser, query-ad, and session-ad interactions keyed by time and context.

Ranking aggregates

Windowed CTR/CVR, dwell, conversion, bid-quality, freshness, fatigue, and engagement counters.

Policy state

Frequency caps, budget windows, pacing counters, safety blocks, consent state, and cooldown periods.

Example: ad eligibility and ranking in one request

A single ad request may need to know whether an ad is eligible, whether the campaign is pacing correctly, whether the user is fatigued, and whether recent actions suggest intent. That is not one feature. It is a temporal decision bundle.

Ad request for user_42:
  targeting sequence:
    searched "cloud gpu pricing" 3 minutes ago
    visited ml-infra page twice today
    clicked competitor campaign yesterday

  frequency cap:
    campaign_9 impressions_1h = 1
    campaign_9 impressions_24h = 4
    cooldown_until = none

  pacing aggregate:
    campaign_9 spend_5m = 812.45
    budget_remaining = 18214.10
    pacing_status = healthy

  ranking aggregate:
    user_advertiser_24h clicks = 2
    user_category_1h conversions = 0
    fatigue_score = low

Serving model

TemporalStore can keep ads state in typed temporal models. Retrieval can read recent intent sequences, ranking can read aggregate windows, and policy can read cap and pacing counters. The important part is that the system stores time as a serving primitive, not as a log field discovered later.

AdsServingState:
  sequence:user:{user_id}:ad_events
    fields = ad_id, advertiser_id, campaign_id, action, surface, ts

  aggregate:user_advertiser:{user_id}:{advertiser_id}:24h
    fields = impressions, clicks, conversions, reports, last_seen_ts

  aggregate:campaign:{campaign_id}:pacing_5m
    fields = spend, impressions, clicks, budget_remaining, last_update_ts

  cap:user_campaign:{user_id}:{campaign_id}
    fields = impressions_1h, impressions_24h, cooldown_until, policy_version

How the latest TemporalStore code informs the ads design

The C++ code shows three useful building blocks for ads serving. Timestamped feature rows support bounded reads over recent actions. Temporal aggregates encode metric, sorted dimensions, bucket width, and bucket id so spend, CTR, CVR, and cap windows can be updated and queried online. The IPS-style model adds table schemas, slots, action types, time ranges, top-k, filters, TTL, compaction, and quota control for large action histories.

Ads workloadTemporalStore primitiveWhy it matters
Targeting and retrieval historyTimestamped sequences with filters and count limits.Recent intent can be served directly instead of being hidden in a stale profile value.
Pacing, fatigue, and frequency capsBucketed aggregates for metric and dimension windows.Policy and ranking can read the same fresh counters through one serving path.
User-advertiser-category stateSchema-driven slots, action types, top-k, sort, and filter operators.High-cardinality histories become queryable without creating a separate service for every ranking idea.

Where it helps

Sequence features

Before: scattered path Raw event stream Session cache Nearline transform job Ranker-specific join code
With TemporalStore Append typed ad event Read bounded user sequence Filter by advertiser, category, surface, time

Aggregated features

Before: many stores Counter service Redis TTL keys Offline hourly table Backfill and repair scripts
With TemporalStore Update typed aggregate windows Read high-cardinality keys online Replay the state behind the decision

Why ads is a strong TemporalStore fit

  • Ads ranking depends on recent intent and temporal feedback loops.
  • Targeting and retrieval need user, query, advertiser, category, campaign, and session dimensions.
  • Frequency caps and pacing counters need online windows with durable recovery.
  • High-cardinality aggregates are expensive to scatter across cache keys, jobs, and services.
  • Replayable serving state helps explain why an ad was eligible, ranked, capped, or blocked.

Compared with the usual ads stack

LayerCommon workaroundTemporalStore advantage
Frequency capsRedis TTL keys plus repair jobs.Windowed cap state with durable recovery and replayable decision metadata.
PacingSeparate counter service and stream jobs.Typed campaign aggregates that ranking and policy can read through one serving API.
Targeting historySession cache plus nearline joins.Bounded user/ad event sequences with filters by advertiser, category, surface, and time.
DebuggingJoin logs, counters, cache snapshots, and ranker traces after the fact.Replay the temporal state that made the ad eligible, ranked, capped, or blocked.