Skip to main content
Proximity Service (Yelp, Nearby)lesson 3 of 4 · 2 min read

Splitting Reads from Writes

Two services, opposite needs

Your business service and your search service want opposite things.

Editing a business wants normalised rows, transactions and immediate consistency, because an owner who changes their opening hours expects to see the change. Searching wants denormalised, precomputed, heavily cached data that never blocks on a write.

See what forcing both onto one table does: it gives each of them the other's constraints.

Split them, then. Your business service owns a normalised store, and that store is genuinely small at a terabyte and one write a second, so a single primary with read copies is not a compromise but the right answer. Every edit emits an event.

Have your search index consume those events and keep its own denormalised copy: the business, its coordinates, the geohash prefixes at each precision, and the handful of fields ranking needs.

Shard that one by prefix and replicate it per region. It can be rebuilt from the business store at any time, and that is what makes it safe to treat as disposable.

Naming the window

Name the consistency you just bought, eventual, and name the window with it: an edit reaches search in a few seconds.

Say why that is acceptable rather than hoping nobody asks, because an interviewer will probe exactly here. A new restaurant appearing in results five seconds after it was created has no visible consequence, and nobody is refreshing to check.

Contrast it with a payment balance, where those same five seconds would be unacceptable, and the point lands. Consistency requirements come from your domain, not from taste.

Avoid one trap. Deleting a business must remove it from search promptly, because a closed venue showing up in results is a real complaint. Treat deletes as high-priority events instead of waiting for the next rebuild.

the shape of it
Owner editshours changedBusiness svcnormalised, ACIDPostgressource of truthEvent queueedit publishedIndexerrebuild cellsSearch shardsdenormalisedNearby searchreads only thiscommit 4 msemit event~2 s behind20k/sec
step 1 of 4
One store is authoritative and slow-changing, the other is disposable and fast, rebuildable from the first.

Worked example

A cafe owner in Porto updates her closing time at 14:02:31. The business service writes the row and commits in 4 ms, and she sees the new hours immediately because that read goes to the business store. An event lands on the queue, the indexer picks it up 1.8 seconds later, and updates the denormalised row plus invalidates the two cached cells the cafe belongs to. By 14:02:34 a nearby search shows the new hours. Three seconds of staleness that no user could detect, in exchange for a read path that serves 20,000 searches per second without touching the transactional database. When the same team later corrupts a search shard during a bad deploy, they rebuild it from the business store in 40 minutes and lose nothing, because the index was never the source of truth.