Bit arrays (或者说 simply bitmaps): it is possible, using special commands, to
handle String values like an array of bits: you can set and clear individual
bits, count all the bits set to 1, find the first set or unset bit, and so
forth.

HyperLogLogs: this is a probabilistic data structure which is used in order
to estimate the cardinality of a set. Don’t be scared, it is simpler than
it seems… See later in the HyperLogLog section of this tutorial.

It’s not always trivial to grasp how these data types work and what to use in
order to solve a given problem from the command reference, so this
document is a crash course to Redis data types and their most common patterns.

For all the examples we’ll use the redis-cli utility, that’s a simple but
handy command line utility to issue commands against the Redis server.

Automatic creation and removal of keys

So far in our examples we never had to create empty lists before pushing
elements, or removing empty lists when they no longer have elements inside.
It is Redis’ responsibility to delete keys when lists are left empty, or to create
an empty list if the key does not exist and we are trying to add elements
to it, for example, with LPUSH.

This is not specific to lists, it applies to all the Redis data types
composed of multiple elements – Sets, Sorted Sets and Hashes.

Basically we can summarize the behavior with three rules:

When we add an element to an aggregate data type, if the target key does not exist, an empty aggregate data type is created before adding the element.

When we remove elements from an aggregate data type, if the value remains empty, the key is automatically destroyed.

Calling a read-only command such as LLEN (which returns the length of the list), or a write command removing elements, with an empty key, always produces the same result as if the key is holding an empty aggregate type of the type the command expects to find.

Examples of rule 1:

> del mylist
(integer) 1
> lpush mylist 1 2 3
(integer) 3

However we can’t perform operations against the wrong type of the key exists:

While hashes are handy to represent objects, actually the number of fields you can
put inside a hash has no practical limits (other than available memory), so you can use
hashes in many different ways inside your application.

The command HMSET sets multiple fields of the hash, while HGET retrieves
a single field. HMGET is similar to HGET but returns an array of values:

It is worth noting that small hashes (i.e., a few elements with small values) are
encoded in special way in memory that make them very memory efficient.

Redis Sets
—

Redis Sets are unordered collections of strings. The
SADD command adds new elements to a set. It’s also possible
to do a number of other operations against sets like testing if a given element
already exists, performing the intersection, union or difference between
multiple sets, and so forth.

> sadd myset 1 2 3
(integer) 3
> smembers myset
1. 3
2. 1
3. 2

Here I’ve added three elements to my set and told Redis to return all the
elements. As you can see they are not sorted – Redis is free to return the
elements in any order at every call, since there is no contract with the
user about element ordering.

Redis has commands to test for membership. Does a given element exist?

> sismember myset 3
(integer) 1
> sismember myset 30
(integer) 0

“3” is a member of the set, while “30” is not.

Sets are good for expressing relations between objects.
For instance we can easily use sets in order to implement tags.

A simple way to model this problem is to have a set for every object we
want to tag. The set contains the IDs of the tags associated with the object.

Imagine we want to tag news.
If our news ID 1000 is tagged with tags 1, 2, 5 and 77, we can have one set
associating our tag IDs with the news item:

> sadd news:1000:tags 1 2 5 77
(integer) 4

However sometimes I may want to have the inverse relation as well: the list
of all the news tagged with a given tag:

Note: in the example we assume you have another data structure, for example
a Redis hash, which maps tag IDs to tag names.

There are other non trivial operations that are still easy to implement
using the right Redis commands. For instance we may want a list of all the
objects with the tags 1, 2, 10, and 27 together. We can do this using
the SINTER command, which performs the intersection between different
sets. We can use:

Intersection is not the only operation performed, you can also perform
unions, difference, extract a random element, and so forth.

The command to extract an element is called SPOP, and is handy to model
certain problems. For example in order to implement a web-based poker game,
you may want to represent your deck with a set. Imagine we use a one-char
prefix for (C)lubs, (D)iamonds, (H)earts, (S)pades:

Now we want to provide each player with 5 cards. The SPOP command
removes a random element, returning it to the client, so it is the
perfect operation in this case.

However if we call it against our deck directly, in the next play of the
game we’ll need to populate the deck of cards again, which may not be
ideal. So to start, we can make a copy of the set stored in the deck key
into the game:1:deck key.

