From d4992ad4c67a28dae33643bc9ec2baaff7de326e Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Tue, 24 Feb 2026 16:24:10 +0530 Subject: [PATCH] feat: add conversation status counter cache with per-account backfill on feature enable --- .../backfill_conversation_counters_job.rb | 7 +++++ app/models/account.rb | 8 ++++++ .../backfill_conversation_counters_service.rb | 26 +++++++++++++++++++ ...0_backfill_status_conversation_counters.rb | 25 ------------------ 4 files changed, 41 insertions(+), 25 deletions(-) create mode 100644 app/jobs/internal/backfill_conversation_counters_job.rb create mode 100644 app/services/internal/backfill_conversation_counters_service.rb delete mode 100644 db/migrate/20260203125200_backfill_status_conversation_counters.rb diff --git a/app/jobs/internal/backfill_conversation_counters_job.rb b/app/jobs/internal/backfill_conversation_counters_job.rb new file mode 100644 index 000000000..b9f2e2e00 --- /dev/null +++ b/app/jobs/internal/backfill_conversation_counters_job.rb @@ -0,0 +1,7 @@ +class Internal::BackfillConversationCountersJob < ApplicationJob + queue_as :low + + def perform(account) + Internal::BackfillConversationCountersService.new(account: account).perform + end +end diff --git a/app/models/account.rb b/app/models/account.rb index 4816494fb..33cc59ad3 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -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 diff --git a/app/services/internal/backfill_conversation_counters_service.rb b/app/services/internal/backfill_conversation_counters_service.rb new file mode 100644 index 000000000..72512928d --- /dev/null +++ b/app/services/internal/backfill_conversation_counters_service.rb @@ -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 diff --git a/db/migrate/20260203125200_backfill_status_conversation_counters.rb b/db/migrate/20260203125200_backfill_status_conversation_counters.rb deleted file mode 100644 index 80ea2c245..000000000 --- a/db/migrate/20260203125200_backfill_status_conversation_counters.rb +++ /dev/null @@ -1,25 +0,0 @@ -class BackfillStatusConversationCounters < ActiveRecord::Migration[7.1] - def up - Account.find_each do |account| - # Count conversations by status for this account - open_count = account.conversations.open.count - resolved_count = account.conversations.resolved.count - pending_count = account.conversations.pending.count - snoozed_count = account.conversations.snoozed.count - - # Update the counter cache columns - # rubocop:disable Rails/SkipsModelValidations - account.update_columns( - open_conversations_count: open_count, - resolved_conversations_count: resolved_count, - pending_conversations_count: pending_count, - snoozed_conversations_count: snoozed_count - ) - # rubocop:enable Rails/SkipsModelValidations - end - end - - def down - # No-op - end -end