Basj
Basj

Reputation: 46493

Multi-criteria query with redis

Let's create 5 items with :

HMSET id1 x 0 y 1 z 3
HMSET id2 x 2 y 3 z 5
HMSET id3 x 8 y 4 z 3
HMSET id4 x 9 y 8 z 6
HMSET id5 x 1 y 3 z 5

How to find the elements such that 0 < x < 2, 2 < y < 5 and 4 < z < 8 ?

More generally, how to perform a multiple-criteria search with redis ?


Sidenote: In this nice tutorial, I read the following notice, is that true?

Redis Querying

In Redis, data can only be queried by its key. Even if we use a hash, we can't say get me the keys wherever the field ... is equal to ...

Upvotes: 4

Views: 2504

Answers (1)

Itamar Haber
Itamar Haber

Reputation: 49962

IMPORTANT UPDATE: there's more relevant information to be found at Secondary indexing with Redis

The tutorial is right to an extent - Redis does not offer functionality for searching values out of the box... but you can use Redis' data structures to maintain indices for just about any value.

Continuing your example, since you are looking to do range queries, you'll want to use sorted sets to store index data. For each indexed hash field, maintain a separate sorted set where the members are the key names and the score is the field value. Therefore, for your data, do the following:

ZADD _x 0 id1 2 id2 8 id3 9 id4 1 id5
ZADD _y 1 id1 3 id2 4 id3 8 id4 3 id5
ZADD _z 3 id1 5 id2 3 id3 6 id4 5 id5

To perform the actual query, use ZRANGEBYSCORE on each sorted set, and intersect the results to apply the logical and operator. This could be executed in your application's code or via the use of a server-side Lua script.

EDIT: here's a Redis Lua script that performs this type of query. I chose to use Lua both because my Node.js skills are sub-par (:)) and also because of efficiency - this approach will save round trips of the data to the client.:

local tmps = {}

for i = 1, #KEYS / 2, 1 do
  local range
  range = redis.call("ZRANGEBYSCORE", KEYS[i], "(" .. ARGV[i*2-1], "(" .. ARGV[i*2])

  local tmp = KEYS[#KEYS / 2 + i]
  redis.call("DEL", tmp)
  for k, v in ipairs(range) do
    redis.call("SADD", tmp, v)
  end

  tmps[#tmps + 1] = tmp               
end

return redis.call("SINTER", unpack(tmps))

Invoke this as follows:

redis-cli --eval query.lua _x _y _z __x __y __z , 0 2 2 5 4 8

Note that the script accepts 6 keys - the 3 indices as well as 3 temporary variable names (this is required per the spec for cluster compatibility).

Answer to comment #1: yes - Redis' sorted set scores can be floats and there shouldn't be any issue with thousands of values

Answer to comment #2: without benchmarking this I can't be certain but I have good reason to believe this will be at least as performant as any SQL solution.

Lastly, you may find this presentation by yours truely helpful in this context: http://www.slideshare.net/itamarhaber/20140922-redis-tlv-redis-indices

Upvotes: 4

Related Questions