Skip to content

Add per slot memory accounting#10

Open
tomerqodo wants to merge 19 commits into
coderabbit_only-issues-20260113-coderabbit_base_add_per_slot_memory_accounting_pr34from
coderabbit_only-issues-20260113-coderabbit_head_add_per_slot_memory_accounting_pr34
Open

Add per slot memory accounting#10
tomerqodo wants to merge 19 commits into
coderabbit_only-issues-20260113-coderabbit_base_add_per_slot_memory_accounting_pr34from
coderabbit_only-issues-20260113-coderabbit_head_add_per_slot_memory_accounting_pr34

Conversation

@tomerqodo

@tomerqodo tomerqodo commented Jan 17, 2026

Copy link
Copy Markdown

Benchmark PR from qodo-benchmark#34

Summary by CodeRabbit

  • New Features

    • CLUSTER SLOT-STATS command now includes memory-bytes metric to report allocated memory per cluster slot
    • Per-slot memory allocation tracking enabled for improved memory visibility in cluster deployments
  • Tests

    • Added validation tests for per-slot memory accounting functionality

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 17, 2026

Copy link
Copy Markdown

Walkthrough

This pull request introduces per-slot memory allocation tracking infrastructure for Redis cluster deployments. New public APIs compute allocation sizes for various data structures, and internal hooks update per-slot accounting whenever objects are modified. A new server configuration flag enables memory tracking per slot when cluster slot stats are active, with corresponding cluster command updates to report memory-bytes metrics.

Changes

Cohort / File(s) Summary
Memory Allocation Size APIs
src/server.h, src/object.c, src/t_hash.c, src/t_list.c, src/t_set.c, src/t_zset.c, src/intset.*
New public functions to compute allocation sizes: stringObjectAllocSize(), kvobjAllocSize(), listTypeAllocSize(), hashTypeAllocSize(), setTypeAllocSize(), zsetAllocSize(), zuiAllocSize(), intsetAllocSize(). These enable centralized size calculations for core data structures.
Per-Slot Accounting Infrastructure
src/db.c, src/kvstore.h, src/kvstore.c, src/server.*
Introduces per-slot allocation tracking via updateSlotAllocSize() and dbgAssertAllocSizePerSlot() functions. Adds memory_tracking_per_slot and dbg_assert_alloc_per_slot server fields. Extends kvstoreDictMetadata with alloc_size field and adds kvstoreDictAllocSize() query function.
Memory Tracking in Data Mutations
src/bitops.c, src/hyperloglog.c, src/module.c, src/defrag.c, src/rdb.c, src/sort.c, src/t_string.c, src/t_stream.c
Integrates per-slot memory tracking into data modification paths: captures old allocation size before mutations and calls updateSlotAllocSize() after operations complete. Applied consistently across string, hyperloglog, list, set, zset, hash, and stream operations.
Cluster Slot Stats Integration
src/cluster_slot_stats.c, src/commands/cluster-slot-stats.json, src/pubsub.c
Adds MEMORY_BYTES enum value to slot statistics; updates SLOT-STATS reply schema with memory-bytes field; adds memory-bytes to ORDERBY options (when memory tracking enabled); replaces direct flag checks with clusterSlotStatsEnabled() helper.
Debug & Testing Support
src/debug.c, tests/unit/cluster/slot-stats.tcl, tests/unit/info-keysizes.tcl
Adds DEBUG ALLOCSIZE-SLOTS-ASSERT subcommand for testing allocation size invariants; extends cluster slot-stats tests to validate memory-bytes metric ordering.
SDS Allocation Correction
src/sds.h
Adjusts sdsAllocSize() calculation to include null terminator byte in total allocation across all SDS types.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A rabbit bounds through memory trails,
Counting bytes in every detail,
Per-slot tracking, oh what a sight,
Allocations flowing left and right,
Through lists and sets and zsets so bright,
The cluster now tracks memory tight! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add per slot memory accounting' directly describes the main feature introduced across all modified files: per-slot memory tracking and allocation accounting infrastructure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
src/t_stream.c (1)

4391-4443: Fix per-slot accounting when DELREF removes PEL refs without deleting entries.

streamCleanupEntryCGroupRefs() can reduce s->alloc_size even when no stream items are deleted. Since the accounting update is gated by if (deleted), per-slot stats can become stale. Move the update outside the deletion check (and guard by size change).

🐛 Proposed fix
-    /* Update the stream's first ID. */
-    if (deleted) {
-        if (server.memory_tracking_per_slot)
-            updateSlotAllocSize(c->db,getKeySlot(c->argv[1]->ptr),old_alloc,s->alloc_size);
+    if (server.memory_tracking_per_slot && old_alloc != s->alloc_size)
+        updateSlotAllocSize(c->db,getKeySlot(c->argv[1]->ptr),old_alloc,s->alloc_size);
+
+    /* Update the stream's first ID. */
+    if (deleted) {
         if (s->length == 0) {
             s->first_id.ms = 0;
             s->first_id.seq = 0;
src/kvstore.h (1)

42-123: Add explicit alloc_size reset per-dict in kvstoreEmpty, matching keysizes_hist pattern.

The alloc_size field is initialized to 0 via zcalloc() on kvstore creation, but in kvstoreEmpty() it lacks explicit per-dict reset. Line 298 resets keysizes_hist within the dict loop, but alloc_size relies solely on destructor callbacks to decrement it via kvstoreTrackDeallocation(). Since kvstoreTrackDeallocation() returns early if server.memory_tracking_per_slot is disabled (line 221), alloc_size may not reach 0, causing the assertion at line 306 (via freeDictIfNeeded()) to fail.

Add explicit reset for alloc_size after dictEmpty() or alongside keysizes_hist:

if (kvs->flags & KVSTORE_ALLOC_META_KEYS_HIST) {
    kvstoreDictMetaEx *metaExt = (kvstoreDictMetaEx *) metadata;
    memset(&metaExt->meta.keysizes_hist, 0, sizeof(metaExt->meta.keysizes_hist));
    metaExt->meta.alloc_size = 0;  // Add this line
}
src/t_list.c (2)

972-981: Potential accounting drift if shrinking conversion occurs.

The updateSlotAllocSize call at lines 972-973 happens before the shrinking conversion at line 980. If listTypeTryConversion changes the encoding (e.g., quicklist → listpack), the slot allocation size recorded won't reflect the post-conversion size.

Compare with listElementsRemoved where the conversion happens first, then the update. Consider reordering to update after the potential conversion.

Suggested fix
     notifyKeyspaceEvent(NOTIFY_LIST,"ltrim",c->argv[1],c->db->id);
     if ((llenNew = listTypeLength(o)) == 0) {
         dbDeleteSkipKeysizesUpdate(c->db,c->argv[1]);
         notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
         llenNew = -1; /* Indicate key deleted to updateKeysizesHist() */
     } else {
         listTypeTryConversion(o,LIST_CONV_SHRINKING,NULL,NULL);
     }
+    if (clusterSlotStatsEnabled())
+        updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, listTypeAllocSize(o));
-    if (clusterSlotStatsEnabled())
-        updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, listTypeAllocSize(o));
     updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_LIST, llen, llenNew);

Actually, the fix requires moving the updateSlotAllocSize call after the if/else block to capture post-conversion size:

-    if (clusterSlotStatsEnabled())
-        updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, listTypeAllocSize(o));
     notifyKeyspaceEvent(NOTIFY_LIST,"ltrim",c->argv[1],c->db->id);
     if ((llenNew = listTypeLength(o)) == 0) {
+        if (clusterSlotStatsEnabled())
+            updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, listTypeAllocSize(o));
         dbDeleteSkipKeysizesUpdate(c->db,c->argv[1]);
         notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
         llenNew = -1; /* Indicate key deleted to updateKeysizesHist() */
     } else {
         listTypeTryConversion(o,LIST_CONV_SHRINKING,NULL,NULL);
+        if (clusterSlotStatsEnabled())
+            updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, listTypeAllocSize(o));
     }

