Reduce redundant key slot calculations on expiration checks (#13796)

On high-pipeline/fast commands use-cases, expireIfNeeded can take up to
3% cpu cycles. 

This PR introduces an optimization where key expiration checks leverage
key slots to improve efficiency.

---------

Co-authored-by: debing.sun <debing.sun@redis.com>
Co-authored-by: ShooterIT <wangyuancode@163.com>
This commit is contained in:
Filipe Oliveira (Redis) 2025-02-25 03:55:30 +00:00 committed by GitHub
parent 0200e8ada6
commit 985bf68f34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 48 additions and 13 deletions

View File

@ -35,9 +35,12 @@ typedef enum {
KEY_DELETED /* The key was deleted now. */ KEY_DELETED /* The key was deleted now. */
} keyStatus; } keyStatus;
static inline keyStatus expireIfNeededWithSlot(redisDb *db, robj *key, int flags, const int keySlot);
keyStatus expireIfNeeded(redisDb *db, robj *key, int flags); keyStatus expireIfNeeded(redisDb *db, robj *key, int flags);
int keyIsExpired(redisDb *db, robj *key); int keyIsExpired(redisDb *db, robj *key);
static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite, dictEntry *de); static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite, dictEntry *de);
static inline dictEntry *dbFindWithKeySlot(redisDb *db, void *key, int keySlot);
static inline dictEntry *dbFindExpiresWithKeySlot(redisDb *db, void *key, int keySlot);
/* Update LFU when an object is accessed. /* Update LFU when an object is accessed.
* Firstly, decrement the counter if the decrement time is reached. * Firstly, decrement the counter if the decrement time is reached.
@ -136,7 +139,8 @@ void updateKeysizesHist(redisDb *db, int didx, uint32_t type, int64_t oldLen, in
* expired on replicas even if the master is lagging expiring our key via DELs * expired on replicas even if the master is lagging expiring our key via DELs
* in the replication link. */ * in the replication link. */
robj *lookupKey(redisDb *db, robj *key, int flags, dictEntry **deref) { robj *lookupKey(redisDb *db, robj *key, int flags, dictEntry **deref) {
dictEntry *de = dbFind(db, key->ptr); const int key_slot = getKeySlot(key->ptr);
dictEntry *de = dbFindWithKeySlot(db, key->ptr, key_slot);
robj *val = NULL; robj *val = NULL;
if (de) { if (de) {
val = dictGetVal(de); val = dictGetVal(de);
@ -156,7 +160,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags, dictEntry **deref) {
expire_flags |= EXPIRE_AVOID_DELETE_EXPIRED; expire_flags |= EXPIRE_AVOID_DELETE_EXPIRED;
if (flags & LOOKUP_ACCESS_EXPIRED) if (flags & LOOKUP_ACCESS_EXPIRED)
expire_flags |= EXPIRE_ALLOW_ACCESS_EXPIRED; expire_flags |= EXPIRE_ALLOW_ACCESS_EXPIRED;
if (expireIfNeeded(db, key, expire_flags) != KEY_VALID) { if (expireIfNeededWithSlot(db, key, expire_flags, key_slot) != KEY_VALID) {
/* The key is no longer valid. */ /* The key is no longer valid. */
val = NULL; val = NULL;
} }
@ -453,7 +457,7 @@ robj *dbRandomKey(redisDb *db) {
* return a key name that may be already expired. */ * return a key name that may be already expired. */
return keyobj; return keyobj;
} }
if (expireIfNeeded(db,keyobj,0) != KEY_VALID) { if (expireIfNeededWithSlot(db,keyobj,0,randomSlot) != KEY_VALID) {
decrRefCount(keyobj); decrRefCount(keyobj);
continue; /* search for another key. This expired. */ continue; /* search for another key. This expired. */
} }
@ -2058,6 +2062,17 @@ void setExpireWithDictEntry(client *c, redisDb *db, robj *key, long long when, d
rememberSlaveKeyWithExpire(db,key); rememberSlaveKeyWithExpire(db,key);
} }
/* Return the expire time of the specified key, or -1 if no expire
* is associated with this key (i.e. the key is non volatile) */
static inline long long getExpireWithSlot(redisDb *db, robj *key, int keySlot) {
dictEntry *de;
if ((de = dbFindExpiresWithKeySlot(db, key->ptr, keySlot)) == NULL)
return -1;
return dictGetSignedIntegerVal(de);
}
/* Return the expire time of the specified key, or -1 if no expire /* Return the expire time of the specified key, or -1 if no expire
* is associated with this key (i.e. the key is non volatile) */ * is associated with this key (i.e. the key is non volatile) */
long long getExpire(redisDb *db, robj *key) { long long getExpire(redisDb *db, robj *key) {
@ -2175,23 +2190,27 @@ void propagateDeletion(redisDb *db, robj *key, int lazy) {
decrRefCount(argv[1]); decrRefCount(argv[1]);
} }
/* Check if the key is expired. */ /* Internal Check if the key is expired based upon mstime_t. */
int keyIsExpired(redisDb *db, robj *key) { static inline int keyIsExpiredInternal(mstime_t when) {
/* Don't expire anything while loading. It will be done later. */ /* Don't expire anything while loading. It will be done later. */
if (server.loading) return 0; if (server.loading) return 0;
mstime_t when = getExpire(db,key);
mstime_t now;
if (when < 0) return 0; /* No expire for this key */ if (when < 0) return 0; /* No expire for this key */
const mstime_t now = commandTimeSnapshot();
now = commandTimeSnapshot();
/* The key expired if the current (virtual or real) time is greater /* The key expired if the current (virtual or real) time is greater
* than the expire time of the key. */ * than the expire time of the key. */
return now > when; return now > when;
} }
/* Check if the key is expired. */
static inline int keyIsExpiredWithSlot(redisDb *db, robj *key, int keySlot) {
return keyIsExpiredInternal(getExpireWithSlot(db,key,keySlot));
}
/* Check if the key is expired. */
int keyIsExpired(redisDb *db, robj *key) {
return keyIsExpiredInternal(getExpire(db,key));
}
/* This function is called when we are going to perform some operation /* This function is called when we are going to perform some operation
* in a given key, but such key may be already logically expired even if * in a given key, but such key may be already logically expired even if
* it still exists in the database. The main way this function is called * it still exists in the database. The main way this function is called
@ -2224,9 +2243,13 @@ int keyIsExpired(redisDb *db, robj *key) {
* The function returns KEY_EXPIRED if the key is expired BUT not deleted, * The function returns KEY_EXPIRED if the key is expired BUT not deleted,
* or returns KEY_DELETED if the key is expired and deleted. */ * or returns KEY_DELETED if the key is expired and deleted. */
keyStatus expireIfNeeded(redisDb *db, robj *key, int flags) { keyStatus expireIfNeeded(redisDb *db, robj *key, int flags) {
return expireIfNeededWithSlot(db,key,flags,getKeySlot(key->ptr));
}
static inline keyStatus expireIfNeededWithSlot(redisDb *db, robj *key, int flags, const int keySlot) {
if ((server.allow_access_expired) || if ((server.allow_access_expired) ||
(flags & EXPIRE_ALLOW_ACCESS_EXPIRED) || (flags & EXPIRE_ALLOW_ACCESS_EXPIRED) ||
(!keyIsExpired(db,key))) (!keyIsExpiredWithSlot(db,key,keySlot)))
return KEY_VALID; return KEY_VALID;
/* If we are running in the context of a replica, instead of /* If we are running in the context of a replica, instead of
@ -2304,6 +2327,10 @@ int dbExpandExpires(redisDb *db, uint64_t db_size, int try_expand) {
return dbExpandGeneric(db->expires, db_size, try_expand); return dbExpandGeneric(db->expires, db_size, try_expand);
} }
static inline dictEntry *dbFindGenericWithKeySlot(kvstore *kvs, void *key, int keySlot) {
return kvstoreDictFind(kvs, keySlot, key);
}
static dictEntry *dbFindGeneric(kvstore *kvs, void *key) { static dictEntry *dbFindGeneric(kvstore *kvs, void *key) {
return kvstoreDictFind(kvs, getKeySlot(key), key); return kvstoreDictFind(kvs, getKeySlot(key), key);
} }
@ -2312,6 +2339,14 @@ dictEntry *dbFind(redisDb *db, void *key) {
return dbFindGeneric(db->keys, key); return dbFindGeneric(db->keys, key);
} }
static inline dictEntry *dbFindWithKeySlot(redisDb *db, void *key, int keySlot) {
return dbFindGenericWithKeySlot(db->keys, key, keySlot);
}
static inline dictEntry *dbFindExpiresWithKeySlot(redisDb *db, void *key, int keySlot) {
return dbFindGenericWithKeySlot(db->expires, key, keySlot);
}
dictEntry *dbFindExpires(redisDb *db, void *key) { dictEntry *dbFindExpires(redisDb *db, void *key) {
return dbFindGeneric(db->expires, key); return dbFindGeneric(db->expires, key);
} }