Getting started with Redis …

Rahul Kapoor
9 min readAug 12, 2023

Introduction to Redis (all the jibber-jabber) => https://medium.com/swlh/the-amazing-redis-620a621f3b2

Moving to the interesting part now…

Will be covering redis setup, strings and lists in this article.

Setting up Redis

Installing Redis on Mac via terminal

brew install redis

To run the redis server on your local 127.0.0.1 with a default port of 6379

redis-server

execute now redis-cli on new terminal: To run a Redis command and return a standard output at the terminal.

redis-cli

Redis Strings

Strings are the simplest type of value you can associate with a Redis key.

Let’s start with GET key and SET key value commands

127.0.0.1:6379> SET name testuser
OK
127.0.0.1:6379> get name
"testuser"

To set and get multiple key-value pairs we use MSET and MGET followed by all the key-value pairs you want to set and get respectively

127.0.0.1:6379> MSET name "test" age "26" email "xyz@abc.com"
OK
127.0.0.1:6379> MGET name age
1) "test"
2) "26"
127.0.0.1:6379> MGET name age email
1) "test"
2) "26"
3) "xyz@abc.com"

So all these operation we can see are done in O(1) time complexity. However if we do say SETRANGE or GETRANGE it is done in O(N) where N is the length of the string

GETRANGE key start end

127.0.0.1:6379> SET name testuser
OK
127.0.0.1:6379> GETRANGE name 2 4
"stu"

We can also set a counter value and then keep on incrementing it based on our logic

The INCR command parses the string value as an integer, increments it by one, and finally sets the obtained value as the new value

127.0.0.1:6379> SET counter 1
OK
127.0.0.1:6379> get counter
"1"
127.0.0.1:6379> incr counter
(integer) 2
127.0.0.1:6379> incr counter
(integer) 3
127.0.0.1:6379> incr counter
(integer) 4
127.0.0.1:6379> get counter
"4"

We can supply further arguments as well while setting the key value in redis strings, one such is use of nx flag

What nx does is if the key already exists, then it will not update the value of the key when provided with SET command

127.0.0.1:6379> set data testuser
OK
127.0.0.1:6379> get data
"testuser"
127.0.0.1:6379> set data abcd nx
(nil)
127.0.0.1:6379> get data
"testuser"

We can also set the expiry to a key in Redis using expire key seconds

127.0.0.1:6379> SET data test
OK
127.0.0.1:6379> GET data
"test"
127.0.0.1:6379> expire data 5
(integer) 1
127.0.0.1:6379> GET data
"test"

After 5 seconds we get nil as the key has been expired:

127.0.0.1:6379> GET data
(nil)

To get all the keys we have added till now:

KEYS pattern

127.0.0.1:6379> KEYS *
1) "test"
2) "namex"
3) "email"
4) "name"
5) "visitors"
6) "counter"
7) "age"

get all keys we have added till now that have anything in the start but ends at e:

127.0.0.1:6379> KEYS *e
1) "name"
2) "age"

We can also delete all the keys and their data using FLUSHALL ASYNC|SYNC command:

127.0.0.1:6379> FLUSHALL SYNC
OK
127.0.0.1:6379> KEYS *
(empty array)

Redis Lists

Redis lists are linked lists of string values.

Redis lists are implemented via Linked Lists. This means that even if you have millions of elements inside a list, the operation of adding a new element in the head or in the tail of the list is performed in constant time. The speed of adding a new element with the LPUSH command to the head of a list with ten elements is the same as adding an element to the head of list with 10 million elements.

What’s the downside?
Accessing an element by index is very fast in lists implemented with an Array (constant time indexed access) and not so fast in lists implemented by linked lists (where the operation requires an amount of work proportional to the index of the accessed element).

The LPUSH command adds a new element into a list, on the left (at the head), while the RPUSH command adds a new element into a list, on the right (at the tail). Finally the LRANGE command extracts ranges of elements from lists:

127.0.0.1:6379> LPUSH food burger
(integer) 1
127.0.0.1:6379> LPUSH food fries
(integer) 2
127.0.0.1:6379> LPUSH food pizza
(integer) 3
127.0.0.1:6379> LPUSH food taco
(integer) 4
127.0.0.1:6379> LRANGE food 0 -1
1) "taco"
2) "pizza"
3) "fries"
4) "burger"

As we can see everything added to the HEAD of the list as we used LPUSH command

Let’s try to use RPUSH and see if we can get things added to the TAIL of the list

127.0.0.1:6379> RPUSH food burrito
(integer) 5
127.0.0.1:6379> LRANGE food 0 -1
1) "taco"
2) "pizza"
3) "fries"
4) "burger"
5) "burrito"

To get all the elements present in our Redis list:

127.0.0.1:6379> LLEN food
(integer) 5

Similar to LPUSH and RPUSH we have LPOP and RPOP

LPOP will take out the data from the HEAD side and RPOP will take out the data from the TAIL side

