TSTier SlateAll templates

Location & logistics

Proximity search - the index is the design

Teaches how to size a geospatial index against the query it serves, and why the nearest answer is usually in the cell next door.

  • System design
  • starter
  • 8 step walkthrough
  • 5 tables

Share

  • Facebook
  • X

What this board gets wrong on purpose

The tension

The board picks one cell size - geohash length 6, about 1.2 km by 0.6 km - and a fixed nine-cell fan-out, which is the correct trade only for a search radius near 1 km in a city of average density. In Manhattan those nine cells hold 4,000 candidate places and the exact distance filter throws away 97% of what it fetched; in rural Montana they hold two, so the query widens to length 4 and now scans an area 30 km across to find anything at all. The board handles that by switching index, which means there are two indexes to keep consistent and a discontinuity in result quality exactly at the boundary between them. A true answer - S2 or an adaptive quadtree that splits on density, so a cell holds a roughly constant NUMBER of places rather than a constant AREA - is deliberately not drawn here, because it replaces a lookup you can compute in the client with a tree you must traverse on the server. Geohash cells are also not square and get narrower towards the poles, so the nine-cell neighbourhood covers a different real area in Oslo than in Nairobi, and nothing on this board corrects for it.

1 · Requirements

Fifty million places that barely move, and twenty-five million searches a day that all ask the same question from a different point. Everything below is downstream of one decision: how big is a cell.

Ask these before designing

  • What is the query radius, and is it fixed? The whole index is sized against it. "Within 1 km" and "the 20 nearest" are different systems: the first is a bounded area, the second is an unbounded expansion that stops when it has enough.
  • Is "near" the ranking, or a filter before the ranking? If results are ranked by rating and distance is a filter, the index returns a set. If they are ranked by distance, the index must return them in order, and that changes whether a cell scan is enough.
  • How stale may a place be? A restaurant that closed last month still being returned is a product bug, not an index bug, but it decides whether the index can be rebuilt nightly or must be written synchronously.
  • Do we need polygons, or only points? "Within 2 km of me" is a point query. "Inside this delivery zone" is a polygon containment test, which geohash cannot do and PostGIS can. Answering the wrong one of these is how a geohash index ends up with a PostGIS instance bolted to its side.
  • What fraction of searches are in the ten densest cells? If it is most of them, a constant-area grid is the wrong shape and the honest answer is an adaptive tree.
  • Is the searcher’s own position accurate? A 50 m GPS error and a 1 km radius are fine together. A 50 m error and a "what is in this building" query is not a search problem at all.

Functional

  • Given a point and a radius, return the places within it, ranked.
  • Filter by category, open-now and rating, applied after the geometry rather than before it.
  • Widen automatically when a cell neighbourhood is too sparse to fill a page of results.
  • Owners create, move and close places; the index reflects a change within a minute.
  • Serve the exact-distance number, not just the ordering, because the UI shows "420 m".

Non-functional

  • Search p99 under 120 ms server-side, of which the index lookup is under 15 ms and the rest is ranking and hydration.
  • Recall of the true nearest 20 must be 100% within the stated radius. This is what the 9-cell fan-out buys, and it is a correctness requirement: a proximity search that silently misses the closest result looks perfect in a demo.
  • Read-dominated by four orders of magnitude - see the capacity page. Design for reads and let writes be slow.
  • Availability 99.99% for search, degrading to a cached and slightly stale result set rather than an error.
  • Cell ids are computed on the client as well as the server, so the encoding must be a pure function with no service call in it.

Explicitly out of scope

Routing and travel time (that is the routing board), continuous tracking of the searcher, polygon containment and enter/exit events (that is the geofencing board), personalised ranking and ads.

The rest of this board

Board preview

Available on Tier Slate

This page publishes the question. The answer — 5 more written pages, an 8-step narrated walkthrough and a 5-table schema — is the board itself, and it opens in Tierslate.

  • 5written pages
  • 8walkthrough steps
  • 5tables34 columns

5 pages behind this one

  • Capacity estimation
  • Storage estimation
  • Availability
  • How it works
  • Deployment plan
Open this board in TierslateBrowse every board

More boards

Geospatial indexes, matching and moving things.

  • Fleet tracking - 21,600 writes per vehicle per day
  • Geofencing - many points against many polygons
  • Maps routing - precompute, then watch it go stale
  • Ride matching - the nearest car is the wrong car
All 50 templatesOpen Tierslate
Tierslate

tierslate.com

HomeTemplatesPrivacyTerms