Repository: valkey-io/valkey
File: src/db.c (C)

Code before:
                numkeys++;
            }
        }
        if (c->flag.close_asap) break;
    }
    if (kvs_di) kvstoreReleaseHashtableIterator(kvs_di);
    if (kvs_it) kvstoreIteratorRelease(kvs_it);
    setDeferredArrayLen(c, replylen, numkeys);
}

#define DEFAULT_SCAN_COMMAND_COUNT 10

/* The SCAN command's default COUNT is 10.
 * Since it may store keys + values, the
 * buffer size is roughly 10 * 2 = 20.
 * Adding a 20% buffer (20 * 1.2) gives 24. */
#define MAX_SCAN_ARRAY_BUFFER 24

/* Used to store the iteration results from the SCAN/HSCAN/SSCAN/ZSCAN commands. */
typedef struct {
    sds *array;  /* Points to buf or heap */
    size_t used; /* Used size of the array */
    size_t size; /* Allocated size of the array */
    void (*free)(void *);
    sds buf[MAX_SCAN_ARRAY_BUFFER]; /* Pre-allocated buffer reduces heap allocation */
} scanArray;

/* Initialize a scanArray structure. */
static void scanArrayInit(scanArray *result, void (*free)(void *)) {
    /* The array is initialized with a pre-allocated buffer of size MAX_SCAN_ARRAY_BUFFER,
     * which will be used until the number of elements exceeds this size. */
    result->used = 0;
    result->array = result->buf;
    result->size = MAX_SCAN_ARRAY_BUFFER;

    /* The free function is used to free the elements in the array,
     * if NULL then no free will be performed. */
    result->free = free;
}

/* Append a value to the scanArray. */
static void scanArrayAppend(scanArray *result, sds v) {
    if (result->used == result->size) {
        result->size *= 2;
        if (result->array == result->buf) {
            result->array = zmalloc(sizeof(sds) * result->size);
            memcpy(result->array, result->buf, sizeof(result->buf));
        } else {
            result->array = zrealloc(result->array, sizeof(sds) * result->size);
        }
    }

Review comment (performance):
This looks like a generic dynamic array to me.

To make it useful for other things in the future, like as replacing `list` in more places, can we make this a generic dynamic array instead? We can put it in a new file `array.c` and `array.h`. I imagine we could replace things like blocked keys that are currently lists with this kind of dynamic array instead.

To make it more slim, we could skip the pre-allocated buffer. It makes the struct fit within 32 bytes. Jemalloc is quite very fast and it will often return memory that is already in L1-L2-L3 cache.

Code after:
                addReplyBulkCBuffer(c, key, sdslen(key));
                numkeys++;
            }
        }
        if (c->flag.close_asap) break;
    }
    if (kvs_di) kvstoreReleaseHashtableIterator(kvs_di);
    if (kvs_it) kvstoreIteratorRelease(kvs_it);
    setDeferredArrayLen(c, replylen, numkeys);
}

#define DEFAULT_SCAN_COMMAND_COUNT 10

/* The SCAN command's default COUNT is 10.
 * Since it may store keys + values, the
 * buffer size is roughly 10 * 2 = 20.
 * Adding a 20% buffer (20 * 1.2) gives 24. */
#define SCAN_VECTOR_INITIAL_ALLOC 24

/* Data used by the dict scan callback. */
typedef struct {
    vector *result; /* elements that collect from dict */
    robj *o;        /* o must be a hash/set/zset object, NULL means current db */
    serverDb *db;   /* database currently being scanned */
    long long type; /* the particular type when scan the db */
    sds pattern;    /* pattern string, NULL means no pattern */
    long sampled;   /* cumulative number of keys sampled */
    int only_keys;  /* set to 1 means to return keys only */
} scanData;

/* Helper function to compare key type in scan commands */
int objectTypeCompare(robj *o, long long target) {
    if (o->type != OBJ_MODULE) {
        if (o->type != target)
            return 0;
        else
            return 1;
    }
    /* module type compare */
    long long mt = (long long)VALKEYMODULE_TYPE_SIGN(((moduleValue *)o->ptr)->type->id);
    if (target != -mt)
        return 0;
    else
        return 1;
}

/* Hashtable scan callback used by scanCallback when scanning the keyspace. */
void keysScanCallback(void *privdata, void *entry) {
    scanData *data = (scanData *)privdata;
    robj *obj = entry;
    data->sampled++;