Skip to content

Commit f7fa481

Browse files
authored
Optimize finding the slot for a given key count in a fenwick tree (redis#12704)
This PR optimizes the time complexity of findSlotByKeyIndex from O(log^2(N)) to O(log(N)) by using the tree structure of binary index tree to find a slot in one search of the index.
1 parent 4145d62 commit f7fa481

File tree

1 file changed

+20
-27
lines changed

1 file changed

+20
-27
lines changed

src/db.c

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -488,24 +488,14 @@ unsigned long long cumulativeKeyCountRead(redisDb *db, int slot, dbKeyType keyTy
488488
}
489489

490490
/* Returns fair random slot, probability of each slot being returned is proportional to the number of elements that slot dictionary holds.
491-
* Implementation uses binary search on top of binary index tree.
492491
* This function guarantees that it returns a slot whose dict is non-empty, unless the entire db is empty.
493-
* Time complexity of this function is O(log^2(CLUSTER_SLOTS)). */
492+
* Time complexity of this function is O(log(CLUSTER_SLOTS)). */
494493
int getFairRandomSlot(redisDb *db, dbKeyType keyType) {
495494
unsigned long target = dbSize(db, keyType) ? (randomULong() % dbSize(db, keyType)) + 1 : 0;
496495
int slot = findSlotByKeyIndex(db, target, keyType);
497496
return slot;
498497
}
499498

500-
static inline unsigned long dictSizebySlot(redisDb *db, int slot, dbKeyType keyType) {
501-
if (keyType == DB_MAIN)
502-
return dictSize(db->dict[slot]);
503-
else if (keyType == DB_EXPIRES)
504-
return dictSize(db->expires[slot]);
505-
else
506-
serverPanic("Unknown keyType");
507-
}
508-
509499
/* Finds a slot containing target element in a key space ordered by slot id.
510500
* Consider this example. Slots are represented by brackets and keys by dots:
511501
* #0 #1 #2 #3 #4
@@ -516,27 +506,30 @@ static inline unsigned long dictSizebySlot(redisDb *db, int slot, dbKeyType keyT
516506
* In this case slot #3 contains key that we are trying to find.
517507
*
518508
* This function is 1 based and the range of the target is [1..dbSize], dbSize inclusive.
519-
* Time complexity of this function is O(log^2(CLUSTER_SLOTS))
520-
* */
509+
*
510+
* To find the slot, we start with the root node of the binary index tree and search through its children
511+
* from the highest index (2^14 in our case) to the lowest index. At each node, we check if the target
512+
* value is greater than the node's value. If it is, we remove the node's value from the target and recursively
513+
* search for the new target using the current node as the parent.
514+
* Time complexity of this function is O(log(CLUSTER_SLOTS))
515+
*/
521516
int findSlotByKeyIndex(redisDb *db, unsigned long target, dbKeyType keyType) {
522517
if (!server.cluster_enabled || dbSize(db, keyType) == 0) return 0;
523518
serverAssert(target <= dbSize(db, keyType));
524-
int lo = 0, hi = CLUSTER_SLOTS - 1;
525-
/* We use binary search to find a slot, we are allowed to do this, because we have a quick way to find a total number of keys
526-
* up until certain slot, using binary index tree. */
527-
while (lo <= hi) {
528-
int mid = lo + (hi - lo) / 2;
529-
unsigned long keys_up_to_mid = cumulativeKeyCountRead(db, mid, keyType); /* Total number of keys up until a given slot (inclusive). */
530-
unsigned long keys_in_mid = dictSizebySlot(db, mid, keyType);
531-
if (target > keys_up_to_mid) { /* Target is to the right from mid. */
532-
lo = mid + 1;
533-
} else if (target <= keys_up_to_mid - keys_in_mid) { /* Target is to the left from mid. */
534-
hi = mid - 1;
535-
} else { /* Located target. */
536-
return mid;
519+
520+
int result = 0, bit_mask = 1 << CLUSTER_SLOT_MASK_BITS;
521+
for (int i = bit_mask; i != 0; i >>= 1) {
522+
int current = result + i;
523+
/* When the target index is greater than 'current' node value the we will update
524+
* the target and search in the 'current' node tree. */
525+
if (target > db->sub_dict[keyType].slot_size_index[current]) {
526+
target -= db->sub_dict[keyType].slot_size_index[current];
527+
result = current;
537528
}
538529
}
539-
serverPanic("Unable to find a slot that contains target key.");
530+
/* Unlike BIT, slots are 0-based, so we need to subtract 1, but we also need to add 1,
531+
* since we want the next slot. */
532+
return result;
540533
}
541534

542535
/* Helper for sync and async delete. */

0 commit comments

Comments
 (0)