From d0cebae6688c41203e72e94dc8f2b721fb8339ef Mon Sep 17 00:00:00 2001 From: aakashb95 Date: Thu, 8 Jan 2026 22:35:09 +0530 Subject: [PATCH] fix: atomic update of captain usage under load --- .../enterprise/account/plan_usage_and_limits.rb | 14 +++++++++++--- spec/enterprise/models/account_spec.rb | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/enterprise/app/models/enterprise/account/plan_usage_and_limits.rb b/enterprise/app/models/enterprise/account/plan_usage_and_limits.rb index ce03efa41..6ade7f68d 100644 --- a/enterprise/app/models/enterprise/account/plan_usage_and_limits.rb +++ b/enterprise/app/models/enterprise/account/plan_usage_and_limits.rb @@ -15,10 +15,18 @@ module Enterprise::Account::PlanUsageAndLimits } end + # Use atomic SQL update to prevent race condition when multiple jobs + # increment concurrently. The previous read-modify-write pattern caused + # lost updates under high concurrency + # Skipping validations is safe: no validations on custom_attributes, no dependent callbacks. def increment_response_usage - current_usage = custom_attributes[CAPTAIN_RESPONSES_USAGE].to_i || 0 - custom_attributes[CAPTAIN_RESPONSES_USAGE] = current_usage + 1 - save + self.class.where(id: id).update_all( # rubocop:disable Rails/SkipsModelValidations + "custom_attributes = jsonb_set( + custom_attributes, + '{#{CAPTAIN_RESPONSES_USAGE}}', + (COALESCE((custom_attributes->>'#{CAPTAIN_RESPONSES_USAGE}')::int, 0) + 1)::text::jsonb + )" + ) end def reset_response_usage diff --git a/spec/enterprise/models/account_spec.rb b/spec/enterprise/models/account_spec.rb index 1c727328e..58cf127ab 100644 --- a/spec/enterprise/models/account_spec.rb +++ b/spec/enterprise/models/account_spec.rb @@ -70,6 +70,7 @@ RSpec.describe Account, type: :model do ## Responses it 'incrementing responses updates usage_limits' do account.increment_response_usage + account.reload responses_limits = account.usage_limits[:captain][:responses]