Redis Sort with Node

09.20.2021

Intro

Sorting is a common programming task, and you may be tempted to pull data from Redis and sort it client side. However, using Redis's built in sort function will be more performant and general and offers a robust functionality. In this article, we will learn how to sort with Redis using Python.

Setting up Redis

For setting up Redis, I would recommend using a service for you in prod. Azure for example, has a great redis service that scales easily. However, you will want to learn redis and eventually how to scale it yourself. This will help with debugging cloud services or eventually, saving money and not using them.

We will start our intro to redis via using docker compose. Create a docker-compose.yml file and add the following.

version: "3.2"
services:
  redis:
    image: "redis:alpine"
    command: redis-server
    ports:
      - "6379:6379"
    volumes:
      - $PWD/redis-data:/var/lib/redis
      - $PWD/redis.conf:/usr/local/etc/redis/redis.conf
    environment:
      - REDIS_REPLICATION_MODE=master

Ensure you have docker installed and run

docker-compose up

Installing Redis Modules

There are two modules I see often used in nodejs. I will tend towards ioredis as it has built in support for promises and many other features in redis.

npm install ioredis

Creating the Subscriber

We will need two methods for subscribing to a channel. The first is the subscribe method where we can pass one or more channels to listen to. Next, we will use the on method with the message parameter to list for message events coming to our Redis connection.

Writing the Code

Let's open up a new file, index.js and add a a few keys. The sorting method works with list, sets, and sorted sets.

We start by connecting to our redis instance.

const Redis = require("ioredis")
const redis = new Redis()

Redis can sort a list, set or sorted set at a particular key. Let's start by creating a simple list with numbers. The default sort is by ascending by numbers.

async function main() {
    await redis.lpush("my-list", 1)
    await redis.lpush("my-list", 2)
    await redis.lpush("my-list", 3)

    res = await redis.lrange("my-list", 0, -1)
    console.log(res) // [ '3', '2', '1' ]

    res = await redis.sort("my-list")
    console.log(res) // [ '1', '2', '3' ]
}

(async () => {
    await main()
})()

We can also sort descending by using the desc property.

res = await redis.sort("my-list", "desc")
console.log(res) // [ '3', '2', '1' ]

The next type of sorting is alpha sorting. Let's create a list with letters. We can sort by alpha using the alpha parameter. You can also use the desc parameter with alpha.

await redis.lpush("my-list", "b")
await redis.lpush("my-list", "c")
await redis.lpush("my-list", "a")

res = await redis.lrange("my-list", 0, -1)
console.log(res) // [ 'a', 'c', 'b' ]

res = await redis.sort("my-list", "alpha")
console.log(res) // [ 'a', 'b', 'c' ]


res = await redis.sort("my-list", "alpha", "desc")
console.log(res) // [ 'c', 'b', 'a' ]

Another feature of the sort is the ability to limit and offset using the limit parameter respectively. This how you can paginate through large lists or sets. This can also be combined with other properties.

res = await redis.sort("my-list", "LIMIT", 1, 2, "alpha", "desc")
console.log(res) // ['b', 'a']

Getting to a more advanced sorting, we can use scores set in other keys to sort our list. For example, let's say we have the ids of some restaurants in a DB and the avg ratings sort in other keys. We can use the keys to sort by matching. Here is an example.

// Creating the ratings
await redis.set("restaurant_rating_1", 10)
await redis.set("restaurant_rating_2", 5)

// Add the ids to a list
await redis.lpush('res_ids', 1)
await redis.lpush('res_ids', 2)

// Sort using the ratings
res = await redis.sort('res_ids', "by", "restaurant_rating_*", "desc")
console.log(res) // [ '1', '2' ]

Continuing with this example, let's say we have some extra keys to store the restaurant locations. We can return those values instead of the ids in a list using the get property.

// Creating the ratings
await redis.set("restaurant_rating_1", 10)
await redis.set("restaurant_rating_2", 5)

await redis.set("restaurant_location_1", "colorado")
await redis.set("restaurant_location_2", "texas")


// Add the ids to a list
await redis.lpush('res_ids', 1)
await redis.lpush('res_ids', 2)

// Sort using the ratings
res = await redis.sort('res_ids', "by", "restaurant_rating_*", "get", "restaurant_location_*", "desc")
console.log(res) // ['colorado', 'texas']

Finally, let's say that we stored hashmap data of the restaurants instead of all the extra keys above. We can sort by properties with the following matching methods.

// Creating the ratings
await redis.hset("restaurant_1", "rating", 10)
await redis.hset("restaurant_1", "location", "colorado")

await redis.hset("restaurant_2", "rating", 5)
await redis.hset("restaurant_2", "location", "texas")


//// Add the ids to a list
await redis.lpush('res_ids', 1)
await redis.lpush('res_ids', 2)

//// Sort using the ratings
res = await redis.sort('res_ids', "by", "restaurant_*->rating", "get", "restaurant_*->location", "desc")
console.log(res) // ['colorado', 'texas']