jonnify·redis /redis-patterns/data-modeling/geospatial/the-geohash-sorted-set

R7.06.3 · dive 3

The geohash sorted set.

A GEO set is a Sorted Set. GEOADD encodes (lon, lat) into a 52-bit interleaved integer stored as the ZSET score. GEOSEARCH is a bounded range-scan over that score space.

The source states it three times: “Coordinates are stored as geohashes in a Sorted Set” (line 5) · “The results are stored as a Sorted Set with geohash scores” (line 71) · “Since GEO data is stored in a Sorted Set, use ZREM” (line 125). Select a row in the command-mapping below to see what ZSET operation each GEO command corresponds to.

Lon/lat bits interleaved into a geohash score. Select a row to see what it represents: raw longitude bits, raw latitude bits, or the interleaved result stored as the ZSET score.
lon lat score 1 0 1 1 0 0 1 0 8 bits 0 1 1 0 1 0 0 1 8 bits 1 0 0 1 1 1 1 0 0 1 0 0 1 0 0 1 ← ZSET score (52 bits total) longitude bit latitude bit alternating: lon, lat, lon, lat…
readout · Select a row. Longitude and latitude bits are interleaved into a single integer (the Z-order / Morton curve). That integer is the Sorted Set score. GEOSEARCH translates the search shape into a range on that score axis.
§1

A GEO set IS a Sorted Set — the structure reveal

geospatial.md.txt · lines 5, 71, 125 — figure 18

The author source states the fact three times, in plain terms:

  • Line 5: “Coordinates are stored as geohashes in a Sorted Set.”
  • Line 71: “The results are stored as a Sorted Set with geohash scores.”
  • Line 125: “Since GEO data is stored in a Sorted Set, use ZREM.”

GEOADD is not a separate data type at the storage level. It encodes the (longitude, latitude) pair into a 52-bit interleaved geohash integer and calls the same underlying ZADD that any Sorted Set uses — the integer is the score. GEOSEARCH translates the shape argument into a range on that score axis and does a bounded scan. The GEO command family is arithmetic and range-query sugar over a plain Sorted Set.

geospatial.md.txt · lines 5, 71, 125 — figure 18 — the GEO-is-a-ZSET factfrozen
# "Coordinates are stored as geohashes in a Sorted Set" (line 5)
GEOADD locations -122.4194 37.7749 "san_francisco"
# → stores score = interleaved_geohash(-122.4194, 37.7749) in the ZSET

# "The results are stored as a Sorted Set with geohash scores" (line 71)
GEOSEARCHSTORE nearby locations FROMLONLAT -122.4 37.8 BYRADIUS 50 km
# → output key is a plain Sorted Set, queryable with ZRANGE/ZCARD/ZREM

# "Since GEO data is stored in a Sorted Set, use ZREM" (line 125)
ZREM locations "san_francisco"
# → there is no GEOREM; ZREM is the correct deletion command

A GEO set is a Sorted Set whose score is a geohash integer. Every ZSET operation applies to it directly. GEOADD, GEOSEARCH, and GEOHASH are computational wrappers; the storage primitive is unchanged.

§2

The 52-bit interleaved geohash — Z-order score

geospatial.md.txt · Geohash

The geohash encodes two dimensions into one. Longitude and latitude are each divided into binary intervals (is the coordinate in the left half? right half? left quarter?) and the result bits are interleaved: longitude bit 0, latitude bit 0, longitude bit 1, latitude bit 1, and so on. The resulting integer follows a Z-order curve (also called a Morton curve): as the integer increases, the coordinates it encodes trace a Z-shaped path through the 2-D grid.

The Z-order property that makes spatial queries possible: points that are close in 2-D space tend to have numerically close geohash integers. This is not exact — the curve reverses direction at region boundaries, so some neighbouring cells land in distant score bins. But it is close enough that a bounded score range covers the spatial area, with a small set of boundary cells to handle separately. GEOSEARCH handles that adjustment automatically.

At 52 bits, the coordinate precision is approximately 0.6 metres. GEOHASH returns the human-readable base32 encoding of the same bits (e.g. 9q8yy4s0k…); WITHHASH in a GEOSEARCH returns the raw 52-bit integer score.

Notes on ValkeyGEOHASH and WITHHASH both expose the same geohash encoding. GEOHASH returns the base32 string (interoperable with other geo systems); WITHHASH on a GEOSEARCH returns the raw 52-bit integer stored as the ZSET score. Both are core Valkey — no module required. See valkey.io/commands/geohash.

The geohash integer IS the ZSET score. Every GEO coordinate is stored as a number on the Sorted Set’s score axis. Spatial proximity maps to numeric proximity (approximately), so a score range scan answers a spatial query.

§3

GEO commands are ZSET commands underneath

