Compare commits

...
Author SHA1 Message Date
Tanmay Deep Sharma 6b7240661f fix: clear counter_cache_optimization flag for existing accounts on migration 2026-03-02 12:30:48 +05:30
Tanmay Deep Sharma f2d20905c1 Merge remote-tracking branch 'origin/perf/conversation_count' into perf/conversation_count 2026-02-27 16:33:10 +05:30
Tanmay Deep Sharma 9cd08f4075 reclaim existing deprecated feature flag 2026-02-27 12:15:57 +05:30
Tanmay Deep Sharma 62f77d1091 Merge remote-tracking branch 'origin/develop' into perf/conversation_count 2026-02-26 18:15:47 +05:30
Tanmay Deep Sharma 6d38b917c9 Merge branch 'develop' into perf/conversation_count 2026-02-26 18:13:56 +05:30
Tanmay Deep SharmaandGitHub 5f6cd48a53 Merge branch 'develop' into perf/conversation_count 2026-02-24 18:26:06 +05:30
Tanmay Deep Sharma af511b39c4 Merge remote-tracking branch 'origin/perf/conversation_count' into perf/conversation_count 2026-02-24 16:24:31 +05:30
Tanmay Deep Sharma d4992ad4c6 feat: add conversation status counter cache with per-account backfill on feature enable 2026-02-24 16:24:10 +05:30
Tanmay Deep SharmaandGitHub d752c379b0 Merge branch 'develop' into perf/conversation_count 2026-02-23 14:17:23 +05:30
Tanmay Deep Sharma d0525660ab Merge remote-tracking branch 'origin/develop' into perf/conversation_count 2026-02-18 20:47:47 +05:30
Tanmay Deep Sharma efc58eef9a fix review changes 2026-02-09 10:56:02 +05:30
Tanmay Deep Sharma 96f1aa7205 fix review changes 2026-02-04 12:44:38 +05:30
Tanmay Deep Sharma ace5c90007 use cache for only admins 2026-02-03 20:51:34 +05:30
Tanmay Deep Sharma 6e85dcbbce use counter culture and move the optimisation under feature flag 2026-02-03 20:26:03 +05:30
Tanmay Deep Sharma 6a053e975e perf(conversations): optimize /conversations/meta with counter cache for all_count 2026-02-03 14:20:35 +05:30
11 changed files with 93 additions and 6 deletions
+1
View File
@@ -12,6 +12,7 @@ gem 'bootsnap', require: false
gem 'acts-as-taggable-on'
gem 'attr_extras'
gem 'browser'
gem 'counter_culture'
gem 'hashie'
gem 'jbuilder'
gem 'kaminari'
+4
View File
@@ -192,6 +192,9 @@ GEM
commonmarker (0.23.10)
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
counter_culture (3.12.1)
activerecord (>= 4.2)
activesupport (>= 4.2)
crack (1.0.0)
bigdecimal
rexml
@@ -1041,6 +1044,7 @@ DEPENDENCIES
cld3 (~> 3.7)
climate_control
commonmarker
counter_culture
csv-safe
database_cleaner
datadog (~> 2.0)
+17 -1
View File
@@ -2,6 +2,9 @@ class ConversationFinder
attr_reader :current_user, :current_account, :params
DEFAULT_STATUS = 'open'.freeze
# Filter params that affect conversation count
# NOTE: When adding a new filter, add it here to prevent incorrect cache usage
FILTER_PARAMS = %i[inbox_id team_id labels q source_id conversation_type].freeze
SORT_OPTIONS = {
'last_activity_at_asc' => %w[sort_on_last_activity_at asc],
'last_activity_at_desc' => %w[sort_on_last_activity_at desc],
@@ -184,10 +187,23 @@ class ConversationFinder
end
def set_count_for_all_conversations
# Use cache only for admins with no filters other than status
use_cache = current_account.feature_enabled?(:counter_cache_optimization) &&
@is_admin &&
FILTER_PARAMS.none? { |key| params[key] } &&
params[:status] != 'all'
all_count = if use_cache
status = params[:status].presence_in(Conversation.statuses.keys) || DEFAULT_STATUS
current_account.public_send("#{status}_conversations_count")
else
@conversations.count
end
[
@conversations.assigned_to(current_user).count,
@conversations.unassigned.count,
@conversations.count
all_count
]
end
@@ -0,0 +1,7 @@
class Internal::BackfillConversationCountersJob < ApplicationJob
queue_as :low
def perform(account)
Internal::BackfillConversationCountersService.new(account: account).perform
end
end
+8
View File
@@ -144,6 +144,7 @@ class Account < ApplicationRecord
before_validation :validate_limit_keys
after_create_commit :notify_creation
after_destroy :remove_account_sequences
after_save :backfill_conversation_counters_if_needed
def agents
users.where(account_users: { role: :agent })
@@ -208,6 +209,13 @@ class Account < ApplicationRecord
"execute format('create sequence IF NOT EXISTS camp_dpid_seq_%s', NEW.id);"
end
def backfill_conversation_counters_if_needed
return unless saved_change_to_feature_flags?
return unless feature_counter_cache_optimization?
Internal::BackfillConversationCountersJob.perform_later(self)
end
def validate_limit_keys
# method overridden in enterprise module
end
+2
View File
@@ -98,6 +98,8 @@ class Conversation < ApplicationRecord
}
belongs_to :account
counter_culture :account,
column_name: proc { |model| "#{model.status}_conversations_count" }
belongs_to :inbox
belongs_to :assignee, class_name: 'User', optional: true, inverse_of: :assigned_conversations
belongs_to :assignee_agent_bot, class_name: 'AgentBot', optional: true
@@ -0,0 +1,26 @@
class Internal::BackfillConversationCountersService
# Backfills the conversation status counter cache columns on accounts
# (open_conversations_count, resolved_conversations_count, pending_conversations_count, snoozed_conversations_count).
#
# Safe to re-run to correct any drift.
#
# Usage:
# Internal::BackfillConversationCountersService.new.perform
# Internal::BackfillConversationCountersService.new(account: Account.find(id)).perform
#
def initialize(account: nil)
@account = account
end
def perform
scope = @account ? Account.where(id: @account.id) : Account.all
scope.find_each do |account|
account.update(
open_conversations_count: account.conversations.open.count,
resolved_conversations_count: account.conversations.resolved.count,
pending_conversations_count: account.conversations.pending.count,
snoozed_conversations_count: account.conversations.snoozed.count
)
end
end
end
+4 -4
View File
@@ -17,10 +17,10 @@
display_name: Facebook Channel
enabled: true
help_url: https://chwt.app/hc/fb
- name: channel_twitter
display_name: Twitter Channel
enabled: true
deprecated: true
- name: counter_cache_optimization
display_name: Counter Cache Optimization
enabled: false
chatwoot_internal: true
- name: ip_lookup
display_name: IP Lookup
enabled: false
@@ -0,0 +1,8 @@
class AddStatusConversationCountersToAccounts < ActiveRecord::Migration[7.1]
def change
add_column :accounts, :open_conversations_count, :integer, default: 0, null: false
add_column :accounts, :resolved_conversations_count, :integer, default: 0, null: false
add_column :accounts, :pending_conversations_count, :integer, default: 0, null: false
add_column :accounts, :snoozed_conversations_count, :integer, default: 0, null: false
end
end
@@ -0,0 +1,11 @@
# counter_cache_optimization reclaims the deprecated channel_twitter bit (position 4).
# channel_twitter was enabled: true, so all existing accounts have bit 4 set to 1.
# counter_cache_optimization is enabled: false, so we must clear bit 4 for all accounts.
class DisableCounterCacheOptimizationForExistingAccounts < ActiveRecord::Migration[7.0]
def up
Account.feature_counter_cache_optimization.find_each(batch_size: 100) do |account|
account.disable_features(:counter_cache_optimization)
account.save!(validate: false)
end
end
end
+5 -1
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2026_02_26_084618) do
ActiveRecord::Schema[7.1].define(version: 2026_02_26_125101) do
# These extensions should be enabled to support this database
enable_extension "pg_stat_statements"
enable_extension "pg_trgm"
@@ -73,6 +73,10 @@ ActiveRecord::Schema[7.1].define(version: 2026_02_26_084618) do
t.integer "status", default: 0
t.jsonb "internal_attributes", default: {}, null: false
t.jsonb "settings", default: {}
t.integer "open_conversations_count", default: 0, null: false
t.integer "resolved_conversations_count", default: 0, null: false
t.integer "pending_conversations_count", default: 0, null: false
t.integer "snoozed_conversations_count", default: 0, null: false
t.index ["status"], name: "index_accounts_on_status"
end