system-design/design-yelp.md

40. Design Yelp / Geo-Search

Tests: spatial indexing, faceted search, ranking, photos, reviews, abuse handling.

~2 min read·updated 5/29/2026

40. Design Yelp / Geo-Search

Tests: spatial indexing, faceted search, ranking, photos, reviews, abuse handling.

40.1 Requirements

Functional

  • Search businesses near a location.
  • Filter by category, price, rating, hours.
  • Browse business detail (photos, reviews, hours).
  • Write reviews and rate.
  • Photos.

Non-functional

  • 200M businesses globally; 200M MAU.
  • P95 search < 200 ms.
  • Reviews must persist forever.

40.2 Architecture

[ Client ]──→[ API gateway ]──→[ Search svc ]──→[ Geo + text index (ES with geo + facets) ]
                                  ↓
                            [ Business svc ]──→[ Postgres / DynamoDB ]
                            [ Reviews svc ]──→[ Reviews DB sharded by business_id ]
                            [ Photos svc ]──→[ Object store + CDN ]
                            [ Recommendations / personalization ]

40.3 Geo-search

(See chapter 25 / 35 for spatial indexing.)

Options:

  • R-tree (PostGIS): great in single DB.
  • Geohash: prefix-based; works in any KV.
  • S2 / H3: Google / Uber hex/cell hierarchies.
  • Elasticsearch geo_distance / geo_bounding_box queries: built-in.

For Yelp's read-heavy workload: Elasticsearch with geo + text + facets is the standard.

Indexed fields:

  • name, categories, description (text)
  • location (geo_point)
  • price_level (numeric)
  • rating (float)
  • is_open_now (computed)

Query:

{
  "bool": {
    "must": [
      { "match": { "name": "pizza" } }
    ],
    "filter": [
      { "geo_distance": { "distance": "5km", "location": {lat, lon} } },
      { "term": { "categories": "Italian" } },
      { "range": { "price_level": { "lte": 2 } } }
    ]
  },
  "sort": ["_score", { "rating": "desc" }]
}

40.4 Business data

businesses (
  id, name, address, location (lat,lon), categories[],
  hours, price_level, rating_avg, review_count, photo_urls[],
  owner_id, created_at, ...
)

Postgres for OLTP / source of truth; mirror to ES for search.

40.5 Reviews

reviews (
  id, business_id, user_id, rating, text, photos[], created_at,
  helpful_count, status (visible/hidden)
)

Sharded by business_id (queries are usually "show reviews for this business").

Rating aggregation:

  • Stream from Kafka (ReviewCreated).
  • Update materialized aggregates: per-business rating_avg, count.
  • Stored in business record (denormalized).

40.6 Photos

(See chapter 37 / 34.) Object storage + CDN. Multiple resolutions for grid views.

40.7 Search ranking

Beyond geo + text match:

  • Quality: rating, review count, response time, hours-open-now.
  • Distance (closer is better, but small weight after a threshold).
  • Personalization: user's history, similar users.
  • Sponsored: paid placement (clearly labeled).
  • Recency: new businesses get a boost.

Final score = weighted combination + ML re-rank for top candidates.

40.8 Categories

Hierarchical taxonomy: "Restaurants > Italian > Pizza."

Indexed as multi-value field; query uses terms filter with sub-categories.

40.9 "Open now" computation

Business hours stored per day. "Open now" = check current time against day's hours.

Computed in real-time at query time (cheap) or precomputed every minute and indexed.

40.10 Reviews moderation

Spam, fake reviews, abuse:

  • Heuristic flags (spam keywords, bursts from one IP, too positive on a new business).
  • ML classifier.
  • Manual review queue for borderline cases.
  • Yelp's notorious "review filter" hides reviews algorithmically.

Deceptive practices: review-bombing detected by sudden spikes; rate-gate new reviews.

40.11 Personalization

  • User's previous searches, visits, reviews → preferences.
  • Collaborative filtering: "people who liked X also liked Y."
  • Diversity: don't show 10 pizza places when "Italian" was searched (mix in pasta, etc.).

40.12 Caching

  • Search results for popular queries cached (key: query + location grid + filters).
  • Business details cached (popular places).
  • Photos via CDN.

Hot cells (Times Square at noon): cache aggressively; pre-warm.

40.13 Sketch

[Client]──search params──→[Search svc]──→[ES (geo + text + facets)]
                                   │
                            [Business svc]──→[Postgres]
                                   │
                            [Reviews svc]──→[Reviews DB]
                                   │
                            [Photos]──→[CDN/object store]

Write paths:
  [Reviews svc]──Kafka(ReviewCreated)──→[Aggregator (Flink)]──→update business.rating_avg
  [Business svc]──Kafka(BusinessUpdated)──→[Indexer]──→ES

40.14 What an interviewer wants

  • Geo-search via ES geo queries (or H3/S2 if Uber-style).
  • Business denormalized (rating_avg, review_count) for fast ranking.
  • Faceted filters as part of search query.
  • Reviews sharded by business_id.
  • Async aggregation for ratings.
  • Photos in object storage + CDN.

Key takeaways

  • Elasticsearch with geo + text + facets is the standard search backend.
  • Denormalize hot fields (rating_avg) onto business records.
  • Async aggregation pipelines update denormalized fields.
  • Spam / fake review handling is essential.
  • Personalization on top of popularity for ranking.

// 2 views

main
UTF-8·typescript