optimize unwatchAllKeys() (#11511)

In unwatchAllKeys() function, we traverse all the keys watched by the client,
and for each key we need to remove the client from the list of clients watching that key.
This is implemented by listSearchKey which traverses the list of clients.

If we can reach the node of the list of clients from watchedKey in O(1) time,
then we do not need to call listSearchKey anymore.

Changes in this PR: put the node of the list of clients of each watched key in the
db inside the watchedKey structure. In this way, for every key watched by the client,
we can get the watchedKey structure and then reach the node in the list of clients in
db->watched_keys to remove it from that list.
From the perspective of the list of clients watching the key, the list node is inside a
watchedKey structure, so we can get to the watchedKey struct from the listnode by
struct member offset math. And because of this, node->value is not used, we can point
node->value to the list itself, so that we don't need to fetch the list of clients from the dict.
This commit is contained in:
Mingyi Kang 2022-11-23 23:39:08 +08:00 committed by GitHub
parent f36eb5a1ba
commit 3b462ce566
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 56 additions and 12 deletions

View File

@ -139,6 +139,22 @@ list *listAddNodeTail(list *list, void *value)
return list;
}
/*
* Add a node that has already been allocated to the tail of list
*/
void listLinkNodeTail(list *list, listNode *node) {
if (list->len == 0) {
list->head = list->tail = node;
node->prev = node->next = NULL;
} else {
node->prev = list->tail;
node->next = NULL;
list->tail->next = node;
list->tail = node;
}
list->len++;
}
list *listInsertNode(list *list, listNode *old_node, void *value, int after) {
listNode *node;

View File

@ -90,6 +90,7 @@ void listRotateHeadToTail(list *list);
void listJoin(list *l, list *o);
void listInitNode(listNode *node, void *value);
void listLinkNodeHead(list *list, listNode *node);
void listLinkNodeTail(list *list, listNode *node);
void listUnlinkNode(list *list, listNode *node);
/* Directions for iterators */

View File

@ -264,17 +264,38 @@ void execCommand(client *c) {
* Also every client contains a list of WATCHed keys so that's possible to
* un-watch such keys when the client is freed or when UNWATCH is called. */
/* In the client->watched_keys list we need to use watchedKey structures
* as in order to identify a key in Redis we need both the key name and the
* DB. This struct is also referenced from db->watched_keys dict, where the
* values are lists of watchedKey pointers. */
/* The watchedKey struct is included in two lists: the client->watched_keys list,
* and db->watched_keys dict (each value in that dict is a list of watchedKey structs).
* The list in the client struct is a plain list, where each node's value is a pointer to a watchedKey.
* The list in the db db->watched_keys is different, the listnode member that's embedded in this struct
* is the node in the dict. And the value inside that listnode is a pointer to the that list, and we can use
* struct member offset math to get from the listnode to the watchedKey struct.
* This is done to avoid the need for listSearchKey and dictFind when we remove from the list. */
typedef struct watchedKey {
listNode node;
robj *key;
redisDb *db;
client *client;
unsigned expired:1; /* Flag that we're watching an already expired key. */
} watchedKey;
/* Attach a watchedKey to the list of clients watching that key. */
static inline void watchedKeyLinkToClients(list *clients, watchedKey *wk) {
wk->node.value = clients; /* Point the value back to the list */
listLinkNodeTail(clients, &wk->node); /* Link the embedded node */
}
/* Get the list of clients watching that key. */
static inline list *watchedKeyGetClients(watchedKey *wk) {
return listNodeValue(&wk->node); /* embedded node->value points back to the list */
}
/* Get the node with wk->client in the list of clients watching that key. Actually it
* is just the embedded node. */
static inline listNode *watchedKeyGetClientNode(watchedKey *wk) {
return &wk->node;
}
/* Watch for the specified key */
void watchForKey(client *c, robj *key) {
list *clients = NULL;
@ -303,8 +324,8 @@ void watchForKey(client *c, robj *key) {
wk->db = c->db;
wk->expired = keyIsExpired(c->db, key);
incrRefCount(key);
listAddNodeTail(c->watched_keys,wk);
listAddNodeTail(clients,wk);
listAddNodeTail(c->watched_keys, wk);
watchedKeyLinkToClients(clients, wk);
}
/* Unwatch all the keys watched by this client. To clean the EXEC dirty
@ -319,12 +340,11 @@ void unwatchAllKeys(client *c) {
list *clients;
watchedKey *wk;
/* Lookup the watched key -> clients list and remove the client's wk
* from the list */
/* Remove the client's wk from the list of clients watching the key. */
wk = listNodeValue(ln);
clients = dictFetchValue(wk->db->watched_keys, wk->key);
clients = watchedKeyGetClients(wk);
serverAssertWithInfo(c,NULL,clients != NULL);
listDelNode(clients,listSearchKey(clients,wk));
listUnlinkNode(clients, watchedKeyGetClientNode(wk));
/* Kill the entry at all if this was the only client */
if (listLength(clients) == 0)
dictDelete(wk->db->watched_keys, wk->key);
@ -367,7 +387,7 @@ void touchWatchedKey(redisDb *db, robj *key) {
/* Check if we are already watching for this key */
listRewind(clients,&li);
while((ln = listNext(&li))) {
watchedKey *wk = listNodeValue(ln);
watchedKey *wk = member2struct(watchedKey, node, ln);
client *c = wk->client;
if (wk->expired) {
@ -420,7 +440,7 @@ void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with) {
if (!clients) continue;
listRewind(clients,&li);
while((ln = listNext(&li))) {
watchedKey *wk = listNodeValue(ln);
watchedKey *wk = member2struct(watchedKey, node, ln);
if (wk->expired) {
if (!replaced_with || !dictFind(replaced_with->dict, key->ptr)) {
/* Expired key now deleted. No logical change. Clear the

View File

@ -100,6 +100,13 @@ typedef struct redisObject robj;
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
/* Offset of a member in a struct */
#define member_offset(struct_name, member_name) ((size_t)&(((struct_name *)0)->member_name))
/* Get the pointer of the outer struct from a member address */
#define member2struct(struct_name, member_name, member_addr) \
((struct_name *)((uint8_t*)member_addr - member_offset(struct_name, member_name)))
/* Error codes */
#define C_OK 0
#define C_ERR -1