feat: Use resolved contacts as base relation for filtering (#12520)

This PR has two changes to speed up contact filtering

### Updated Base Relation

Update the `base_relation` to use resolved contacts scope to improve
perf when filtering conversations. This narrows the search space
drastically, and what is usually a sequential scan becomes a index scan
for that `account_id`

ref: https://github.com/chatwoot/chatwoot/pull/9347
ref: https://github.com/chatwoot/chatwoot/pull/7175/

Result: https://explain.dalibo.com/plan/c8a8gb17f0275fgf#plan


## Selective filtering in Compose New Conversation

We also cost of filtering in compose new conversation dialog by reducing
the search space based on the search candidate. For instance, a search
term that obviously can’t be a phone, we exclude that from the filter.
Similarly we skip name lookups for email-shaped queries.

Removing the phone number took the query times from 50 seconds to under
1 seconds

### Comparison

1. Only Email: https://explain.dalibo.com/plan/h91a6844a4438a6a 
2. Email + Name: https://explain.dalibo.com/plan/beg3aah05ch9ade0
3. Email + Name + Phone:
https://explain.dalibo.com/plan/c8a8gb17f0275fgf
This commit is contained in:
Shivam Mishra
2025-09-25 15:26:44 +05:30
committed by GitHub
parent f44e47a624
commit b75ea7a762
4 changed files with 42 additions and 13 deletions

View File

@@ -7,9 +7,25 @@ describe Contacts::FilterService do
let!(:first_user) { create(:user, account: account) }
let!(:second_user) { create(:user, account: account) }
let!(:inbox) { create(:inbox, account: account, enable_auto_assignment: false) }
let!(:en_contact) { create(:contact, account: account, additional_attributes: { 'country_code': 'uk' }) }
let!(:el_contact) { create(:contact, account: account, additional_attributes: { 'country_code': 'gr' }) }
let!(:cs_contact) { create(:contact, :with_phone_number, account: account, additional_attributes: { 'country_code': 'cz' }) }
let!(:en_contact) do
create(:contact,
account: account,
email: Faker::Internet.unique.email,
additional_attributes: { 'country_code': 'uk' })
end
let!(:el_contact) do
create(:contact,
account: account,
email: Faker::Internet.unique.email,
additional_attributes: { 'country_code': 'gr' })
end
let!(:cs_contact) do
create(:contact,
:with_phone_number,
account: account,
email: Faker::Internet.unique.email,
additional_attributes: { 'country_code': 'cz' })
end
before do
create(:inbox_member, user: first_user, inbox: inbox)
@@ -103,7 +119,12 @@ describe Contacts::FilterService do
context 'with standard attributes - blocked' do
it 'filter contacts by blocked' do
blocked_contact = create(:contact, account: account, blocked: true)
blocked_contact = create(
:contact,
account: account,
blocked: true,
email: Faker::Internet.unique.email
)
params = { payload: [{ attribute_key: 'blocked', filter_operator: 'equal_to', values: ['true'],
query_operator: nil }.with_indifferent_access] }
result = filter_service.new(account, first_user, params).perform