Sweaty Imposter

Design Yelp / Nearby Places

System DesignGeospatialRead HeavyMid3 min readUpdated

Searches outnumber business edits by thousands to one, and the whole location index fits in a few gigabytes. The design turns a radius query into a lookup of a few geohash cells, served from replicas and a cache.

1 · Requirements

The Prompt

Return businesses within a chosen radius of a user, ranked by distance and rating, for 100M daily users. Business details change rarely.

Functional

  • Find businesses within a radius of a location, optionally by category
  • Rank results by distance and rating, with paging
  • Show business details
  • Owners add and edit businesses

Non-Functional

  • Nearby searches return in under 200 ms at p99
  • Handles about 15,000 searches a second at peak
  • Business edits can take until the next day to appear in search

2 · Estimates

Drill This Phase First

Show the Answer and Estimates

Answer

About 5,800 a second

100M times 5 is 500M searches a day. 500M divided by 86,400 seconds is about 5,800 a second, and about 15,000 at a 2.5x peak.

Searches
100M daily users at 5 searches a day: 500M a day, about 5,800 a second and 15,000 at peak
Businesses
200M businesses at about 1 KB each: roughly 200 GB of details in SQL
Geo Index
200M entries of a geohash and an ID at 16 bytes each: about 3.2 GB, small enough for memory on every replica
Writes
About 100,000 business edits a day, roughly 1 a second, so writes barely register
Cache
Hot cells in dense cities take most of the traffic. An 80% hit rate leaves about 3,000 index queries a second at peak
Replicas
Each read replica serves about 1,000 queries a second, so 3 carry the misses and 5 leave room for a failure

3 · API

GET /search/nearby?lat=&lng=&radius_m=&category=&page_token= → { businesses[{ id, name, distance_m, rating }], next_page_token }
GET /businesses/:id → { name, address, hours, rating, photos[] }
POST /businesses { name, lat, lng, category, hours } → { id } (owner-authenticated)
PUT /businesses/:id { fields } → 204 (searchable after the nightly index rebuild)

Data Model

businesses (SQL, primary + read replicas):
  business_id (PK)  : bigint
  name              : string
  lat, lng          : double
  category          : string
  rating            : decimal      updated in batch from reviews
  hours             : json

geo_index (SQL, read replicas):
  geohash           : string(6)
  business_id       : bigint
  PK (geohash, business_id)

cache (Redis):
  "cell:{geohash}"  → list<business_id>
  "biz:{id}"        → details, TTL 1h

4 · High-Level Design

A search computes the user's geohash, reads that cell and its 8 neighbors from the cache or the index, filters the candidates by true distance, and ranks them. Owner edits go to the primary and reach search after the nightly rebuild.

5 · Deep Dives

Geohash Cells and Neighbors

A geohash turns a point into a string where a longer shared prefix means a closer cell. A 6 character cell is about 1.2 km by 0.6 km, which suits a 500 m search. A user near a cell edge would miss places just across the line, so read the cell plus its 8 neighbors, then filter by true distance.

Geohash or Quadtree

A quadtree splits a region only where businesses are dense, so every leaf holds about 100 places and a rural search widens on its own. It lives in memory and has to be rebuilt when a server starts. A geohash index is an ordinary indexed column with no rebuild step, which is why it wins when the data changes slowly.

Ranking and Sparse Areas

Rank candidates by a blend of distance and rating after the distance filter, and page through dense downtown cells with a token. When a rural search finds too few results, drop one geohash character, which covers 32 times the area, and tell the user the radius was widened.

6 · Wrap Up

Name the next risks: an open-now filter that depends on hours and time zones, a text search index for queries by cuisine or name, and owners who expect their edits within minutes.

The Short Version

Reads dwarf writes, so index locations by geohash on read replicas and cache hot cells. Search the user's cell plus its 8 neighbors, then filter by true distance. Business edits can wait for the nightly index rebuild.