Karthik
Karthik

Reputation: 5040

Redis - Alternative to check existence of multiple values in a set

In my application I need have a set of values and I need to check how many of these values are present in a set in Redis.

Just to make it simple, What I want to do is something like :

> Sadd myset field1
(integer) 1
> Sadd myset field2
(integer) 1
> Sadd myset field4
(integer) 1

> Sismember myset field1 field4 // which is not possible as of now.

Since I can not give multiple parameters for SISMEMBER, I might have to multiple redis call which is very time consuming.

I was thinking about alternatives like pipelining but then I thought this will be a good (hacky) way of achieving it :

> Hset myhash field1 "true"
(integer) 0
> Hset myhash field2 "true"
(integer) 0
> Hset myhash field4 "true"
(integer) 1

> Hmget myhash field1 field2 field3
1) "true"
2) "true"
3) (nil)

> Hmget myhash field1 field2 field3 field4
1) "true"
2) "true"
3) (nil)
4) "true"

Redis HMGET page says the following :

Available since 2.0.0.

Time complexity: O(N) where N is the number of fields being requested.

Which is really good when compared to making multiples calls for SADD, But I am really not sure If I am 100% correct and I also don't know if there is any serious drawback of using hmget this way.

So I just wanted to the drawbacks of using hmget this way and any better approaches to solve this.

Upvotes: 6

Views: 4350

Answers (4)

Ersoy
Ersoy

Reputation: 9586

Starting from Redis 6.2 you can use SMISMEMBER command check the existence of multiple element is the given set. It will return the array reply of the elements given in the same order as they are requested. You can get the sum of the array on the client side to get how many of these values are present in the set.

redis> SADD myset foo bar zet que
(integer) 4
redis> SMISMEMBER myset foo bar not
1) (integer) 1
2) (integer) 1
3) (integer) 0

Upvotes: 2

Nikita Koksharov
Nikita Koksharov

Reputation: 10763

Here is much easier way to do it on Java with Redisson framework:

RSet<String> set = redisson.getSet("mySet");

// uses lua-script under the hood
if (set.containsAll(Arrays.asList("obj1", "obj2", "obj3", "obj4"))) {
   // ...
}

Upvotes: 1

Karthik
Karthik

Reputation: 5040

Following @itamar 's answer, I was able to do this using lua script.

I used the following script:

 local r = {}
 for i, m in pairs(KEYS) do
    r[i] = redis.call('SISMEMBER',ARGV[1],m)
 end
 return r

May be this will be useful for someone in future, so just writing how to call this script from java (Spring Data Redis 1.5.0 & jedis 2.6.2)

    redisTemplate.opsForSet().add("mySet","3");
    redisTemplate.opsForSet().add("mySet","4");
    redisTemplate.opsForSet().add("mySet","35");
    redisTemplate.opsForSet().add("mySet", "6");
    List<String> list = new LinkedList<>();
    list.add("1"); 
    list.add("2");
    list.add("3");
    list.add("35");
    list.add("6");
    System.out.println(redisTemplate.execute(script, list,"mySet"));

prints the following :

   [0, 0, 1, 1, 1]

EDIT I am not sure everybody is saying that mySet is KEYS and list is ARGV, but in SPRING DATA REDIS, execute function is defined in the following way :

public <T> T execute(RedisScript<T> script, List<K> keys, Object... args) {
    return this.scriptExecutor.execute(script, keys, args);
}

Upvotes: 1

Itamar Haber
Itamar Haber

Reputation: 49932

This is certainly a valid solution, although a little wasteful as you'll be maintaining a bunch of true values - a RAM overhead. FYI, Redis' Sets are implemented internally using the same hash table structs as Hashes are, so you're not that far off :)

While there is no variadic form of SISMEMBER, it as an easily scriptable flow with Lua so you may want to consider that as well. For example something like the following:

local r = {}

for _, m in pairs(ARGV) do
  r[#r+1] = redis.call('SISMEMBER', KEYS[1], m)
end

return r

Upvotes: 2

Related Questions