Problem RestatementProblem
Meta asked: design the backend of a fitness tracking app. Users record workouts (runs, rides, walks) with a phone or watch: GPS route, time, distance, pace, heart rate. They see their history and statistics (weekly distance, personal records), follow friends and see an activity feed, and optionally compete on segments (popular stretches of road with leaderboards).
RequirementsRequirements
- Upload workouts (maybe recorded offline) with GPS points every second and sensor data.
- Show a workout: map, splits, charts. Show history and totals.
- Social: follow, feed, kudos and comments.
- Segments and leaderboards (optional).
- Privacy: hide the start and end near home (privacy zones), and private activities.
1.1 Scale
- 50M users, 5M workouts/day. A 1-hour workout = 3,600 GPS points ≈ 50–100 KB compressed → ~500 GB/day of route data.
ArchitectureArchitecture
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
DEV["Phone / watch - records offline"] -->|"upload file (FIT/GPX/JSON)"| API["Upload API"]
API --> OS[("Object storage - raw files")]
API --> Q[("Processing queue")]
Q --> PROC["Processor - clean, stats, privacy zones"]
PROC --> DB[("Activities DB - summary")]
PROC --> RT[("Route store - encoded polyline")]
PROC --> SEG["Segment matcher"]
SEG --> LB[("Leaderboards")]
PROC --> K[("Activity events")]
K --> FEED["Feed fan-out"]
FEED --> FS[("Feed store")]
APP["App"] --> RAPI["Read APIs"]
RAPI --> DB
RAPI --> RT
RAPI --> FSData ModelData model
activities: activity_id, user_id, type, start_time, duration_s, distance_m, elevation_m,
avg_pace, avg_hr, visibility, summary_polyline, created_at
routes: activity_id → full-resolution encoded polyline + sensor streams (object storage / blob)
stats: user_id, week, distance_m, time_s, count (pre-aggregated)
follows: follower_id, followee_id- Summary data (small) goes in the DB for lists and feeds. Full streams (big) go in object storage, loaded only when the user opens the activity.
- Routes are stored as an encoded polyline (a compact text encoding of coordinates), plus a simplified version for thumbnails.
Key FlowsFlows
- Upload: the device records locally (works offline) and uploads the file when connected, with an idempotency key (the device's activity UUID) so retries don't duplicate. The API stores the raw file and queues processing, then returns right away.
- Processing: parse, smooth GPS noise, compute distance, pace, splits and elevation, apply privacy zones (trim points near home), save the summary and streams, update weekly stats, and match segments.
- Feed: publish "activity created". Fan out to followers' feeds (on write for normal users, on read for accounts with huge followings).
Deep Dive — Matching a run against every segmentDeep dive
A user finishes a 10 km run. The app has to work out which of millions of user-created segments they just covered, and where they place on each leaderboard.
Compare the activity against every segment
For each segment, check whether the route covers it.
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
ACT["New activity - 3,000 GPS points"] --> LOOP["Loop over 8,000,000 segments"]
LOOP --> GEO["Polyline comparison per segment"]
GEO --> COST["Billions of point comparisons per upload"]
COST --> NEVER["Minutes of CPU for one run, x every upload"]The work is the product of activities and segments, and almost all of it compares a run in Amsterdam against segments in Colorado. The geometry is the expensive part and it is being spent on pairs that a bounding box would have rejected instantly.
Find candidate segments by location
Index each segment's bounding box in a geospatial index, and query it with the activity's bounding box. Now only segments physically near the route are considered — a handful instead of millions.
The right filter, and it is only a filter. A bounding box overlap does not mean the runner covered the segment: they may have crossed it perpendicularly, run it backwards, or passed its start and end on different streets. Accepting every candidate produces leaderboard entries for runs that never happened.
Cheap candidates, then an ordered traversal check
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
ACT["Activity route"] --> BB["Geospatial index - segments whose bbox overlaps"]
BB --> CAND["Candidate segments - tens, not millions"]
CAND --> START{"Route passes near the segment start?"}
START -->|"no"| DROP["Discard"]
START -->|"yes"| END{"Passes near the end, later in time?"}
END -->|"no"| DROP
END -->|"yes"| PATH{"Route stays close to the polyline between them?"}
PATH -->|"yes"| EFF["Compute elapsed time"]
EFF --> LB[("Sorted set per segment - overall, age group, friends")]- Order matters, and so does time. The route must pass the start before the end. Checking proximity alone matches people who ran it in reverse.
- Verify the middle, not just the endpoints. Requiring the route to stay within a tolerance of the polyline rejects the runner who took a different street between the same two corners.
- Leaderboards are sorted sets keyed by segment, one per view — overall, by age group, among friends. Inserting one entry updates all the ranks for free, which is what makes "you're 412th" instantly answerable.
Do the matching asynchronously after the upload. The user wants their run saved immediately; segment results appearing a few seconds later is normal and expected, and it keeps a slow match out of the upload path.
Wrap-UpWrap-up
Devices record offline and upload whole activity files idempotently. An async processor cleans GPS, computes stats, applies privacy zones, stores small summaries in the DB and full streams as encoded polylines in object storage, and updates pre-aggregated weekly stats. Activity events drive feed fan-out (hybrid for popular users), and a geospatial segment matcher updates sorted-set leaderboards.