Multi-parameter match-finder with Redis - redis

I need to create an match-finder system for some data set, as follows:
There is a set of objects, each identified by a string ObjectID.
Each object has exactly N properties Pi. Each property value is a string.
Database example for N = 3 (in real life N = 8).
ObjectID: P1 P2 P3
--------------------------------
APPLE: RED ROUND FRUIT
ORANGE: ORANGE ROUND FRUIT
CARROT: RED LONG VEGETABLE
The system must return sets of ObjectIDs, matching given query on object properties. In query user must specify all property values. Alternatively, for some or all properties in query user may specify a "wildcard" *, meaning that any property value would match criteria.
Example queries:
P1 P2 P3 => Result
------------------------------------
* ROUND FRUIT => APPLE, ORANGE
RED LONG VEGETABLE => CARROT
RED * * => CARROT, APPLE
All of this is trivially done with SQL.
The question is: is there a neat way to do that with Redis?
Note that I'm interested in Redis-based solutions specifically, for self-education purposes; other DBs are offtopic for this particular question.
Update: Trivial solution with explicit ObjectID lists for each Pi and application-side filtering does not look neat enough to me :-)

What you are trying to do here is an inverted index.
For each column, have it map to a "set". Then, you can intersect the sets to get the result.
So, APPLE: RED ROUND FRUIT would map to the following inserts:
SADD p1:RED APPLE
SADD p2:ROUND APPLE
SADD p3:FRUIT APPLE
Then, let's say I want to query for * ROUND FRUIT, I would do:
SINTER p2:ROUND p3:FRUIT
This command is taking the intersection of the items in the p2:ROUND set and the p3:FRUIT set. This will return all the items that are ROUND and FRUIT, not caring what p1 is.
Some other examples:
SMEMBERS p1:GREEN
SINTER p1:RED p2:ROUND p3:FRUIT
SUNION p1:RED p1:GREEN
My above answer is going to use some computation power because the intersection operation is O(N*M). Here is a way of doing it that is more memory intensive, but will have faster retrieval because it effectively precomputes the indexes.
For each combination of properties, make a key that stores a set:
So, APPLE: RED ROUND FRUIT would map to the following inserts:
SADD RED:ROUND:FRUIT APPLE
SADD :ROUND:FRUIT APPLE
SADD RED::FRUIT APPLE
SADD RED:ROUND: APPLE
SADD RED:: APPLE
SADD :ROUND: APPLE
SADD ::FRUIT APPLE
SADD ::: APPLE
Then, to query, you simply access the respective key. For example, * ROUND FRUIT would simply be
SMEMBERS :ROUND:FRUIT
Obviously, this doesn't scale well at all in terms of memory when you have many dimensions, but it will be extremely snappy to retrieve results.

Related

Can I define a custom compare function for redis zset?

Background:
I'm now using php+redis as my backend to store a rank.
And zset seems to be a good solution to handle this.
However the rank contains multiple scores, if the first score equals, I need to compare the second score to decide the ordering. There are 3 scores in total.
I thought there will be an interface that I can set a custom compare function for a specific zset so that I can do the sort job inside it but I failed to find it. Besides, I'd like the rank to be sorted when it's added. if I need to sort again everytime there is a request to get the rank, then this is wasteful I think.
expect result:
zadd myset 1000_100_3000 matchId1
zadd myset 1000_2500_250 matchId2
zadd myset 1000_2500_200 matchId3
zrange myset 0 -1 returns:
matchId2
matchId3
matchId1
something like this
Short answer: no, you can't do that
However, you can often compose multiple keys in such a way to make them sortable. In your case, an obvious candidate would be to determine the max range of the three pieces, and just compose them all into a single big number. For example, 1000_100_3000 could perhaps be the number 100001003000 (4 decimal digits per chunk), which can be trivially compared or decomposed. You might also want to think in terms of bits rather than digits, though. For example, maybe allow 20 bits per segment, and use shift/mask bit operations to compose/decompose (i.e. (1000 << 40) | (100 << 20) | (3000))