127.0.0.1:6379> LPOP food
"taco"
127.0.0.1:6379> RPOP food
"burrito"

I can also pop out multiple elements from either side using LPOP or RPOP key count

127.0.0.1:6379> LPOP food 2
1) "pizza"
2) "fries"

Pushing or popping elements from head or tail of the list is done in O(1) time complexity. But if we want to add/update something at a particular index its done in O(n) time complexity.

127.0.0.1:6379> LRANGE food 0 -1
1) "ramen"
2) "fries"
3) "taco"
4) "burger"
127.0.0.1:6379> LSET food 2 burrito
OK
127.0.0.1:6379> LRANGE food 0 -1
1) "ramen"
2) "fries"
3) "burrito"
4) "burger"

LINSERT key AFTER|BEFORE pivot element which will insert our new elements after or before (as chosen) to a pivot element in the list

127.0.0.1:6379> LINSERT food AFTER burrito taco
(integer) 5
127.0.0.1:6379> LRANGE food 0 -1
1) "ramen"
2) "fries"
3) "burrito"
4) "taco"
5) "burger"

we can also fetch the elements from the list at a particular index, rather than just popping it out and getting it from the head or tail side.

127.0.0.1:6379> LINDEX food 1
"fries"
127.0.0.1:6379> LINDEX food 0
"ramen"

With the LPUSH command if even our list doesn't exist it will create a new list and push the element into it.

127.0.0.1:6379> LRANGE movies 0 -1
(empty array)
127.0.0.1:6379> LPUSH movies inception
(integer) 1
127.0.0.1:6379> LRANGE movies 0 -1
1) "inception"

To avoid creating a new list if there is no such list with the supplied name we can use LPUSHX

Here LRANGE still returns an empty array as list with name series doesn’t exist and we used LPUSHX instead of LPUSH

127.0.0.1:6379> LPUSHX series "squid game"
(integer) 0
127.0.0.1:6379> LRANGE series 0 -1
(empty array)

We can also sort our list into a lexicographic ordering using SORT name ALPHA

Currently, it is as below:

127.0.0.1:6379> LRANGE food 0 -1
1) "ramen"
2) "fries"
3) "burrito"
4) "taco"
5) "burger"

After the execution of the command, everything gets sorted to a lexicographic ordering as it is in a dictionary.

127.0.0.1:6379> SORT food ALPHA
1) "burger"
2) "burrito"
3) "fries"
4) "ramen"
5) "taco"

Redis Sets

A Redis set is an unordered collection of unique strings (members).

To add a new member to a Set we use SADD key member

Members added to the set will be unique, as we can see below on adding taco to food set again it returned 0

127.0.0.1:6379> SADD food taco
(integer) 1
127.0.0.1:6379> SADD food taco
(integer) 0
127.0.0.1:6379> SADD food burger
(integer) 1
127.0.0.1:6379> SADD food fries
(integer) 1

Now to validate if the member exists or not in the set we can SISMEMBER command and to get the size we use SCARD command

127.0.0.1:6379> SISMEMBER food fries
(integer) 1
127.0.0.1:6379> SISMEMBER food taco
(integer) 1
127.0.0.1:6379> SCARD food
(integer) 3

To list all the members inside the set:

127.0.0.1:6379> SMEMBERS food
1) "taco"
2) "burger"
3) "fries"

To find the intersection of 2 sets or common members to both the sets we can use SINTER command:

127.0.0.1:6379> SADD backend javascript golang c# c++
(integer) 4
127.0.0.1:6379> SMEMBERS backend
1) "golang"
2) "javascript"
3) "c++"
4) "c#"
127.0.0.1:6379> SADD frontend javascript react angular
(integer) 3
127.0.0.1:6379> SMEMBERS frontend
1) "angular"
2) "react"
3) "javascript"
127.0.0.1:6379> SINTER backend frontend
1) "javascript"

To find the differences from both the sets we can use SDIFF

127.0.0.1:6379> SDIFF backend frontend
1) "golang"
2) "c++"
3) "c#"

Similarly to take the union of both the sets we have SUNION

127.0.0.1:6379> SUNION backend frontend
1) "c++"
2) "c#"
3) "angular"
4) "javascript"
5) "golang"
6) "react"

Most set operations, including adding, removing, and checking whether an item is a set member, are O(1). This means that they’re highly efficient. However, for large sets with hundreds of thousands of members or more, you should exercise caution when running the SMEMBERS command. This command is O(n) and returns the entire set in a single response.

Redis Sorted Sets

A Redis sorted set is a collection of unique strings (members) ordered by an associated score. When more than one string has the same score, the strings are ordered lexicographically. Some use cases for sorted sets include:

  • Leaderboards. For example, you can use sorted sets to easily maintain ordered lists of the highest scores in a massive online game.

