The problem/use-case that the feature addresses
Arbitral: "There are many cases in which this function would useful"
Use case for me is simple. Atomic removal of items per score from ZSET. I often find my self storing elements in ZSET with unix time being score. Having ability without writing Lua to remove eg. up to 100 items with score less than X would be ideal for this.
It would let me to not worry about locks in multi thread / process environment.
Description of the feature
when calling ZPOP{MIN,MAX}BYSCORE key min max [count] eg.
ZPOPMINBYSCORE myzet -inf 123456 10
We would expect up to 10 elements to be removed and returned that have score above -inf and below or equal 123456.
Support of "WITHSCORES" should be included.
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZADD myzset 2 "two"
(integer) 1
redis> ZADD myzset 3 "three"
(integer) 1
redis> ZRANGEBYSCORE myzset -inf +inf
1) "one"
2) "two"
3) "three"
redis> ZPOPMINBYSCORE myzet -inf 2 2
1) "one"
2) "two"
redis> ZRANGEBYSCORE myzset -inf +inf
3) "three"
Alternatives you've considered
Lua script that kinda solves it.
local key = KEYS[1]
local result = redis.call('ZRANGE', key, 0, 0)
local member = result[1]
if member then
redis.call('ZREM', key, member)
return member
else
return nil
end
Additional information
Maybe I simply don't see something. But It would be a great feature to have. For anyone using ZSET as priority queue or in any other form that has "time" as SCORE it would benefit great the scenarios where same ZSET is used by many different Processes / Threads. One atomic operation feels really safe for multi thread.
Comment From: itamarhaber
Hello @JakubOboza - nice meeting you here as well!
The suggestion makes sense to me, although it may be possible to avoid making a new command (so we avoid later repeating what we did with 6.2's extended ZRANGE) and get away with modifying the existing syntax, i.e. from:
ZPOPMIN key [count]
to something like:
ZPOPMIN key [count [~1~ | n [BYSCORE min max]]
or even (if it makes sense):
ZPOPMIN key [count [~1~ | n [(BYSCORE|BYLEX) start end]]
Note: a ~...~ means default
Comment From: JakubOboza
Hey @itamarhaber
I think Your proposed solution with extended ZPOPMIN / ZPOPMAX is even better. Might need extra examples in docs so extra options are well understood and documented. But for sure it sounds better.
What about "WITHSCORES" option ? We would also have it. It isn't supported by ZPOPMIN / ZPOPMAX right now.
Comment From: madolson
My thought is that this might be better served by a new command like:
ZPOPRANGE key min max [BYSCORE|BYLEX| [REV] [COUNT N] [WITHSCORES]
It would behave just like ZRANGE, except it would remove the items. My thoughts about this: * I think it's a little confusing the ZPOPMIN might not pop the min item. * ZPOP{MIN|MAX} is somewhat constrained by the positional count argument. * Simplifies our internal code, since we can generalize the RANGE code. * Doesn't cause a divergence between the blocking variants of ZPOPMIN annd BZPOPMIN.
Comment From: itamarhaber
Agreeing w/ @madolson on everything here :)
Comment From: nmvk
Taking a look
Comment From: JakubOboza
I agree with @madolson. Single command is probably best outcome. ZPOPRANGE is also a better name to what I had in my mind ;) it indicates you are getting 0 to n elements rather than 0 or 1. 👍
Comment From: itamarhaber
Sorry, but on top of my stupid comment above, I just realized I even didn't bother to read the OP's requirement.
@JakubOboza I may still be missing the point, but wouldn't ZREMRANGEBYSCORE work for you (albeit without a support for WITHSCORES)?
Comment From: JakubOboza
@itamarhaber the gist is that ZREMRANGEBYSCORE doesn't return the values back. It only removes them unless i'm mistaken.
Example of ZREMRANGEBYSCORE:
127.0.0.1:6379> ZADD myzset 1 "11"
(integer) 1
127.0.0.1:6379> ZADD myzset 2 "22"
(integer) 1
127.0.0.1:6379> ZADD myzset 3 "33"
(integer) 1
127.0.0.1:6379> ZADD myzset 4 "44"
(integer) 1
127.0.0.1:6379> ZADD myzset 5 "55"
(integer) 1
127.0.0.1:6379> ZREMRANGEBYSCORE myzset -inf 3
(integer) 3
It only removes the elements and returns the count of removed values. While for my case the key behaviour is to get the elements removed back.
Unless I'm missing a flag or something.
example of what I would want:
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZADD myzset 2 "two"
(integer) 1
redis> ZADD myzset 3 "three"
(integer) 1
redis> ZPOPRANGE myzet -inf 2 2
1) "one"
2) "two"
This is why I based everything on ZPOP because ZPOP commands return the actual value and not the count of elements removed.
Comment From: itamarhaber
ZREMRANGEBYSCORE doesn't return the values back
Correct, but this:
MULTI
ZRANGE myzset -inf 2 BYSCORE WITHSCORES
ZREMRANGEBYSCORE myzset inf -2
EXEC
appears to achieve what you're looking for iiuc.
Comment From: madolson
@JakubOboza Did that solve your use case?
Comment From: JakubOboza
Yes! lets give it a good old close. @madolson
Comment From: classicalguss
What if I wanted to limit the items I want to "pop"? I really believe that this should be reconsidered.
Comment From: jamesw6811
What if I wanted to limit the items I want to "pop"? I really believe that this should be reconsidered.
This is useful to me as well. potentially having a limit on zremrangebyscore would fix this.