1144-1154: Same ordering issue: shrinking conversion after slot update.

Similar to ltrimCommand, the shrinking conversion at line 1153 happens after updateSlotAllocSize at lines 1144-1145. If the list encoding changes due to shrinking, the slot allocation will not reflect the post-conversion size.

Suggested fix
     if (removed) {
         long ll = listTypeLength(subject);
-        if (clusterSlotStatsEnabled())
-            updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, listTypeAllocSize(subject));
         updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_LIST, ll + removed, ll);
         notifyKeyspaceEvent(NOTIFY_LIST,"lrem",c->argv[1],c->db->id);
         
         if (ll == 0) {
+            if (clusterSlotStatsEnabled())
+                updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, listTypeAllocSize(subject));
             dbDelete(c->db,c->argv[1]);
             notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
         } else {
             listTypeTryConversion(subject,LIST_CONV_SHRINKING,NULL,NULL);
+            if (clusterSlotStatsEnabled())
+                updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, listTypeAllocSize(subject));
         }
         signalModifiedKey(c,c->db,c->argv[1]);
     }
src/module.c (1)

4610-4634: Update per-slot accounting after list shrink conversion.

listTypeTryConversion() can change allocations after the pop, but the per-slot delta is only updated before it. This can skew slot memory stats when a pop triggers a shrink. Consider mirroring the RM_ListDelete pattern to capture the post-conversion delta (Line 4633).

🛠️ Proposed fix
-    if (!moduleDelKeyIfEmpty(key)) {
-        listTypeTryConversion(key->kv, LIST_CONV_SHRINKING, moduleFreeListIterator, key);
-    }
+    if (!moduleDelKeyIfEmpty(key)) {
+        if (server.memory_tracking_per_slot)
+            oldsize = listTypeAllocSize(key->kv);
+        listTypeTryConversion(key->kv, LIST_CONV_SHRINKING, moduleFreeListIterator, key);
+        if (server.memory_tracking_per_slot)
+            updateSlotAllocSize(key->db, getKeySlot(key->key->ptr),
+                                oldsize, listTypeAllocSize(key->kv));
+    }
src/server.h (1)

3102-3118: Fix line 1236: use pre-captured oldsize instead of recalculating after mutation.

The call at line 1236 in src/t_list.c incorrectly passes listTypeAllocSize(kvsrc) as the oldsize parameter, but this is calculated after the listTypePop(kvsrc, wherefrom) mutation on line 1236. The pre-mutation size was already captured on line 1235. Change:

listElementsRemoved(c, skey, wherefrom, kvsrc, 1, listTypeAllocSize(kvsrc), 1, NULL);

to:

listElementsRemoved(c, skey, wherefrom, kvsrc, 1, oldsize, 1, NULL);

The other four callers (lines 705, 848, 860, 1336) correctly capture oldsize before mutations.

🤖 Fix all issues with AI agents
In `@src/debug.c`:
- Around line 550-555: The DEBUG subcommand "ALLOCSIZE-SLOTS-ASSERT" is
implemented but missing from the DEBUG help output; add an entry for
"ALLOCSIZE-SLOTS-ASSERT" to the DEBUG help list (the help text array or the
block that builds the DEBUG help reply in the debug command handler) so users
can discover it, include a short description like "enable/disable per-slot
allocsize assertions" and ensure it appears alongside the other DEBUG
subcommands and matches the existing formatting/placement conventions used for
help entries in the debug command output.

In `@src/server.c`:
- Around line 2848-2850: The config flag cluster-slot-stats-enabled is
MODIFIABLE_CONFIG but server.memory_tracking_per_slot is only set in
initServer(), so runtime CONFIG SET won't flip memory tracking; fix by adding a
CONFIG SET update handler that updates server.memory_tracking_per_slot whenever
cluster-slot-stats-enabled changes (or alternatively change the config
declaration to IMMUTABLE_CONFIG). Implement the handler to read the new boolean
value, assign it to server.memory_tracking_per_slot, and perform any start/stop
work needed for memory tracking (referencing memory_tracking_per_slot and
initServer() for where it's originally set) so the runtime change takes effect
immediately.

In `@src/server.h`:
- Around line 2044-2045: Add an apply callback for the MODIFIABLE_CONFIG
"cluster-slot-stats-enabled" that updates the global memory_tracking_per_slot by
setting memory_tracking_per_slot = clusterSlotStatsEnabled() when the config is
changed at runtime; register this callback in the config table entry for
"cluster-slot-stats-enabled" and, after updating memory_tracking_per_slot, call
the existing slot metadata reinitialization routine (e.g., the function used
elsewhere to rebuild or reinit per-slot metadata) so per-slot tracking state is
consistent with the new setting.

In `@src/t_hash.c`:
- Around line 1343-1357: hashTypeAllocSize is undercounting memory for
OBJ_ENCODING_HT by not including the dict metadata bytes; update the
OBJ_ENCODING_HT branch in hashTypeAllocSize(const robj *o) to add the dict
metadata size returned by htGetMetadataSize(d) (i.e., include
*htGetMetadataSize(d)) alongside sizeof(dict) and dictMemUsage(d) so the total
allocation includes the metadata bytes for the dict structure.

In `@src/t_set.c`:
- Around line 784-799: oldsize is never captured before membership checks so
updateSlotAllocSize() receives 0 and over-increments the slot; capture the
current allocation size into oldsize by calling oldsize = setTypeAllocSize(set)
once after confirming set exists (replace the discarded setTypeAllocSize(set)
call), then after the loop call updateSlotAllocSize(c->db,
getKeySlot(c->argv[1]->ptr), oldsize, setTypeAllocSize(set)) so you pass the
original and the new size; references: lookupKeyRead, checkType,
setTypeAllocSize, setTypeIsMember, updateSlotAllocSize.
- Around line 658-675: When a set becomes empty we must pass newsize=0 to
updateSlotAllocSize so the slot's allocation is cleared synchronously; replace
the call to updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize,
setTypeAllocSize(set)) that occurs inside the branch where setTypeSize(set) == 0
with updateSlotAllocSize(..., oldsize, 0) immediately before calling
dbDeleteSkipKeysizesUpdate(c->db, c->argv[1]); apply the same change in the
equivalent locations referenced in t_hash.c (around the hash deletion path) and
in t_zset.c (the three zset deletion paths) by updating their
updateSlotAllocSize calls to use newsize=0 when the data structure becomes
empty, using the surrounding symbols setTypeRemove, setTypeSize,
setTypeAllocSize, updateSlotAllocSize and dbDeleteSkipKeysizesUpdate to locate
the spots.
🧹 Nitpick comments (8)
src/intset.h (1)

52-52: Consider const-correctness for the new API.

The allocation-size helper doesn’t mutate the intset, so accepting const intset * would better communicate intent and align with other read-only APIs.

src/commands/cluster-slot-stats.json (1)

39-41: Optional: document memory-bytes semantics in schema

Add a short description (and note it’s present only when per-slot tracking is enabled) to make the schema self‑explanatory for clients.

src/rdb.c (1)

1449-1454: Only update slot accounting after a successful save.

updateSlotAllocSize() runs before the res < 0 check. If the save fails, slot stats shouldn’t be touched. Consider moving the error check above the update.

