Skip to content

Ask the HotKV docs

Answers come only from these docs and can be wrong; check the linked pages before you rely on them.

Sets

Last updated

A set is an unordered collection of unique strings. Adding a member that is already there changes nothing, and checking whether a member is present takes constant time however large the set is. Sets are a natural fit for tags on an object, the unique visitors of a page, who follows whom, and any question of the form “which items are in both of these groups?”.

If you need the members in a particular order or with a score, use a sorted set. If you only need an approximate count of unique items and want it in a few kilobytes, use a HyperLogLog.

Adding to a missing key creates the set, and removing its last member deletes it. SMEMBERS returns members in no particular order.

127.0.0.1:6379> SADD article:7:tags "redis" "databases" "caching"
(integer) 3
127.0.0.1:6379> SADD article:7:tags "caching"
(integer) 0
127.0.0.1:6379> SISMEMBER article:7:tags "redis"
(integer) 1
127.0.0.1:6379> SMISMEMBER article:7:tags "redis" "python"
1) (integer) 1
2) (integer) 0
127.0.0.1:6379> SCARD article:7:tags
(integer) 3
127.0.0.1:6379> SREM article:7:tags "databases"
(integer) 1
127.0.0.1:6379> SMEMBERS article:7:tags
1) "redis"
2) "caching"

Commands that return members (SMEMBERS, SINTER, SUNION, …) return them in no defined order, so yours may come back differently.

Other ways to read and change a set:

  • SRANDMEMBER returns random members without removing them; SPOP removes and returns them.
  • SMOVE moves a member from one set to another atomically.
  • SSCAN iterates a large set in pages, so you never pull millions of members in one reply.

Combine sets on the server instead of downloading them:

127.0.0.1:6379> SADD user:1:follows "ann" "bob" "cid"
(integer) 3
127.0.0.1:6379> SADD user:2:follows "bob" "cid" "dee"
(integer) 3
127.0.0.1:6379> SINTER user:1:follows user:2:follows
1) "bob"
2) "cid"
127.0.0.1:6379> SDIFF user:1:follows user:2:follows
1) "ann"
127.0.0.1:6379> SINTERCARD 2 user:1:follows user:2:follows
(integer) 2
127.0.0.1:6379> SUNIONSTORE everyone user:1:follows user:2:follows
(integer) 4

SUNION, SINTER and SDIFF return the result; the ...STORE forms write it to a destination key and return its size. SINTERCARD returns only the size of the intersection, and its LIMIT option stops counting early when you only need to know “at least N”.

In a cluster, multi-key commands need all their keys in the same hash slot. Give related sets a common hash tag, for example {user:1}:follows and {user:1}:blocked.

Unique visitors. SADD visitors:2026-10-02 <user-id> once per visit; SCARD gives the exact count of distinct visitors that day.

Mutual friends and recommendations. SINTER of two follow sets gives the people both follow; SDIFF gives the ones only one of them follows.

Deduplicating work. SADD seen:urls <url> replies 1 the first time and 0 after that, so a crawler can skip URLs it has already queued.

Random picks. SRANDMEMBER with a count draws samples; SPOP draws winners that should not be drawn again.

A time to live applies to the whole set. Single members cannot expire. For members that should age out on their own, use a sorted set scored by time and trim it with ZREMRANGEBYSCORE, or a hash with field TTLs.

Each member is a string up to proto-max-bulk-len (512 MB by default). SADD, SREM and SISMEMBER take constant time per member. SMEMBERS and the set algebra commands touch every member involved, so on large sets prefer SSCAN, SINTERCARD with a LIMIT, or the ...STORE forms that keep the result on the server.

Command What it does
SADD Add members
SREM Remove members
SISMEMBER / SMISMEMBER Test membership
SCARD Number of members
SMEMBERS All members
SSCAN Iterate members in pages
SRANDMEMBER Random members
SPOP Remove and return random members
SMOVE Move a member to another set
SUNION / SUNIONSTORE Union
SINTER / SINTERSTORE Intersection
SINTERCARD Size of the intersection
SDIFF / SDIFFSTORE Difference
import hotkv
client = hotkv.HotKV(host="localhost", port=6379, decode_responses=True)
client.sadd("user:1:follows", "ann", "bob", "cid")
client.sadd("user:2:follows", "bob", "cid", "dee")
print(client.sismember("user:1:follows", "ann")) # True
print(client.sinter("user:1:follows", "user:2:follows")) # {'bob', 'cid'}
print(client.scard("user:1:follows")) # 3
client.close()