This is accomplished using SUNIONSTORE, which normally performs the
union between multiple sets, and stores the result into another set.
However, since the union of a single set is itself, I can copy my deck
with:

Now it’s a good time to introduce the set command that provides the number
of elements inside a set. This is often called the cardinality of a set
in the context of set theory, so the Redis command is called SCARD.

> scard game:1:deck
(integer) 47

The math works: 52 - 5 = 47.

When you need to just get random elements without removing them from the
set, there is the SRANDMEMBER command suitable for the task. It also features
the ability to return both repeating and non-repeating elements.

Redis Sorted sets
—

Sorted sets are a data type which is similar to a mix between a Set and
a Hash. Like sets, sorted sets are composed of unique, non-repeating
string elements, so in some sense a sorted set is a set as well.

However while elements inside sets are not ordered, every element in
a sorted set is associated with a floating point value, called the score
(this is why the type is also similar to a hash, since every element
is mapped to a value).

Moreover, elements in a sorted sets are taken in order (so they are not
ordered on request, order is a peculiarity of the data structure used to
represent sorted sets). They are ordered according to the following rule:

If A and B are two elements with a different score, then A > B if A.score is > B.score.

If A and B have exactly the same score, then A > B if the A string is lexicographically greater than the B string. A and B strings can’t be equal since sorted sets only have unique elements.

Let’s start with a simple example, adding a few selected hackers names as
sorted set elements, with their year of birth as “score”.

As you can see ZADD is similar to SADD, but takes one additional argument
(placed before the element to be added) which is the score.
ZADD is also variadic, so you are free to specify multiple score-value
pairs, even if this is not used in the example above.

With sorted sets it is trivial to return a list of hackers sorted by their
birth year because actually they are already sorted.

Implementation note: Sorted sets are implemented via a
dual-ported data structure containing both a skip list and a hash table, so
every time we add an element Redis performs an O(log(N)) operation. That’s
good, but when we ask for sorted elements Redis does not have to do any work at
all, it’s already all sorted:

We asked Redis to return all the elements with a score between negative
infinity and 1950 (both extremes are included).

It’s also possible to remove ranges of elements. Let’s remove all
the hackers born between 1940 and 1960 from the sorted set:

> zremrangebyscore hackers 1940 1960
(integer) 4

ZREMRANGEBYSCORE is perhaps not the best command name,
but it can be very useful, and returns the number of removed elements.

Another extremely useful operation defined for sorted set elements
is the get-rank operation. It is possible to ask what is the
position of an element in the set of the ordered elements.

> zrank hackers "Anita Borg"
(integer) 4

The ZREVRANK command is also available in order to get the rank, considering
the elements sorted a descending way.

Lexicographical scores

With recent versions of Redis 2.8, a new feature was introduced that allows
getting ranges lexicographically, assuming elements in a sorted set are all
inserted with the same identical score (elements are compared with the C
memcmp function, so it is guaranteed that there is no collation, and every
Redis instance will reply with the same output).

The main commands to operate with lexicographical ranges are ZRANGEBYLEX,
ZREVRANGEBYLEX, ZREMRANGEBYLEX and ZLEXCOUNT.

For example, let’s add again our list of famous hackers, but this time
use a score of zero for all the elements:

Ranges can be inclusive or exclusive (depending on the first character),
also string infinite and minus infinite are specified respectively with
the + and - strings. See the documentation for more information.

This feature is important because it allows us to use sorted sets as a generic
index. For example, if you want to index elements by a 128-bit unsigned
integer argument, all you need to do is to add elements into a sorted
set with the same score (for example 0) but with an 8 byte prefix
consisting of the 128 bit number in big endian. Since numbers in big
endian, when ordered lexicographically (in raw bytes order) are actually
ordered numerically as well, you can ask for ranges in the 128 bit space,
and get the element’s value discarding the prefix.

Updating the score: leader boards

Just a final note about sorted sets before switching to the next topic.
Sorted sets’ scores can be updated at any time. Just calling ZADD against
an element already included in the sorted set will update its score
(and position) with O(log(N)) time complexity. As such, sorted sets are suitable
when there are tons of updates.