Suggested fix
-        res = rdbSaveKeyValuePair(rdb, &key, kv, expire, dbid);
-        if (server.memory_tracking_per_slot)
-            updateSlotAllocSize(db, curr_slot, oldsize, kvobjAllocSize(kv));
-        if (res < 0) goto werr;
+        res = rdbSaveKeyValuePair(rdb, &key, kv, expire, dbid);
+        if (res < 0) goto werr;
+        if (server.memory_tracking_per_slot)
+            updateSlotAllocSize(db, curr_slot, oldsize, kvobjAllocSize(kv));
src/sort.c (1)

480-492: Avoid redundant alloc-size updates in the read-only zset iteration.

This block doesn’t mutate the zset, so zsetAllocSize() before/after is a no-op with extra cost. Consider removing it.

Suggested cleanup
-        if (server.memory_tracking_per_slot)
-            oldsize = zsetAllocSize(sortval);
         dictInitIterator(&di, set);
         while((setele = dictNext(&di)) != NULL) {
             sdsele =  dictGetKey(setele);
             vector[j].obj = createStringObject(sdsele,sdslen(sdsele));
             vector[j].u.score = 0;
             vector[j].u.cmpobj = NULL;
             j++;
         }
         dictResetIterator(&di);
-        if (server.memory_tracking_per_slot)
-            updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, zsetAllocSize(sortval));
src/t_list.c (1)

507-508: Inconsistent guard condition for oldsize capture.

This function uses server.memory_tracking_per_slot to guard the oldsize capture, but all other functions in this file (e.g., linsertCommand, lsetCommand, popGenericCommand, ltrimCommand, etc.) use clusterSlotStatsEnabled() for the same purpose. For consistency and maintainability, consider using the same guard condition throughout.

Suggested fix for consistency
-    if (server.memory_tracking_per_slot)
+    if (clusterSlotStatsEnabled())
         oldsize = listTypeAllocSize(lobj);
src/object.c (1)

1221-1250: Implementation is correct; note the MODULE type limitation.

The base size calculation and per-type delegation are correct. The embedded string handling (line 1230-1231) combined with stringObjectAllocSize returning 0 for EMBSTR avoids double-counting.

Note: The TODO at line 1247 means MODULE objects have incomplete per-slot memory accounting (only the kvobj header is counted, not the module value). This is acceptable as a known limitation, but consider adding a brief comment at the updateSlotAllocSize call sites indicating that MODULE types are undercounted.

src/t_zset.c (2)

2952-2958: Consider removing memory tracking for read-only source sets.

The ZUNION/ZINTER/ZDIFF operations iterate over source sets but never modify them. The source sets' memory allocation cannot change during these read-only operations, so zuiAllocSize(&src[i]) will always equal src[i].oldsize.

This tracking adds overhead without providing value. Consider removing it if per-slot accounting is only needed for actual mutations.


3918-3926: Memory tracking on read-only commands is unnecessary.

Commands like ZSCORE, ZMSCORE, ZRANK, ZSCAN, ZRANGE, and ZRANDMEMBER are read-only operations that cannot modify the zset's memory allocation. The updateSlotAllocSize call will always compute a zero delta.

This applies to multiple locations in this file where read-only commands have tracking added. While the pattern is consistent and harmless, it adds overhead. Consider gating these updates behind a check like:

// Only track if command can modify the object
if (is_write_operation && server.memory_tracking_per_slot)
    updateSlotAllocSize(...);

Or simply remove tracking from read-only command paths.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b043ac5 and f56c224.

📒 Files selected for processing (27)
  • src/bitops.c
  • src/cluster_slot_stats.c
  • src/commands/cluster-slot-stats.json
  • src/db.c
  • src/debug.c
  • src/defrag.c
  • src/hyperloglog.c
  • src/intset.c
  • src/intset.h
  • src/kvstore.c
  • src/kvstore.h
  • src/module.c
  • src/object.c
  • src/pubsub.c
  • src/rdb.c
  • src/sds.h
  • src/server.c
  • src/server.h
  • src/sort.c
  • src/t_hash.c
  • src/t_list.c
  • src/t_set.c
  • src/t_stream.c
  • src/t_string.c
  • src/t_zset.c
  • tests/unit/cluster/slot-stats.tcl
  • tests/unit/info-keysizes.tcl
🧰 Additional context used
🧬 Code graph analysis (19)
src/intset.h (1)
src/intset.c (1)
  • intsetAllocSize (105-109)
src/debug.c (2)
src/object.c (1)
  • getLongLongFromObjectOrReply (1122-1134)
src/networking.c (1)
  • addReply (461-476)
src/hyperloglog.c (2)
src/object.c (2)
  • stringObjectAllocSize (1020-1033)
  • stringObjectLen (1011-1018)
src/db.c (3)
  • updateSlotAllocSize (128-137)
  • getKeySlot (410-437)
  • updateKeysizesHist (76-126)
src/sort.c (2)
src/t_zset.c (2)
  • zsetAllocSize (1235-1249)
  • zsetConvert (1285-1287)
src/db.c (2)
  • updateSlotAllocSize (128-137)
  • getKeySlot (410-437)
src/kvstore.h (1)
src/kvstore.c (2)
  • kvstoreDictAllocSize (770-775)
  • kvstoreTrackDeallocation (220-231)
src/pubsub.c (1)
src/server.h (1)
  • clusterSlotStatsEnabled (3738-3738)
src/t_string.c (3)
src/object.c (1)
  • stringObjectAllocSize (1020-1033)
src/sds.c (2)
  • sdsgrowzero (502-513)
  • sdscatlen (520-529)
src/db.c (2)
  • updateSlotAllocSize (128-137)
  • getKeySlot (410-437)
src/t_list.c (4)
src/quicklist.c (1)
  • quicklistAllocSize (201-201)
src/listpack.c (1)
  • lpBytes (1533-1535)
src/server.h (1)
  • clusterSlotStatsEnabled (3738-3738)
src/db.c (2)
  • updateSlotAllocSize (128-137)
  • getKeySlot (410-437)
src/rdb.c (2)
src/object.c (1)
  • kvobjAllocSize (1221-1250)
src/db.c (1)
  • updateSlotAllocSize (128-137)
src/bitops.c (3)
src/object.c (1)
  • stringObjectAllocSize (1020-1033)
src/sds.c (1)
  • sdsgrowzero (502-513)
src/db.c (2)
  • updateSlotAllocSize (128-137)
  • getKeySlot (410-437)
src/server.c (3)
src/kvstore.c (1)
  • kvstoreTrackDeallocation (220-231)
src/server.h (1)
  • clusterSlotStatsEnabled (3738-3738)
src/db.c (1)
  • dbgAssertAllocSizePerSlot (183-204)
src/t_hash.c (5)
src/listpack.c (1)
  • lpBytes (1533-1535)
src/db.c (1)
  • updateSlotAllocSize (128-137)
src/object.c (1)
  • kvobjGetKey (234-246)
src/dict.c (1)
  • dictMemUsage (1096-1099)
src/server.h (1)
  • htGetMetadataSize (3599-3601)
src/kvstore.c (2)
src/object.c (1)
  • kvobjAllocSize (1221-1250)
src/dict.c (1)
  • dictEmpty (1778-1793)
src/db.c (3)
src/kvstore.c (6)
  • kvstoreGetDictMetadata (1006-1013)
  • kvstoreIteratorInit (642-649)
  • kvstoreIteratorNext (689-704)
  • kvstoreIteratorGetCurrentDictIndex (683-686)
  • kvstoreIteratorRelease (652-658)
  • kvstoreNumDicts (604-606)
src/server.h (1)
  • dictGetKV (3810-3810)
src/object.c (1)
  • kvobjAllocSize (1221-1250)
src/t_set.c (5)
src/dict.c (1)
  • dictMemUsage (1096-1099)
src/server.h (1)
  • htGetMetadataSize (3599-3601)
src/intset.c (1)
  • intsetAllocSize (105-109)
src/listpack.c (2)
  • lpBytes (1533-1535)
  • lpNew (221-228)
src/db.c (2)
  • updateSlotAllocSize (128-137)
  • getKeySlot (410-437)
src/defrag.c (2)
src/object.c (1)
  • kvobjAllocSize (1221-1250)
src/db.c (1)
  • updateSlotAllocSize (128-137)
src/server.h (5)
src/t_list.c (2)
  • listTypeAllocSize (215-226)
  • listElementsRemoved (787-808)
src/object.c (2)
  • stringObjectAllocSize (1020-1033)
  • kvobjAllocSize (1221-1250)
src/t_zset.c (1)
  • zsetAllocSize (1235-1249)
src/t_hash.c (1)
  • hashTypeAllocSize (1343-1358)
src/db.c (2)
  • updateSlotAllocSize (128-137)
  • dbgAssertAllocSizePerSlot (183-204)
src/module.c (2)
src/t_list.c (4)
  • listTypeAllocSize (215-226)
  • listTypePush (147-170)
  • listTypeLength (205-213)
  • listTypePop (176-203)
src/t_zset.c (1)
  • zsetAllocSize (1235-1249)
src/object.c (7)
src/sds.h (1)
  • sdsAllocSize (167-181)
src/zmalloc.c (1)
  • zmalloc_size (458-462)
src/module.c (1)
  • moduleGetMemUsage (12456-12469)
src/t_list.c (1)
  • listTypeAllocSize (215-226)
src/t_set.c (1)
  • setTypeAllocSize (482-496)
src/t_zset.c (1)
  • zsetAllocSize (1235-1249)
src/t_hash.c (1)
  • hashTypeAllocSize (1343-1358)
🔇 Additional comments (99)
src/t_set.c (12)

482-496: LGTM: setTypeAllocSize covers all encodings.

The allocation accounting looks consistent across HT/intset/listpack.


617-640: LGTM: per-slot alloc deltas around conversion and add loop are consistent.


694-761: LGTM: SMOVE per-slot alloc tracking is consistent for src/dst.


766-779: LGTM: sismember per-slot tracking is benign for read-only access.


822-951: LGTM: per-slot alloc tracking in SPOP(count) branches is consistent.


1048-1074: LGTM: SPOP alloc tracking around mutation looks correct.


1318-1336: LGTM: SRANDMEMBER per-slot tracking is safe for read-only access.


1344-1364: LGTM: setopsrc wrapper + comparator updates look sound.


1379-1517: LGTM: SINTER per-slot tracking is consistently applied.


1561-1587: LGTM: SMEMBERS per-slot tracking is safe for read-only access.


1629-1791: LGTM: SUNION/SDIFF per-slot tracking integrates cleanly with setopsrc.


1852-1862: LGTM: SSCAN per-slot tracking is benign for read-only access.

tests/unit/cluster/slot-stats.tcl (1)

789-789: LGTM: ORDERBY test now covers memory-bytes.

src/sds.h (1)

170-178: LGTM: sdsAllocSize now includes the implicit null terminator.

tests/unit/info-keysizes.tcl (1)

487-494: Confirm per-slot tracking is enabled for the debug assert

DEBUG ALLOCSIZE-SLOTS-ASSERT appears to depend on per-slot allocation tracking. This block doesn’t explicitly enable cluster-slot-stats-enabled (or other tracking flags), so please verify the command itself turns tracking on, or add the required config to keep the test deterministic.

src/pubsub.c (1)

485-486: LGTM: helper keeps gating consistent

The helper preserves existing behavior while simplifying the condition.

src/bitops.c (1)

787-804: LGTM: alloc delta tracking on string growth

The old/new allocation accounting is gated correctly and captures growth deltas.

src/intset.c (1)

105-109: LGTM: safe alloc-size helper

The overflow guard aligns with resize logic and makes allocation sizing explicit.

src/sort.c (2)

187-188: LGTM — oldsize pre-declaration is clean.


342-348: Per-slot accounting around zsetConvert looks solid.

src/t_string.c (2)

585-590: Looks good — per-slot accounting around SETRANGE growth.


803-830: Looks good — per-slot accounting around APPEND growth.

src/server.c (3)

352-356: Per-slot deallocation hook looks correct.

Calling kvstoreTrackDeallocation() before decrRefCount() keeps accounting consistent with the object still intact.


2239-2244: Debug flag default is sensible.

Initializing dbg_assert_alloc_per_slot to disabled by default matches the existing debug-assert patterns.


4033-4038: Debug per-slot assertion hook looks good.

The new assertion mirrors the existing debug histogram check and is safely gated.

src/defrag.c (2)

984-1071: Per-slot accounting around defragged KV updates looks consistent.

The oldsize capture and post-defrag update are correctly guarded and scoped to the current slot.


1197-1218: Per-slot accounting for deferred defrag looks correct.

Capturing oldsize before the deferred step and updating afterward aligns with incremental defrag behavior.

src/t_stream.c (1)

2346-2394: Memory-tracking hooks around XADD look solid.

Old/new alloc accounting is captured and updated in both the success and error paths.

src/kvstore.c (4)

152-157: Test-only alloc_size assert looks good.

Nice safety check to ensure per-dict allocation metadata is fully drained before releasing an empty dict.


220-231: Per-slot deallocation tracking is consistent.

The guard on server.memory_tracking_per_slot and metadata flags keeps the update safe and scoped.


326-332: Test-time cleanup invariants are solid.

The explicit dictEmpty() plus alloc_size assert makes release-time checks deterministic in tests.


770-775: Alloc-size accessor is straightforward.

Returning 0 when metadata is unavailable avoids unexpected callers dereferencing NULL.

src/hyperloglog.c (3)

1614-1647: PFADD memory accounting is correctly bracketed.

Capturing oldsize before mutation and updating in both error and success paths is solid.

Also applies to: 1653-1654


1765-1765: PFMERGE memory tracking looks consistent.

The update after potential sparse→dense conversion uses the pre-mutation size as expected.

Also applies to: 1807-1808, 1836-1837


1970-2001: PFDEBUG tracking integration is clean.

The size capture before mutation and updates only on encoding changes keep accounting accurate.

Also applies to: 2014-2015, 2083-2085

src/cluster_slot_stats.c (4)

18-26: MEMORY_BYTES stat wiring is clear.

Enum addition and stat retrieval via kvstoreDictAllocSize() are aligned with new tracking.

Also applies to: 49-55


96-108: Reply shape update is correct.

Conditional map length and memory-bytes emission match the enablement gates.


139-139: Gating via clusterSlotStatsEnabled() is a good consolidation.

Both network byte counters now respect the unified enablement logic.

Also applies to: 251-252


303-305: ORDERBY memory-bytes gate is correct.

Nice to restrict sorting to cases where memory tracking is active.

src/t_hash.c (19)

429-435: ListpackEx expiry tracking is consistent.

The old/new size delta around lpDeleteRange() keeps per-slot accounting aligned.


739-759: Lazy-expire accounting in hashTypeGetValue() is handled well.

Capturing size before mutations and updating after deletion keeps metadata stable.

Also applies to: 794-799


2073-2099: HSETNX tracking is consistent.

The oldsize capture and post-update accounting align with the mutation flow.


2104-2134: HSET tracking hooks look correct.

Updates are applied once after the batch mutation, which is ideal.


2324-2345: HSETEX tracking and lazy flags are well-integrated.

The HFE_LAZY_NO_UPDATE_* flags prevent double counting, and the final update consolidates changes.

Also applies to: 2356-2357, 2430-2432


2463-2502: HINCRBY accounting is correct.

Pre/post size tracking matches the mutation path.


2515-2560: HINCRBYFLOAT accounting is consistent.

Oldsize/newsize boundaries are in the right places.


2647-2704: HGETDEL memory tracking is aligned.

Using the lazy flags and a single post-update keeps accounting correct and avoids double updates.

Also applies to: 2727-2731


2754-2841: HGETEX tracking is in good shape.

Single post-update after possible mutations is the right approach.


2905-2937: HDEL tracking is correct.

The conditional update on early key removal avoids inconsistent deltas.


3011-3049: HGETALL iterator path accounting is clean.

Accounting is properly bracketing potential lazy-expiry side effects.


3079-3090: HSCAN tracking looks fine.

Size updates are gated and occur after scan execution.


3123-3339: HRANDFIELD (count) tracking is correct.

The single update at out: ensures all paths are covered.


3376-3411: HRANDFIELD single-element tracking is consistent.

Old/new size capture brackets the random access safely.


3531-3545: Active-expire path accounting is correct.

Tracking around hashTypeDelete() properly reflects memory changes.


3657-3687: HTTL tracking is consistent with the read path.

Accounting remains guarded and stable.


3731-3809: HEXPIRE tracking is consistent.*

Pre/post sizing around hashTypeSetExDone() keeps per-slot usage accurate.


3954-3989: HPERSIST listpackEx tracking is correct.

The update after TTL removal keeps memory stats aligned.


3994-4023: HPERSIST HT tracking is consistent.

Accounting is gated and stable for the HT encoding.

src/t_list.c (9)

215-226: LGTM!

The implementation correctly handles both list encodings and follows the established pattern in this file for encoding-specific dispatch with a panic fallback for unknown encodings.


572-573: LGTM!

The oldsize is correctly captured before potential encoding conversion, and the slot allocation is updated after the insert operation completes.

Also applies to: 589-590


670-673: LGTM!

Good comment explaining why the update happens unconditionally—encoding conversion may occur regardless of whether the replace succeeds.


700-705: LGTM!

The oldsize is correctly captured before the delete operation and passed to listElementsRemoved for proper accounting.


787-808: LGTM!

The signature change correctly threads the oldsize parameter through to enable accurate slot accounting. The update logic properly handles both the deletion case (where update precedes dbDelete) and the shrinking case (where update follows listTypeTryConversion).


838-860: LGTM!

The oldsize is correctly captured before mutation and passed to listElementsRemoved for both single-element and multi-element pop operations.


1163-1174: LGTM!

Correctly handles both the new list case (where oldsize is appropriately 0) and the existing list case. The update happens after both conversion and push operations.


1226-1236: LGTM!

The two-stage update is intentional and correct:

  1. Lines 1230-1231 account for the pop operation (pre-pop → post-pop size)
  2. Line 1236 passes the post-pop size to listElementsRemoved, which handles potential shrinking conversion and/or deletion

This correctly separates the pop accounting from the shrinking/deletion accounting.


1326-1336: LGTM!

Standard pattern correctly applied: capture oldsize before mutation, pass to listElementsRemoved for accounting.

src/module.c (12)

4569-4593: Per-slot list push accounting looks solid.

Capturing oldsize before conversion and applying updateSlotAllocSize after the push correctly reflects both encoding changes and element growth.


4684-4707: Per-slot list set accounting is consistent.

Capturing oldsize before mutation and applying the delta regardless of success keeps the slot stats aligned with possible encoding changes.


4727-4762: Per-slot list insert accounting looks correct.

The delta update after insertion (plus the keysizes histogram update) keeps both size and memory accounting aligned.


4779-4792: Per-slot list delete accounting is thorough.

Tracking both the delete and the post-delete shrink conversion mirrors the actual allocation changes well.


4877-4895: Per-slot zset add accounting is correct.

The old/new allocation tracking is properly gated and mirrors the mutation paths.


4914-4931: Per-slot zset incr accounting is consistent.

Capturing the pre-change size and updating after zsetAdd keeps slot memory deltas accurate.


4958-4977: Per-slot zset remove accounting is correct.

The empty-key guard plus the delta update on both remove and no-op paths keeps stats stable.


5379-5462: Per-slot hash accounting is well integrated.

Updating per-field deltas during set/delete keeps slot memory stats aligned with hash mutations.


5701-5710: Per-slot stream add accounting looks correct.

Using s->alloc_size before/after append provides accurate deltas.


5754-5759: Per-slot stream delete accounting is correct.

The delta update after deletion reflects alloc_size changes without overcounting.


6057-6062: Per-slot stream trim-by-length accounting is correct.

Tracking alloc_size before/after trim ensures accurate slot deltas even when trimming is approximate.


6093-6098: Per-slot stream trim-by-id accounting is correct.

The delta update around streamTrimByID mirrors the allocation changes properly.

src/db.c (7)

128-137: LGTM - Clean per-slot size update helper.

The implementation correctly handles the NULL dictMeta case (for released slot dicts) and performs atomic delta updates. The test-only underflow assertion at lines 133-134 is appropriate for catching bugs during testing.


179-204: LGTM - Solid debugging assertion for per-slot allocation consistency.

The function correctly iterates through all keys, computes expected per-slot sizes using kvobjAllocSize, and validates against the stored metadata. This will be valuable for catching accounting bugs during development.


386-388: LGTM!

Correctly accounts for newly added keys with oldsize=0 and captures the full allocation size after the key is fully initialized (including expire handling at line 381).


496-498: LGTM!

Mirrors the accounting pattern in dbAddInternal for RDB-loaded keys.


523-523: LGTM - Correctly captures old size before replacement.

The oldsize capture at lines 553-554 correctly happens after the RM_StringDMA refetch at line 551, ensuring we measure the actual object being replaced.

Also applies to: 553-554, 609-611


2517-2517: LGTM - Correctly handles reallocation accounting for expire field addition.

The logic properly:

  1. Only captures oldsize when the key didn't previously have an expire (line 2530-2531)
  2. Only updates accounting when actual reallocation occurs (kv != kvnew at line 2540)

This ensures no spurious accounting updates when just modifying an existing expire value.

Also applies to: 2530-2531, 2542-2543


792-799: The per-slot accounting is properly applied in both sync and async deletion paths. For synchronous deletion, kvstoreDictTwoPhaseUnlinkFree calls dictFreeKey, which invokes the dict type's key destructor (dictDestructorKV). This destructor calls kvstoreTrackDeallocation, which decrements the slot's alloc_size. The async path explicitly calls updateSlotAllocSize before the asynchronous free. Both mechanisms ensure proper accounting—no fix is required.

src/object.c (2)

1020-1033: LGTM - Correctly handles all string encoding types.

The function properly returns:

  • 0 for INT encoding (value stored in pointer)
  • sdsAllocSize() for RAW encoding (external sds allocation)
  • 0 for EMBSTR (already counted in kvobj base size via line 1230-1231)

1207-1219: LGTM - Clean simplification leveraging pre-computed allocation sizes.

The refactoring correctly uses kvobjAllocSize() for all core types (where allocation sizes are computed from data structure properties or maintained incrementally like stream->alloc_size), while preserving the sampling-based approach for MODULE types via moduleGetMemUsage().

src/server.h (5)

2381-2382: Nice addition for per-slot debug assertions.

The new debug flag aligns well with dbg_assert_keysizes and provides a clear knob for targeted checks.


3164-3203: LGTM: allocation helpers exposed in the header.

The prototypes for string/kvobj allocation sizing are consistent with the new per-slot accounting surface.


3776-3779: Header prototypes for slot accounting APIs look good.

No concerns with the added updateSlotAllocSize / dbgAssertAllocSizePerSlot declarations.


3738-3738: No duplicate or conflicting definitions found. The clusterSlotStatsEnabled function is defined exactly once as a static inline in src/server.h:3738 and is correctly used across multiple files (t_list.c, server.c, pubsub.c, cluster_slot_stats.c). The implementation is sound.


3424-3625: HFE_LAZY_NO_UPDATE_ALLOCSIZES flag and allocsize helpers are properly wired throughout all mutation paths.

The flag is correctly respected in lazy hash-field deletion code:

  • hashTypeGetValue() checks the flag before tracking oldsize and updating allocsize (lines 755, 757, 794, 797)
  • onFieldExpire() active expiration handler properly calls updateSlotAllocSize() with hashTypeAllocSize()
  • Batch operations (HGET, HSET variants) set the flag to suppress per-field updates and perform a single bulk allocsize update after the loop completes

All three alloc-size helpers (hashTypeAllocSize, zsetAllocSize, setTypeAllocSize) are used consistently across their respective data types with corresponding updateSlotAllocSize() calls. No per-slot allocation drift issues detected.

src/t_zset.c (6)

1235-1249: LGTM: Memory size computation for zsets is correctly implemented.

The function properly handles both encodings:

  • Listpack: returns lpBytes() for the contiguous memory block
  • Skiplist: aggregates the zset struct, skiplist cached size, dict struct, and dict memory usage

1882-1912: Memory tracking for ZADD is correctly implemented.

The tracking properly handles:

  1. Encoding conversion via zsetTypeMaybeConvert (lines 1882-1886)
  2. Element additions in the loop (lines 1889-1912)
  3. Error path on NaN (lines 1901-1903)

2291-2302: LGTM: Helper function for union/inter/diff operations.

Correctly delegates to the appropriate type-specific allocator based on the source object type.


4086-4165: LGTM: Memory tracking for ZPOP operations is correctly implemented.

The tracking properly captures size before the removal loop and updates after all elements are popped. This is a write operation where tracking is necessary.


1951-1966: LGTM: Memory tracking for ZREM is correctly implemented.

The tracking correctly handles both the key deletion path (updating before dbDeleteSkipKeysizesUpdate) and the partial removal path (updating after the loop when key remains).


4389-4389: Clean exit consolidation via goto out for tracking updates.

The goto out pattern ensures all exit paths converge at the memory tracking update. However, as noted earlier, ZRANDMEMBER is a read-only command, so this tracking won't observe any changes.

Also applies to: 4419-4419, 4440-4440, 4529-4531

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread src/debug.c
Comment on lines +550 to +555
} else if (!strcasecmp(c->argv[1]->ptr,"ALLOCSIZE-SLOTS-ASSERT") && c->argc == 3) {
long long flag;
if (getLongLongFromObjectOrReply(c, c->argv[2], &flag, NULL) != C_OK)
return;
server.dbg_assert_alloc_per_slot = (flag != 0);
addReply(c, shared.ok);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add the new subcommand to DEBUG help output.

The handler is present, but the help list doesn’t mention ALLOCSIZE-SLOTS-ASSERT, which makes it undiscoverable.

✏️ Suggested help entry
 "KEYSIZES-HIST-ASSERT <0|1>",
 "    Enable/disable keysizes histogram assertion after each command.",
+"ALLOCSIZE-SLOTS-ASSERT <0|1>",
+"    Enable/disable per-slot alloc-size assertion after each command.",
🤖 Prompt for AI Agents
In `@src/debug.c` around lines 550 - 555, The DEBUG subcommand
"ALLOCSIZE-SLOTS-ASSERT" is implemented but missing from the DEBUG help output;
add an entry for "ALLOCSIZE-SLOTS-ASSERT" to the DEBUG help list (the help text
array or the block that builds the DEBUG help reply in the debug command
handler) so users can discover it, include a short description like
"enable/disable per-slot allocsize assertions" and ensure it appears alongside
the other DEBUG subcommands and matches the existing formatting/placement
conventions used for help entries in the debug command output.

Comment thread src/server.c
Comment on lines 2848 to 2850
server.client_mem_usage_buckets = NULL;
server.memory_tracking_per_slot = clusterSlotStatsEnabled();
resetReplicationBuffer();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify where server.memory_tracking_per_slot and cluster_slot_stats_enabled are updated.
rg -n -C3 '\bmemory_tracking_per_slot\b'
rg -n -C3 '\bcluster_slot_stats_enabled\b'

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 50396


🏁 Script executed:

#!/bin/bash
# Search for where cluster_slot_stats_enabled is assigned
rg -n 'cluster_slot_stats_enabled\s*=' --max-count=20

# Search for where memory_tracking_per_slot is assigned
rg -n 'memory_tracking_per_slot\s*=' --max-count=20

# Search for CONFIG SET handler
rg -n 'CONFIG.*SET|configSet|config.*set' -i --max-count=20

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 50397


🏁 Script executed:

#!/bin/bash
# Search for cluster-slot-stats-enabled configuration handling
rg -n 'cluster-slot-stats-enabled|cluster_slot_stats_enabled' src/config.c

# Search for cluster_slot_stats_enabled in server.h with context
rg -n -B2 -A2 'cluster_slot_stats_enabled' src/server.h

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 840


🏁 Script executed:

#!/bin/bash
# Search for any set or apply function related to cluster-slot-stats-enabled
rg -n -B5 -A10 'cluster-slot-stats-enabled' src/config.c | head -40

# Search for any place memory_tracking_per_slot is updated after initialization
rg -n 'memory_tracking_per_slot\s*=' src/ --type c

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 2202


🏁 Script executed:

#!/bin/bash
# Verify the clusterSlotStatsEnabled function logic
rg -n -A3 'static inline int clusterSlotStatsEnabled' src/server.h

# Verify cluster_enabled doesn't change at runtime
rg -n 'server.cluster_enabled\s*=' src/server.c

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 293


🏁 Script executed:

#!/bin/bash
# Search for any function that might update memory_tracking_per_slot at runtime
rg -n 'updateSlotAllocSize|memory_tracking_per_slot' src/server.c | head -20

# Check if cluster_enabled can change at runtime
rg -n 'MODIFIABLE_CONFIG|IMMUTABLE_CONFIG' src/config.c | grep cluster-enabled

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 262


Runtime updates to cluster-slot-stats-enabled don't sync memory_tracking_per_slot.

The config cluster-slot-stats-enabled is marked MODIFIABLE_CONFIG (runtime-mutable), but memory_tracking_per_slot is set only during initServer() and never updated afterward. Enabling this config at runtime won't activate memory tracking, and disabling it won't stop the overhead. Either mark cluster-slot-stats-enabled as IMMUTABLE_CONFIG or add an update handler that synchronizes memory_tracking_per_slot when the setting changes via CONFIG SET.

🤖 Prompt for AI Agents
In `@src/server.c` around lines 2848 - 2850, The config flag
cluster-slot-stats-enabled is MODIFIABLE_CONFIG but
server.memory_tracking_per_slot is only set in initServer(), so runtime CONFIG
SET won't flip memory tracking; fix by adding a CONFIG SET update handler that
updates server.memory_tracking_per_slot whenever cluster-slot-stats-enabled
changes (or alternatively change the config declaration to IMMUTABLE_CONFIG).
Implement the handler to read the new boolean value, assign it to
server.memory_tracking_per_slot, and perform any start/stop work needed for
memory tracking (referencing memory_tracking_per_slot and initServer() for where
it's originally set) so the runtime change takes effect immediately.

Comment thread src/server.h
Comment on lines +2044 to 2045
int memory_tracking_per_slot; /* Account used memory per slot */
unsigned int max_new_tls_conns_per_cycle; /* The maximum number of tls connections that will be accepted during each invocation of the event loop. */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n "memory_tracking_per_slot" src
rg -n "cluster_slot_stats_enabled" src
rg -n "clusterSlotStatsEnabled" src

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 15194


🏁 Script executed:

# Check where cluster_slot_stats_enabled is modified at runtime
rg -n "cluster_slot_stats_enabled\s*=" src

# Look for resetSlotStats and slot metadata initialization
rg -n "resetSlotStats|resetSlotUsage|slot.*alloc" src/cluster_slot_stats.c

# Check server.c for initialization ordering
grep -n "memory_tracking_per_slot\|cluster_slot_stats_enabled\|clusterSlotStatsEnabled" src/server.c | head -30

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 153


🏁 Script executed:

# Look for config-related callbacks that might handle cluster_slot_stats_enabled changes
rg -n "cluster_slot_stats_enabled.*callback|setModuleConfig.*cluster|CONFIG SET" src/config.c | head -20

# Check cluster init code
rg -n "clusterInit|resetSlot" src/cluster.c | head -20

# Look for any metadata reset on cluster enable/disable
rg -n "resetSlot|initSlot|clearSlot" src/cluster_slot_stats.c

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 1391


🏁 Script executed:

# Check how createBoolConfig is defined and its callback parameters
rg -n "createBoolConfig.*cluster_slot_stats_enabled" -A 1 src/config.c

# Look at the actual createBoolConfig definition
rg -n "^[^/]*createBoolConfig" src/config.c | head -5

# Check if there's an afterUpdateConfig or similar hook
rg -n "afterUpdate|onUpdate|modifyConfig" src/config.c | head -10

# Look for where cluster_slot_stats_enabled config is actually applied
rg -n "cluster_slot_stats_enabled.*=" src --type c | grep -v "server\."

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 732


🏁 Script executed:

# Look for cluster reinitialization or slot stats reset on config change
rg -n "cluster.*(reset|reinit|init)" src/cluster.c | head -20

# Check if there's any documentation or comments about dynamic cluster_slot_stats_enabled changes
rg -B2 -A2 "cluster_slot_stats_enabled.*MODIFIABLE" src

# Look for server-wide config revalidation or hook after CONFIG SET
rg -n "configSetCommand|processConfigSet" src/config.c | head -10

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 122


🏁 Script executed:

# Look at configSetCommand implementation
sed -n '801,900p' src/config.c

# Check for any post-config hooks or reinitialization
rg -n "afterConfigUpdate|postConfigSet|configReload" src

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 4032


🏁 Script executed:

# Continue reading configSetCommand to see if there's any post-apply hooks
sed -n '900,950p' src/config.c

# Check if cluster_slot_stats_enabled has any apply callback or if it's just NULL
rg -B5 -A5 "createBoolConfig.*cluster-slot-stats-enabled" src/config.c

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 3180


🏁 Script executed:

# Check if there's any code that re-syncs memory_tracking_per_slot after startup
rg -n "memory_tracking_per_slot\s*=" src

# Look at the broader context of clusterSlotStatsEnabled usage to understand intent
rg -B3 -A3 "clusterSlotStatsEnabled()" src/server.c

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 507


🏁 Script executed:

# Get more context around line 2849
sed -n '2840,2860p' src/server.c

# Check if there's intentional duplication or if this is in different code paths
rg -n "memory_tracking_per_slot = clusterSlotStatsEnabled" src/server.c

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 1013


🏁 Script executed:

# Check if there's any comment or documentation about dynamic cluster_slot_stats_enabled changes
rg -B5 "cluster_slot_stats_enabled" src/server.h

# Look for any warning or restriction about runtime changes to cluster config
rg -n "cluster.*dynamic|runtime.*cluster|toggle.*cluster" src

# Check if cluster is cluster-only (can't be toggled at runtime)
rg -n "cluster_enabled.*IMMUTABLE|cluster.*PROTECTED" src/config.c

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 890


🏁 Script executed:

# Check if cluster_enabled is immutable
rg -n "cluster.*IMMUTABLE|createBoolConfig.*cluster-enabled" src/config.c

# Look at the initialization sequence - when is cluster_enabled determined?
rg -n "cluster_enabled.*=" src/server.c | head -20

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 826


🏁 Script executed:

# Check if there's any discussion or TODO about syncing memory_tracking_per_slot on config change
rg -B3 -A3 "memory_tracking|slot.*stats" src/server.c | head -40

# Look for any config validation or post-set hooks related to cluster
rg -n "configNeedsApply" src/config.c | head -10

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 1442


🏁 Script executed:

# Check if clusterInit initializes slot stats metadata
sed -n '1,100p' src/cluster.c | grep -A20 "clusterInit"

# Look at what cluster_slot_stats initialization looks like
rg -n "cluster_slot_stats\s*=" src/server.c

# Check if there's any documentation about when cluster_slot_stats_enabled should be set
rg -B5 -A5 "cluster_slot_stats_enabled" src/config.c | grep -A10 "createBoolConfig"

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 1350


🏁 Script executed:

# Verify the actual runtime behavior: can cluster_slot_stats_enabled be changed at runtime in practice?
# Check if there are any tests or examples
rg -n "CONFIG SET.*cluster-slot-stats" . --type md --type txt

# Check if there's any guard against changing this at runtime
rg -n "cluster_slot_stats_enabled" src/cluster_slot_stats.c | head -20

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 748


🏁 Script executed:

# Let me check the actual slot stats initialization more carefully
rg -n "clusterSlotStat|slot.*stats.*=" src/cluster.c | head -30

# Check if slot alloc metadata is initialized anywhere
rg -n "slot.*alloc|alloc_size.*slot" src/cluster_slot_stats.c

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 119


🏁 Script executed:

# Check what clusterSlotStatResetAll does
rg -B5 -A15 "clusterSlotStatResetAll" src/cluster_slot_stats.c

# Look for alloc_size initialization in slot stats
rg -n "alloc_size" src/cluster_slot_stats.c

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 1236


Add apply callback for cluster-slot-stats-enabled to sync memory_tracking_per_slot at runtime.

cluster-slot-stats-enabled is MODIFIABLE_CONFIG but lacks an apply callback. If toggled at runtime via CONFIG SET, memory_tracking_per_slot remains stale, causing inconsistent per-slot tracking. Implement an apply callback to update memory_tracking_per_slot = clusterSlotStatsEnabled() on config change, and reinitialize slot metadata as needed.

🤖 Prompt for AI Agents
In `@src/server.h` around lines 2044 - 2045, Add an apply callback for the
MODIFIABLE_CONFIG "cluster-slot-stats-enabled" that updates the global
memory_tracking_per_slot by setting memory_tracking_per_slot =
clusterSlotStatsEnabled() when the config is changed at runtime; register this
callback in the config table entry for "cluster-slot-stats-enabled" and, after
updating memory_tracking_per_slot, call the existing slot metadata
reinitialization routine (e.g., the function used elsewhere to rebuild or reinit
per-slot metadata) so per-slot tracking state is consistent with the new
setting.

Comment thread src/t_hash.c
Comment on lines +1343 to +1357
size_t hashTypeAllocSize(const robj *o) {
serverAssertWithInfo(NULL,o,o->type == OBJ_HASH);
size_t size = 0;
if (o->encoding == OBJ_ENCODING_LISTPACK) {
size = lpBytes(o->ptr);
} else if (o->encoding == OBJ_ENCODING_LISTPACK_EX) {
listpackEx *lpt = o->ptr;
size = sizeof(listpackEx) + lpBytes(lpt->lp);
} else if (o->encoding == OBJ_ENCODING_HT) {
dict *d = o->ptr;
size += sizeof(dict) + dictMemUsage(d) + *htGetMetadataSize(d);
} else {
serverPanic("Unknown hash encoding");
}
return size;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Include dict metadata bytes in hashTypeAllocSize().

hashTypeAllocSize() currently omits the dict metadata allocation (e.g., sizeof(size_t) or sizeof(htMetadataEx)), which causes per-slot memory undercounting. Please add the metadata bytes from the dict type.

🛠️ Proposed fix
 } else if (o->encoding == OBJ_ENCODING_HT) {
     dict *d = o->ptr;
-    size += sizeof(dict) + dictMemUsage(d) + *htGetMetadataSize(d);
+    size_t meta_bytes = d->type->dictMetadataBytes ? d->type->dictMetadataBytes(d) : 0;
+    size += sizeof(dict) + meta_bytes + dictMemUsage(d) + *htGetMetadataSize(d);
 } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
size_t hashTypeAllocSize(const robj *o) {
serverAssertWithInfo(NULL,o,o->type == OBJ_HASH);
size_t size = 0;
if (o->encoding == OBJ_ENCODING_LISTPACK) {
size = lpBytes(o->ptr);
} else if (o->encoding == OBJ_ENCODING_LISTPACK_EX) {
listpackEx *lpt = o->ptr;
size = sizeof(listpackEx) + lpBytes(lpt->lp);
} else if (o->encoding == OBJ_ENCODING_HT) {
dict *d = o->ptr;
size += sizeof(dict) + dictMemUsage(d) + *htGetMetadataSize(d);
} else {
serverPanic("Unknown hash encoding");
}
return size;
size_t hashTypeAllocSize(const robj *o) {
serverAssertWithInfo(NULL,o,o->type == OBJ_HASH);
size_t size = 0;
if (o->encoding == OBJ_ENCODING_LISTPACK) {
size = lpBytes(o->ptr);
} else if (o->encoding == OBJ_ENCODING_LISTPACK_EX) {
listpackEx *lpt = o->ptr;
size = sizeof(listpackEx) + lpBytes(lpt->lp);
} else if (o->encoding == OBJ_ENCODING_HT) {
dict *d = o->ptr;
size_t meta_bytes = d->type->dictMetadataBytes ? d->type->dictMetadataBytes(d) : 0;
size += sizeof(dict) + meta_bytes + dictMemUsage(d) + *htGetMetadataSize(d);
} else {
serverPanic("Unknown hash encoding");
}
return size;
}
🤖 Prompt for AI Agents
In `@src/t_hash.c` around lines 1343 - 1357, hashTypeAllocSize is undercounting
memory for OBJ_ENCODING_HT by not including the dict metadata bytes; update the
OBJ_ENCODING_HT branch in hashTypeAllocSize(const robj *o) to add the dict
metadata size returned by htGetMetadataSize(d) (i.e., include
*htGetMetadataSize(d)) alongside sizeof(dict) and dictMemUsage(d) so the total
allocation includes the metadata bytes for the dict structure.

Comment thread src/t_set.c
Comment on lines 658 to +675
unsigned long oldSize = setTypeSize(set);
if (server.memory_tracking_per_slot)
oldsize = setTypeAllocSize(set);

for (j = 2; j < c->argc; j++) {
if (setTypeRemove(set,c->argv[j]->ptr)) {
deleted++;
if (setTypeSize(set) == 0) {
if (server.memory_tracking_per_slot)
updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, setTypeAllocSize(set));
dbDeleteSkipKeysizesUpdate(c->db, c->argv[1]);
keyremoved = 1;
break;
}
}
}
if (server.memory_tracking_per_slot && !keyremoved)
updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, setTypeAllocSize(set));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n "dbDeleteSkipKeysizesUpdate" -C 5

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 8726


🏁 Script executed:

rg -n "updateSlotAllocSize" -C 3 src/db.c src/kvstore.c

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 2267


🏁 Script executed:

fd -e c -e h | xargs grep -l "dbDeleteSkipKeysizesUpdate" | head -5

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 143


🏁 Script executed:

rg -n "int dbGenericDelete" -A 50 src/db.c | head -100

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 2325


🏁 Script executed:

sed -n '750,820p' src/db.c

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 2723


🏁 Script executed:

sed -n '800,810p' src/db.c

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 371


🏁 Script executed:

rg -n "server.lazyfree_lazy_server_del" -C 2 | head -30

Repository: qodo-benchmark/redis-combined-coderabbit

Length of output: 1740


Pass newsize=0 to updateSlotAllocSize() before deletion to account for all allocations being freed.

Currently line 667 calls updateSlotAllocSize(..., oldsize, setTypeAllocSize(set)) where the set is already empty. The subsequent dbDeleteSkipKeysizesUpdate() only updates per-slot allocation to 0 if async deletion is enabled. In sync mode, the slot retains the empty-set overhead.

Pass 0 as newsize to ensure the slot allocation is properly cleared regardless of deletion mode:

updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, 0);
dbDeleteSkipKeysizesUpdate(c->db, c->argv[1]);

Apply the same fix to:

  • t_hash.c line 2927
  • t_zset.c lines 1957, 2060, 2082
🤖 Prompt for AI Agents
In `@src/t_set.c` around lines 658 - 675, When a set becomes empty we must pass
newsize=0 to updateSlotAllocSize so the slot's allocation is cleared
synchronously; replace the call to updateSlotAllocSize(c->db,
getKeySlot(c->argv[1]->ptr), oldsize, setTypeAllocSize(set)) that occurs inside
the branch where setTypeSize(set) == 0 with updateSlotAllocSize(..., oldsize, 0)
immediately before calling dbDeleteSkipKeysizesUpdate(c->db, c->argv[1]); apply
the same change in the equivalent locations referenced in t_hash.c (around the
hash deletion path) and in t_zset.c (the three zset deletion paths) by updating
their updateSlotAllocSize calls to use newsize=0 when the data structure becomes
empty, using the surrounding symbols setTypeRemove, setTypeSize,
setTypeAllocSize, updateSlotAllocSize and dbDeleteSkipKeysizesUpdate to locate
the spots.

Comment thread src/t_set.c
Comment on lines +784 to +799
size_t oldsize = 0;
kvobj *set = lookupKeyRead(c->db, c->argv[1]);
if (set && checkType(c,set,OBJ_SET)) return;

addReplyArrayLen(c,c->argc - 2);

if (server.memory_tracking_per_slot && set)
setTypeAllocSize(set);
for (int j = 2; j < c->argc; j++) {
if (set && setTypeIsMember(set,c->argv[j]->ptr))
addReply(c,shared.cone);
else
addReply(c,shared.czero);
}
if (server.memory_tracking_per_slot && set)
updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldsize, setTypeAllocSize(set));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix per-slot alloc accounting in SMISMEMBER (oldsize never set).

oldsize stays 0 because the alloc size result is discarded, so updateSlotAllocSize() will inflate the slot’s alloc_size on every call.

🐛 Proposed fix
-    if (server.memory_tracking_per_slot && set)
-        setTypeAllocSize(set);
+    if (server.memory_tracking_per_slot && set)
+        oldsize = setTypeAllocSize(set);
🤖 Prompt for AI Agents
In `@src/t_set.c` around lines 784 - 799, oldsize is never captured before
membership checks so updateSlotAllocSize() receives 0 and over-increments the
slot; capture the current allocation size into oldsize by calling oldsize =
setTypeAllocSize(set) once after confirming set exists (replace the discarded
setTypeAllocSize(set) call), then after the loop call updateSlotAllocSize(c->db,
getKeySlot(c->argv[1]->ptr), oldsize, setTypeAllocSize(set)) so you pass the
original and the new size; references: lookupKeyRead, checkType,
setTypeAllocSize, setTypeIsMember, updateSlotAllocSize.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants