Two Special-Purpose Structures
The ship also has a map. Given your position, you can ask "what is within 2 km of me?" and get a list, nearest first. That is Redis geospatial search.
This tutorial covers both: streams for event logs, and geo for nearby search.
Streams — An Append-Only Log
A stream is a log of events. XADD writes a new event at the end and returns its ID.
The ID looks like 1758300000000-0: the time in milliseconds, a dash, and a sequence
number. Because IDs always go up, the log stays in order forever.
Each XADD appends one entry at the tail. Entries are never changed. Readers move
forward from an ID.
# add events; * means "auto-generate the next ID"
XADD events * type "login" user 57
XADD events * type "purchase" user 57 item "book"
# -> each returns an ID like "1758300000000-0"
# how many events are in the log?
XLEN events
# read every event from the very start
XRANGE events - +
# read the newest events; $ means "only entries added after now"
XREAD BLOCK 5000 STREAMS events $
# waits up to 5 seconds for the next event, then returns it
An ID like 1758300000000-0 is the millisecond time plus a counter for events in
the same millisecond. IDs only ever increase, so the log is always in time order and a reader
can say "give me everything after this ID".
Stream vs Pub/Sub vs List
| Fire and forget |
| Offline listeners miss messages |
| No history, no replay |
| Good for live-only signals |
| Events are kept in the log |
| Late readers can catch up |
| Replay old events any time |
| Consumer groups share the work |
A stream keeps every event, so trim it. Add MAXLEN ~ 100000 to
XADD to keep roughly the newest 100,000 entries and drop older ones. The
~ makes trimming faster and is almost always what you want.
Consumer Groups — Sharing the Work
One reader is fine for a small log. But to process many events fast, use a consumer group. Several workers join the group and read the same stream. Redis hands each event to only one worker, so the work is split and nothing is done twice.
The group tracks who got what. Each new entry goes to one free worker. After a worker finishes,
it sends XACK so the entry is marked done.
# create a group that starts at the end of the stream
XGROUP CREATE orders workers $ MKSTREAM
# a worker reads new entries meant for it ( > means "not yet delivered" )
XREADGROUP GROUP workers worker-1 COUNT 1 STREAMS orders >
# after finishing, acknowledge so it is not re-delivered
XACK orders workers 1760-0
# see entries delivered but not yet acked (stuck work)
XPENDING orders workers
An entry stays "pending" until a worker sends XACK. If a worker dies before
acking, the entry is still in the pending list, so another worker can pick it up with
XCLAIM. This makes streams safe for real job processing.
Geospatial — Nearby Search
Redis can store places by their location and answer "what is near me?". You add each place with its longitude and latitude, then search within a radius. Results come back sorted by distance.
GEOADD takes longitude before latitude. This
trips up almost everyone. For Bengaluru (lat 12.97, long 77.59) you write
GEOADD places 77.59 12.97 "...". Longitude, then latitude.
Search from a point with a radius. Places inside the circle are returned, nearest first. Places outside are ignored.
# add places: longitude, latitude, name (long FIRST!)
GEOADD places 77.595 12.972 "cafe-mtr"
GEOADD places 77.610 12.980 "pizza-hub"
GEOADD places 77.601 12.965 "deli-corner"
# find places within 2 km of a point, nearest first, with distance
GEOSEARCH places FROMLONLAT 77.600 12.970 BYRADIUS 2 km ASC WITHDIST
# or search around an existing place
GEOSEARCH places FROMMEMBER "cafe-mtr" BYRADIUS 1 km ASC
# distance between two places, and a place's coordinates
GEODIST places "cafe-mtr" "pizza-hub" km # -> "1.8"
GEOPOS places "cafe-mtr"
How Geo Works — It Is a Sorted Set
Here is the neat part. Redis geo is not a new structure. It turns each longitude and latitude into a single number called a geohash, and stores that number as the score in a sorted set. Places that are near each other get scores that are near each other, so a radius search is a fast score range.
Because geo is a sorted set, your places also work with sorted-set commands, and a
GEOSEARCH is really a clever score range. Nearby points have nearby scores, so
Redis can find them without checking every place.
Command Reference
| Command | What it does |
|---|---|
XADD key * f v ... | Append an event, auto ID |
XLEN key | Count events in the stream |
XRANGE key - + | Read a range of events |
XREAD BLOCK ms STREAMS key $ | Wait for and read new events |
XGROUP CREATE / XREADGROUP / XACK | Share work across a group of workers |
GEOADD key lon lat name | Add a place (longitude first) |
GEOSEARCH ... BYRADIUS r km | Find places within a radius |
GEODIST / GEOPOS | Distance between places / a place's coordinates |
Golden Rules
MAXLEN ~
N on XADD so it does not fill your memory.
XACK when done, and reclaim stuck entries with XCLAIM.