geospatial.md.txt · Commands Reference

Every GEO operation maps onto a ZSET operation. Select a row to see the correspondence:

GEO commands as ZSET operations. Select a row; the readout explains the correspondence between the GEO command and the underlying ZSET primitive.
GEOADD → ZADD key geohash(lon,lat) member GEOSEARCH → bounded ZRANGEBYSCORE ZREM → ZREM (no GEOREM exists) WITHHASH → expose the raw ZSET score
readout · Select a row. Each GEO command corresponds to a ZSET operation on the geohash score axis — GEOADD inserts a member with a computed score; GEOSEARCH scans a score range; ZREM removes a member; WITHHASH returns the raw score.
valkey.io/commands/geohash/ · geospatial.md.txt — the full command surfacefrozen
GEOADD locations -122.4194 37.7749 "san_francisco" # → ZADD score=geohash
GEOSEARCH locations FROMLONLAT -122.4 37.8 BYRADIUS 50 km  # → bounded range scan
GEOHASH locations "san_francisco"                  # base32-encoded geohash string
ZREM locations "san_francisco"                     # there is no GEOREM

ZREM is the only deletion command because there is no GEOREM: the GEO set is a Sorted Set. Use the ZSET toolchain directly wherever no geo-specific wrapper exists.

§4

Precision vs proximity

geospatial.md.txt · Geohash

At 52 bits, the coordinate precision is approximately 0.6 metres — sufficient for any practical proximity query. But precision and proximity are distinct:

  • Precision: how accurately the stored coordinate represents the actual position. At 52 bits, the error is sub-metre.
  • Proximity: whether two nearby points have numerically close geohash scores. The Z-order curve guarantees this approximately — not exactly. Two points on opposite sides of a grid cell boundary may be physically close but land in different geohash regions, producing numerically distant scores.

GEOSEARCH compensates by checking the neighbouring cells of the target range. The caller receives correct results without managing this correction; the command handles it internally.

The GEOHASH command returns the base32-encoded string (the geohash common in web apps and mapping tools), which is the same bits rendered differently. A longer geohash string means more bits, which means a smaller cell and higher precision. The 52-bit internal representation is always used for storage regardless of the string length returned by GEOHASH.

Precision (sub-metre at 52 bits) and proximity (approximate: the Z-order curve reverses at boundaries) are two separate properties of the geohash encoding. GEOSEARCH corrects for the boundary problem; the caller receives spatially correct results.

§5

The sortable-scalar parallel — place, time, and identity

geospatial.md.txt · the cross-chapter idea

The geohash is one instance of a recurring design move: pack a structured value into a sortable scalar so an ordered structure can answer a structured query. The same move appears across the course:

What is packedScalarStructureQuery answered
Place (lon, lat) geohash integer Sorted Set (GEO) “what is near here?”
Time (fire-at timestamp) Unix timestamp Sorted Set “what is due now?” — R4 time-delay-priority
Creation order (time + node + seq) branded snowflake Any ordered set “what was minted first?”

A geohash packs 2-D space. A scheduled-job score packs a point in time. A branded snowflake packs creation order. In each case, the scalar is designed so that the ordering it produces in a Sorted Set corresponds to a meaningful structured query. The data structure is the same; the arithmetic that produces the scalar is what changes.

the pattern

Encode a structured value (2-D coordinates, a timestamp, a creation sequence) into a sortable scalar. Store it as a ZSET score. Let the Sorted Set’s ordered scan answer the structured query.

the cross-chapter connection

Geospatial packs place. R4 packs time (delayed jobs via ZADD score=fire_at). A branded snowflake packs creation order. One idea; three applications. The R7.04 bitmap module similarly packs a boolean per bit-offset.

A geohash, a delayed-job score, and a branded snowflake are the same idea in different domains: a structured value encoded as a scalar so a Sorted Set answers the structured query in O(log N). The encoding arithmetic differs; the pattern is identical.

R4 · in this course

Time-delay-priority

The Sorted Set as a scheduler: pack a fire-at timestamp into the ZSET score; ZRANGEBYSCORE sweeps due jobs. The same sortable-scalar move, applied to time. time-delay-priority.

R7.05 · in this course

Vectors & similarity search

Another structure that answers “what is like this?” — but over a vector neighbourhood rather than a geohash score range. vector-sets.

§6

References

sources · related

Sources

  • Valkey — GEOHASH — the base32-encoded geohash string for stored members; the bit encoding behind the score.
  • Valkey — GEOSEARCH — bounded range-scan over the geohash score space; WITHHASH exposes the raw ZSET score.
  • Valkey — GEOADD — encodes (lon, lat) into a geohash score and stores it as a ZSET member.
  • Valkey — GEODIST — distance between two members, computed from their stored geohash scores.

Related in this course