Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Fair Candidate Selection

The serve-time selection pipeline applies multiple filters before Thompson Sampling runs, ensuring only eligible candidates are considered.

Complete Selection Pipeline

From the AdServer source code, the exact order of operations:

1. Lookup ServeView from DData
   Key: "siteId|slotId"
   → Vector[CandidateView]

2. Content Recency Filter
   Keep if: (now - classifiedAtMs) ≤ contentRecencyWindowMs (48h)

3. Frequency Cap Check (if userId provided AND any caps exist)
   → Group candidates by advertiserId
   → Query AdvertiserEntity for user impression counts (100ms timeout)
   → Filter: keep if impressions < frequencyCap
   → Fail open on timeout (include all)

4. Rate Tracking (synchronous)
   → TrafficObserver.recordRequest(nowMs)
   → Update EMA-smoothed request rate (1s window, α=0.3)
   → BEFORE any async operations

5. Pacing Gate (BEFORE Thompson Sampling)
   → Fetch CachedSpendInfo for all participating campaigns
   → Compute aggregate PacingContext
   → PacingStrategy.throttleProbability(ctx) → [0.0, 0.99]
   → if random() < throttleProb: return NoCandidates (204)
   → Pacing gates VOLUME, not CHOICE

6. Thompson Sampling Selection
   → Cold start strategy selection (full cold / warmup / partial / standard)
   → Score: sampledCTR × log(1 + CPM)
   → Select argmax

7. Budget Reservation
   → CampaignEntity.Reserve(spend estimate)
   → AdvertiserEntity.GetBudgetStatus()
   → On failure: loop to next-best Thompson score candidate
   → All exhausted: return NoCandidates

Why Pacing Before Thompson Sampling?

If pacing ran after TS:

  • TS picks a creative → pacing throttles → wasted exploration (we learned nothing)
  • TS would consistently select a high-CTR creative that gets throttled, biasing future selection

With pacing before TS:

  • Throttle decision is independent of creative choice
  • When a request passes the gate, TS explores the full eligible set
  • Every Thompson Sampling decision contributes useful data

Campaign Mix Change Detection

When the set of participating campaigns changes between requests:

if lastCampaignSet.nonEmpty && currentCampaignSet != lastCampaignSet:
    log campaign mix changed (added/removed)
    pacingStrategy.reset()  // Don't let PI compensate for mix changes

This prevents the PI controller from making corrections based on stale campaign data.

Orphaned Creative Preservation

When new auction results arrive, creatives from the previous auction that aren’t in the new set are preserved as “orphaned”:

orphanedCreatives = existingCandidates.filterNot(c =>
    newAuctionCreativeIds.contains(c.creativeId)
)
mergedCandidates = (newCandidates ++ orphanedCreatives).distinctBy(_.creativeId)

This ensures multi-campaign diversity survives across auction cycles and approval status is preserved.