fix: throttle filtered unread count rebuilds (#14980)
Reduces database pressure from filtered unread-count cache rebuilds during high-traffic account rollouts by serving stale snapshots longer and limiting inline saved-filter rebuild fanout. ## Closes None ## What changed - Increase filtered unread-count refresh throttling from 30 seconds to 5 minutes. - Increase the stale snapshot window from 30 minutes to 1 hour. - Reduce inline saved-filter count rebuilds per request from 10 to 3. - Update unread-count specs to assert refresh and stale behavior through the shared constants. ## How to test - Enable `conversation_unread_counts` and `unread_count_for_filters` for an account with conversation custom filters. - Open the dashboard and verify unread-count badges still return values. - Mutate conversations and verify stale filtered counts are served while rebuilds are throttled, instead of repeatedly rebuilding every 30 seconds.
This commit is contained in:
@@ -2,9 +2,9 @@ module Conversations::UnreadCounts
|
||||
READY_TTL = 24.hours.to_i
|
||||
SET_TTL = 25.hours.to_i
|
||||
FILTERED_COUNT_FRESH_TTL = 5.minutes.to_i
|
||||
FILTERED_COUNT_STALE_WINDOW = 30.minutes.to_i
|
||||
FILTERED_COUNT_STALE_WINDOW = 1.hour.to_i
|
||||
FILTERED_COUNT_REDIS_TTL = FILTERED_COUNT_FRESH_TTL + FILTERED_COUNT_STALE_WINDOW
|
||||
FILTERED_COUNT_VERSION_TTL = SET_TTL
|
||||
FILTERED_COUNT_MIN_REFRESH_INTERVAL = 30.seconds.to_i
|
||||
MAX_INLINE_FILTER_BUILDS = 10
|
||||
FILTERED_COUNT_MIN_REFRESH_INTERVAL = 5.minutes.to_i
|
||||
MAX_INLINE_FILTER_BUILDS = 3
|
||||
end
|
||||
|
||||
@@ -96,7 +96,14 @@ RSpec.describe Conversations::UnreadCounts::FilteredCountStore do
|
||||
|
||||
described_class.bump_built_in_filter_version!(account_id: account_id, user_id: user_id)
|
||||
expect(described_class.built_in_filter_counts_state(account_id: account_id, user_id: user_id, now: built_at + 2.minutes)).to be_stale
|
||||
expect(described_class.built_in_filter_counts_state(account_id: account_id, user_id: user_id, now: built_at + 36.minutes)).to be_expired
|
||||
expect(
|
||||
described_class.built_in_filter_counts_state(
|
||||
account_id: account_id,
|
||||
user_id: user_id,
|
||||
now: built_at + Conversations::UnreadCounts::FILTERED_COUNT_FRESH_TTL +
|
||||
Conversations::UnreadCounts::FILTERED_COUNT_STALE_WINDOW + 1.second
|
||||
)
|
||||
).to be_expired
|
||||
|
||||
Redis::Alfred.delete(described_class.built_in_filter_counts_key(account_id, user_id))
|
||||
expect(described_class.built_in_filter_counts_state(account_id: account_id, user_id: user_id)).to be_missing
|
||||
@@ -202,8 +209,18 @@ RSpec.describe Conversations::UnreadCounts::FilteredCountStore do
|
||||
)
|
||||
|
||||
snapshot = described_class.built_in_filter_counts(account_id: account_id, user_id: user_id)
|
||||
expect(described_class.refresh_due?(snapshot, now: built_at + 10.seconds)).to be(false)
|
||||
expect(described_class.refresh_due?(snapshot, now: built_at + 31.seconds)).to be(true)
|
||||
expect(
|
||||
described_class.refresh_due?(
|
||||
snapshot,
|
||||
now: built_at + Conversations::UnreadCounts::FILTERED_COUNT_MIN_REFRESH_INTERVAL - 1.second
|
||||
)
|
||||
).to be(false)
|
||||
expect(
|
||||
described_class.refresh_due?(
|
||||
snapshot,
|
||||
now: built_at + Conversations::UnreadCounts::FILTERED_COUNT_MIN_REFRESH_INTERVAL + 1.second
|
||||
)
|
||||
).to be(true)
|
||||
|
||||
expect(described_class.claim_built_in_filter_refresh!(account_id: account_id, user_id: user_id)).to be(true)
|
||||
expect(described_class.claim_built_in_filter_refresh!(account_id: account_id, user_id: user_id)).to be(false)
|
||||
|
||||
@@ -48,10 +48,22 @@ RSpec.describe Conversations::UnreadCounts::FilteredCounter do
|
||||
create(:mention, account: account, conversation: second_mention, user: agent)
|
||||
store.bump_conversation_version!(account.id)
|
||||
|
||||
expect(described_class.new(account: account, user: agent, now: now + 10.seconds).perform[:mentions_count]).to eq(1)
|
||||
expect(
|
||||
described_class.new(
|
||||
account: account,
|
||||
user: agent,
|
||||
now: now + Conversations::UnreadCounts::FILTERED_COUNT_MIN_REFRESH_INTERVAL - 1.second
|
||||
).perform[:mentions_count]
|
||||
).to eq(1)
|
||||
|
||||
Redis::Alfred.delete(store.built_in_filter_refresh_throttle_key(account.id, agent.id))
|
||||
expect(described_class.new(account: account, user: agent, now: now + 31.seconds).perform[:mentions_count]).to eq(2)
|
||||
expect(
|
||||
described_class.new(
|
||||
account: account,
|
||||
user: agent,
|
||||
now: now + Conversations::UnreadCounts::FILTERED_COUNT_MIN_REFRESH_INTERVAL + 1.second
|
||||
).perform[:mentions_count]
|
||||
).to eq(2)
|
||||
end
|
||||
|
||||
it 'returns stale built-in counts when a refresh build hits a database error' do
|
||||
@@ -62,7 +74,11 @@ RSpec.describe Conversations::UnreadCounts::FilteredCounter do
|
||||
|
||||
store.bump_conversation_version!(account.id)
|
||||
Redis::Alfred.delete(store.built_in_filter_refresh_throttle_key(account.id, agent.id))
|
||||
failing_counter = described_class.new(account: account, user: agent, now: now + 31.seconds)
|
||||
failing_counter = described_class.new(
|
||||
account: account,
|
||||
user: agent,
|
||||
now: now + Conversations::UnreadCounts::FILTERED_COUNT_MIN_REFRESH_INTERVAL + 1.second
|
||||
)
|
||||
allow(failing_counter).to receive(:built_in_counts_from_database).and_raise(ActiveRecord::StatementInvalid.new('statement timeout'))
|
||||
|
||||
expect(failing_counter.perform[:mentions_count]).to eq(1)
|
||||
|
||||
Reference in New Issue
Block a user