Moreover, elements in a sorted set 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 B and A are two elements with a different score, then A > B if A.score is > B.score.
  • If B and A have exactly the same score, then A > B if the A string is lexicographically greater than the B string. B and A strings can’t be equal since sorted sets only have unique elements.

To add members with their respective score to a sorted set we can use:
ZADD key score member

127.0.0.1:6379> ZADD food 3 fries
(integer) 1
127.0.0.1:6379> ZADD food 2 burger
(integer) 1
127.0.0.1:6379> ZADD food 1 taco
(integer) 1
127.0.0.1:6379> ZADD food 1 pizza
(integer) 1
127.0.0.1:6379> ZRANGE food 0 -1
1) "pizza"
2) "taco"
3) "burger"
4) "fries"

Now as the taco and pizza had the same scoring but pizza comes first than taco in lexicographic ordering so when we did ZRANGE we got pizza first followed by taco.

ZADD is similar to SADD its just an additional scoring is added to the member

similarly we can have reverse ordering as well using ZREVRANGE

127.0.0.1:6379> ZREVRANGE food 0 -1
1) "fries"
2) "burger"
3) "taco"
4) "pizza"

We can also get the members of our sorted-set with the scores supplied to them by passing an additional withscores filter to it:

127.0.0.1:6379> zrange food 0 -1 withscores
1) "pizza"
2) "1"
3) "taco"
4) "1"
5) "burger"
6) "2"
7) "fries"
8) "3"

We can also retrieve our members based on the score range supplied, say we want to get the no. 1 foods declared in our sorted-set

127.0.0.1:6379> zrangebyscore food 0 1
1) "pizza"
2) "taco"

we can also remove members based on score range as well

127.0.0.1:6379> zremrangebyscore food -INF 1
(integer) 2
127.0.0.1:6379> zrangebyscore food 0 INF withscores
1) "burger"
2) "2"
3) "fries"
4) "3"

Let’s take an example of leaderboard with users and their scores

127.0.0.1:6379> zadd leaderboard 50 user:1
(integer) 1
127.0.0.1:6379> zadd leaderboard 101 user:2
(integer) 1
127.0.0.1:6379> zadd leaderboard 40 user:3
(integer) 1
127.0.0.1:6379> zadd leaderboard 11 user:4
(integer) 1
127.0.0.1:6379> zadd leaderboard 23 user:2
(integer) 0
127.0.0.1:6379> zrange leaderboard 0 -1
1) "user:4"
2) "user:2"
3) "user:3"
4) "user:1"
127.0.0.1:6379> zrange leaderboard 0 -1 withscores
1) "user:4"
2) "11"
3) "user:2"
4) "23"
5) "user:3"
6) "40"
7) "user:1"
8) "50"
127.0.0.1:6379> zrangebyscore leaderboard 0 25
1) "user:4"
2) "user:2"

To increment score of a member we can use ZINCRBY command. Here we are incrementing the score of user:4 by 5 to make it 16 from 11.

127.0.0.1:6379> zrange leaderboard 0 -1 withscores
1) "user:4"
2) "11"
3) "user:2"
4) "23"
5) "user:3"
6) "40"
7) "user:1"
8) "50"
127.0.0.1:6379> zincrby leaderboard 5 user:4
"16"
127.0.0.1:6379> zrange leaderboard 0 -1 withscores
1) "user:4"
2) "16"
3) "user:2"
4) "23"
5) "user:3"
6) "40"
7) "user:1"
8) "50"

Redis Hashes

Redis hashes are record types structured as collections of field-value pairs. You can use hashes to represent basic objects and to store groupings of counters, among other things.

Here we use HSET, HGET and HGETALL for setting field-value pairs, getting specific field and value and getting all the field-value pairs:

127.0.0.1:6379> hset users name testuser1 age 26 email abc@abc.com
(integer) 3
127.0.0.1:6379> hget users name
"testuser1"
127.0.0.1:6379> hgetall users
1) "name"
2) "testuser1"
3) "age"
4) "26"
5) "email"
6) "abc@abc.com"

As we can see hgetall returned both fields and values, to just get all fields or values we can use hkeys and hvals command

127.0.0.1:6379> hkeys users
1) "name"
2) "age"
3) "email"
127.0.0.1:6379> hvals users
1) "testuser1"
2) "26"
3) "abc@abc.com"

to increment a particular value of a field we can use HINCRBY command. Currently the age was 26 and when we executed the above command it returned 31

127.0.0.1:6379> hincrby users age 5
(integer) 31
127.0.0.1:6379> hgetall users
1) "name"
2) "testuser1"
3) "age"
4) "31"
5) "email"
6) "abc@abc.com"

To remove field-value pairs from our hash we can use HDEL command

127.0.0.1:6379> hdel users email
(integer) 1
127.0.0.1:6379> hgetall users
1) "name"
2) "testuser1"
3) "age"
4) "31"

Pending to cover topics: transactions and redis pub-sub

--

--