SIGMOD '21 — ACM International Conference on Management of Data (2021)arXiv:2104.00087 ↗
Real-time Data
Infrastructure at Uber
Kafka, Flink, Pinot, HDFS and Presto — re-engineered for trillions of messages a day
Yupeng Fu and Chinmay Soman (Uber Technologies)
This is the paper that explains how Uber turns trillions of Kafka messages and petabytes of data per day into decisions made in seconds — surge prices, fraud checks, restaurant dashboards, ML monitoring. Rather than building proprietary systems, Uber assembled an entire real-time stack from open source — Apache Kafka for streaming storage, Flink for stream processing, Pinot for sub-second OLAP, HDFS for archival, Presto for interactive queries — then re-engineered each layer to survive three kinds of scaling: data, use cases, and users. It reads as a field guide to the freshness-vs-consistency trade-offs, multi-region failover machinery, and self-service tooling behind one of the industry's largest real-time deployments.
PBs
of data per day
collected across all regions
trillions
of messages / day
one of the largest Kafka fleets
99.99%
availability SLA
with seconds-level freshness
< 1 s
p99 query latency
Pinot, at 10,000s of QPS
Introduction
The paper opens by framing real-time analytics as mission-critical rather than nice-to-have: customer incentives, fraud detection, and ML model prediction all require decisions in seconds over streams that reached trillions of messages and petabytes per day (October 2020). The real-time ecosystem decomposes into three broad areas: a messaging platform ( transport), , and for near-real-time analytical queries.
Three scaling axes structure everything that follows: scaling data (exponential year-over-year volume growth from several thousands of microservices), scaling use cases (new business verticals with competing requirements — a financial dashboard and a log pipeline want opposite trade-offs), and scaling users (a technical-skill spectrum from operations staff with no engineering background to advanced platform engineers). Each open-source adoption — Kafka, Flink, Pinot, HDFS, Presto — is judged against all three.
challenge 1
Scaling data
Incoming real-time volume grows exponentially year over year, produced by several thousands of microservices — trillions of messages, petabytes daily.
challenge 2
Scaling use cases
New business verticals keep arriving with competing requirements — a financial ledger and a log pipeline want opposite trade-offs from the same platform.
challenge 3
Scaling users
The people querying this data range from operations staff with no engineering background to platform engineers — the tooling must serve the whole spectrum.
Requirements
Seven requirement categories, each with a concrete anchor: consistency (mission-critical financial dashboards need zero data loss inter- and intra-region), availability (99.99% guarantee — an outage directly impacts the business), data freshness (most use cases need events queryable seconds after production), query latency (user-facing tools need p99 under 1 second), scalability (petabytes/day with continuous organic growth), cost (Uber is a low-margin business), and flexibility (both programmatic APIs and SQL-style declarative interfaces, push- and pull-based).
The section closes with the structural insight of the paper: by , consistency and freshness/availability cannot both be guaranteed at Uber's scale. Dynamic pricing explicitly prioritizes freshness over consistency — late-arriving data is simply excluded from time-window computations — while financial use cases invert the choice. The whole architecture is a menu of such per-use-case prioritizations over the same five systems.
| Surge pricing | Freshness, availability (active-active) | Consistency — late messages dropped from windows; lossy high-throughput Kafka | Kafka → Flink (ML) → key-value store |
| Restaurant Manager | Query latency, freshness | Query flexibility — aggressive Flink pre-aggregation fixes the query shapes | Kafka → Flink (roll-ups) → Pinot |
| Prediction monitoring | Scalability (100Ks of time series, millions of points/sec) | Query-time flexibility — pre-aggregated OLAP cubes | Kafka → Flink (join + aggregate) → Pinot |
| Eats ops automation | Reliability, exploration→production continuity | Latency — minutes-level aggregation windows suffice | Pinot ← Presto (ad-hoc → productionized rules) |
| Financial dashboards | Consistency — zero data loss (active-passive) | Freshness — offset-synced failover over instant availability | Aggregate Kafka → single named consumer |
Abstractions & System Overview
Section 3 defines seven logical layers with minimum requirements. Storage: generic blob/object store, read-after-write consistency, optimized for high write rates (reads are for bootstrap and ). Stream: interface with partitioning and delivery. Compute: arbitrary computation over stream + storage, at-least-once semantics. OLAP: limited SQL over fresh data (filters, aggregations, group/order by), with ingestion via primary keys for critical uses. SQL: full SQL over OLAP and compute (joins included). API: programmatic access for advanced users. Metadata: versioned, backward-compatible schema management for all layers.
Figure 3 of the paper maps abstractions to systems: Kafka (stream + intermediate storage), Flink (compute), Pinot (OLAP), HDFS (archival storage), Presto (SQL). The interesting property is what sits between layers: schema services, ingestion frameworks, and SQL compilers that let the same logical query or dataset move across systems — the glue that the later sections describe.
3.1 · The seven layers
Seven abstraction layers — click any layer
Stream → Apache Kafka
Publish-subscribe over partitioned topics: producers append, any subscriber consumes event-by-event. Minimum requirements: data partitioning and at-least-once delivery.
3.2 · The layers, made concrete
The real-time stack — an event's journey
1 · Produce
Several thousands of microservices — plus rider/driver apps and database changelogs — publish trillions of messages and petabytes of data per day into Kafka. Topics auto-provision when services deploy; federation hides the physical clusters (each kept under ~150 nodes).
Apache Kafka
Stream + intermediate storagePub-sub backbone: app events, DB changelogs, lake ingestion.
Uber's additions: Cluster federation, dead letter queues, Consumer Proxy (gRPC push), uReplicator + Chaperone.
Apache Flink
ComputeStateful stream processing with checkpointing.
Uber's additions: FlinkSQL (Calcite), resource estimation + auto-scaling, rule-based auto-healing, 3-layer job platform.
Apache Pinot
OLAPSub-second analytical serving on fresh data.
Uber's additions: Upsert (shared-nothing), peer-to-peer segment recovery, schema inference, FlinkSQL sink.
HDFS
Storage (archival)Source of truth: Avro landing → Parquet for engines.
Uber's additions: Also holds Flink checkpoints, Pinot segment archives, and all backfill history.
Presto
SQL (interactive)In-memory MPP engine for ad-hoc full SQL.
Uber's additions: Pinot connector with projection / aggregation / limit pushdown (planner + Connector API extensions).
3.3 · The stack in numbers
Kafka throughput
trillions / day
Messages per day across all regions (Oct 2020) — one of the largest Kafka deployments in the industry.
Data volume
PBs / day
Raw analytical data continuously collected from riders, drivers and eaters across all regions.
Producers
1000s of microservices
Several thousands of microservices produce into the stream layer; topics auto-provision on deploy.
Availability SLA
99.99%
Platform guarantee — unavailability directly impacts business functions like driver payments.
Data freshness
seconds
Events must be queryable seconds after production for most use cases.
Query latency
< 1 s
User-facing analytical queries (e.g., Restaurant Manager) target p99 under one second.
Kafka cluster size
< 150
Empirically ideal broker count per physical cluster — federation stitches clusters into one logical cluster.
Flink workload
1000s of jobs
Flink is the de facto stream processor at Uber, growing ~30% year over year.
OLAP footprint
100s of TBs
Grown from dozens of GBs in two years; query load from hundreds to tens of thousands of QPS.
ML monitoring
100Ks of time series
Millions of data points per second — beyond Uber's dedicated time-series database.
Kafka retention
a few days
Why backfill must run from HDFS/Hive instead of replaying Kafka (the Kappa problem).
Client languages
4
Java, Python, Go, NodeJS — why thin generated clients and the Consumer Proxy exist.
Apache Kafka for Streaming Storage
Kafka carries trillions of messages and PBs/day, feeding both real-time consumers and the Hadoop lake (event data from rider/driver apps, database , microservice logs). Four Uber additions: — a metadata server exposes a logical cluster, routing producers/consumers to physical clusters (empirically ideal at < 150 nodes), enabling transparent topic migration and horizontal growth without client restarts. — after repeated consumer failures, messages publish to a DLQ topic for later purge or on-demand retry, so a poison message can't stall trip-receipt processing.
— with tens of thousands of consuming applications, thick client libraries became unmanageable; the proxy consumes on behalf of users and pushes via gRPC to registered endpoints, machine-generated thin clients replace the consumer library, and the push model both improves throughput and escapes Kafka's partition-count ceiling on consumer-group parallelism. — Uber's open-sourced cross-cluster replicator with a rebalancing algorithm that minimizes partition movement and adaptively sheds bursts to standby workers; audits every pipeline stage by comparing windowed message counts and alerting on mismatch (zero-data-loss evidence).
Consumer Proxy — Kafka's pull becomes gRPC push
1 · Proxy consumes, so apps don't have to
The Consumer Proxy subscribes to the topic's partitions using the full-featured Kafka client — the last place that complexity lives. Applications keep only a thin, machine-generated gRPC client (Java, Python, Go or NodeJS).
Apache Flink for Stream Processing
Flink won the 2016 evaluation on robustness: built-in state management and checkpointing, backlog recovery in 20 minutes vs several hours for Storm (millions-of-messages backlog), 5–10× less memory than Spark for equivalent jobs, and no Samza-style hard Kafka dependency for state. It now powers several thousands of jobs, growing ~30% YoY. (built on Apache Calcite) compiles SQL → logical plan → optimized physical plan → Flink job, putting streaming pipelines in production 'in a span of mere hours' for users of all technical levels.
Hidden costs of SQL-for-everyone, and their fixes: resource estimation via empirical job-type profiles (stateless jobs are CPU-bound; stream-stream joins are memory-bound) plus GC-monitoring-driven auto-scaling; and a rule-based monitor that compares job metrics to desired state and auto-restarts or rescales stuck jobs. The unified architecture (Figure 5) has three layers: Platform (business-specific integrations — ML feature training, workflow systems, SQL compilation — normalized into standard job definitions), Job management (lifecycle: validation, deployment, monitoring, failure recovery; unified Start/Stop/List API; checkpoint + metadata persistence; dispatch by job type, importance, priority), and Infrastructure (cluster + storage abstraction: YARN or Uber's Peloton schedulers; HDFS, S3 or GCS for checkpoints).
The 2016 bake-off
Apache Flink
adoptedRecovered from a massive backlog (millions of messages) in 20 minutes; built-in state management and checkpointing.
Apache Storm
rejectedPerformed poorly under back pressure — took several hours to recover from the same backlog Flink cleared in 20 minutes.
Apache Spark
rejectedConsumed 5–10× more memory than the corresponding Flink job for the same workload.
Apache Samza
rejectedStrict dependency on Kafka for state management would have created significant operational overhead at Uber scale.
One platform for thousands of jobs
The unified Flink platform — three layers, click each
Owns the job lifecycle: validation, deployment, monitoring and failure recovery. Exposes a unified API (Start/Stop/List), persists checkpoints and metadata, and acts as a proxy dispatching jobs to physical clusters by type, importance and priority. A shared health monitor auto-recovers transient failures — vital when the job's author wrote SQL, not Java.
Apache Pinot for Real-time OLAP
Pinot was adopted in 2018 on benchmark evidence: vs Elasticsearch, 4× lower memory, 8× lower disk, 2–4× faster queries (filter/aggregation/group-order-by mixes); vs Druid (architecturally similar), Pinot's bit-compressed forward indices and specialized indices (star-tree, sorted, range) gave order-of-magnitude latency wins on some workloads. Growth in 2 years: dozens of GBs → several hundreds of TBs; hundreds of QPS → tens of thousands of QPS.
Uber's four contributions: Upsert — the only open-source real-time OLAP store with it; input streams are partitioned by primary key so all records for a key land on one node, giving a shared-nothing design (no centralized coordination) with better scalability, no single point of failure, easier operation. Full SQL via the Presto integration (§4.5) — Pinot freshness + Presto joins/subqueries. Ecosystem integration — schema inference from Kafka samples (with cardinality estimation), a FlinkSQL sink, and Piper/Spark workflows building offline Pinot tables from Hive. Peer-to-peer segment recovery — replacing synchronous archival through a single controller (a scalability bottleneck and freshness violation) with async archival + recovery from server replicas.
Elasticsearch's resource cost, as a multiple of Pinot
Same data ingested into both systems (Uber benchmark, §4.3). Dashed line = Pinot's footprint (1×).
Pinot (baseline, 1×) Elasticsearch overhead
Two years of growth
Data footprint
dozens of GBsseveral hundreds of TBs
≈4 orders of magnitude in 2 years
Query workload
a few hundred QPStens of thousands of QPS
≈2 orders of magnitude in 2 years
Recovery, redesigned
Segment recovery, before / after — drag to compare
Uber replaced Pinot's centralized segment store with a peer-to-peer scheme: same consistency, better freshness, no single-controller bottleneck.
HDFS Archival & Presto Interactive Queries
HDFS (§4.4) is the archival source of truth: Kafka streams land as Avro, compaction jobs rewrite them to Parquet for Hive/Presto/Spark. HDFS also backs (new pipelines, ML training over months of history, bug-fix reprocessing), Flink checkpoints (stream offsets + per-container operator state snapshots), and Pinot segment archival (replica repair, server bootstrap).
Presto (§4.5) provides interactive full SQL via an in-memory engine that avoids intermediate materialization. Uber exploited Presto's Connector API with its high-performance I/O interface to integrate Pinot as a backend: initially only predicate , then — after extending Presto's query planner and Connector API — projection, aggregation and limit pushdown into Pinot. The combination yields sub-second PrestoSQL latencies over seconds-fresh data, 'not possible on standard backends such as HDFS/Hive,' and supports the exploration→production workflow of §5.4.
Use Cases Analysis
Surge pricing (§5.1): Kafka → Flink (complex ML algorithm over trip events, rider/driver status in time windows, per-hexagon geofences) → key-value store for fast multiplier lookup. Prioritizes freshness + availability over consistency: late messages are excluded from windows, Kafka runs in high-throughput (not lossless) configuration, and the pipeline runs active-active across regions (§6). Restaurant Manager (§5.2): owner-facing dashboards (satisfaction, popular items, sales, service quality) with strict latency needs and fixed query patterns — Flink preprocessors do 'aggressive filtering, partial aggregates and roll-ups' so Pinot meets the latency SLA, trading query flexibility for speed (transformation-time vs query-time processing).
Prediction monitoring (§5.3): joins model predictions to observed outcomes for live accuracy tracking; with thousands of models × hundreds of features, that's several hundreds of thousands of time series and millions of data points per second — beyond Uber's internal time-series database — handled by Flink's horizontal scaling plus pre-aggregated OLAP cubes in Pinot. Eats ops automation (§5.4): COVID-era capacity compliance; Presto-on-Pinot ad-hoc queries validated insights, then the same queries were productionized in a rule engine aggregating location statistics over minutes and alerting couriers/restaurants — 'a seamless path from ad-hoc exploration to production rollout.' Table 1 maps which abstraction layers each use case exercises.
| API | client libraries | ✓ | — | ✓ | — |
| SQL | FlinkSQL / PrestoSQL | — | ✓ | ✓ | ✓ |
| OLAP | Apache Pinot | — | ✓ | ✓ | ✓ |
| Compute | Apache Flink | ✓ | ✓ | ✓ | ✓ |
| Stream | Apache Kafka | ✓ | ✓ | ✓ | ✓ |
| Storage | HDFS | — | ✓ | ✓ | — |
How many of the four use cases touch each layer
Derived from Table 1 — Compute (Flink) and Stream (Kafka) carry every single use case
The life of one event
t = 0
A rider taps, a courier accepts, a driver moves
The event is produced into a regional Kafka cluster — one of thousands of microservices publishing trillions of messages a day. The topic was auto-provisioned when the service deployed.
milliseconds
Replicated everywhere
uReplicator copies the event into aggregate Kafka clusters in every region, giving each region the global view. Chaperone counts messages at every hop — proof that nothing was lost.
seconds
Flink computes
Streaming jobs window, join and aggregate: the surge model updates a hexagon's multiplier; the Restaurant Manager pre-aggregations roll up orders; prediction monitoring joins the event to an ML forecast.
seconds later
Queryable in Pinot
Results land in Pinot (upserted by primary key) or a key-value store — the event is now part of every dashboard aggregate, seconds after it happened.
sub-second
Someone — or something — acts
A restaurant owner's dashboard refreshes (p99 < 1 s), a Presto-powered ops rule fires an alert, a surge multiplier changes. The loop from physical world to decision closed in seconds.
All-Active Strategy
Multi-region Kafka: regional clusters replicate into aggregate clusters in each region (via uReplicator), giving every region a global view. Active-active (surge): each region runs an identical, computationally intensive Flink job over its aggregate feed; an all-active coordinating service designates a primary region whose 'update service' writes multipliers to an active-active key-value store. On regional failure another region is promoted — the design recomputes state everywhere because the state is too large to replicate synchronously across regions; convergence follows from identical aggregate inputs.
Active-passive (consistency-critical, e.g., payments/auditing): a single uniquely-identified consumer reads from the aggregate cluster in the primary region only. Failover hinges on cross-region offset mapping: uReplicator 'periodically checkpoints the offset mapping from source cluster to destination cluster in an active-active database,' and an offset sync job continuously synchronizes consumer offsets between regions. On failover, the standby's consumer resumes from the latest synced offset — no data loss and no excessive reprocessing backlog.
Surviving a region failure — two strategies
Backfill
Backfill triggers: testing new pipelines against historical data, ML training over months of history, and reprocessing after bugs. Lambda (parallel batch + streaming stacks) duplicates logic and invites divergence; Kappa (replay from Kafka alone) demands retention far beyond Uber's few-day Kafka retention — infeasible at PB/day. Uber's two Flink-based solutions: SQL-based — the same FlinkSQL query compiles into two jobs, a streaming job on Kafka (DataStream API) and a batch job on Hive (DataSet API); Lambda-shaped, but with a single user-maintained implementation.
Kappa+ (API-based): reuse the streaming job's code as-is, reading directly from archived Hive/HDFS data with small configuration changes. The hard parts the paper enumerates: identifying boundaries of bounded input, throttling the effectively infinite read rate of historical data so job state and downstream systems survive, and handling out-of-order data — archived data isn't ordered like the original stream, requiring larger watermark/buffering windows. Both approaches are declared 'an active area of investigation with lots of edge cases.'
One query → two Flink jobs
The same FlinkSQL query compiles into a streaming job (DataStream API, reading Kafka) and a batch job (DataSet API, reading Hive). Lambda-shaped execution, but the user maintains exactly one implementation — the compiler owns the divergence risk instead of the engineer.
Best for: the FlinkSQL crowd — anyone whose pipeline is already SQL.
Related Work & Lessons Learned
Related work (§8) positions the stack against: traditional messaging (ActiveMQ, RabbitMQ, Oracle EMS, IBM MQ — outperformed by Kafka's ecosystem; Pulsar noted as promising but less mature), stream processors (Storm, Samza, Heron, Spark Streaming, Apex; proprietary Photon, Facebook Puma/Swift, Kinesis), OLAP (Druid, ClickHouse), SQL systems (Hive, Dremel, Spark SQL, Impala, Drill; Procella, Trill), and full proprietary stacks (Facebook's realtime infra, Google F1 Lightning, Alibaba Hologres) — Uber's differentiator being commitment to open source and community leverage.
Lessons (§9) in four groups. Open-source adoption: each system was built for a narrow purpose; Uber re-worked them across use cases, languages (RESTful Kafka clients for Java/Python/Go/NodeJS), infrastructure, and security. Rapid evolution: interface standardization, monorepo review, thin clients (Kafka client upgrades used to take months), language consolidation (Java/Go for infra, PrestoSQL for analytics), and CI/CD continuously staging open-source updates. Operations: declarative automation of cluster management, config generation, containerization, plus per-use-case dashboards, alerts and chargeback. Onboarding/debugging: central metadata + lineage for data discovery; event auditing (unique IDs, timestamps, service name, tier stamped on events, tracked across Kafka→Flink→Pinot→Hive in every datacenter to spot loss/duplication); auto-provisioned, quota-enforced topics; drag-and-drop Flink/Pinot pipeline deployment.
lesson 1
Open source is a head start, not a solution
Kafka in 2014 was a Java log-shipper. Uber added REST clients in 4 languages, throttling, metadata discovery, DLQs and federation before it fit. Budget for the gap between 'adopted' and 'works here.'
lesson 2
Thin clients, or upgrades take quarters
Fleet-wide Kafka client upgrades used to take months. Thin, machine-generated clients + interface standardization + a monorepo turned library migrations from projects into deploys.
lesson 3
Automate operations before they outgrow the team
Machines multiply faster than engineers: declarative cluster management, generated configs, containerization — plus per-use-case dashboards, alerts and chargeback so owners see their own cost and health.
lesson 4
Self-service or die by support ticket
Topics auto-provision on deploy; schemas are inferred and centrally discoverable with lineage; events carry ID/timestamp/service/tier so loss is detectable everywhere; pipelines deploy by drag-and-drop.
Conclusion & Future Work
Conclusion: the open-source-based stack processes multiple petabytes/day with years of reliable production operation. Against the three challenges — data: thin Kafka clients, federation, Flink checkpointing, Pinot peer-to-peer recovery let thousands of microservices ride one platform (among the industry's largest Kafka deployments); use cases: one consumer protocol spans logging (availability-first) to financial data (zero-loss); Pinot serves both low-latency OLAP and Presto-powered exploration; users: FlinkSQL puts data scientists' and ops personnel's pipelines in production within hours, PrestoSQL federates queries, and backfill needs no custom code.
Future work, four items: unified stream/batch processing (users currently express the same logic twice, in different languages on different frameworks); multi-region and multi-zone deployments with the challenge to 'optimize data placement to balance redundancy for reliability against the storage cost of excessive copies'; on-prem/cloud agnosticism so the stack moves freely as Uber's cloud posture evolves; and tiered storage for Kafka and Pinot (cost efficiency via cold tiers, elasticity via storage-serving separation), pursued with the open-source communities.
The 2021 wishlist, scored from the future
Unified stream + batch
Users write the same logic twice, in two languages, on two frameworks.
Realized: Flink's unified runtime, the lakehouse wave.
Multi-region & multi-zone
Balance data redundancy for reliability against the storage cost of excessive copies.
Still the open cost/reliability frontier.
Cloud agnosticism
Abstract away DC/cloud specifics so the stack moves freely.
Realized: table stakes for every data platform.
Tiered storage
Cold data on cheap media; separate storage from serving for elasticity.
Realized: shipped in both Kafka (KIP-405) and Pinot.
Code & Resources
uber/uReplicator→
The open-sourced cross-cluster Kafka replicator from §4.1 — Helix-based controller, minimal-movement rebalancing, adaptive burst handling. The offset-mapping checkpoints it writes are what make the active-passive failover of §6 lossless.
apache/pinot→
The OLAP store of §4.3 — Uber's upsert design and the peer-to-peer segment recovery described in this paper both live upstream now, alongside the star-tree, sorted and range indices behind the benchmark numbers.
The whole stack, as annotated pseudocode
The paper describes an architecture rather than an algorithm; this pseudocode traces one event through every layer — Kafka, Flink(SQL), Pinot, Presto, and backfill — preserving the reported numbers and design choices from §3–§7.
# Uber's real-time stack, end to end — annotated pseudocode (paper sections 3-7).
# The paper describes architecture, not code; this traces one event's life
# through every layer, preserving the reported numbers.
def rider_opens_app(event): # ~trillions of events/day
"""Producers: thousands of microservices, 4 client languages."""
kafka.produce(topic="trip-events", value=event, acks="throughput")
# Surge config favors throughput over lossless-ness (freshness > consistency).
# Regional cluster (<150 nodes; federation adds clusters behind one
# logical cluster) -> uReplicator -> aggregate cluster in EVERY region.
# Chaperone audits per-stage windowed counts: zero-loss evidence.
def surge_pricing_job(stream): # Apache Flink, active-active
"""Same job runs in each region on the same aggregate feed."""
for window in stream.window(minutes=2, late_data="drop"):
multipliers = ml_pricing_model(window.group_by(hexagon))
kv_store.put(multipliers) # served if this region is primary
# Meanwhile, an analyst ships a dashboard pipeline in HOURS, in SQL only:
FLINK_SQL = """
SELECT restaurant_id, COUNT(*) AS orders, window_start
FROM eats_orders -- Kafka topic (schema inferred)
GROUP BY restaurant_id, TUMBLE(rowtime, INTERVAL '5' MINUTE)
"""
# Calcite: SQL -> logical plan -> optimized physical plan -> Flink job.
# Platform does the ops the analyst can't: stateless jobs are CPU-bound,
# stream-stream joins memory-bound -> auto-scaling + rule-based healing.
flink.submit(FLINK_SQL, sink=pinot.table("orders", upsert_key="order_id"))
# Pinot upsert: partition stream by primary key -> same node, shared-nothing.
def restaurant_dashboard(owner): # p99 < 1 s over seconds-fresh data
return presto.query( # planner pushes work INTO Pinot:
"SELECT ... FROM pinot.orders WHERE ...",
pushdown=["predicate", "projection", "aggregation", "limit"])
def backfill(job, months=6): # Kafka keeps only a few days
"""Kappa+: unchanged streaming code over the HDFS/Hive archive."""
return job.run(source=hive("trip-events"), bounded=True,
throttle=True, watermark_slack="large") # out-of-order historyGlossary
Every dotted-underlined term on this page links back here. Hover any of them in context, or browse the full definitions below.
Apache Kafka
A distributed, partitioned, replicated commit log. Producers append messages to topics; consumers read them independently at their own pace. At Uber it is the transport for app events, database changelogs and lake ingestion — one of the largest deployments in the industry.
Apache Flink
A stream processor with built-in state management and checkpointing, so long-running jobs recover from failure. Chosen over Storm (hours vs 20 minutes of backlog recovery), Spark (5–10× more memory) and Samza (hard Kafka state dependency) in Uber's 2016 evaluation.
Apache Pinot
A columnar OLAP datastore that ingests directly from Kafka and serves filter/aggregation/group-by queries at tens of thousands of QPS. Uber contributed upsert support and peer-to-peer segment recovery upstream.
Presto
A massively parallel processing (MPP) query engine performing all computation in memory. Uber extended its planner and Connector API to push projection, aggregation and limit down into Pinot, achieving sub-second full SQL over fresh data.
HDFS
Stores all raw Kafka data (Avro, compacted to Parquet), Flink checkpoints, and Pinot segment archives. Every backfill ultimately reads from here, because Kafka retains only a few days of data.
OLAP
The workload class of dashboards and monitoring: scan-and-aggregate queries over recent data, as opposed to point lookups (OLTP). The paper defines OLAP as its fourth abstraction layer, implemented by Pinot.
Stream processing
Applying computational logic (aggregation, joins, ML inference) to unbounded event streams with results in seconds. The paper's Compute abstraction layer, implemented by Flink.
Publish-subscribe
The decoupling pattern of the Stream layer: producers don't know their consumers. The paper's minimum requirements for this layer are data partitioning and at-least-once delivery.
At-least-once
The baseline delivery guarantee of the Stream and Compute layers: no loss, but retries can duplicate. Systems needing correctness on top deduplicate downstream — e.g., Pinot's exactly-once ingestion via primary keys.
Exactly-once ingestion
The paper requires exactly-once semantics for critical OLAP use cases: ingestion deduplicates on a primary key, so retried or replicated messages don't double-count. This is what makes financial-grade dashboards possible on an at-least-once transport.
Data freshness
Distinct from query latency (response time of one query). Most Uber use cases need seconds-level freshness; much of the Pinot engineering (async archival, upsert without coordination) defends this SLA.
CAP theorem
The formal reason the paper's seven requirements are jointly unsatisfiable. Uber resolves it per use case: surge pricing chooses freshness/availability over consistency; payments choose the reverse via active-passive consumption.
Upsert
Uber's headline Pinot contribution: the first upsert in an open-source real-time OLAP store. The input stream is partitioned by primary key so all versions of a record land on one node — shared-nothing, no central coordinator, no single point of failure.
Dead letter queue
Open-source Kafka forces a failing consumer to drop messages or retry forever. Uber's DLQ publishes persistently failing messages to a separate topic for later purge or on-demand retry — critical for flows like trip-receipt processing.
Cluster federation
Kafka clusters perform best under ~150 nodes, so Uber federates: producers and consumers see a logical cluster while a metadata server routes to physical ones — enabling transparent topic migration, horizontal growth, and consumer redirection without restarts.
Consumer Proxy
With tens of thousands of consuming apps in four languages, thick Kafka client libraries became unmanageable. The proxy consumes on the app's behalf and dispatches via gRPC push — thin generated clients, per-message retry with DLQ, and parallelism beyond Kafka's partition-count limit.
uReplicator
Replicates topics between regional and aggregate clusters. Its rebalancing algorithm minimizes affected partitions and adaptively sheds bursty load to standby workers. It also checkpoints source→destination offset mappings — the key to active-passive failover.
Chaperone
Collects statistics such as unique message counts per time window from every stage of every Kafka pipeline, compares them, and alerts on mismatch — continuous end-to-end evidence that nothing was lost or duplicated.
FlinkSQL
Built on Apache Calcite: SQL → logical plan → optimized physical plan → Flink job. It made streaming accessible to data scientists, operations and product managers — and made the platform responsible for sizing, scaling and healing jobs its users can't see.
Changelog / CDC
Change Data Capture turns database mutations into events. At Uber, database changelogs are one of the three big Kafka sources, alongside app event data and microservice logs.
Lambda architecture
The classic answer to stream/batch duality. The paper rejects it for maintenance complexity and implementation divergence, but its SQL-based backfill (one query compiled to two Flink jobs) is a deliberately Lambda-shaped compromise with a single user codebase.
Kappa architecture
Eliminates the batch stack but requires weeks-to-months of Kafka retention. Infeasible at Uber's trillions of messages/day with few-day retention — hence Kappa+, which runs unchanged streaming code against the Hive/HDFS archive instead.
Backfill
Because Kafka keeps only days of data, backfill reads from HDFS/Hive. Uber's two mechanisms: compile the same SQL into streaming and batch Flink jobs, or Kappa+ — the streaming code reading bounded historical data with throttling and enlarged out-of-order buffers.
Active-active
Uber's availability-first multi-region pattern (surge pricing): identical Flink jobs in each region consume the same aggregate Kafka feed, so state converges without cross-region replication. Failover = promote another region.
Active-passive
The consistency-first pattern (payments, auditing): a single named consumer reads in the primary region, and uReplicator's checkpointed offset mappings plus an offset sync job let the standby region resume from exactly the right message.
Segment
Pinot tables are stored as segments. Originally each completed segment was synchronously backed up through a single controller (a bottleneck and freshness risk); Uber made archival asynchronous with failed replicas recovering from peer servers instead.
Operator pushdown
Uber extended Presto's planner and Connector API to push predicates, projections, aggregations and limits down into Pinot — the work runs next to Pinot's indices, making sub-second full SQL over fresh data possible.
Avro
A compact, schema'd, row-based format suited to write-heavy landing. Compaction jobs later rewrite Avro into columnar Parquet for efficient analytical scans.
Parquet
Column-oriented storage with compression and predicate-skipping, ideal for analytics. The Avro→Parquet compaction pipeline is the boundary between landing fast and querying fast.
MPP
Presto's execution model: queries are split across a fleet of workers that exchange data in memory without materializing intermediate results, delivering interactive latencies over large datasets.
Watermark
Watermarks let windowed computations close despite out-of-order arrival. Backfilling from archives makes this harder — historical data is far less ordered than the live stream, requiring larger buffering windows in Kappa+.
FAQ
Deliberate strategy, stated in the paper: adopt the strongest open-source system for each layer, re-engineer it for Uber's scale, and contribute the fixes back — leveraging community velocity instead of competing with it. Google (Photon, F1 Lightning, Procella), Facebook (Puma/Swift/Scuba) and Alibaba (Hologres) built proprietary stacks; Uber got comparable capability while uReplicator, Pinot upsert and the Presto pushdown work became community features others now use.
Your trip events land in a regional Kafka cluster and replicate into aggregate clusters in every region. In each region, an identical Flink job consumes that aggregate feed and runs a machine-learning algorithm over time windows, computing a pricing multiplier for each hexagonal geofence. A coordinating service designates one region primary; its update service writes multipliers to an active-active key-value store that the ride-hailing services read. End to end: seconds — and late-arriving events are simply excluded, because a fresh, slightly imperfect price beats a perfect, stale one.
Each occupies a different abstraction layer with different guarantees: Kafka moves events (pub-sub, at-least-once), Flink computes on them continuously (stateful, checkpointed), Pinot answers analytical queries in sub-seconds (indexed, columnar), HDFS remembers everything forever (cheap, consistent archive), and Presto asks flexible questions across all of it (in-memory MPP SQL). The paper's Figure 2 formalizes this as seven layers; remove any system and some layer's guarantee disappears.
Four big things. Federation: physical clusters stay under ~150 nodes (the empirical sweet spot) behind one logical cluster, so scaling and migrations don't touch clients. Dead letter queues: poison messages park in a side topic instead of blocking consumers or being dropped. Consumer Proxy: a middle layer consumes on behalf of apps and pushes via gRPC — thin generated clients in four languages, and parallelism beyond partition count. And uReplicator + Chaperone: audited cross-cluster replication with proof of zero loss.
FlinkSQL. A user writes SQL; the platform (built on Apache Calcite) compiles it through logical and physical plans into a Flink job — 'in production in a span of mere hours.' The catch is operational: users can't reason about the parallelism or memory of jobs they didn't write, so the platform estimates resources from empirical job profiles (stateless jobs are CPU-bound, stream-stream joins memory-bound), auto-scales on load and GC signals, and auto-restarts stuck jobs via a rule engine.
Upsert means updating a record that already arrived — a corrected fare, a delivery status change — while queries keep running at tens of thousands of QPS. The hard part is that all versions of a record must be reconciled somewhere without a central coordinator becoming a bottleneck. Uber's design partitions the input stream by primary key, so every version of a record lands on the same server — shared-nothing, no single point of failure. It was the first upsert support in any open-source real-time OLAP system and is now a flagship Pinot feature.
Two patterns by use case. Active-active (surge pricing): every region independently runs the same Flink job on the same aggregated Kafka feed, so each has the answer ready; a coordinator just promotes a new primary. Active-passive (payments): only one named consumer reads, and because Kafka offsets differ between clusters, uReplicator continuously checkpoints an offset translation table — on failover, the standby resumes from exactly the right message with no loss and no reprocessing storm.
Because streams forget. Kafka retains a few days; testing a new pipeline, training an ML model, or fixing a bug needs months of history that only exists in HDFS/Hive — a different system with different ordering and rate characteristics. Lambda architecture answers with two codebases (drift), Kappa with huge Kafka retention (cost). Uber's compromise: compile one SQL query into both a streaming and a batch Flink job, or run the unchanged streaming code on archived data (Kappa+) with throttling and larger out-of-order buffers.
The Kafka + Flink + Pinot (or Druid/ClickHouse) + object-store + Presto/Trino shape remains the canonical open-source real-time stack — this paper is its reference citation. And the 2021 future-work list predicted the ecosystem's actual trajectory: unified stream/batch processing (Flink's runtime convergence), tiered storage (landed in both Kafka and Pinot), and cloud-agnostic deployment. The systems evolved; the architecture and its trade-off doctrine aged remarkably well.
References
The works the paper builds on — every entry links to the cited paper.
- [1]
Kreps, J., Narkhede, N., & Rao, J. (2011). Kafka: a distributed messaging system for log processing. NetDB 2011
- [2]
Carbone, P., Katsifodimos, A., Ewen, S., et al. (2015). Apache Flink: Stream and batch processing in a single engine. IEEE Data Engineering Bulletin 38(4)
- [3]
Im, J.-F., Gopalakrishna, K., Subramaniam, S., et al. (2018). Pinot: Realtime OLAP for 530 million users. SIGMOD 2018
- [4]
Sethi, R., Traverso, M., Sundstrom, D., et al. (2019). Presto: SQL on everything. ICDE 2019
- [5]
Shvachko, K., Kuang, H., Radia, S., & Chansler, R. (2010). The Hadoop Distributed File System. IEEE MSST 2010
- [6]
Uber Engineering (2016). uReplicator: Uber Engineering's robust Kafka replicator. Uber Engineering Blog / GitHub
- [7]
Uber Engineering (2016). Chaperone: Audit Kafka end-to-end. Uber Engineering Blog
- [8]
Begoli, E., Camacho-Rodríguez, J., Hyde, J., et al. (2018). Apache Calcite: A foundational framework for optimized query processing over heterogeneous data sources. SIGMOD 2018
- [9]
Toshniwal, A., Taneja, S., Shukla, A., et al. (2014). Storm @Twitter. SIGMOD 2014
- [10]
Noghabi, S. A., Paramasivam, K., Pan, Y., et al. (2017). Samza: Stateful scalable stream processing at LinkedIn. VLDB 2017
- [11]
Yang, F., Tschetter, E., Léauté, X., et al. (2014). Druid: A real-time analytical data store. SIGMOD 2014
- [12]
Ananthanarayanan, R., Basker, V., Das, S., et al. (2013). Photon: Fault-tolerant and scalable joining of continuous data streams. SIGMOD 2013
- [13]
Chen, G. J., Wiener, J. L., Iyer, S., et al. (2016). Realtime data processing at Facebook. SIGMOD 2016
- [14]
Chattopadhyay, B., Dutta, P., Liu, W., et al. (2019). Procella: Unifying serving and analytical data at YouTube. VLDB 2019
- [15]
Yang, J., Rae, I., Xu, J., et al. (2020). F1 Lightning: HTAP as a service. VLDB 2020
- [16]
Akidau, T., Bradshaw, R., Chambers, C., et al. (2015). The Dataflow model: A practical approach to balancing correctness, latency, and cost. VLDB 2015
- [17]
Kreps, J. (2014). Questioning the Lambda architecture (the Kappa architecture). O'Reilly Radar
- [18]
Gilbert, S., & Lynch, N. (2002). Brewer's conjecture and the feasibility of consistent, available, partition-tolerant web services (CAP). ACM SIGACT News 33(2)
Cite This Paper
@inproceedings{fu2021realtime,
title = {Real-time Data Infrastructure at Uber},
author = {Fu, Yupeng and Soman, Chinmay},
booktitle = {Proceedings of the 2021 International Conference on
Management of Data (SIGMOD '21)},
year = {2021},
publisher = {ACM},
doi = {10.1145/3448016.3457552},
url = {https://arxiv.org/abs/2104.00087}
}