Since redis does not support expiring set members, one common pattern is to use ordered sets instead and ZREMRANGEBYSCORE -inf $now to delete expired keys periodically (see #135).

My use case requires choosing N distinct elements from an ordered set at random, which is not supported by ordered sets currently, i.e. ZRANDMEMBER doesn't exist.

I think ZRANDMEMBER shall have the following signature:

ZRANDMEMBER key [count] [min max]

where min and max are both inclusive and allow minus/plus infinity (which are also their default values, respectively).

ZRANDMEMBER shall return abs(count) elements (whose semantics are the same as SRANDMEMBER) from the ordered set key where each returned element has a score s between min and max (inclusive).

I'd be happy to implement it too if anyone is willing to supervise.

Comment From: bionicles

yes please, need this today

Comment From: oranagra

Sounds like a good addition. Anyone wanna step in to code it?

Comment From: bionicles

I would love to but I don't know C and I'm not familiar with the codebase.

What's the most efficient way to learn to add new commands to Redis?

Comment From: oranagra

@bionicles this one is quite simple since it's easy to just follow the footsteps of SRANDMEMBER and do something similar for sorted sets. But if you don't know C, you might lose a lot of time on things that might be trivial for others, so maybe if you don't feel like playing, you can leave it for someone else.

Comment From: bionicles

https://github.com/redis/redis/blob/51eb0da824d2dae45bf5966e83f2089fba8d9c24/src/t_set.c#L794

void srandmemberCommand(client *c) {
    robj *set;
    sds ele;
    int64_t llele;
    int encoding;

    if (c->argc == 3) {
        srandmemberWithCountCommand(c);
        return;
    } else if (c->argc > 3) {
        addReply(c,shared.syntaxerr);
        return;
    }

    if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]))
        == NULL || checkType(c,set,OBJ_SET)) return;

    encoding = setTypeRandomElement(set,&ele,&llele);
    if (encoding == OBJ_ENCODING_INTSET) {
        addReplyBulkLongLong(c,llele);
    } else {
        addReplyBulkCBuffer(c,ele,sdslen(ele));
    }
}

Comment From: bionicles

https://github.com/redis/redis/blob/51eb0da824d2dae45bf5966e83f2089fba8d9c24/src/t_set.c#L195

/* Return random element from a non empty set.
 * The returned element can be an int64_t value if the set is encoded
 * as an "intset" blob of integers, or an SDS string if the set
 * is a regular set.
 *
 * The caller provides both pointers to be populated with the right
 * object. The return value of the function is the object->encoding
 * field of the object and is used by the caller to check if the
 * int64_t pointer or the redis object pointer was populated.
 *
 * Note that both the sdsele and llele pointers should be passed and cannot
 * be NULL since the function will try to defensively populate the non
 * used field with values which are easy to trap if misused. */
int setTypeRandomElement(robj *setobj, sds *sdsele, int64_t *llele) {
    if (setobj->encoding == OBJ_ENCODING_HT) {
        dictEntry *de = dictGetFairRandomKey(setobj->ptr);
        *sdsele = dictGetKey(de);
        *llele = -123456789; /* Not needed. Defensive. */
    } else if (setobj->encoding == OBJ_ENCODING_INTSET) {
        *llele = intsetRandom(setobj->ptr);
        *sdsele = NULL; /* Not needed. Defensive. */
    } else {
        serverPanic("Unknown set encoding");
    }
    return setobj->encoding;
}

Comment From: bionicles

Does dictGetFairRandomKey work for sorted sets?

Comment From: oranagra

sorted sets can be encoded with two encoding types: 1. a combination of a dict (for fast lookup) and a skiplist (for the sorting part). 2. ziplist. a blob of bytes containing the elements (sorted) one after the other.

so if it's "skiplist" encoded, you can get random elements form the dict. and if it's "ziplist" encoded (usually when there are not many elements), you can just use random by using the number of elements (ziplistLen), and then ziplistIndex and ziplistGet.

Comment From: bionicles

I'd love to add it, but I think I ought to study C for a bit first.

Would it be possible for someone familiar with C and Redis to show how it's done?

This function could dramatically simplify our project code and save a lot of memory

Comment From: madolson

It looks like we didn't implement the exact signature. The general idea is implemented though, so it's probably reasonable to close this. We can re-evaluate if someone wants this in the future.