More Than Just Strings
Redis works the same way. A plain value is a string. An ordered line of items is a list. A small object with named fields is a hash. Picking the right structure makes your commands simpler and your app faster. This tutorial covers the three you will use most.
Strings and Counters
A string is the simplest value. But a string that holds a number becomes a fast, safe counter.
INCR adds one, DECR takes one away, and APPEND adds text
to the end. These run in memory, so they are extremely quick.
# a plain string value
SET greeting "hello"
GET greeting
# a counter: start at 0, then add or subtract
SET page:views 0
INCR page:views # -> 1
INCR page:views # -> 2
INCRBY page:views 10 # -> 12 (add 10 at once)
DECR page:views # -> 11
# APPEND adds text to the end of a string
SET log "line1;"
APPEND log "line2;" # value is now "line1;line2;"
GET log
Many app servers can hit the same counter at once. Because Redis runs one command at a time,
every INCR is counted. No two updates ever clash.
In an RDBMS, counting often means read the value, add one, and write it back. Two users at
once can read the same number and one update is lost. INCR does the whole thing
in one atomic step, so it is always correct and much faster.
| Command | What it does | Example reply |
|---|---|---|
INCR key | Add 1 to the number | (integer) 1 |
DECR key | Subtract 1 | (integer) 0 |
INCRBY key n | Add any amount | (integer) 10 |
DECRBY key n | Subtract any amount | (integer) 5 |
APPEND key text | Add text to the end | new length |
Page views, likes, downloads, "items in stock", and rate limiting (count requests per minute, then let the key expire). Any number that many users change at once is a good fit.
Lists — Ordered Lines of Items
A list keeps items in order. You can add or remove from either end. The left
end is the head; the right end is the tail. LPUSH adds on the
left, RPUSH adds on the right, and LPOP / RPOP remove
from those ends.
Positions are numbered from 0 at the head. The last item is also -1, the one
before it -2, and so on.
# build a list from the right
RPUSH tasks "a" "b" "c" # list is now: a, b, c
# read the whole list (0 to -1 means start to end)
LRANGE tasks 0 -1 # -> "a" "b" "c"
# add on the left
LPUSH tasks "z" # list is now: z, a, b, c
# remove from an end
LPOP tasks # -> "z" (list: a, b, c)
RPOP tasks # -> "c" (list: a, b)
# how many items?
LLEN tasks # -> 2
Lists as a Queue
Put items in one end and take them out of the other, and a list becomes a queue. A common
pattern is a background job queue: producers LPUSH jobs on the left, and a worker
RPOPs them from the right. First in, first out (FIFO).
LPUSH jobs "send-email:57".RPOP jobs (or BRPOP to wait for one). FIFO order.# producer side: add jobs on the left
LPUSH jobs "send-email:57"
LPUSH jobs "resize-image:88"
# worker side: take the oldest job from the right (FIFO)
RPOP jobs # -> "send-email:57"
# BRPOP waits up to 5 seconds if the queue is empty
BRPOP jobs 5
Lists for Recent Items (LTRIM)
Lists are perfect for "the latest N things": recent chat messages, a notification feed, or the
last searches. The trick is LTRIM. After each push, trim the list so it keeps only
the newest items and drops the old ones.
A new message is pushed to the head. LTRIM keeps the newest few, so the oldest
item falls off the tail. The list never grows without limit.
# cache the newest chat messages, keep only the last 50
LPUSH chat:42 "{id:9001, body:'hi'}"
LTRIM chat:42 0 49 # keep positions 0..49 (newest 50)
# read the newest 10 to show on screen
LRANGE chat:42 0 9
Push the newest item, then trim. The list stays a fixed size forever, so it never eats your memory. This is exactly how a "recent messages" or "recent activity" cache is built.
| Command | What it does |
|---|---|
LPUSH / RPUSH key v | Add an item on the left / right |
LPOP / RPOP key | Remove and return the left / right item |
LRANGE key start stop | Read a range (0 -1 = all) |
LTRIM key start stop | Keep only that range, drop the rest |
LLEN key | Count the items |
Hashes — Storing Objects
A hash is a key that holds many named fields. It is the natural way to store an object like a user profile. One key, many fields, read together or one at a time.
# store a whole profile under one key, many fields at once
HSET user:1 name "Asha" email "asha@example.com" age 29 city "Baddi"
# read one field
HGET user:1 email # -> "asha@example.com"
# read the whole object
HGETALL user:1 # -> name Asha email ... age 29 city Baddi
# update just one field, or bump a number field
HSET user:1 city "Shimla"
HINCRBY user:1 age 1 # age -> 30
# remove a field, or check if one exists
HDEL user:1 city
HEXISTS user:1 email # -> 1
One hash vs many string keys
| user:1:name = "Asha" |
| user:1:email = "asha@..." |
| user:1:age = 29 |
| 4 keys, 4 round trips to read |
| user:1 → {name, email, age, city} |
| 1 key, tidy and grouped |
| read all with one HGETALL |
| uses less memory |
A small hash is stored in a compact way inside Redis, so one hash of ten fields uses less memory than ten separate string keys. For objects like profiles, carts, and settings, a hash is both tidier and lighter.
Which Structure Should You Use?
Golden Rules
INCR is one atomic
step, so it is always correct even when many writers hit the same key.
0 is the first item and -1 is the last.