Case study
Design Yelp Nearby Places
Search and rank nearby businesses by location, category, and reviews for 200M monthly users.
geospatialsearchreviews
Requirements
- Functional: search businesses near lat/lng; filter by category, price, rating; read/write reviews
- Sort by distance, rating, or relevance
- Non-functional: search p99 < 200ms; 200M MAU; 100M businesses indexed
- Reviews: 5M new reviews/month; display aggregated rating
Back-of-envelope Estimation
| Metric | Calculation | Result |
|---|---|---|
| Search QPS | 200M × 10 searches/mo / (30×86400) | ~800/sec avg; ~3K peak |
| Business index size | 100M × 2 KB | ~200 GB metadata |
| Reviews storage | 5M/mo × 1 KB × 120 mo | ~600 GB text |
| Geo index | 100M points | Geohash + Elasticsearch geo_shape |
API Design
| Endpoint | Description |
|---|---|
| GET /v1/search?lat=&lng=&radius=5km&category=pizza&sort=rating | Paginated results |
| GET /v1/businesses/{id} | Details, hours, photos, rating aggregate |
| POST /v1/businesses/{id}/reviews | { rating, text } — authenticated |
| GET /v1/businesses/{id}/reviews?cursor= | Paginated reviews |
Data Model
| Entity | Index |
|---|---|
| businesses | business_id, name, lat, lng, categories[], price, avg_rating — ES + SQL |
| reviews | review_id, business_id, user_id, rating, text, ts — sharded by business_id |
| rating_aggregate | business_id, sum, count — updated async from review stream |
High-level Design
Client ──► Search API ──► Elasticsearch (geo_distance + filters)
│
Redis cache (lat/lng geohash-6 + category + sort key)
│
Review Service (aggregate rating from cache/SQL)Deep Dive: Geo Search in Elasticsearch
Index businesses with geo_point field. Query: geo_distance around user location, filter terms on category, range on rating. Sort by _geo_distance or custom score blending distance + rating + review count.
Cache key
Round lat/lng to geohash-6 (~1.2 km) for cache key — nearby users share results.
Deep Dive: Review Aggregation
- New review → Kafka → aggregator increments sum/count atomically in Redis
- Periodic flush to SQL for durability
- Display avg = sum/count; bayesian average for low-review businesses to reduce noise
- Spam detection ML before publishing
Failure Modes & Monitoring
| SLO | Target |
|---|---|
| Search p99 | < 200ms |
| Rating staleness | < 5 min after new review |
| Index freshness | < 1 hour for new businesses |