summaryrefslogtreecommitdiff
path: root/src/multi.c
diff options
context:
space:
mode:
authorMingyi Kang <jerrykang026@gmail.com>2022-11-23 23:39:08 +0800
committerGitHub <noreply@github.com>2022-11-23 17:39:08 +0200
commit3b462ce566e577ffcb35822a0a2372f691326cd4 (patch)
treeb3f348a75e58251171bd42eafa468000f556dc97 /src/multi.c
parentf36eb5a1ba81f7f5522a5e43000d3e6997651e12 (diff)
downloadredis-3b462ce566e577ffcb35822a0a2372f691326cd4.tar.gz
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.
Diffstat (limited to 'src/multi.c')
-rw-r--r--src/multi.c44
1 files changed, 32 insertions, 12 deletions
diff --git a/src/multi.c b/src/multi.c
index 5249c58bf..d3a91e866 100644
--- a/src/multi.c
+++ b/src/multi.c
@@ -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