redis how to combine multiple commands in 1 query

How to Query
red ferrari with limited edition
with topSpeed between 200 To 210 And
Price between 190 to 205
DATA
HMSET cars:1 make ferrari Price 199 limited yes color red topSpeed 202
HMSET cars:2 make porsche Price 555 limited no color yellow topSpeed 500
SADD make:ferrari 1
SADD color:red 1
SADD limited:yes 1
ZADD Price 199 1
ZADD topSpeed 202 1
SADD make:porsche 2
SADD color:yellow 2
SADD limited:no 2
ZADD Price 555 2
ZADD topSpeed 500 2
I Tried & Don't know how to add multiple range for price and topSpeed both?
multi.ZINTERSTORE('tempTom',4,
'color:red',
'make:ferrari',
'limited:yes',
'topSpeed'
);
multi.ZRANGEBYSCORE('tempTom' , 202 ,205) //range for topSpeed
//so how to add range for Price also ?
Output
[1,[]]
What am i doing wrong , how to query with multiple commands 1 after another ?
AFAIK - This is pretty much impossible to do with 1 query in Redis. You are trying to use a key-vale store as a relational database. There is a way to do it in Redis but with 2 queries. However, you can wrap it as a single transaction in MULTI / EXEC and thus effectively making it 1 query.
For example:
Create 2 more sets, one for topSpeed and another for price. Then just perform a SINTER between those two (first query). Then use that result to query your car (second query).
Explanation:
# Inserting cars top speed
ZADD car:top-speed:210 "ferrari"
ZADD car:top-speed:300 "porsche"
# Inserting cars price (e.g. both cars have the same price)
ZADD car:price:190 "ferrari" "porsche"
# Using SINTER to get all cars with top speed of 210 and a price of 190
SINTER car:top-speed:210 car:price:300 // output "ferrari"
Use the output to query your car set and get all the other details (Don't forget the MULTI / EXEC).
You can add more 'filters' by simply adding more sets and performing the intersection on all of them. You would however, have to populate all these sets whenever you add a new car. But this is normal in Redis and does not suffer big performance issues. You can check here for another similar example if my explanation is not clear.
Hope this helps
Perhaps EVAL is what you are looking for? (perhaps not)..
It uses LUA scripts to execute a batch of commands in one call (and optionally return the result).
It is KIND OF like a stored procedure for REDIS. The uploaded LUA gets cached so you don't need to load it over and over.
The examples I see aren't EXACTLY what you are looking for but demonstrate multi-part queries. You need to read down into them to see the meat before you give up on EVAL (I almost did).
Object Queries with Redis
A Speed Guide To Redis Lua Scripting
EVAL (from the redis docs)
The only reason I know about this is because a coworker here insists on using redis as a relational database. It is not unlike using a hammer as a screwdriver.

How to get subset of SMEMBERS result? Or should I use SortedSet with the same value for both score & member?

I am new to Redis. For example, if I have the following schema:
INCR id:product
SET product:<id:product> value
SADD color:red <id:product>
(Aside: I am not sure how to express a variable in Redis. I will just use <id:product> as the primary key value. In production, I will use golang client for this job)
To query products which have red color, I can do:
SMEMBERS color:red
But the problem is I just want to display 10 of them in the first page, and then next 10 in the second page and so on. How to let Redis return only part of them by specifying offset and limit arguments?
What do redis experts normally do for this case? Return all IDs even if I just want 10 of them? Is that efficient? What if it has millions of values in the set, but I only want 10?
Edited 1
Incidentally, I use sets instead of lists and sorted sets because I will need to do SINTER jobs for other queries.
For example:
SADD type:foo <id:product>
SINTER color:red type:foo
And then I will have pagination problem again. Because I actually just want to find 10 of the intersection at a time. (eg: if the intersection returns millions of keys, but actually I just want 10 of them at a time for pagination).
Edited 2
Should I use a sorted set instead? I am not sure if this is the expert choice or not. Something like:
ZADD color:red <id:product> <id:product>
ZADD type:foo <id:product> <id:product>
ZRANGE color:red 0 9 // for the first page of red color products
ZINTERSTORE out 2 color:red type:foo AGGREGATE MIN
ZRANGE out 0 9 // for the first page of red color and type foo products
I have no ideas if the above way is suggested or not.
What will happen if multiple clients are creating the same out sorted set?
Is that meaningful to use the same value for both score and member?
Using sorted sets is the standard way to do pagination in Redis.
The documentation of ZINTERSTORE says that: "If destination already exists, it is overwritten."
Therefore, you shouldn't use "out" as the destination key name. You should instead use a unique or sufficiently random key name and then delete it when you're done.
I'm not sure what you mean by "meaningful". It's a fine choice if that's the order you want them to be in.

Why there is no ordered hashmap in Redis?

Redis Data types includes sorted set and other necessary data-structures for key-value storage. But I wonder why it doesn't have any sorted map like Java's TreeMap or C++'s std::map. I think the underlying data-structure would be mostly similar of sorted set as both are supposed to be balanced binary search tree.
There must be some use-cases where we have to store key-value pair in specific order according to key. But current sorted set only serves the purpose of storing key according to score.
There must be some use-cases where we have to store key-value pair in specific order according to key
Since Redis keys are binary strings, I assume that the specific order you mentioned, is lexicographical order (specifically, keys are compared with the memcmp function). In that case, you can easily implement a C++'s std::map with SORTED SET. You can achieve this with 2 steps:
Build a std::set with Redis' Sorted Set
If 2 elements in a SORTED SET have the same score, they're ordered in lexicographical order. So in order to build a std::set, just give all members in a SORTED SET with the same score:
zadd std::set 0 c
zadd std::set 0 a
zadd std::set 0 b
// since all these members have the same score,
// the result is lexicographical ordered:
// a b c
zrange std::set 0 -1
// the following command will fail, since 'c' already exists.
zadd std::set 0 c
Since Redis 2.8, it supports some commands to operate on the lexicographical ranges, so that you can build something similar to std::set::lower_bound, or std::set::upper_bound
// something similar to lower_bound: find all members not less than b
zrangebylex std::set [b +
// something similar to upper_bound: find all members greater than b
zrangebylex std::set (b +
Map each key in the set with a value
Since you already get a std::set, then map the key with a value, you can get a std::map.
set a value_a
set b value_b
set c value_c
Combine these 2 steps together
You can wrap the whole work into a lua script to have a built-in std::map. And use it like this:
redis-cli --eval map.lua map_name , key value

Use Cases for Redis' "Score" and "Ranking" Features for Sets

What are some use cases for Redis' "score" and "ranking" features for sets (outside of the typical "leaderboard" examples for games? I'm trying to figure out how to make use of these dynamic new features as I anticipate moving from using a traditional relational database to Redis as a persistent data store.
ZSETs are great for selections or ranges based on scores, but scores can be any numerical value, like a timestamp.
We store daily stock prices for all US stocks in redis. Here's an example for ebay...
ZADD key score member [score member ...]
...
ZADD stocks:ebay 1 30.39 2 32.70 3 31.25 4 31.75 5 29.12 6 29.87 7 29.93
The score values in this case would normally be long timestamps, with that aside, if we want daily prices for the last 3 days, we simply convert two dates to timestamps and pull from redis using the timestamp range 1 3...
zrangebyscore stocks:ebay 1 3
1) "30.39"
2) "32.70"
3) "31.25"
The query is very fast and works well for our needs.
Hope it helps!
zset is the only type of key who can be sorted
by example you can imagine puts all comments key id of a specific article in a zset,
users will vote up/down each comments and this will change the score value
after that when you need to draw comments you can get them ordered, better comments in first place (like here)
using ZREMRANGEBYSCORE you can imagine delete all pretty bad comments each days
but as each redis type, they still basic, give you a dedicated use case is hard there can be some :- )