dot Stop testing, start deploying your AI apps. See how with MIT Technology Review’s latest research.

Download now

3.2 Lists

back to home

3.2 Lists

As you may remember from chapter 1, LISTs allow you to push and pop items from both ends of a sequence, fetch individual items, and perform a variety of other operations that are expected of lists. LISTs by themselves can be great for keeping a queue of work items, recently viewed articles, or favorite contacts.

In this section, we’ll talk about LISTs, which store an ordered sequence of STRING values. We’ll cover some of the most commonly used LIST manipulation commands for pushing and popping items from LISTs. After reading this section, you’ll know how to manipulate LISTs using the most common commands. We’ll start by looking at table 3.3, where you can see some of the most frequently used LIST commands.

Table 3.3 Some commonly used LIST commands
Command Example use and description
RPUSH RPUSH key-name value [value …] — Pushes the value(s) onto the right end of the list
LPUSH LPUSH key-name value [value …] — Pushes the value(s) onto the left end of the list
RPOP RPOP key-name — Removes and returns the rightmost item from the list
LPOP LPOP key-name — Removes and returns the leftmost item from the list
LINDEX LINDEX key-name offset — Returns the item at the given offset
LRANGE LRANGE key-name start end — Returns the items in the list at the offsets from start to end, inclusive
LTRIM LTRIM key-name start end — Trims the list to only include items at indices between start and end, inclusive

The semantics of the LIST push commands shouldn’t be surprising, and neither should the pop commands. We covered a couple of these, along with both LINDEX and LRANGE, back in chapter 1. The next listing shows some uses of these push and pop commands.

Listing 3.3 A sample interaction showing LIST push and pop commands in Redis
>>> conn.rpush('list-key', 'last')
1L

When we push items onto the list, it returns the length of the list after the push has completed.

>>> conn.lpush('list-key', 'first')

We can easily push on both ends of the list.

2L
>>> conn.rpush('list-key', 'new last')
3L
>>> conn.lrange('list-key', 0, -1)
['first', 'last', 'new last']

Semantically, the left end of the list is the beginning, and the right end of the list is the end.

>>> conn.lpop('list-key')
'first'
>>> conn.lpop('list-key')
'last'

Popping off the left items repeatedly will return items from left to right.

>>> conn.lrange('list-key', 0, -1)
['new last']
>>> conn.rpush('list-key', 'a', 'b', 'c')

We can push multiple items at the same time.

4L
>>> conn.lrange('list-key', 0, -1)
['new last', 'a', 'b', 'c']
>>> conn.ltrim('list-key', 2, -1)
True
>>> conn.lrange('list-key', 0, -1)
['b', 'c']

We can trim any number of items from the start, end, or both.

The LTRIM command is new in this example, and we can combine it with LRANGE to give us something that functions much like an LPOP or RPOP call that returns and pops multiple items at once. We’ll talk more about how to make these kinds of composite commands atomic1 later in this chapter, as well as dive deeper into more advanced Redis-style transactions in chapter 4.

Among the LIST commands we didn’t introduce in chapter 1 are a few commands that allow you to move items from one list to another, and even block while waiting for other clients to add items to LISTs. Table 3.4 shows our blocking pop and item moving commands.

Table 3.4 Some LIST commands for blocking LIST pops and moving items between LISTs
Command Example use and description
BLPOP BLPOP key-name [key-name …] timeout — Pops the leftmost item from the first non-empty LIST, or waits the timeout in seconds for an item
BRPOP BRPOP key-name [key-name …] timeout — Pops the rightmost item from the first non-empty LIST, or waits the timeout in seconds for an item
RPOPLPUSH RPOPLPUSH source-key dest-key — Pops the rightmost item from the source and LPUSHes the item to the destination, also returning the item to the user
BRPOPLPUSH BRPOPLPUSH source-key dest-key timeout — Pops the rightmost item from the source and LPUSHes the item to the destination, also returning the item to the user, and waiting up to the timeout if the source is empty

This set of commands is particularly useful when we talk about queues in chapter 6. The following listing shows some examples of moving items around with BRPOPLPUSH and popping items from multiple lists with BLPOP.

Listing 3.4 Blocking LIST pop and movement commands in Redis
>>> conn.rpush('list', 'item1')
1
>>> conn.rpush('list', 'item2')
2
>>> conn.rpush('list2', 'item3')
1

Let’s add some items to a couple of lists to start.

>>> conn.brpoplpush('list2', 'list', 1)
'item3'

Let’s move an item from one list to the other, also returning the item.

>>> conn.brpoplpush('list2', 'list', 1)

When a list is empty, the blocking pop will stall for the timeout, and return None (which isn’t displayed in the interactive console).

>>> conn.lrange('list', 0, -1)
['item3', 'item1', 'item2']

We popped the rightmost item from “list2” and pushed it to the left of “list”.

>>> conn.brpoplpush('list', 'list2', 1)
'item2'
>>> conn.blpop(['list', 'list2'], 1)
('list', 'item3')
>>> conn.blpop(['list', 'list2'], 1)
('list', 'item1')
>>> conn.blpop(['list', 'list2'], 1)
('list2', 'item2')
>>> conn.blpop(['list', 'list2'], 1)

Blocking left-popping items from these will check lists for items in the order that they are passed until they are empty.

>>>

The most common use case for using blocking pop commands as well as the pop/ push combination commands is in the development of messaging and task queues, which we’ll cover in chapter 6.

Exercise: Reducing memory use with LISTs

Back in sections 2.1 and 2.5, we used ZSETs to keep a listing of recently viewed items. Those recently viewed items included timestamps as scores to allow us to perform analytics during cleanup or after purchase. But including these timestamps takes space, and if timestamps aren’t necessary for our analytics, then using a ZSET just wastes space. Try to replace the use of ZSETs in update_token() with LISTs, while keeping the same semantics. Hint: If you find yourself stuck, you can skip ahead to section 6.1.1 for a push in the right direction.

One of the primary benefits of LISTs is that they can contain multiple string values, which can allow you to group data together. SETs offer a similar feature, but with the caveat that all items in a given SET are unique. Let’s look at how that changes what we can do with SETs.

1 In Redis, when we talk about a group of commands as being atomic, we mean that no other client can read or change data while we’re reading or changing that same data.