RTB Architecture Overview
Real-Time Bidding is the programmatic ad auction system that runs every time a user loads a web page with an ad slot. Timeline: user visits a page → publisher sends a bid request to an SSP (Supply-Side Platform) → SSP broadcasts the bid request to 10-50 DSPs (Demand-Side Platforms) → each DSP evaluates and responds with a bid in < 100ms → SSP runs the auction (second-price) → winning DSP is notified → ad is served. The entire cycle must complete in < 100-150ms wall-clock time before the page renders, which means DSP bidding logic has a hard budget of ~50ms including network round-trip. This is one of the most latency-sensitive distributed systems at scale: 5-10 million bid requests per second globally.
Bid Request and Response
The OpenRTB standard (IAB) defines the bid request/response schema. Key fields in the bid request: id (auction ID), imp (impression array with ad slot specs: size, floor price, placement type), site/app (publisher URL, category, language), user (user ID, age, gender if available), device (IP, user agent, geo, device type), at (auction type: 1=first-price, 2=second-price). The DSP responds with: id (matches request ID), seatbid[].bid: price (CPM in USD), adid (creative ID), adm (ad markup), nurl (win notification URL), lurl (loss notification URL).
# Bid request processing (DSP side)
class BidEngine:
def process_bid_request(self, request: BidRequest) -> Optional[BidResponse]:
start = time.monotonic_ns()
# 1. User lookup: fetch user profile from Redis ( 45: # timeout guard
return None
return BidResponse(
id=request.id,
seatbid=[SeatBid(bid=[Bid(
price=bid_price,
adid=best_campaign.creative_id,
nurl=f"https://dsp.example.com/win?aid={request.id}&price=${{AUCTION_PRICE}}"
)])]
)
Budget Pacing with Redis
Advertisers set daily budgets. DSPs must pace spend evenly (not blow the budget in the first hour). Token bucket pacing: each campaign has a Redis counter for tokens. A background job refills tokens every second: refill_rate = daily_budget / (86400 * avg_cpm / 1000). On each bid: DECRBY the tokens counter by the bid price. If tokens 1.0, slow down; if < 0.8, speed up.
Win/Loss Notification and Attribution
When the SSP selects a winner: the SSP fires the win notification URL (nurl) with the clearing price substituted for ${AUCTION_PRICE}. The DSP receives this HTTP callback and: (1) records the win and clearing price, (2) deducts the actual clearing price from the budget (vs. the bid price deducted optimistically), (3) credits back the difference. For second-price auctions, the clearing price < bid price in most cases. Loss notifications (lurl) are optionally fired for losing bids. Attribution: when the served ad is clicked or viewed, the DSP fires impression/click pixels, logging event data to Kafka. Attribution joins click events to the original bid record to credit conversions to the correct campaign.
Frequency Capping
Frequency cap: limit how many times a user sees an ad (e.g., max 3 impressions per day per campaign). Implementation: Redis sorted set per (user_id, campaign_id) with impression timestamps. On each bid: ZRANGEBYSCORE to count impressions in the last 24 hours. If count >= cap: do not bid. ZADD the timestamp on win notification. Expiry: ZREMRANGEBYSCORE to remove entries older than 24 hours. TTL on the key = 25 hours. Cross-device frequency capping: use a device graph to link user IDs across devices and aggregate frequency counts. For very high scale (100M+ users): use a Bloom filter or Count-Min Sketch per campaign to approximate frequency — allows false positives (skip some valid impressions) but prevents over-serving with O(1) operations.
See also: Snap Interview Prep
See also: Twitter/X Interview Prep
See also: Meta Interview Prep