Skip to content

Commit

Permalink
Make KEYS to be an exact match if there is no pattern
Browse files Browse the repository at this point in the history
Although KEYS is a dangerous command and we recommend people
to avoid using it, some people who are not familiar with it
still using it, and even use KEYS with no pattern at all.

Once KEYS is using with no pattern, we can convert it to an
exact match to avoid iterating over all data.

Signed-off-by: Binbin <[email protected]>
  • Loading branch information
enjoy-binbin committed Jul 16, 2024
1 parent 1a8bd04 commit e9f3565
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 1 deletion.
35 changes: 35 additions & 0 deletions src/db.c
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,21 @@ void randomkeyCommand(client *c) {
decrRefCount(key);
}

/* Returns 1 if the pattern can be an exact match in KEYS context. */
int patternExactMatch(const char *pattern, int length) {
for (int i = 0; i < length; i++) {
if (pattern[i] == '*' || pattern[i] == '?' || pattern[i] == '[') {
/* Wildcard or character class found. Keys can be in anywhere. */
return 0;
} else if (pattern[i] == '\\') {
/* Escaped character. Computing the key name in this case is not
* implemented. We would need a temp buffer. */
return 0;
}
}
return 1;
}

void keysCommand(client *c) {
dictEntry *de;
sds pattern = c->argv[1]->ptr;
Expand All @@ -801,7 +816,27 @@ void keysCommand(client *c) {
allkeys = (pattern[0] == '*' && plen == 1);
if (server.cluster_enabled && !allkeys) {
pslot = patternHashSlot(pattern, plen);
} else if (!server.cluster_enabled) {
pslot = 0;
}

/* Once the pattern can do an exact match, we can convert
* it to a kvstoreDictFind to avoid iterating over all data. */
if (patternExactMatch(pattern, plen) && pslot != -1) {
de = kvstoreDictFind(c->db->keys, pslot, pattern);
if (de) {
robj keyobj;
sds key = dictGetKey(de);
initStaticStringObject(keyobj, key);
if (!keyIsExpired(c->db, &keyobj)) {
addReplyBulkCBuffer(c, key, sdslen(key));
numkeys++;
}
}
setDeferredArrayLen(c, replylen, numkeys);
return;
}

kvstoreDictIterator *kvs_di = NULL;
kvstoreIterator *kvs_it = NULL;
if (pslot != -1) {
Expand Down
45 changes: 44 additions & 1 deletion tests/unit/keyspace.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,20 @@ start_server {tags {"keyspace"}} {
}
assert_equal [lsort [r keys "{a}*"]] [list "{a}x" "{a}y" "{a}z"]
assert_equal [lsort [r keys "*{b}*"]] [list "{b}a" "{b}b" "{b}c"]
}
}

test {KEYS with no pattern} {
r debug populate 1000
r set foo bar
r set foo{t} bar
r del non-exist
assert_equal [r keys foo] {foo}
assert_equal [r keys foo{t}] {foo{t}}
assert_equal [r keys non-exist] {}

r set foo\\ bar
assert_equal [r keys foo\\] {foo\\}
} {} {needs:debug}

test {DEL all keys} {
foreach key [r keys *] {r del $key}
Expand Down Expand Up @@ -548,3 +561,33 @@ foreach {type large} [array get largevalue] {
r flushall
} {OK} {singledb:skip}
}

start_cluster 1 0 {tags {"keyspace external:skip cluster"}} {
# SET keys for random slots, for random noise.
set num_keys 0
while {$num_keys < 1000} {
set random_key [randomInt 163840]
r SET $random_key VALUE
incr num_keys 1
}

test {KEYS with hashtag in cluster mode} {
foreach key {"{a}x" "{a}y" "{a}z" "{b}a" "{b}b" "{b}c"} {
r set $key hello
}
assert_equal [lsort [r keys "{a}*"]] [list "{a}x" "{a}y" "{a}z"]
assert_equal [lsort [r keys "*{b}*"]] [list "{b}a" "{b}b" "{b}c"]
}

test {KEYS with no pattern in cluster mode} {
r set foo bar
r set foo{t} bar
r del non-exist
assert_equal [r keys foo] {foo}
assert_equal [r keys foo{t}] {foo{t}}
assert_equal [r keys non-exist] {}

r set foo\\ bar
assert_equal [r keys foo\\] {foo\\}
}
}

0 comments on commit e9f3565

Please sign in to comment.