Permalink
Browse files

Update --bigkeys to use SCAN

This commit changes the findBigKeys() function in redis-cli.c to use the new
SCAN command for iterating the keyspace, rather than RANDOMKEY.  Because we
can know when we're done using SCAN, it will exit after exhausting the keyspace.
  • Loading branch information...
1 parent d3a3ef0 commit 013a4ce242eb4c13c4eee68b54a68f3e7b23f0f3 @michael-grunder michael-grunder committed Feb 25, 2014
Showing with 82 additions and 58 deletions.
  1. +82 −58 src/redis-cli.c
View
@@ -1300,81 +1300,105 @@ static void pipeMode(void) {
static void findBigKeys(void) {
unsigned long long biggest[5] = {0,0,0,0,0};
unsigned long long samples = 0;
- redisReply *reply1, *reply2, *reply3 = NULL;
- char *sizecmd, *typename[] = {"string","list","set","hash","zset"};
+ redisReply *reply1, *reply2, *reply3 = NULL, *keys;
+ char *key, *sizecmd, *typename[] = {"string","list","set","hash","zset"};
char *typeunit[] = {"bytes","items","members","fields","members"};
- int type;
+ int type, it=0, i;
printf("\n# Press ctrl+c when you have had enough of it... :)\n");
- printf("# You can use -i 0.1 to sleep 0.1 sec every 100 sampled keys\n");
+ printf("# You can use -i 0.1 to sleep 0.1 sec per 100 SCANS\n");
printf("# in order to reduce server load (usually not needed).\n\n");
- while(1) {
- /* Sample with RANDOMKEY */
- reply1 = redisCommand(context,"RANDOMKEY");
- if (reply1 == NULL) {
- fprintf(stderr,"\nI/O error\n");
+
+ do {
+ /* Grab some keys with SCAN */
+ reply1 = redisCommand(context, "SCAN %d", it);
+ if(reply1 == NULL) {
+ fprintf(stderr, "\nI/O error\n");
exit(1);
- } else if (reply1->type == REDIS_REPLY_ERROR) {
- fprintf(stderr, "RANDOMKEY error: %s\n",
- reply1->str);
+ } else if(reply1->type == REDIS_REPLY_ERROR) {
+ fprintf(stderr, "SCAN error: %s\n", reply1->str);
exit(1);
- } else if (reply1->type == REDIS_REPLY_NIL) {
- fprintf(stderr, "It looks like the database is empty!\n");
+ } else if(reply1->type != REDIS_REPLY_ARRAY) {
+ fprintf(stderr, "Non ARRAY response from SCAN!\n");
exit(1);
- }
-
- /* Get the key type */
- reply2 = redisCommand(context,"TYPE %s",reply1->str);
- assert(reply2 && reply2->type == REDIS_REPLY_STATUS);
- samples++;
-
- /* Get the key "size" */
- if (!strcmp(reply2->str,"string")) {
- sizecmd = "STRLEN";
- type = TYPE_STRING;
- } else if (!strcmp(reply2->str,"list")) {
- sizecmd = "LLEN";
- type = TYPE_LIST;
- } else if (!strcmp(reply2->str,"set")) {
- sizecmd = "SCARD";
- type = TYPE_SET;
- } else if (!strcmp(reply2->str,"hash")) {
- sizecmd = "HLEN";
- type = TYPE_HASH;
- } else if (!strcmp(reply2->str,"zset")) {
- sizecmd = "ZCARD";
- type = TYPE_ZSET;
- } else if (!strcmp(reply2->str,"none")) {
- freeReplyObject(reply1);
- freeReplyObject(reply2);
- continue;
- } else {
- fprintf(stderr, "Unknown key type '%s' for key '%s'\n",
- reply2->str, reply1->str);
+ } else if(reply1->elements!=2) {
+ fprintf(stderr, "Invalid SCAN result!\n");
exit(1);
}
-
- reply3 = redisCommand(context,"%s %s", sizecmd, reply1->str);
- if (reply3 && reply3->type == REDIS_REPLY_INTEGER) {
- if (biggest[type] < reply3->integer) {
- printf("Biggest %-6s found so far '%s' with %llu %s.\n",
- typename[type], reply1->str,
- (unsigned long long) reply3->integer,
- typeunit[type]);
- biggest[type] = reply3->integer;
+
+ /* Validate the SCAN response */
+ assert(reply1->element[0]->type == REDIS_REPLY_STRING);
+ assert(reply1->element[1]->type == REDIS_REPLY_ARRAY);
+
+ /* Update iterator and grab pointer to keys */
+ it = atoi(reply1->element[0]->str);
+ keys = reply1->element[1];
+
+ /* Iterate keys that SCAN returned */
+ for(i=0;i<keys->elements;i++) {
+ /* Make sure we've got a string, grab it, and increment samples */
+ assert(keys->element[i]->type == REDIS_REPLY_STRING);
+ key = keys->element[i]->str;
+ samples++;
+
+ /* Get the key type */
+ reply2 = redisCommand(context, "TYPE %s", key);
+ assert(reply2 && reply2->type == REDIS_REPLY_STATUS);
+
+ /* Get the key "size" */
+ if (!strcmp(reply2->str,"string")) {
+ sizecmd = "STRLEN";
+ type = TYPE_STRING;
+ } else if (!strcmp(reply2->str,"list")) {
+ sizecmd = "LLEN";
+ type = TYPE_LIST;
+ } else if (!strcmp(reply2->str,"set")) {
+ sizecmd = "SCARD";
+ type = TYPE_SET;
+ } else if (!strcmp(reply2->str,"hash")) {
+ sizecmd = "HLEN";
+ type = TYPE_HASH;
+ } else if (!strcmp(reply2->str,"zset")) {
+ sizecmd = "ZCARD";
+ type = TYPE_ZSET;
+ } else if (!strcmp(reply2->str,"none")) {
+ freeReplyObject(reply1);
+ freeReplyObject(reply2);
+ continue;
+ } else {
+ fprintf(stderr, "Unknown key type '%s' for key '%s'\n",
+ reply2->str, key);
+ exit(1);
+ }
+
+ /* The size command */
+ reply3 = redisCommand(context,"%s %s", sizecmd, key);
+ if (reply3 && reply3->type == REDIS_REPLY_INTEGER) {
+ if (biggest[type] < reply3->integer) {
+ printf("Biggest %-6s found so far '%s' with %llu %s.\n",
+ typename[type], key,
+ (unsigned long long) reply3->integer,
+ typeunit[type]);
+ biggest[type] = reply3->integer;
+ }
}
+
+ freeReplyObject(reply2);
+ if(reply3) freeReplyObject(reply3);
}
- if ((samples % 1000000) == 0)
+ if (samples && (samples % 1000000) == 0)
printf("(%llu keys sampled)\n", samples);
- if ((samples % 100) == 0 && config.interval)
+ if (samples && (samples % 100) == 0 && config.interval)
usleep(config.interval);
freeReplyObject(reply1);
- freeReplyObject(reply2);
- if (reply3) freeReplyObject(reply3);
- }
+ } while(it != 0);
+
+ /* We've finished scanning the keyspace */
+ printf("\n# Scanned all %llu keys in the keyspace!\n", samples);
+ exit(0);
}
/* Return the specified INFO field from the INFO command output "info".

0 comments on commit 013a4ce

Please sign in to comment.