skip to content

Redis 6.2 introduced the GEOSEARCH and GEOSEARCHSTORE commands and deprecated the older GEORADIUS family. What do the newer commands add, and why could the original GEORADIUS not be executed on a read-only replica?

level: middleimportance: nice to knowfreq 22%

answer

  1. GEOSEARCH = FROMLONLAT|FROMMEMBER × BYRADIUS|BYBOX
  2. BYBOX = map viewport
  3. GEORADIUS had STORE → flagged write → replicas rejected it
  4. _RO variants exist only for that reason
  5. GEOSEARCHSTORE + STOREDIST = scores become distances

basics

~20 s

GEOSEARCH unifies GEORADIUS and GEORADIUSBYMEMBER (FROMLONLAT or FROMMEMBER) and adds rectangle search with BYBOX. GEORADIUS had an optional STORE clause, so Redis classified it as a write command and replicas refused it — hence the separate GEORADIUS_RO. GEOSEARCH is read-only; GEOSEARCHSTORE is the writing variant.

solid answer

~50 s

Three things changed. **Unification**: `GEORADIUS` (search around a point) and `GEORADIUSBYMEMBER` (search around a stored member) collapse into one `GEOSEARCH` with `FROMLONLAT lon lat` or `FROMMEMBER member`. **A new shape**: `BYBOX width height unit` searches an axis-aligned rectangle alongside the existing `BYRADIUS`. A box is the natural query for a map viewport, where a circle either over- or under-covers the visible area. **A clean read/write split**: `GEORADIUS` carried an optional `STORE`/`STOREDIST` clause that wrote results into a destination key. Because the command *could* write, Redis flagged the whole command as a write command — so replicas rejected it, and it had to be replicated and journalled even for pure reads. That is why `GEORADIUS_RO`/`GEORADIUSBYMEMBER_RO` existed. In 6.2 the concern is split: `GEOSEARCH` is purely read-only and runs on replicas, while `GEOSEARCHSTORE dst src ... [STOREDIST]` is the explicit write command. The old commands still work but are deprecated; new code should use `GEOSEARCH`.

code

text · 10 lines
text
# pre-6.2
GEORADIUS drivers -122.41 37.77 5 km ASC COUNT 10
GEORADIUSBYMEMBER drivers driver:17 2 km ASC
GEORADIUS drivers -122.41 37.77 5 km STORE nearby   # write command

# 6.2+
GEOSEARCH drivers FROMLONLAT -122.41 37.77 BYRADIUS 5 km ASC COUNT 10
GEOSEARCH drivers FROMMEMBER driver:17 BYRADIUS 2 km ASC
GEOSEARCH drivers FROMLONLAT -122.41 37.77 BYBOX 4 3 km ASC   # map viewport
GEOSEARCHSTORE nearby drivers FROMLONLAT -122.41 37.77 BYRADIUS 5 km ASC STOREDIST

go deeper

for a junior

Know that GEOSEARCH is the modern query command and that it supports both a point and a member as the centre, and both a radius and a box.

for a middle

Explain the read/write split: GEORADIUS's optional STORE flagged it as a write, hence the _RO variants; GEOSEARCH is read-only and GEOSEARCHSTORE is the writer.

for a senior

Add the operational payoff — proximity reads can be served from replicas — plus cluster slot co-location for GEOSEARCHSTORE and the ANY short-circuit tradeoff.

for a principal

Use it as an example of command-table flags being static per command: capability, replication behaviour and replica routing all follow the flag, so mixing a write option into a read command has architectural cost.