Because of this characteristic a common use case is leader boards.
The typical application is a Facebook game where you combine the ability to
take users sorted by their high score, plus the get-rank operation, in order
to show the top-N users, and the user rank in the leader board (e.g., “you are
the #4932 best score here”).

Bitmaps
—

Bitmaps are not an actual data type, but a set of bit-oriented operations
defined on the String type. Since strings are binary safe blobs and their
maximum length is 512 MB, they are suitable to set up to 2^32 different
bits.

Bit operations are divided into two groups: constant-time single bit
operations, like setting a bit to 1 or 0, or getting its value, and
operations on groups of bits, for example counting the number of set
bits in a given range of bits (e.g., population counting).

One of the biggest advantages of bitmaps is that they often provide
extreme space savings when storing information. For example in a system
where different users are represented by incremental user IDs, it is possible
to remember a single bit information (for example, knowing whether
a user wants to receive a newsletter) of 4 billion of users using just 512 MB of memory.

The SETBIT command takes as its first argument the bit number, and as its second
argument the value to set the bit to, which is 1 or 0. The command
automatically enlarges the string if the addressed bit is outside the
current string length.

GETBIT just returns the value of the bit at the specified index.
Out of range bits (addressing a bit that is outside the length of the string
stored into the target key) are always considered to be zero.

There are three commands operating on group of bits:

BITOP performs bit-wise operations between different strings. The provided operations are AND, OR, XOR and NOT.

BITCOUNT performs population counting, reporting the number of bits set to 1.

BITPOS finds the first bit having the specified value of 0 or 1.

Both BITPOS and BITCOUNT are able to operate with byte ranges of the
string, instead of running for the whole length of the string. The following
is a trivial example of BITCOUNT call:

For example imagine you want to know the longest streak of daily visits of
your web site users. You start counting days starting from zero, that is the
day you made your web site public, and set a bit with SETBIT every time
the user visits the web site. As a bit index you simply take the current unix
time, subtract the initial offset, and divide by 3600*24.

This way for each user you have a small string containing the visit
information for each day. With BITCOUNT it is possible to easily get
the number of days a given user visited the web site, while with
a few BITPOS calls, or simply fetching and analyzing the bitmap client-side,
it is possible to easily compute the longest streak.

Bitmaps are trivial to split into multiple keys, for example for
the sake of sharding the data set and because in general it is better to
avoid working with huge keys. To split a bitmap across different keys
instead of setting all the bits into a key, a trivial strategy is just
to store M bits per key and obtain the key name with bit-number/M and
the Nth bit to address inside the key with bit-number MOD M.

HyperLogLogs
—

A HyperLogLog is a probabilistic data structure used in order to count
unique things (technically this is referred to estimating the cardinality
of a set). Usually counting unique items requires using an amount of memory
proportional to the number of items you want to count, because you need
to remember the elements you have already seen in the past in order to avoid
counting them multiple times. However there is a set of algorithms that trade
memory for precision: you end with an estimated measure with a standard error,
in the case of the Redis implementation, which is less than 1%. The
magic of this algorithm is that you no longer need to use an amount of memory
proportional to the number of items counted, and instead can use a
constant amount of memory! 12k bytes in the worst case, or a lot less if your
HyperLogLog (We’ll just call them HLL from now) has seen very few elements.

HLLs in Redis, while technically a different data structure, is encoded
as a Redis string, so you can call GET to serialize a HLL, and SET
to deserialize it back to the server.

Conceptually the HLL API is like using Sets to do the same task. You would
SADD every observed element into a set, and would use SCARD to check the
number of elements inside the set, which are unique since SADD will not
re-add an existing element.

While you don’t really add items into an HLL, because the data structure
only contains a state that does not include actual elements, the API is the
same:

Every time you see a new element, you add it to the count with PFADD.

Every time you want to retrieve the current approximation of the unique elements added with PFADD so far, you use the PFCOUNT.

> pfadd hll a b c d
(integer) 1
> pfcount hll
(integer) 4

An example of use case for this data structure is counting unique queries
performed by users in a search form every day.

Redis is also able to perform the union of HLLs, please check the
full documentation for more information.

Other notable features

There are other important things in the Redis API that can’t be explored
in the context of this document, but are worth your attention: