From 98328ae2ecd0b7fd299d45e63ee25ec46aa92adc Mon Sep 17 00:00:00 2001 From: Martin Dimitrov Date: Fri, 13 Feb 2026 23:45:54 -0700 Subject: [PATCH] Pause dict auto-resize during multi-field deletion (#14783) The idea comes directly from ValKey: https://github.com/valkey-io/valkey/pull/3144 Deleting many fields from a hash/zset/set stored as a dict can trigger repeated shrink/rehash work during the loop. --------- Co-authored-by: Binbin Co-authored-by: debing.sun --- src/t_hash.c | 9 +++++++++ src/t_set.c | 6 ++++++ src/t_zset.c | 6 ++++++ tests/unit/type/set.tcl | 9 ++++++++- 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/t_hash.c b/src/t_hash.c index d3720360b..830ecc52c 100644 --- a/src/t_hash.c +++ b/src/t_hash.c @@ -1,6 +1,9 @@ /* * Copyright (c) 2009-Present, Redis Ltd. * All rights reserved. + * + * Copyright (c) 2024-present, Valkey contributors. + * All rights reserved. * * Licensed under your choice of (a) the Redis Source Available License 2.0 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the @@ -2926,6 +2929,8 @@ void hdelCommand(client *c) { * field with expiration and removes it from global HFE DS. */ int isHFE = hashTypeIsFieldsWithExpire(o); + if (o->encoding == OBJ_ENCODING_HT) + dictPauseAutoResize((dict*)o->ptr); for (j = 2; j < c->argc; j++) { if (hashTypeDelete(o,c->argv[j]->ptr)) { deleted++; @@ -2939,6 +2944,10 @@ void hdelCommand(client *c) { } } } + if (!keyremoved && o->encoding == OBJ_ENCODING_HT) { + dictResumeAutoResize((dict*)o->ptr); + dictShrinkIfNeeded((dict*)o->ptr); + } if (server.memory_tracking_enabled && !keyremoved) updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), o, oldsize, kvobjAllocSize(o)); if (deleted) { diff --git a/src/t_set.c b/src/t_set.c index 6f2db737e..709b6ffec 100644 --- a/src/t_set.c +++ b/src/t_set.c @@ -657,6 +657,8 @@ void sremCommand(client *c) { if (server.memory_tracking_enabled) oldsize = kvobjAllocSize(set); + if (set->encoding == OBJ_ENCODING_HT) + dictPauseAutoResize((dict*)set->ptr); for (j = 2; j < c->argc; j++) { if (setTypeRemove(set,c->argv[j]->ptr)) { deleted++; @@ -669,6 +671,10 @@ void sremCommand(client *c) { } } } + if (!keyremoved && set->encoding == OBJ_ENCODING_HT) { + dictResumeAutoResize((dict*)set->ptr); + dictShrinkIfNeeded((dict*)set->ptr); + } if (server.memory_tracking_enabled && !keyremoved) updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), set, oldsize, kvobjAllocSize(set)); if (deleted) { diff --git a/src/t_zset.c b/src/t_zset.c index da57c0998..bfc104f52 100644 --- a/src/t_zset.c +++ b/src/t_zset.c @@ -2118,6 +2118,8 @@ void zremCommand(client *c) { int64_t oldlen = (int64_t) zsetLength(zobj); if (server.memory_tracking_enabled) oldsize = kvobjAllocSize(zobj); + if (zobj->encoding == OBJ_ENCODING_SKIPLIST) + dictPauseAutoResize(((zset*)zobj->ptr)->dict); for (j = 2; j < c->argc; j++) { if (zsetDel(zobj, c->argv[j]->ptr)) deleted++; if (zsetLength(zobj) == 0) { @@ -2129,6 +2131,10 @@ void zremCommand(client *c) { break; } } + if (!keyremoved && zobj->encoding == OBJ_ENCODING_SKIPLIST) { + dictResumeAutoResize(((zset*)zobj->ptr)->dict); + dictShrinkIfNeeded(((zset*)zobj->ptr)->dict); + } if (server.memory_tracking_enabled && !keyremoved) updateSlotAllocSize(c->db, getKeySlot(key->ptr), zobj, oldsize, kvobjAllocSize(zobj)); diff --git a/tests/unit/type/set.tcl b/tests/unit/type/set.tcl index 054c9784e..ae315844d 100644 --- a/tests/unit/type/set.tcl +++ b/tests/unit/type/set.tcl @@ -1026,7 +1026,14 @@ foreach type {single multiple single_multiple} { break } } - r srem $myset {*}$members + r deferred 1 + foreach m $members { + r srem $myset $m + } + foreach m $members { + r read + } + r deferred 0 } proc verify_rehashing_completed_key {myset table_size keys} {