perf: reduce per-request work in conversation filter endpoint (#15321)
## Description Every conversation filter request runs three separate unbounded COUNT queries over the filtered set (mine, unassigned, all) and eager-loads every message of every conversation on the page. On large accounts this adds a fixed 1-2s of latency per request regardless of the filter. This PR trims both: - The three counts are now computed in a single pass using `COUNT(*) FILTER (...)` aggregates. Response shape and count semantics are unchanged. - The `:messages` eager-load is removed from the filter base relation. The list payload fetches messages through scoped queries (last message, last non-activity message, unread messages), which never read the preloaded collection, so it was loaded and discarded on every request. Fixes https://linear.app/chatwoot/issue/CW-7830
This commit is contained in:
@@ -24,8 +24,10 @@ class Conversations::FilterService < FilterService
|
||||
end
|
||||
|
||||
def base_relation
|
||||
# :messages is deliberately not preloaded: the list payload fetches messages through
|
||||
# scoped queries (last message, last_non_activity_message), which bypass the preload.
|
||||
conversations = @account.conversations.includes(
|
||||
:taggings, :inbox, { assignee: { avatar_attachment: [:blob] } }, { contact: { avatar_attachment: [:blob] } }, :team, :messages, :contact_inbox
|
||||
:taggings, :inbox, { assignee: { avatar_attachment: [:blob] } }, { contact: { avatar_attachment: [:blob] } }, :team, :contact_inbox
|
||||
)
|
||||
|
||||
Conversations::PermissionFilterService.new(
|
||||
|
||||
@@ -107,12 +107,16 @@ class FilterService
|
||||
lt_gt_filter_query(updated_query_hash, current_index)
|
||||
end
|
||||
|
||||
# Computes mine/unassigned/all counts in one scan of the filtered set instead of
|
||||
# three separate COUNT queries.
|
||||
def set_count_for_all_conversations
|
||||
[
|
||||
@conversations.assigned_to(@user).count,
|
||||
@conversations.unassigned.count,
|
||||
@conversations.count
|
||||
]
|
||||
counts = @conversations.except(:includes, :order).pick(
|
||||
Arel.sql(ActiveRecord::Base.sanitize_sql_array(['COUNT(*) FILTER (WHERE assignee_id = ?)', @user.id])),
|
||||
Arel.sql('COUNT(*) FILTER (WHERE assignee_id IS NULL)'),
|
||||
Arel.sql('COUNT(*)')
|
||||
)
|
||||
# pick short-circuits to nil on a none relation (e.g. permission scope with no access)
|
||||
counts ? counts.map(&:to_i) : [0, 0, 0]
|
||||
end
|
||||
|
||||
def tag_filter_query(query_hash, current_index)
|
||||
|
||||
Reference in New Issue
Block a user