From ab01ab78539277056e8de7496d34d904b014c211 Mon Sep 17 00:00:00 2001 From: Vishnu Narayanan Date: Thu, 16 Jul 2026 11:41:28 +0530 Subject: [PATCH] fix(perf): back off conversation meta polling when the count is unknown (#15031) ## Description The conversation sidebar counts (`GET /conversations/meta`) are polled on a debounced cadence selected from the last known conversation count (`allCount` in the `conversationStats` store). `allCount` starts at `0` and is only set after a **successful** meta response. When the meta query is slow or failing under load, `allCount` stays `0`, which selected the fastest cadence, so every client polled as aggressively as possible exactly when the endpoint was already struggling. On large accounts with many concurrent agents this multiplies into sustained load that keeps the query slow, a self-reinforcing loop. This PR makes two changes: 1. **Treat an unknown count (`0`) as a large account** and poll at the slowest cadence instead of the fastest (`getMetaDebounceKey`, extracted as a pure, testable function). This breaks the failure loop. 2. **Raise the debounce intervals across all tiers** so counts are polled less frequently under sustained activity: - fast: max 1 poll / 2s -> 1 / 5s - mid: 1 / 10s -> 1 / 20s - slow (large/unknown accounts): 1 / 20s -> 1 / 30s First-load counts still render immediately (the debounce fires on the leading edge for the very first call), so the higher intervals only affect the sustained poll rate, not initial render. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) --- .../store/modules/conversationStats.js | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/app/javascript/dashboard/store/modules/conversationStats.js b/app/javascript/dashboard/store/modules/conversationStats.js index 204b87ea1..1f029d0ec 100644 --- a/app/javascript/dashboard/store/modules/conversationStats.js +++ b/app/javascript/dashboard/store/modules/conversationStats.js @@ -25,24 +25,32 @@ const fetchMetaData = async (commit, params) => { } }; -const debouncedFetchMetaData = debounce(fetchMetaData, 500, false, 2000); -const longDebouncedFetchMetaData = debounce(fetchMetaData, 5000, false, 10000); +const debouncedFetchMetaData = debounce(fetchMetaData, 1000, false, 5000); +const longDebouncedFetchMetaData = debounce(fetchMetaData, 7500, false, 20000); const superLongDebouncedFetchMetaData = debounce( fetchMetaData, - 10000, + 15000, false, - 20000 + 30000 ); +const metaDebouncers = { + default: debouncedFetchMetaData, + long: longDebouncedFetchMetaData, + superLong: superLongDebouncedFetchMetaData, +}; + +// allCount is 0 until a meta request succeeds; under load it stays 0, so treat +// the unknown case as a large account and poll slowest instead of fastest. +export const getMetaDebounceKey = allCount => { + if (allCount > 2000 || allCount === 0) return 'superLong'; + if (allCount > 100) return 'long'; + return 'default'; +}; + export const actions = { - get: async ({ commit, state: $state }, params) => { - if ($state.allCount > 2000) { - superLongDebouncedFetchMetaData(commit, params); - } else if ($state.allCount > 100) { - longDebouncedFetchMetaData(commit, params); - } else { - debouncedFetchMetaData(commit, params); - } + get: ({ commit, state: $state }, params) => { + metaDebouncers[getMetaDebounceKey($state.allCount)](commit, params); }, set({ commit }, meta) { commit(types.SET_CONV_TAB_META, meta);