## The old surface Before Redis 6.2 the geo query API was four commands: - `GEORADIUS key lon lat radius unit ...` — circle around an explicit point. - `GEORADIUSBYMEMBER key member radius unit ...` — circle around an already-indexed member. - `GEORADIUS_RO`, `GEORADIUSBYMEMBER_RO` — read-only twins of the two above. The `_RO` twins existed because of one design mistake: `GEORADIUS` accepted an optional `STORE destkey` (write the matching members into a new sorted set, keeping their geohash scores) and `STOREDIST destkey` (write them scored by distance from the centre instead). Redis classifies a command as read or write **statically, by command table flag**, not per invocation. A command that can write is a write command always. That had three consequences: 1. **Replicas refused it.** A replica is read-only by default; issuing `GEORADIUS` against one returned an error even when you had passed no `STORE` clause. Scaling reads by pointing clients at replicas broke unless you remembered to call `GEORADIUS_RO`. 2. **Replication and AOF overhead.** Being a write command, it participated in the write path bookkeeping instead of being treated as a plain lookup. 3. **Cluster friction.** `STORE` writes a second key, which must live in the same hash slot as the source key — so the destination name has to share a hash tag with the source, or the command fails with a cross-slot error. ## What 6.2 introduced `GEOSEARCH` replaces both query commands with orthogonal arguments: ``` GEOSEARCH key <FROMMEMBER m | FROMLONLAT lon lat> <BYRADIUS r unit | BYBOX w h unit> [ASC|DESC] [COUNT n [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH] ``` - **Centre** and **shape** are now independent choices instead of being baked into the command name. That is why four commands became one. - **`BYBOX`** is genuinely new capability: an axis-aligned rectangle of the given width and height centred on the search point. Map UIs pan and zoom rectangles, so a box matches the viewport exactly; approximating it with a circle either fetches points the user cannot see or misses points in the corners. - **`COUNT n ANY`** (also 6.2) short-circuits: return as soon as `n` matches are found instead of collecting and ranking all of them. Much cheaper in dense areas, but the `n` you get are arbitrary, not the nearest. `GEOSEARCHSTORE dst src <FROM…> <BY…> [ASC|DESC] [COUNT n [ANY]] [STOREDIST]` is the separate write command. By default the destination sorted set keeps the members' geohash scores, so the result is itself a valid geo index you can query again. With `STOREDIST` the scores become the distance from the centre in the requested unit — handy for paging by proximity with `ZRANGE`, but the destination is then no longer a usable geo index. ## Why the split matters operationally With `GEOSEARCH` flagged read-only: - It runs on replicas, so proximity queries can be scaled horizontally by adding replicas rather than by resizing the primary. - It is safe on a primary that has hit a write-blocking condition (for example when a persistence error has put the server into a state that refuses writes). - Intent is visible in the command name: a code reviewer or an audit of the command stream can tell reads from writes without parsing arguments. And `GEOSEARCHSTORE`, being explicitly a write, is where you consciously think about slot co-location in a cluster (`{city}` hash tag on both source and destination), destination TTL, and the memory cost of materialising a result set. ## Migration guidance The old commands are deprecated as of 6.2 but still function — Redis does not remove commands lightly, and client libraries still expose them. Mechanical mapping: - `GEORADIUS k lon lat r unit` → `GEOSEARCH k FROMLONLAT lon lat BYRADIUS r unit` - `GEORADIUSBYMEMBER k m r unit` → `GEOSEARCH k FROMMEMBER m BYRADIUS r unit` - `GEORADIUS … STORE d` → `GEOSEARCHSTORE d k …` - `GEORADIUS_RO …` → `GEOSEARCH …` (the `_RO` variants become pointless) One caveat when targeting mixed fleets: if any server in the deployment predates 6.2, `GEOSEARCH` is simply an unknown command there, so a rollout has to upgrade servers before clients switch.

  • What is the difference between the destination key written by GEOSEARCHSTORE with and without STOREDIST?
    Without `STOREDIST`, the destination sorted set keeps each member's 52-bit geohash score, so the result is itself a valid geo index that further `GEO*` commands can query. With `STOREDIST`, the scores are replaced by the distance from the search centre in the requested unit — convenient for paging by proximity with `ZRANGE`, but the destination is no longer decodable as coordinates.
  • When would you choose BYBOX over BYRADIUS?
    Whenever the query area is genuinely rectangular — most obviously a map viewport that the user has panned or zoomed. A circle inscribed in that rectangle misses the corners, and a circumscribed circle returns points outside the visible area that the client must then discard. A box matches the region exactly and avoids the wasted transfer.

saying these in an interview costs you the question

  • Believing GEORADIUS was rejected on replicas because reads were unsafe, rather than because the optional STORE clause made it a write command
  • Thinking GEOSEARCH removed radius search — it kept BYRADIUS and added BYBOX
  • Assuming the deprecated GEORADIUS commands were removed and old code stopped working
  • Using GEOSEARCHSTORE in a cluster without a shared hash tag between source and destination
  • Treating COUNT n ANY as 'the nearest n' — it returns whichever n it finds first

context