Redis Data Structures
Learn the most common data structures that Redis provides out of the box
Redis is an in-memory database which has a very powerful set of data types and data structures. These data structures are used across a variety of applications like message queues, game leaderboards, etc. Redis has been widely popular because of its performance efficiency and simplicity of using it.
In this article, I have explained the most commonly used data types in Redis with examples.
I would also recommend installing Redis on your local machine and following this article by actually executing the commands hands-on. Once you install Redis(instructions here), run redis-cli
command from your CLI, this will open the command line interface of redis where you can run the commands which are mentioned below in this article.
Before starting with Redis data types, let's understand what are the keys in Redis
Keys
Keys are the primary way to access data in Redis. Almost all the Redis data structures and Redis commands operate using keys.
Usually, it is suggested to have a meaningful domain-specific key name, with a separator if required; for example: product:12
may be used for storing details about a product which has a primary id 12.
Keys are used in almost all data types of Redis like string, list, hashes, sets, etc.
We will now start with a String data type to also get acquainted with keys.
Strings
We will be exploring the below commands which are applied for string data type -
SET <key> <value>
GET <key>
KEYS <regex pattern>
SCAN <cursor>
DEL <key>
UNLINK <key>
EXISTS <key>
SET <key> <value>
- sets the value of the given keyYou can also provide the expiry of this key using EX or EXAT argument.
By default, if the key is not present, it will create the key, else it will update the value of the key. If you want to SET the key only if the key does not already exist, then you can pass the NX argument.
GET <key>
- returns the value of the given key name. ex:GET product:12
MGET <key> [<key>...]
- returns the value of multiple keys passed in the argumentBelow is the demonstration in redis-cli :
# Sets the value as product-12-value with expity of 2 mins 127.0.0.1:6379> SET product:12 product-12-value EX 120 NX # OUTPUT OK 127.0.0.1:6379> SET product:13 p13 # OUTPUT OK 127.0.0.1:6379> get product:12 # OUTPUT "product-12-value" 127.0.0.1:6379> GET product:12 # OUTPUT "product-12-value" 127.0.0.1:6379> MGET product:12 product:13 # OUTPUT 1) "product-12-value" 2) "p13"
Redis also provides commands to retrieve all the keys in the database matching a regex pattern
KEYS <regex pattern>
- returns all the keys matching the specified pattern.This command is not recommended in production applications since it's a blocking command, and it may cause performance issues in the case of large datasets.
127.0.0.1:6379> KEYS product:* # OUTPUT 1) "product:12" 2) "product:13"
SCAN <cursor> [MATCH pattern] [COUNT count]
This command also blocks the thread but it returns only a few keys at a time, along with a cursor. You need to call
SCAN 0
(0 is the cursor here) for the first time and then iteratively call the SCAN command with the updated cursor which was returned in the response.When the server response has the cursor value as 0, it signifies no other keys left to iterate, so you can stop the iteration at this point.
The user can also specify an optional MATCH argument to retrieve based on a specified regex pattern, and a COUNT argument signifying what number of keys to be retrieved per SCAN, though this can block the thread longer if the COUNT value is too large.
To delete the key, Redis provides 2 commands
DEL <key>
- deletes the key in a blocking mannerUNLINK <key>
- deletes the key in a non-blocking way, where the memory associated with the key is reclaimed by an asynchronous process.
You can also check if the key exists or not using EXISTS <key>
command.
Apart from keys
and scan
command, all other commands have a time complexity of O(1). Every scan command is O(1), but it can become O(N) for complete iteration.
Strings are fundamental data types in Redis and can be used to store numerical values, serialized JSON or anything that case which can be represented in binary. All the commands we saw above are used for strings.
π¨ Even if you run SET int-key 12
, and then try to GET the value, it will return value 12 as a string.
127.0.0.1:6379> SET int-key 12
OK
127.0.0.1:6379> GET int-key
"12"
You can check the type of key using type <key>
command. For the above key, it will return a string.
This is because it returns the string representation of the value. The different types that can be returned are: string, list, set, zset, hash and stream (We will look into these types below)
Internally, Redis stores the encoding of the values, and stores the knowledge whether it's a string, integer, floating number, etc. To check the kind of internal representation used to store the value, you need to use object encoding <key>
. This command will return "int" as a value as shown below.
127.0.0.1:6379> TYPE int-key
string
127.0.0.1:6379> OBJECT ENCODING int-key
"int
For numeric values, we can also increment/decrement the value directly using the below Redis commands:
INCR/DECR <key>
- increments/decrements the key value by 1INCRBY/DECRBY <key> <increment value>
- increments/decrements by the specified increment value127.0.0.1:6379> INCR int-key (integer) 13 127.0.0.1:6379> INCR int-key (integer) 14 127.0.0.1:6379> GET int-key "14"
Use cases of Strings:
Caching the API response in JSON string format. for example:
SET product:12 "{\"name\":\"P1\",\"price\":\"121\"}"
Storing session cache.
Hashes
Hashes are a collection of field-value pairs, similar to hashmaps or maps in other languages like Java, Go, etc. They are mutable as well, so we can update or delete the hash and even add/update the field-value pairs.
Hashes store field values as string type, which means we can store flat string and numeric values but not embedded JSON or objects.
Similar to Key and string, we have below commands used for hashes -
HSET <key> <field> <value> [<field> <value> ...]
- sets/creates or updates the hash with given field-values stored at keyHSET hash-example:product:12 name p1 price 22
will create the hash with keyhash-example:product:12
for the first time with given fieldsname
andprice
with their valuesp1
and22
respectively.HSET hash-example:product:12 name p12
will update the above hash's "name" field's value top12
HGET <key> <field>
- returns provided field's value from the given hash name- Example:
HGET hash-example:product:12 name
will returnp1
- Example:
HMGET <key> <field> [<field>...]
- allows retrieving multiple fields values127.0.0.1:6379> HSET hash-example:product:12 name p1 price 22 (integer) 0 127.0.0.1:6379> HMGET hash-example:product:12 name price 1) "p1" 2) "22" 127.0.0.1:6379> HSET hash-example:product:12 name p2 (integer) 0 127.0.0.1:6379> HMGET hash-example:product:12 name price 1) "p2" 2) "22"
HGETALL <key>
- will retrieve all the fields with their values for provided key127.0.0.1:6379> HGETALL hash-example:product:12 1) "price" 2) "22" 3) "name" 4) "p2"
HKEYS <key>
- will only return the field names for a given hashHLEN <key>
- returns the total length(number of fields) of hashHDEL <key>
- deletes the hash stored at the given keyHINCRBY <key> <field> <incr-value>
- will increment the value of the field byincr-value
provided in the argument.127.0.0.1:6379> HINCRBY hash-example:product:12 price 2 (integer) 24 127.0.0.1:6379> HMGET hash-example:product:12 name price 1) "p2" 2) "24"
HSCAN <key> <cursor> [MATCH pattern] [COUNT count]
- Similar toSCAN
command we saw above in the key section, but HSCAN is for scanning hashes.127.0.0.1:6379> hscan hash-example:product:12 0 1) "0" 2) 1) "price" 2) "24" 3) "name" 4) "p2"
Apart from HSCAN
, HKEYS
and HGETALL
all other commands have a time complexity of O(1).
Hashes use cases:
Caching in case the object is too large and we want to only retrieve certain fields instead of all.
Storing User profiles, product information, etc.
List
The list is the sequence of strings, similar to Array or ArrayList in Java, Go, etc. Lists are used for storing arrays of strings, but they can also be used as a stack and queue.
Think of it as a list having a head as left(1st element)and a tail as right(last element). The beauty of this data type is that Redis provide commands to insert and remove the element from both the left and right of the list out of the box, making it ideal for stack and queue use-case implementations.
The list has below list of commands-
LPUSH <key> <element> [<element>...]
- is used to insert an element at the head of the list (left push) stored at the key. Multiple elements can also be passed.LPUSH demo-list 1 2 3
will result in a list with value[3,2,1]
LPOP <key> <count>
- is used to remove the data from the head of the list (left pop). Multiple elements can also be removed at once by providing an optional count valueLPOP demo-list 1
will remove the3
from the above list; updated list -[2,1]
RPUSH <key> <element> [<element>...]
- same as LPUSH, but is used to insert the element or multiple elements at the tail of the list (right push)RPUSH demo-list 4 5
will add to the above list from the right; updated list -[2,1,4,5]
RPOP <key> <count>
- same as LPOP, but is used to remove the data from the tail of the list (right pop)RPOP demo-list 1
will remove the5
from the above list; updated list -[2,1,4]
LRANGE <key> <start> <stop>
- returns the elements of the list stored at the key. Start and stop are zero-based indexes, where the start0
is the 1st element(head) and so on.LRANGE demo-list 0 -1
will retrieve all elements of the listLRANGE demo-list 0 -2
will retrieve all elements from the 1st element till 2nd last element of the list
127.0.0.1:6379> LPUSH demo-list 1 2 3
(integer) 3
127.0.0.1:6379> LRANGE demo-list 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> LPOP demo-list 1
1) "3"
127.0.0.1:6379> LRANGE demo-list 0 -1
1) "2"
2) "1"
127.0.0.1:6379> RPUSH demo-list 4 5
(integer) 4
127.0.0.1:6379> LRANGE demo-list 0 -1
1) "2"
2) "1"
3) "4"
4) "5"
127.0.0.1:6379> RPOP demo-list 1
1) "5"
127.0.0.1:6379> LRANGE demo-list 0 -1
1) "2"
2) "1"
3) "4"
LLEN <key>
- returns the length of the listLTRIM <key> <start> <stop>
- trims the element and keeps only elements from the specified start index to the stop index in the list.
π« You can use a combination of the above commands for stack and queue implementations
For queues, use
RPUSH
andLPOP
For stack, use
LPUSH
andLPOP
List use cases:
Stack or queue implementations like a queue-based worker.
Social networking sites display only the top 5 trending results, etc.
Set
Set are similar to lists, i.e. an unordered sequence of strings, but the difference is the set does not have duplicate elements, even if you try to insert the same element thousand times. Another difference is that the set does not allow insertions or deletions from the head or tail of the array, since it's not an array, it's just a set!
Redis Set also supports standard mathematical set operations like Union, Intersection and Difference π«.
The set has below list of commands-
SADD <key> <member> [<member>...]
- Adds the member or members passed in the argument to the set stored at keySADD demo-set a b
- adds a and b members to the set
SCARD <key>
- returns the cardinality(total number of members) of the set. i.e. 2SISMEMBER <key> <member>
- tells whether the given member is part of the set stored at key or not; return 0 if it is not part of the setSISMEMBER demo-set bxya
- returns 0 asbxya
member is not present in set
SINTER <key> [<key>...]
- returns the intersection, i.e. common members between all the sets stored at the provided keys. The time complexity of this command will beO(N*M)
whereN
is the cardinality of the smallest set andM
is the total number of sets to compare with.127.0.0.1:6379> SADD demo-set a b (integer) 0 127.0.0.1:6379> SCARD demo-set (integer) 2 127.0.0.1:6379> SISMEMBER demo-set x (integer) 0 127.0.0.1:6379> SISMEMBER demo-set a (integer) 1 127.0.0.1:6379> SADD demo-set1 a c d (integer) 0 127.0.0.1:6379> SINTER demo-set demo-set1 1) "a"
SUNION <key> [<key>...]
- - returns the union, i.e. all members in all the given sets stored at the provided keysSUNION demo-set demo-set1
will returnb, c, d, a
as the output
SDIFF <key> [<key>...]
- returns the members of the set resulting from the difference between the first set and all the successive sets.127.0.0.1:6379> SUNION demo-set demo-set1 1) "b" 2) "c" 3) "d" 4) "a" 127.0.0.1:6379> SDIFF demo-set demo-set1 1) "b"
SMEMBERS key
- returns all the members of the set stored at key127.0.0.1:6379> SMEMBERS demo-set 1) "b" 2) "a"
SSCAN <cursor> [MATCH pattern] [COUNT count]
- Similar toSCAN
andHSCAN
we saw in the above sections, butSSCAN
is used for scanning through sets, and is preferred overSMEMBERS
for performance reasons.SREM <key> <member>
- removes the specified member from the set127.0.0.1:6379> sscan demo-set 0 1) "0" 2) 1) "b" 2) "a" 127.0.0.1:6379> srem demo-set a (integer) 1 127.0.0.1:6379> sscan demo-set 0 1) "0" 2) 1) "b"
Set use cases:
Track unique website visitors based on IP, since sets have deduplication.
The union, intersection and difference make it ideal for use cases where we want to find the commonality or combination of sets between multiple domain data which might have the same members, like for example: to find out all the customers who purchased X as well as Y book. We can have two sets
purchase-order:book:X:customers
andpurchase-order:book:Y:customers
whose values are set of customer ids, and we can simply doSINTER
to find out users who bought both books.
Sorted Set
Sorted sets are similar to the sets we discussed above, with an additional property - a score is associated with each member in the set. This is a floating point number by which we can specify a sorting order of the members in a sorted set(like sort ascending by score or sort descending by score)
There can be two elements with the same score, in such cases, precedence will be given to a higher member key with a lexical value. example: a sorted set with key ab
will be given higher precedence over sorted set with key name bc
if both have the same score.
Sorted sets also support union and intersection commands similar to sets.
The sorted set has a similar list of commands as the set, with an additional score value.
Use case: Let's take a use case of players playing in a tennis tournament and earning some scores. We will add 4 players (player1, player2, player3 and player4) along with their total scores in the tournament
ZADD <key> <score> <member> [<score> <member>...]
- adds one more member to the sorted set along with its associated score.127.0.0.1:6379> zadd game:tennis NX 2 player1 (integer) 1 127.0.0.1:6379> zadd game:tennis NX 4 player2 (integer) 1 127.0.0.1:6379> zadd game:tennis NX 1 player3 (integer) 1 127.0.0.1:6379> zadd game:tennis NX 7 player4 (integer) 1
ZINCRBY <key> <increment> <member>
- increments the score of the member by the values provided in the argument127.0.0.1:6379> zincrby game:tennis 2 player3 "3"
ZRANGE <key> <min> <max> [LIMIT offset count] [WITHSCORES]
- retrieves the set members from specified min and max index. You can also pass an optional limit argument to limit the number of returned set members.
WITHSCORES
argument will also return the scores of the returned members127.0.0.1:6379> zrange game:tennis 0 -1 withscores 1) "player3" 2) "1" 3) "player1" 4) "2" 5) "player2" 6) "4" 7) "player4" 8) "7"
ZRANGEBYSCORE <key> <min> <max> [WITHSCORES] [LIMIT offset count]
- returns the members of the set in the order sorted ascending based on score. Do note here that the min and max are not zero-based indexes, instead, it starts with 1.This can be used to show the leaderboards of players, for example: to show the bottom 3 players(which have the lowest score)
ZRANGEBYSCORE game:tennis 1 4
127.0.0.1:6379> ZRANGEBYSCORE game:tennis 1 4 WITHSCORES 1) "player3" 2) "1" 3) "player1" 4) "2" 5) "player2" 6) "4"
ZSCORE <key> <member>
- returns the score of the specified memberZREVRANGEBYSCORE <key> <min> <max> [WITHSCORES] [LIMIT offset count]
- similar toZRANGEBYSCORE
mentioned above but this command will return based on descending order of the scoreThis can be used to show the tournament leaderboard, for example: to show the top 3 players(which has the highest score)
127.0.0.1:6379> ZREVRANGEBYSCORE game:tennis +inf -inf WITHSCORES LIMIT 0 3 1) "player4" 2) "7" 3) "player2" 4) "4" 5) "player1" 6) "2"
ZRANK <key> <member>
- returns the rank of the user based on ascending order of scores. Note that rank is zero-based, which means that the member with the lowest score has rank0
.ZREVRANK <key> <member>
- returns the rank of the user based on descending order of scores. The member with the highest score will have the lowest rank(i.e. rank 0)- This can be used to display the rank of individual players
ZREMRANGEBYRANK <key> <start> <stop>
- removes the members from the set with the rank range specified in the argument. For example: to retain only the top 3 players in the leaderboard and remove others, we can run -# Since the ZREMRANGEBYRANK will evaluate based on scores sorted in ascending order, we will want to retain only bottom 3 elements(which are actually top 3 players by rank, so we will remove from 1st element(index 0) till fourth last element(index -4), hence retaining the top 3 players) 127.0.0.1:6379> ZREMRANGEBYRANK game:tennis 0 -4 (integer) 1 127.0.0.1:6379> ZREVRANGEBYSCORE game:tennis +inf -inf WITHSCORES LIMIT 0 10 1) "player4" 2) "7" 3) "player2" 4) "4" 5) "player1" 6) "2"
ZUNION <numkeys> <key> [<key>..] [AGGREGATE SUM|MIN|MAX] [WITHSCORES]
This performs a union on total N(numkeys) keys specified in the argument and returns the result.
WITHSCORES
is an optional argument by which scores will also be passed in the result.Note: By default, if a key is present in two sets, the
ZUNION
will add the scores in both sets and return the new score. If we don't want to add up the score, we can also passAGGREGATE
function arguments likeMIN
orMAX
ZINTER <numkeys> <key> [<key>..] [AGGREGATE SUM|MIN|MAX] [WITHSCORES]
ZSCAN <cursor> [MATCH pattern] [COUNT count]
- Similar toHSCAN
andSSCAN
mentioned above, but ZSCAN is used to scan over sorted sets.
Sorted sets use cases:
Since sorted sets have a score associated with each member and an extensive set of commands to play on this score, it makes it ideal for use cases like -
Priority queues / task scheduling service
Game Leaderboards
Geo hashing
There are other Redis data structures as well like Bitmaps, Geospatial, and HyperLogLog which I will try to cover in some other article. The ones mentioned in this article are the most basic and commonly used Redis data types and data structures.
Few more facts about Redis π‘
Redis being an in-memory database leverages several efficient low-level data structures without worrying about how to persist them effectively.
Redis is single threaded hence it does not have to deal with locks and other synchronization mechanisms, making it simpler to use and think of for your system design.
Redis is known for its fast performance, although it is single-threaded. It is because Redis supports I/O multiplexing where the operating system allows a single thread to wait on multiple socket connections. Because of this, Redis still accepts a large number of requests concurrently and evaluates them one by one. This evaluation/execution phase is extremely fast because it is an in-memory data store. Applications using Redis are usually I/O heavy and I/O takes time whereas in-memory operations are extremely fast, so with I/O multiplexing, redis can support a large number of TCP connections while being extremely fast at executing the memory operations.
Redis also supports transactions and an optimistic locking mechanism. I will be making a separate article for the same. π
What to do after reading this article
Well, since now you know the basics of Redis data structures, you can build a small project using Java, Go, Ruby or any other language and use Redis as a data store.
Few projects ideas where you can use the above data structures -
Sports event
Product Catalog
Library management
Do comment down if you have more project ideas to work on.
If you found this article helpful, please comment and share this article π.
Let's connect π«. You can follow me on
You can also subscribe to my newsletter below to get an email notification on my latest posts. π»