End-to-End Throughput Analysis

A pipeline's throughput is the minimum of its stages' capacities, and the rest of the analysis follows from finding that minimum under each condition the interviewer names: steady peak, a spike, a degraded dependency, and recovery. The same stage is rarely the minimum in all 4.

Little's Law at each hop

Little's Law ties 3 quantities together for any stable stage: items in flight equal throughput times the time each item spends inside. It sizes concurrency, and it exposes memory limits that a throughput check misses.

HopThroughputTime insideIn flight
One consumer's lookups2,500/s4 ms10 concurrent calls, its 10 threads
Fleet lookups at doubled peak80,000/s4 ms320 concurrent calls at the profile service
One writer's buffer, today's peak6,667/s60 s between flushes400,000 events, 400 MB
One writer's buffer, doubled peak13,333/s60 s between flushes800,000 events, 800 MB

The last row shows a break the capacity table missed. Each writer keeps up with its share, but holds 800 MB just before its 60-second flush. The writer rows show that peak, arrival rate times interval; Little's Law's 30-second average wait gives half, but memory must hold the peak. If the writers run with 1 GB of memory, the doubling leaves 20% headroom, and a slow commit that stretches the interval to 80 seconds tips them over. A 30-second interval halves the memory and doubles the files written.

Recovery is a separate calculation

Take the outage from the interview: the consumer group stops for 30 minutes at doubled peak, so the backlog is 80,000 events per second times 1,800 seconds. Draining it needs capacity above the live input, and the drain rate is the smallest stage capacity minus the 80,000 still arriving. Consumers are only one of the stages in that minimum.

ConfigurationNarrowest stageSurplus over inputTime to drain
24 consumersConsumers, 60,000/sNoneNever; lag keeps growing
48 consumers, 6 writersProfile service and writers, 90,000/s10,000/s4 hours
48 consumers, cache, 6 writersWriters, 90,000/s10,000/s4 hours
48 consumers, cache, 9 writersWriters, 135,000/s55,000/sAbout 44 minutes

The cache alone changes nothing about recovery, because the writers share the same 90,000 ceiling. Adding 3 writers raises that ceiling to 135,000 events per second, still the narrowest stage, and cuts the drain from 4 hours to about 44 minutes. The profile service then sees only 27,000 calls per second. This is the reasoning the question tests: the fix for steady state and the fix for recovery sit in different stages, and an answer has to find both.

A degraded dependency

If profile calls slow from 4 ms to 40 ms, the cached design still answers 80% of lookups locally. The average cost rises to 8.08 ms (0.8 x 0.1 + 0.2 x 40), a consumer handles around 1,238 events per second, and 48 consumers reach roughly 59,400, below the doubled input of 80,000. With half the pings shed at the gateway, input falls to 56,000 and the fleet holds at 94%. The cache absorbs most of the slowdown and shedding covers the rest, so the design keeps both.

Measuring it in production

The same arithmetic runs against real metrics. The query below reads per-partition samples from a consumer_lag_metrics table, takes the latest sample for each partition, and estimates minutes to drain from the current surplus.

WITH latest AS (
  SELECT
    partition_id,
    end_offset - committed_offset AS lag_events,
    produced_per_sec,
    consumed_per_sec,
    ROW_NUMBER() OVER (
      PARTITION BY partition_id
      ORDER BY sample_ts DESC
    ) AS rn
  FROM consumer_lag_metrics
  WHERE consumer_group = 'trip-enrichment'
    AND sample_ts > current_timestamp - INTERVAL '10' MINUTE
)
SELECT
  partition_id,
  lag_events,
  consumed_per_sec - produced_per_sec AS surplus_per_sec,
  CASE
    WHEN consumed_per_sec <= produced_per_sec THEN NULL
    ELSE CAST(lag_events AS DOUBLE) / (consumed_per_sec - produced_per_sec) / 60
  END AS minutes_to_drain
FROM latest
WHERE rn = 1
ORDER BY lag_events DESC
LIMIT 10

A NULL drain time marks a partition that is falling behind. The shape of the top 10 rows is the diagnosis: lag spread evenly across partitions means the fleet is short of capacity, while 1 or 2 partitions far above the rest point to a hot key that more consumers will not fix.

The redesigned pipeline: 96 partitions leave room for the next doubling, the cache cuts profile calls to 20% of events, and 9 writers, still the narrowest stage, raise the recovery ceiling to 135,000/s.
  • Rider and driver apps (source, mobile clients)
  • Ingest gateway (transform, HTTP service); on error: sample pings 50% when lag age > 3 min
  • trip-events queue (queue, partitioned log); parallelism: 96 partitions
  • Enrichment consumers (transform, consumer group); parallelism: 12 to 48 instances; idempotency: commit offsets after write; alerting: lag age > 1 min and rising
  • Profile cache (storage, local cache); freshness: 10 min TTL
  • Driver profile service (custom, lookup API)
  • Table writers (transform, micro-batch writer); parallelism: 9 writers; idempotency: upsert on event_id
  • earnings table (storage, warehouse table); freshness: 5 min
  • Rider and driver apps -> Ingest gateway
  • Ingest gateway -> trip-events queue
  • trip-events queue -> Enrichment consumers
  • Enrichment consumers -> Profile cache
  • Profile cache -> Driver profile service
  • Enrichment consumers -> Table writers
  • Table writers -> earnings table

The answer, in the order you would say it

About this lesson

This section is part of the Backpressure and Scaling: Advanced lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.