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

Rate Tracking (EMA)

Accurate rate measurement is the foundation of the pacing system. Promovolve uses a synchronous Exponential Moving Average (EMA) with a 1-second sliding window.

TrafficObserver

From pacing/TrafficObserver.scala:

class TrafficObserver(
  rateWindowMs: Long = 1000,    // 1-second window
  rateEmaAlpha: Double = 0.3     // EMA smoothing factor
)

Recording (Synchronous)

Called on every Select request, before any async operations:

recordRequest(nowMs):
  if windowStartMs == 0: windowStartMs = nowMs

  requestsInWindow += 1
  windowElapsed = nowMs - windowStartMs

  if windowElapsed >= rateWindowMs:   // window closed
    windowSec = windowElapsed / 1000.0
    instantRate = requestsInWindow / windowSec
    smoothedRate = α × instantRate + (1 - α) × smoothedRate

    windowStartMs = nowMs
    requestsInWindow = 0

  return smoothedRate

EMA Behavior

With α = 0.3:

Window 1: instant=100, smoothed = 0.3×100 + 0.7×0   = 30
Window 2: instant=120, smoothed = 0.3×120 + 0.7×30  = 57
Window 3: instant=110, smoothed = 0.3×110 + 0.7×57  = 73
Window 4: instant=105, smoothed = 0.3×105 + 0.7×73  = 83
Window 5: instant=100, smoothed = 0.3×100 + 0.7×83  = 88

Converges within ~5 windows. Spikes are dampened:

Window 6: instant=500, smoothed = 0.3×500 + 0.7×88  = 212  (spike dampened)
Window 7: instant=100, smoothed = 0.3×100 + 0.7×212 = 178  (recovering)

Why Synchronous?

The rate tracking call is synchronous and runs on the same thread handling the serve request. This ensures:

  • Every request is counted exactly once
  • No race conditions from async updates
  • Rate is always current when the pacing gate runs

Stabilization

The grace period requires EmaStabilizationWindows = 3 windows of data before the EMA is considered stable. During these initial windows, the grace period remains active to prevent PI corrections based on noisy rate estimates.

Usage in PI Control

The smoothed rate feeds into the base throttle calculation:

baseTargetImpsPerSec = (dailyBudget / dayDurationSeconds) / (avgCpm / 1000.0)
baseThrottle = 1.0 - (baseTargetImpsPerSec / requestRate)

Where requestRate is the EMA-smoothed rate from TrafficObserver.