Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
/* delete keys matching a pattern, comments welcome
author: Andreas Hadjiprocopis (https://github.com/hadjiprocopis) / bliako
date : 30/11/2021
*/
/* cvector is a headers-only vector implementation
from https://github.com/eteran/c-vector
*/
#include <cvector.h>
#include <hiredis/hiredis.h>
#define time_difference_seconds(_TSTARTED,_TENDED) (((_TENDED.tv_sec - _TSTARTED.tv_sec) * 1000000 + (_TENDED.tv_nsec - _TSTARTED.tv_nsec) / 1000.0)/1000000.0)
int redis_mdel(
redisContext *redish,
const char *pattern
){
struct timespec t[20];
size_t TI = 0;
clock_gettime(CLOCK_MONOTONIC_RAW, &(t[TI++]));
fprintf(stdout, "redis_mdel() : called to delete with this pattern '%s' ...\n", pattern);
redisReply *reply, *subreply, *keys_reply, *key_reply;
char cmd[100], *akey;
// create a cvector with an initial size (which will be re-sized if needed)
// we will store the keys as they come from SCAN
cvector_vector_type(char*) keys = NULL;
int cursor = 0;
size_t iter = 0;
do {
sprintf(cmd, "SCAN %d MATCH %s", cursor, pattern);
reply = redisCommand(redish, cmd);
if( reply == NULL ){ fprintf(stderr, "redis_mdel() : error, call to redisCommand() has failed (NULL reply) for this command '%s' (scan loop was %zu).\n", cmd, iter); return 1; }
else if( reply->type == REDIS_REPLY_ERROR ){ fprintf(stderr, "redis_mdel() : error, call to redisCommand() has failed for this command '%s' (scan loop was %zu): '%s'\n", cmd, iter, reply->str); return 1;}
else if( reply->type != REDIS_REPLY_ARRAY ){ fprintf(stderr, "redis_mdel() : error, call to redisCommand() has failed ((1): did not receive an REDIS_REPLY_ARRAY but type:%d) for this command '%s' (scan loop was %zu): '%s'\n", reply->type, cmd, iter, reply->str); return 1;}
// we now have reply as an array of 2 items: cursor and array-of-some-or-all-results
cursor = reply->element[0]->integer;
//fprintf(stdout, "redis_mdel() : command '%s', scan loop %zu : got cursor of %d.\n", cmd, iter, cursor);
keys_reply = reply->element[1];
if( keys_reply == NULL ){ fprintf(stderr, "redis_mdel() : error, call to redisCommand() has failed (NULL reply) for this command '%s' (scan loop was %zu).\n", cmd, iter); return 1; }
else if( keys_reply->type == REDIS_REPLY_ERROR ){ fprintf(stderr, "redis_mdel() : error, call to redisCommand() has failed for this command '%s' (scan loop was %zu): '%s'\n", cmd, iter, keys_reply->str); return 1;}
else if( keys_reply->type != REDIS_REPLY_ARRAY ){ fprintf(stderr, "redis_mdel() : error, call to redisCommand() has failed ((2): did not receive an REDIS_REPLY_ARRAY but type:%d) for this command '%s' (scan loop was %zu): '%s'\n", keys_reply->type, cmd, iter, keys_reply->str); return 1;}
size_t num_keys_fetched_this_time = keys_reply->elements;
//fprintf(stdout, "redis_mdel() : command '%s', scan loop %zu : got cursor of %d and %zu items.\n", cmd, iter, cursor, num_keys_fetched_this_time);
for(size_t ix=0;ix<num_keys_fetched_this_time;ix++){
key_reply = keys_reply->element[ix];
if( key_reply == NULL ){ fprintf(stderr, "redis_mdel() : error, call to redisCommand() has failed (NULL reply) for this command '%s' (scan loop was %zu, ix=%zu).\n", cmd, iter, ix); return 1; }
else if( key_reply->type == REDIS_REPLY_ERROR ){ fprintf(stderr, "redis_mdel() : error, call to redisCommand() has failed for this command '%s' (scan loop was %zu): '%s'\n", cmd, iter, key_reply->str); return 1;}
else if( key_reply->type != REDIS_REPLY_STRING ){ fprintf(stderr, "redis_mdel() : error, call to redisCommand() has failed (did not receive an REDIS_REPLY_STRING but type:%d) for this command '%s' (scan loop was %zu): '%s'\n", key_reply->type, cmd, iter, key_reply->str); return 1;}
//fprintf(stdout, "redis_mdel() : command '%s', scan loop %zu : got key back: '%s'.\n", cmd, iter, key_reply->str);
cvector_push_back(keys, strdup(key_reply->str));
}
// free reply will free all sub-elements
freeReplyObject(reply);
iter++;
} while( cursor > 0 );
size_t num_keys = cvector_size(keys);
clock_gettime(CLOCK_MONOTONIC_RAW, &(t[TI++]));
fprintf(stdout, "redis_mdel() : found %zu keys to delete using pattern '%s' in %lf seconds ...\n", num_keys, pattern, time_difference_seconds(t[TI-2], t[TI-1]));
// now we have keys, so del but using a pipeline for more efficiency
for(size_t i=num_keys;i-->0;){
akey = (char *)(keys[i]);
// WARNING: IT DOES NOT LIKE quotes around key-string single or double!!!!
// sprintf(cmd, "DEL '%s'", akey);
sprintf(cmd, "DEL %s", akey);
if( redisAppendCommand(redish, cmd) != REDIS_OK ){ fprintf(stderr, "redis_mdel() : error, call to redisAppendCommand() has failed for command '%s'.\n", cmd); return 1; }
free(akey);
}
clock_gettime(CLOCK_MONOTONIC_RAW, &(t[TI++]));
fprintf(stdout, "redis_mdel() : sent %zu DEL commands via pipeline to Redis in %lf seconds ...\n", num_keys, time_difference_seconds(t[TI-2], t[TI-1]));
for(size_t i=num_keys;i-->0;){
if( redisGetReply(redish, (void **) &reply ) != REDIS_OK ){ fprintf(stderr, "redis_mdel() : error, call to redisGetReply() has failed for reply %zu of %zu.\n", i, num_keys); return 1; }
if( reply == NULL ){ fprintf(stderr, "redis_mdel() : error, reply from 'redisGetReply()' has failed (got NULL) for count %zu of %zu.\n", i, num_keys); return 1; }
else if( reply->type == REDIS_REPLY_ERROR ){ fprintf(stderr, "redis_mdel() : error, reply from 'redisGetReply()' has failed for count %zu of %zu.\n", i, num_keys); return 1; }
else if( reply->type != REDIS_REPLY_INTEGER ){ fprintf(stderr, "redis_mdel() : error, reply from 'redisGetReply()' has failed (did not receive REDIS_REPLY_INTEGER but type:%d) for count %zu of %zu.\n", reply->type, i, num_keys); return 1; }
else if( reply->integer != 0 ){ fprintf(stderr, "redis_mdel() : error, reply (integer) was not zero but %lld, for count %zu of %zu.\n", reply->integer, i, num_keys); return 1; }
freeReplyObject(reply);
}
clock_gettime(CLOCK_MONOTONIC_RAW, &(t[TI++]));
fprintf(stdout, "redis_mdel() : check the reply of %zu DEL commands via pipeline from Redis in %lf seconds ...\n", num_keys, time_difference_seconds(t[TI-2], t[TI-1]));
// epilogue
cvector_free(keys);
clock_gettime(CLOCK_MONOTONIC_RAW, &(t[TI++]));
fprintf(stdout, "redis_mdel() : free'd the vector of %zu keys in %lf seconds ...\n", num_keys, time_difference_seconds(t[TI-2], t[TI-1]));
fprintf(stdout, "redis_mdel() : done deleted %zu keys matching the pattern '%s' in a total of %lf seconds.\n", num_keys, pattern, time_difference_seconds(t[0], t[TI-1]));
return 0;
}
@hadjiprocopis
Copy link
Author

hadjiprocopis commented Nov 30, 2021

Example, possibly lame, on how to delete multiple keys from Redis matching a wildcard pattern using SCAN and pipelined DEL.

@hadjiprocopis
Copy link
Author

hadjiprocopis commented Dec 3, 2021

I have just realised that enclosing key name in quotes (single or double) does not work and so converted sprintf(cmd, "DEL '%s'", akey); to this: sprintf(cmd, "DEL %s", akey);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment