From e409e0cbb78edea1653825c25f2e216a1e91c2e4 Mon Sep 17 00:00:00 2001 From: chx9 Date: Thu, 14 May 2026 20:00:09 +0800 Subject: [PATCH 1/2] Optimize isSlotInTrimJob: replace O(n) list traversal with O(1) bitmap lookup Add trim_slots_bitmap to asmManager struct. Set bits on trim job schedule/trigger, rebuild bitmap on job completion/cancellation. This eliminates per-command linked list scanning on hot paths (command routing, RDB/AOF rewrite). --- src/cluster_asm.c | 59 +++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/src/cluster_asm.c b/src/cluster_asm.c index ac94a5103..cbedbcaac 100644 --- a/src/cluster_asm.c +++ b/src/cluster_asm.c @@ -113,6 +113,9 @@ struct asmManager { size_t sync_buffer_peak; /* Peak size of sync buffer */ asmTask *master_task; /* The task that is currently active on the master */ + /* Bitmap for O(1) slot-in-trim-job lookups (covers pending + active trim jobs) */ + uint32_t trim_slots_bitmap[CLUSTER_SLOTS/32]; + /* Fail point injection for debugging */ int debug_fail_channel; /* Channel where the task will fail */ int debug_fail_state; /* State where the task will fail */ @@ -200,6 +203,32 @@ void asmTrimSlotsIfNotOwned(slotRangeArray *slots); void asmNotifyStateChange(asmTask *task, int event); void activeTrimJobFreeMethod(void *ptr); +/* --- Trim bitmap helpers --- */ +static inline void trimBitmapSetSlotRange(slotRangeArray *slots) { + for (int i = 0; i < slots->num_ranges; i++) { + for (int s = slots->ranges[i].start; s <= slots->ranges[i].end; s++) { + asmManager->trim_slots_bitmap[s >> 5] |= (1U << (s & 31)); + } + } +} + +/* Rebuild the entire trim bitmap from pending and active trim job lists. */ +static void trimBitmapRebuild(void) { + memset(asmManager->trim_slots_bitmap, 0, sizeof(asmManager->trim_slots_bitmap)); + + listIter li; + listNode *ln; + listRewind(asmManager->pending_trim_jobs, &li); + while ((ln = listNext(&li)) != NULL) { + trimBitmapSetSlotRange(listNodeValue(ln)); + } + listRewind(asmManager->active_trim_jobs, &li); + while ((ln = listNext(&li)) != NULL) { + activeTrimJob *job = listNodeValue(ln); + trimBitmapSetSlotRange(job->slots); + } +} + void asmInit(void) { asmManager = zcalloc(sizeof(*asmManager)); asmManager->tasks = listCreate(); @@ -2831,28 +2860,8 @@ int isSlotInAsmTask(int slot) { /* Check if the slot is in a pending trim job. It may happen if we can't trim * the slots immediately due to a write pause or when active trim is in progress. */ int isSlotInTrimJob(int slot) { - slotRange req = {slot, slot}; - - if (!asmManager || !asmIsTrimInProgress()) return 0; - - /* Check if the slot is in any pending trim job. */ - listIter li; - listNode *ln; - listRewind(asmManager->pending_trim_jobs, &li); - while ((ln = listNext(&li)) != NULL) { - slotRangeArray *slots = listNodeValue(ln); - if (slotRangeArrayOverlaps(slots, &req)) - return 1; - } - - /* Check if the slot is in any active trim job. */ - listRewind(asmManager->active_trim_jobs, &li); - while ((ln = listNext(&li)) != NULL) { - activeTrimJob *job = listNodeValue(ln); - if (slotRangeArrayOverlaps(job->slots, &req)) - return 1; - } - return 0; + if (!asmManager) return 0; + return (asmManager->trim_slots_bitmap[slot >> 5] >> (slot & 31)) & 1; } int clusterAsmHandoff(const char *task_id, sds *err) { @@ -3189,6 +3198,7 @@ int asmTrimSlots(asmTrimCtx *ctx, uint64_t client_id, int migration_cleanup) { * For trim method details, see asmTrimSlots(). */ void asmTrimJobSchedule(slotRangeArray *slots) { listAddNodeTail(asmManager->pending_trim_jobs, slotRangeArrayDup(slots)); + trimBitmapSetSlotRange(slots); } /* Process any pending trim jobs. */ @@ -3242,6 +3252,7 @@ void asmTrimJobProcessPending(void) { listDelNode(asmManager->pending_trim_jobs, ln); asmTrimCtxRelease(ctx); /* Release ctx (if bg trim, released later by kvsAsyncFreeDoneCB) */ } + trimBitmapRebuild(); } /* Trim keys in slots not owned by this node (if any). */ @@ -3401,6 +3412,7 @@ void asmCancelPendingTrimJobs(void) { sdsfree(str); slotRangeArrayFree(slots); } + trimBitmapRebuild(); } /* Free an activeTrimJob and unblock pending client if needed. */ @@ -3433,6 +3445,7 @@ void asmCancelTrimJobs(void) { asmManager->active_trim_cancelled += listLength(asmManager->active_trim_jobs); asmActiveTrimEnd(); listEmpty(asmManager->active_trim_jobs); + trimBitmapRebuild(); } /* It's used to trim slots after the migration is completed or import is failed. @@ -3541,6 +3554,7 @@ void asmTriggerActiveTrim(slotRangeArray *slots, uint64_t client_id, int migrati job->migration_cleanup = migration_cleanup; listAddNodeTail(asmManager->active_trim_jobs, job); + trimBitmapSetSlotRange(slots); sds str = slotRangeArrayToString(slots); serverLog(LL_NOTICE, "Active trim scheduled for slots: %s", str); sdsfree(str); @@ -3582,6 +3596,7 @@ void asmActiveTrimEnd(void) { sdsfree(str); listDelNode(asmManager->active_trim_jobs, listFirst(asmManager->active_trim_jobs)); asmManager->active_trim_completed++; + trimBitmapRebuild(); } /* Check if the slot range array overlaps with any trim job. */ From 646a7d98d3fbc027f2bfe62ad69e5d310c62b419 Mon Sep 17 00:00:00 2001 From: chx9 Date: Thu, 14 May 2026 20:00:17 +0800 Subject: [PATCH 2/2] Add jemalloc_purge after background trim completion Call jemalloc_purge() in kvsAsyncFreeDoneCB to release freed memory pages back to OS promptly after BIO thread finishes async dict deletion. Guarded by USE_JEMALLOC preprocessor check. --- src/db.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/db.c b/src/db.c index e2dd50b50..5222bb2b0 100644 --- a/src/db.c +++ b/src/db.c @@ -1283,6 +1283,13 @@ void kvsAsyncFreeDoneCB(uint64_t client_id, void *userdata) { /* Release context and slots */ asmTrimCtxRelease(ctx); + +#if defined(USE_JEMALLOC) + /* Purge jemalloc dirty pages after background trim/flush completes. + * The BIO thread freed a potentially large amount of memory; purging + * ensures those pages are returned to the OS promptly, reducing RSS. */ + jemalloc_purge(); +#endif } /* Unblock client on async flush/trim completion */