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.
GEOSEARCH translates the search shape into a range on that score axis.A GEO set IS a Sorted Set — the structure reveal
geospatial.md.txt · lines 5, 71, 125 — figure 18The 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.
# "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.
The 52-bit interleaved geohash — Z-order score
geospatial.md.txt · GeohashThe 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.
GEO commands are ZSET commands underneath
geospatial.md.txt · Commands ReferenceEvery GEO operation maps onto a ZSET operation. Select a row to see the correspondence:
GEOADD inserts
a member with a computed score; GEOSEARCH scans a score range; ZREM removes a member;
WITHHASH returns the raw score.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.
Precision vs proximity
geospatial.md.txt · GeohashAt 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.
The sortable-scalar parallel — place, time, and identity
geospatial.md.txt · the cross-chapter ideaThe 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 packed | Scalar | Structure | Query 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.
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.
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.
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.
Vectors & similarity search
Another structure that answers “what is like this?” — but over a vector neighbourhood rather than a geohash score range. vector-sets.
References
sources · relatedSources
- 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;
WITHHASHexposes 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
- R7.06.2 · Radius and box queriesBYRADIUS vs BYBOX, GEOSEARCHSTORE, the metadata pattern
- R4 · Time, delay, and prioritytime packed into a ZSET score — the same sortable-scalar family
- R7.05 · Vectors & similarity searcha vector neighbourhood vs a geohash score range
- R7.04 · Bitmapsone bit per flag — another compact scalar encoding