Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

StackExchange.Redis Scan x amount of keys

I have a redis db that has thousands of keys and I'm currently running the following line to get all the keys:

string[] keysArr = keys.Select(key => (string)key).ToArray();

But because I have a lot of keys this takes a long time. I want to limit the number of keys being read. So I'm trying to run an execute command where I get 100 keys at a time:

var keys = Redis.Connection.GetDatabase(dbNum).Execute("scan", 0, "count", 100);

This command successfully runs the command, however unable to access the the value as it is private, and unable to cast it even though RedisResult classs provides a explicit cast to it:

public static explicit operator string[] (RedisResult result);

Any ideas to get x amount of keys at a time from redis?

Thanks

like image 630
Person Avatar asked Jul 25 '18 15:07

Person


People also ask

How many keys can Redis handle?

Redis can handle up to 2^32 keys, and was tested in practice to handle at least 250 million keys per instance. Every hash, list, set, and sorted set, can hold 2^32 elements. In other words your limit is likely the available memory in your system.

How do I get Redis all keys?

Redis GET all Keys To list the keys in the Redis data store, use the KEYS command followed by a specific pattern. Redis will search the keys for all the keys matching the specified pattern. In our example, we can use an asterisk (*) to match all the keys in the data store to get all the keys.

Does scan block Redis?

So yes, SCAN is blocking, but it's usually not a big deal, and is only blocking when Redis is actually processing the command. Omit the COUNT and MATCH arguments, and it's probably not an issue. Whether it becomes an issue after adding MATCH and/or COUNT arguments will depend on your data and your use of SCAN.

How does scan work in Redis?

Redis SCAN SCAN is a cursor-based iteration command, allowing a client to go over all the elements in a table. This cursor-based scanner accepts an integer cursor on each call, and returns a batch of items and the cursor value to be used in the next call to SCAN.


1 Answers

SE.Redis has a .Keys() method on IServer API which fully encapsulates the semantics of SCAN. If possible, just use this method, and consume the data 100 at a time. It is usually pretty easy to write a batching function, i.e.

ExecuteInBatches(server.Keys(), 100, batch => DoSomething(batch));

with:

public void ExecuteInBatches<T>(IEnumerable<T> source, int batchSize,
        Action<List<T>> action)
{
    List<T> batch = new List<T>();
    foreach(var item in source) {
        batch.Add(item);
        if(batch.Count == batchSize) {
             action(batch);
             batch = new List<T>(); // in case the callback stores it
        }
    }
    if (batch.Count != 0) {
        action(batch); // any leftovers
    }
}

The enumerator will worry about advancing the cursor.


You can use Execute, but: that is a lot of work! Also, SCAN makes no gaurantees about how many will be returned per page; it can be zero - it can be 3 times what you asked for. It is ... guidance only.

Incidentally, the reason that the cast fails is because SCAN doesn't return a string[] - it returns an array of two items, the first of which is the "next" cursor, the second is the keys. So maybe:

var arr = (RedisResult[])server.Execute("scan", 0);
var nextCursor = (int)arr[0];
var keys = (RedisKey[])arr[1];

But all this is doing is re-implementing IServer.Keys, the hard way (and significantly less efficiently - ServerResult is not the ideal way to store data, it is simply necessary in the case of Execute and ScriptEvaluate).

like image 170
Marc Gravell Avatar answered Nov 10 '22 21:11

Marc Gravell