Describe the bug

Upgrading the redis-server from 7.2.7 to 7.4.2, there are obvious performance degradation of SREM and ZREMRANGEBYLEX command. The main source of these performance degradation should be commit e2b7932.

To reproduce

Compile the redis-server before and after e2b7932, and using memtier_benchmark to test the SREM command:

memtier_benchmark "--data-size" "10" --pipeline 50 --command "SREM test_set value_set:159 value_set:1 value_set:91 value_set:72 value_set:134 value_set:13 value_set:93 value_set:3" --command-key-pattern="P" --key-minimum=1 --key-maximum 10000 --test-time 180 -c 50 -t 4 --hide-histogram

before e2b7932

ALL STATS
==================================================================================================
Type         Ops/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec 
--------------------------------------------------------------------------------------------------
Srems     1316307.41         7.58762         7.77500        11.07100        12.41500    237809.44 
Totals    1316307.41         7.58762         7.77500        11.07100        12.41500    475618.89 

after e2b7932

ALL STATS
==================================================================================================
Type         Ops/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec 
--------------------------------------------------------------------------------------------------
Srems     1294339.95         7.71696         7.93500        11.26300        12.54300    233840.71 
Totals    1294339.95         7.71696         7.93500        11.26300        12.54300    467681.43 

Also use memtier_benchmark to test the ZREMRANGEBYLEX command:

memtier_benchmark "--data-size" "10" --pipeline 50 --command "ZREMRANGEBYLEX test_zadd1 [sortset105 [sortset146" --command-key-pattern="P" --key-minimum=1 --key-maximum 10000 --test-time 180 -c 50 -t 4 --hide-histogram

before e2b7932

ALL STATS
==========================================================================================================
Type                 Ops/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec 
----------------------------------------------------------------------------------------------------------
Zremrangebylexs   1712281.15         5.83172         5.53500         9.72700        11.19900    137116.26 
Totals            1712281.15         5.83172         5.53500         9.72700        11.19900    274232.53 

after e2b7932

ALL STATS
==========================================================================================================
Type                 Ops/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec 
----------------------------------------------------------------------------------------------------------
Zremrangebylexs   1679914.16         5.94317         6.04700         9.21500        10.62300    134524.38 
Totals            1679914.16         5.94317         6.04700         9.21500        10.62300    269048.75 

Expected behavior

Can this degradation be eliminated or alleviated?

Additional information

I performed instrumentation on the processCommand function in redis-server to measured its execution time. Before and after e2b7932, with SREM command, the average execution time of the processCommand is 2.096us and 2.233us, respectively. And with ZREMRANGEBYLEX command, the average execution time of the processCommand is 1.857us and 1.938us, respectively.

Comment From: sundb

@Gallopm did you populate data before the benchmark?

Comment From: Gallopm

Yes, you can use the following python script to populate similiar data:

import redis
import random
import string

def random_string(length=10):
    letters = string.ascii_letters
    return ''.join(random.choice(letters) for i in range(length))

def load_data_into_redis(host='localhost', port=6379, num_keys=10000):
    r = redis.Redis(host=host, port=port, db=0)

    # Prepare data for SREM test, totally num_keys + 2
    for i in range(num_keys):
        key = f"key_set:{i}"
        random_number = random.randint(15, 199)
        for j in range(random_number):
            value = random_string(50)
            r.sadd(key, value)
    random_SPOP_Num = random.randint(160, 299)
    for i in range(random_SPOP_Num):
        r.sadd('test_SPOP', random_string(50))
        value = f"value_set:{i}"
        r.sadd('test_set', value)

    # Prepare data for ZREMRANGEBYLEX test, totally num_keys + 1
    for i in range(num_keys):
        key = f"key_SortSet:{i}"
        random_number = random.randint(100, 199)
        for j in range(random_number):
            numSorce = random.randint(1, 15)
            value = random_string(50)
            mapping = {value: numSorce}
            r.zadd(key, mapping)
    random_ZADD_Num = random.randint(160, 299)
    for i in range(random_ZADD_Num):
        value = f"sortset{i}"
        numSorce = random.randint(1, 20)
        mapping = {value: numSorce}
        r.zadd('test_zadd1', mapping)

if __name__ == "__main__":
    load_data_into_redis(num_keys=10000)