Understand list-end orientation, bounded ranges, trimming, and the operational limits of an ordered Redis sequence.
Lists: LPUSH/RPUSH, LPOP/RPOP, LRANGE, LTRIM, and Ordered Sequence Semantics
Choose hashes, Redis JSON, or many keys from structure, update/query patterns, expiration granularity, memory/cardinality, indexing, ACL boundaries, and migration evidence.
Learning outcomes
AtlasMart needs a compact recent-activity feed for each store: the feed must preserve arrival order, let an operator inspect a bounded window, and occasionally remove work from either end. A Redis List is an ordered sequence of binary-safe string elements. “Left” means the list head and “right” means the tail; that orientation is part of the API, not merely presentation.
Predict the exact order produced by LPUSH, RPUSH, LPOP, and RPOP, including multi-element pushes.
Use LRANGE and negative indexes without turning full-list reads into an unbounded production habit.
Use LTRIM to keep a bounded recent-history list and explain what it removes.
Relate LLEN, response size, command complexity, persistence, replication, and hot-key behavior to production cost.
Distinguish an ordered list from a durable/replayable messaging system before using it as a queue.
All Chapter 05 mandatory labs reuse the disposable Chapter 01
environment: Redis Open Source 8.10.1 from Docker
Official Image redis:8.10.1, container
atlasmart-redis-ch01, standalone topology, host
publication 127.0.0.1:6379, TLS disabled only
because traffic stays on loopback, default ACL user disabled,
named ACL users atlasmart-app and
academy-admin, logical database 0, AOF with
appendfsync everysec plus RDB snapshots,
persistent /data volume, and no explicit Redis
maxmemory limit or eviction policy. The
application ACL is restricted to ~atlasmart:* and
normal read/write/connection categories. The primary interface
is the redis-cli shipped in the same 8.10.1
image, so server and CLI versions stay aligned. Mandatory
examples use only bounded synthetic keys under
atlasmart:ch05:*.
1. Head and tail are semantic choices
LPUSH prepends elements at the left/head.
RPUSH appends at the right/tail.
LPOP removes from the left;
RPOP removes from the right. A FIFO queue can
therefore use RPUSH + LPOP, while
another equally valid orientation is LPUSH +
RPOP. Pick one convention and write it down; mixing
conventions silently reverses work.
| Producer | Consumer | Observed order | Use |
|---|---|---|---|
RPUSH |
LPOP |
oldest first | simple FIFO |
LPUSH |
RPOP |
oldest first | simple FIFO, opposite orientation |
LPUSH |
LPOP |
newest first | stack/LIFO |
RPUSH |
RPOP |
newest first | stack/LIFO |
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch05:recentdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app RPUSH atlasmart:ch05:recent order:1001 order:1002 order:1003docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LRANGE atlasmart:ch05:recent 0 -1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LPUSH atlasmart:ch05:recent urgent:9001docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LRANGE atlasmart:ch05:recent 0 -1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LPOP atlasmart:ch05:recentdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app RPOP atlasmart:ch05:recentdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LRANGE atlasmart:ch05:recent 0 -1
Expected final list: order:1001,
order:1002. The intermediate outputs prove which
end each command touches; they do not prove anything about
end-to-end job processing.
2. Multi-element pushes can surprise you
When several elements are passed to LPUSH, Redis
inserts them at the head in argument order, so the last argument
becomes the new first element. That is observable and frequently
misread in shell examples.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch05:multidocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LPUSH atlasmart:ch05:multi a b cdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LRANGE atlasmart:ch05:multi 0 -1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch05:multidocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app RPUSH atlasmart:ch05:multi a b cdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LRANGE atlasmart:ch05:multi 0 -1
The first list reads c b a; the second reads
a b c. A batch API that changes from repeated
single pushes to one variadic push must preserve the
application's intended order explicitly.
3. LRANGE is inclusive, supports negative indexes, and still returns bytes
LRANGE key start stop includes both endpoints.
0 -1 means the entire list;
-3 -1 means the last three elements. Convenient
does not mean cheap: the command must locate the requested range
and return every selected element over the network. A full
LRANGE 0 -1 against a very large hot list can
increase server work, response bytes, client memory, and tail
latency.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch05:historydocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app RPUSH atlasmart:ch05:history e1 e2 e3 e4 e5 e6docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LLEN atlasmart:ch05:historydocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LRANGE atlasmart:ch05:history 0 2docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LRANGE atlasmart:ch05:history -3 -1
LLEN gives cardinality without transferring the
list. In production APIs, prefer a bounded contract such as
“latest 50” and measure reply sizes rather than making “all
items” the default.
4. LTRIM keeps a range and discards the rest
LTRIM keeps only the specified inclusive range.
This makes an ordered, bounded recent-history pattern possible:
push a new element, then trim to the window. The commands are
individually atomic; if your correctness requires the pair to be
indivisible, later transaction/function lessons explain how to
combine operations safely.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch05:recent-ordersdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LPUSH atlasmart:ch05:recent-orders order:1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LPUSH atlasmart:ch05:recent-orders order:2docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LPUSH atlasmart:ch05:recent-orders order:3docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LPUSH atlasmart:ch05:recent-orders order:4docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LTRIM atlasmart:ch05:recent-orders 0 2docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LRANGE atlasmart:ch05:recent-orders 0 -1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LLEN atlasmart:ch05:recent-orders
The retained sequence should be
order:4 order:3 order:2. This bounds list length,
but it does not create a compliance archive: trimmed elements
are gone from the logical list.
5. Deliberately wrong: an unbounded “show everything” endpoint
A tempting endpoint is
LRANGE atlasmart:orders 0 -1. It works in
development and then degrades as the list grows. The mechanism
is straightforward: Redis must traverse/return the requested
elements, serialize the reply, send it, and the client must
allocate/decode it. Persistence does not mitigate that
request-time cost.
Do not create millions of elements merely to prove a point. The lab uses 5,000 short synthetic entries, compares a bounded range with the full reply, then deletes only the chapter key. On your machine, measure wall-clock time and response bytes rather than copying any timing from this lesson.
# Bash, PowerShell, or another shell can generate fixtures; below uses redis-cli --pipe inside the Linux container.docker exec -i atlasmart-redis-ch01 sh -lc 'for i in $(seq 1 5000); do echo "RPUSH atlasmart:ch05:bounded item:$i"; done | REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 redis-cli --user atlasmart-app --pipe'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LLEN atlasmart:ch05:boundeddocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LRANGE atlasmart:ch05:bounded 0 49# Full-range retrieval is shown as the unsafe pattern; do not make it an API default.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch05:bounded
The repair is a bounded API, explicit retention, and observability for list length and response size. If the use case needs durable replay, consumer state, or independent readers, a Stream is a better semantic starting point than simply allowing the list to grow forever.
6. Memory, persistence, replication, and Cluster boundaries
A list is one Redis key containing many elements. That can reduce top-level key cardinality, but it can also create a hot key: many clients contend on one key and one Cluster hash slot. AOF/RDB persistence can recover list state according to their configured durability windows, but they do not make an application workflow exactly-once. Redis replication is asynchronous, so a failover can have a different acknowledged-write boundary than the client assumes unless the architecture is designed and tested around it.
List operations are key-local, so basic pushes/pops work naturally in Cluster. Multi-key blocking/move operations have additional same-slot/routing constraints; Chapter 21 treats those precisely.
7. Hands-on lab: AtlasMart bounded order feed
Build one store feed with a deliberate 20-item retention target.
Use only atlasmart:ch05:* keys.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch05:store:42:ordersdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LPUSH atlasmart:ch05:store:42:orders order:2001 order:2002 order:2003docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LRANGE atlasmart:ch05:store:42:orders 0 -1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LTRIM atlasmart:ch05:store:42:orders 0 19docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app LLEN atlasmart:ch05:store:42:ordersdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TYPE atlasmart:ch05:store:42:ordersdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch05:store:42:orders
Verification: state which item is head/tail;
explain the multi-element LPUSH order; verify
length ≤20; record memory as an environment-specific
observation, not a universal byte-per-element constant.
8. Production judgment
Use a List when the important abstraction is a key-local ordered deque: recent history, a bounded stack, or a simple work queue whose loss/retry semantics you fully own. Define maximum length, payload size, consumer orientation, timeouts, and observability. Avoid unbounded ranges and unbounded growth. If a worker needs acknowledgment, replay, pending-message inspection, or recovery by another consumer, a Stream usually expresses those requirements more directly.
9. Summary and next step
Lists preserve sequence and offer O(1)-style end operations for common push/pop cases, but they do not automatically provide delivery acknowledgments or replay. Next, blocking operations make a list behave like a waiting work queue—and expose exactly where “simple queue” reliability stops.
Check your understanding
- With RPUSH producers and LPOP consumers, which item is processed first?
- Why can LPUSH key a b c appear reversed when read left-to-right?
- Why is LRANGE 0 -1 dangerous as a default production API?
- What does LTRIM 0 99 guarantee and what does it destroy?
- Why does AOF persistence not make a list-backed worker exactly-once?
Review the answers
FIFO: the oldest element at the head is popped first.
Each argument is prepended to the head; the last argument becomes the first visible element.
It returns the entire list, so server work, reply bytes, network time, and client memory grow with the result.
It retains the first 100 indexed elements and removes the rest from that list; it is retention, not archival.
Persistence concerns Redis state recovery. It does not add application processing acknowledgment, idempotency, or crash recovery between pop and side effect.
Authoritative references
- Redis Open Source 8.10 release notes — 8.10.1 security baseline and 8.10 list/set additions
- Redis lists — list semantics and common queue patterns
- Redis sets — set uniqueness, membership, and algebra
- Redis Streams — persistent entries, consumer groups, pending entries, and acknowledgments
- Redis 8.10 command reference — current command syntax and complexity
- Redis Cluster specification — hash slots and multi-key locality
- LPUSH — prepend semantics and complexity
- LRANGE — inclusive ranges, negative indexes, and complexity
- LTRIM — bounded list retention semantics