Rebuilding Tubi's Advertising System with Scala and Akka – Part 1: Request Parsing, Validation, and Filtering
This article explains why Tubi rewrote its legacy PHP ad platform, how it adopted Scala, Akka, and Reactive Streams to model the ad request lifecycle as a reactive stream, and details the first three processing steps—parsing, enrichment, and precise filtering—along with sample Scala filter code.
Tubi’s ad‑service originally ran on a PHP stack backed by MySQL and Memcached, which became hard to maintain after five years of ad‑hoc evolution and a shortage of PHP expertise. To eliminate technical debt and enable richer bidding, monitoring, and testing, the team decided to rebuild the system from scratch.
The new stack is based on Scala and Akka. Akka Streams provides a high‑throughput data‑analysis pipeline, while Akka Actors give a low‑latency foundation for machine‑learning services. The team also leverages existing Scala experience from Spark‑based ML projects.
The advertising workflow is modeled as a Reactive Stream. An incoming video‑play request triggers an ad‑request that must be answered in under one second. The request passes through a series of asynchronous stages, each represented as an Akka‑Stream Graph that can be developed, reasoned about, and tested independently.
Step 1 – Request Parsing and Validation : Using Akka‑Http’s routing DSL (augmented with custom DSLs), the service validates all request parameters and captures metrics before proceeding to production.
Step 2 – Request Enrichment : gRPC calls retrieve auxiliary metadata. Each user is represented by an Actor that caches the last 30 days of ad‑view history, enabling frequency capping and preventing repetitive ad exposure.
Step 3 – Ad Filtering and Precise Targeting : The enriched request is flattened into individual ad‑creative units. Each unit flows through a series of filter graphs; units that fail a filter generate log events for later analysis. An example Scala filter that checks genre constraints is shown below.
class GenresFilter extends BaseFilter("genre") {
override def predicate: (RichContext, AdCandidate) => Boolean = {
case (context: BaseRequestContext, ad: AdCandidate) =>
if (ad.targeting.genreBlacklist.nonEmpty) {
!ad.targeting.genreBlacklist.exists(context.content.genres.contains)
} else if (ad.targeting.genre.nonEmpty) {
ad.targeting.genre.exists(context.content.genres.contains)
} else {
true
}
}
}All filtering logic is encapsulated in reusable graph components, making each predicate easy to unit‑test.
Conclusion : The article covered the motivations for the rewrite, the choice of Scala and Akka, and a detailed walkthrough of the first three stream‑based processing steps. Future posts will explore rate‑control, bidding, and final ad selection. The team invites Scala‑enthusiasts to join Tubi.
Bitu Technology
Bitu Technology is the registered company of Tubi's China team. We are engineers passionate about leveraging advanced technology to improve lives, and we hope to use this channel to connect and advance together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.