The most loved in-memory store: an introduction to Redis
🔹 Hi! This post aims to be a starting point for developers who are approaching the most loved in-memory store: Redis
An introduction to Redis, created by Salvatore Sanfilippo in 2009. Redis is a REmote DIctionary Server written in C, properly an in-memory data structure store, open source, which offers a variety of data structures to solve a large variety of problems (caching, queueing, event processing, streaming).
This post will focus on:
- How to get started (setup Redis quickly in a local environment)
- Data types (I will cover only Strings, Lists, Sets, Sorted Sets, Hashes)
- Basic usage of RedisInsight browser and CLI
How to get started
I have chosen Docker to set up a local environment because it is a quick way to get started.
First, create a folder on your desktop or in your desired location.
I created a folder named redis-intro.
Inside the new folder create a file .env and put the following: REDIS_PASSWORD=your_pass_1
(This is the environment variable for Redis password. It is not mandatory, but I strongly recommend it as a good practice).
Then, create file docker-compose.yml
version: '3.8'
services:
redis-store:
image: 'redis:latest'
container_name: redis-store
ports:
- 6379:6379
command: ["redis-server", "--requirepass", "$REDIS_PASSWORD"]
volumes:
- ./data:/data
redis-insights:
image: 'redislabs/redisinsight:latest'
ports:
- 8001:8001
depends_on:
- redis-store
In brief, redis-store is the Redis store and redis-insights is the official RedisInsight GUI to explore and use Redis.
📘 Note: at the time I wrote this post the official RedisInsight docker image linked to the “latest” tag is still at version 1.13.0 although version 2.0 is available since November 2021
To start the containers run: docker-compose up -d
After a couple of minutes (only the first time) your containers will be running. Check out your Docker dashboard or launch this command from your terminal: docker ps to ensure all containers are up and running.
To connect to RedisInsight open an internet browser and go to http://localhost:8001
Now, it’s time to connect RedisInsight to the redis instance. Click on “I already have a database”
Then, “Connect to a Redis Database”.
Fill the following fields:
- Host: redis-store
- Port: 6379
- Name: redis-store
- Username: (leave empty)
- Password: your_pass_1
Finally, click to “Add Redis Database“. Now, click on the redis-store card to start using the instance.
I mainly focus this post on Browser and CLI usage.
Data Types
In this post I will focus only on Strings, Lists, Sets, Sorted Sets and Hashes to provide a basic understanding of Redis. You can explore other data types in the Redis documentation.
Strings
String is the basic data type offered by Redis. A value for a String data type is stored as a sequence of bytes (binary-safe). A common use case for this data type is caching or counters.
SET is the command used to create a new key or eventually change the value for an existing key.
Let’s create a new key of type String from CLI: SET user:1002 marco
How to get the value? GET user:1002
You could also save a serialized JSON as value: SET user:1002 “\”{ ‘id’: 1002, ‘name’: ‘Giovanni Marco’ }\””
A recommended naming convention for a Redis key is to use a colon to describe the attributes of the object stored in the best possible way: <OBJECT_NAME>:<OBJECT_ID> and so on.
For example: <OBJECT_NAME>:<OBJECT_ID>:<ATTRIBUTE>…
🔹 In my example I used user:1002 to describe an entity user with id 1002. In this way it is easier to retrieve the key associated with a real user.
If you want to explore keys from the GUI go to Browser section:
By default a key never expires. Technically it has a TTL (time to live) of -1, but it’s possible to set a TTL. For example: SET user:1002:cookieaccepted true EX 60
Where EX is the time in seconds (alternative, PX for time in milliseconds).
It’s also possible to create a counter. From the CLI: INCR user:1002:failedlogin
The command above will increment the actual store value by 1.
To increment the key you should use INCRBY: INCRBY user:1002:failedlogin 1
The command above will increment the actual stored value by 1.
A final note on String values: there is a limit to a max value of 512MB.
Lists
Redis offers the choice to implement a List as a stack or as a queue of values. The commands to implement a Queue (First-in, First-out) are RPUSH to insert and LPOP to remove an element .
RPUSH command, which stands for right push, allows to make a queue where the key is the list name. In the example retrogames:1986 is the key of the list and the following string values are the elements: RPUSH retrogames:1986 “Wonder Boy” “Arkanoid” “Dragon Quest” “Might and Magic”
To print all the elements of a list use LRANGE: LRANGE retrogames:1986 0 -1
It prints all the values from retrogames:1986 key from index 0 to -1 (to the end).
📘 Note for production: Avoid querying the whole range if you have lots of data. If you don’t know what to expect try to use LLEN to get the size of the list. For large lists you should use Streams.
To remove an element from the queue: LPOP retrogames:1986
A quick look from RedisInsight:
If you want to implement a list as Stack, you should use LPUSH (left push) to insert and RPOP (right pop) to remove elements.
Sets
Another common data type you can implement in Redis is the Set of unordered and unique elements. In addition, you can perform intersection, union, difference operations between sets.
📘 Note: RedisInsight CLI is very useful to discover information on how to use a command properly. Here an example if you have never used SADD to add elements to a set. All you have to do is to write che command, then a how-to popup will appears. On the right you can read more detailed information.
Now from CLI, simply for demonstration, you can create two sets related to user’s favorite bands: SADD user:1003:favorites “Iron Maiden” “Metallica” “Dream Theater” “Dokken”
SADD user:1004:favorites “Skid Row” “Iron Maiden” “Pantera” “Ratt”
To check if an element is a member of a set, you can use SISMEMBER. It returns:
- 1 if the element belongs to the set
- 0 if not
In my example:
SISMEMBER user:1004:favorites “Pantera”
(integer) 1
To check the cardinality:
SCARD user:1004:favorites
(integer) 4
Union and intersection operations:
SUNION user:1003:favorites user:1004:favorites
1) “Skid Row”
2) “Metallica”
3) “Dokken”
4) “Iron Maiden”
5) “Pantera”
6) “Ratt”
7) “Dream Theater”
SINTER user:1003:favorites user:1004:favorites
1) “Iron Maiden”
Sometimes it could be useful to store the result of an intersection. The command SINTERSTORE creates a new key, in the example favorites, with the result of the intersection between the keys user:1003:favorites and user:1004:favorites
SINTERSTORE favorites user:1003:favorites user:1004:favorites
To store a result of a union, you can use SUNIONSTORE in the same way.
Sorted Sets
Sorted Sets are really useful when you have to collect unique elements by score. A common use case is to track top players of a game. ZADD is the command used to add or update one or more members of the set. The syntax is quite simple:
ZADD <key> <score member> … <score member>
For example I wanted to track the top players’ score in October 2022, the key would be topplayers:202210
ZADD topplayers:202210 40 user:1001 120 user:1004 90 user:1003 50 user:1005
If a member already exists the score will be updated
ZADD topplayers:202210 60 user:1001
(integer) 0
The output will be 0 if the member exists and its values will be updated or 1 if member will be added to the set.
To print the list of the top 3 players you should use ZRANGE:
ZRANGE topplayers:202210 0 2 REV WITHSCORES
1) “user:1004”
2) “120”
3) “user:1003”
4) “90”
5) “user:1001”
6) “60”
Note that values 0 and 2 are respectively the starting and the ending index.
⚠️ CAUTION: in production use with caution ZRANGE with start of 0 and end of -1 index because the complexity is O(log(n) + m) where n is the number of members and m is the results returned. When the sorted set has lots of members you will probably raise performance issues.
Players ranking:
- ZRANK to consider ranking in descending order
ZRANK topplayers:202210 user:1005
(integer) 0
user:1005 has the lowest score (50)
- ZREVRANK to find ranking in ascending order
ZREVRANK topplayers:202210 user:1004
(integer) 0
user:1004 has the highest score (120)
Hashes
Redis hash is data type structured as collection of key-value pairs. A common scenario would be to store detailed user info. For example, I will store a hash of a user to store the refresh token which will expire after some time.
To create a new hash:
HSET user:1010 email hello@test.com refreshToken 3b3ebf636ade1b707433315158116d3a66044bcf
Then, set expiration of the hash after 3600 seconds with EXPIRE:
EXPIRE user:1010 3600
Check the hash in the Browser:
If you want to check the time left to expiration:
TTL user:1010
If you want to query the entire hash:
HGETALL user:1010
Alternatively, it is possible to get one or more keys of an hash
HMGET user:1010 email
1) “hello@test.com”
Finally, you can also set only a subset of keys using HMSET
HMSET user:1010 name “Giovanni Marco”
Here the final result of an HGETALL user:1010
HGETALL user:1010
1) “email”
2) “hello@test.com”
3) “refreshToken”
4) “3b3ebf636ade1b707433315158116d3a66044bcf”
5) “name”
6) “Giovanni Marco”
Why choose Redis?
- Fast
- Easy to learn and use. Data structures are very familiar in programming. Every data structure has its own subset of commands at database level. A key identifies a particular data type, such as Strings, Bitmaps, Bit field, Hashed, Lists, Sets, Sorted Sets.
- Easy to integrate in many languages
- Could be turned into a multi-model database with modules (RedisJSON, RediSearch, RedisGraph, RedisGears, RedisTimeSeries, Rebloom)
- Built-in replication
- Automatic partitioning with Redis Cluster
- Very large and active community