Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: reduce contention when updating the contract_data_updated_at fi…
Browse files Browse the repository at this point in the history
…eld for integrations
bethesque committed Mar 21, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 92a97d5 commit a829402
Showing 2 changed files with 28 additions and 7 deletions.
12 changes: 12 additions & 0 deletions lib/pact_broker/dataset.rb
Original file line number Diff line number Diff line change
@@ -101,6 +101,18 @@ def order_ignore_case column_name = :name
def order_append_ignore_case column_name = :name
order_append(Sequel.function(:lower, column_name))
end

# Executes a SELECT query FOR UPDATE, with SKIP LOCKED if supported (postgres only).
# With FOR UPDATE SKIP LOCKED, the SELECT will run immediately, not waiting for any other transactions,
# and only return rows that are not already locked by another transaction.
# The FOR UPDATE is required to make it work this way - SKIP LOCKED on its own does not work.
def for_update_skip_locked_if_supported
if supports_skip_locked?
for_update.skip_locked
else
self
end
end
end
end

23 changes: 16 additions & 7 deletions lib/pact_broker/integrations/repository.rb
Original file line number Diff line number Diff line change
@@ -50,18 +50,27 @@ def delete(consumer_id, provider_id)
# @param [PactBroker::Domain::Pacticipant, nil] consumer the consumer for the integration, or nil if for a provider-only event (eg. Pactflow provider contract published)
# @param [PactBroker::Domain::Pacticipant] provider the provider for the integration
def set_contract_data_updated_at(consumer, provider)
Integration
.where({ consumer_id: consumer&.id, provider_id: provider.id }.compact )
.update(contract_data_updated_at: Sequel.datetime_class.now)
set_contract_data_updated_at_for_multiple_integrations([OpenStruct.new(consumer: consumer, provider: provider)])
end


# Sets the contract_data_updated_at for the integrations as specified by an array of objects which each have a consumer and provider
# @param [Array<Object>] where each object has a consumer and a provider
# Sets the contract_data_updated_at for the integrations as specified by an array of objects which each have a consumer and provider.
#
# The contract_data_updated_at attribute is only ever used for ordering the list of integrations on the index page of the *Pact Broker* UI,
# so that the most recently updated integrations (the ones you're likely working on) are showed at the top of the first page.
# There is often contention around updating it however, which can cause deadlocks, and slow down API responses.
# Because it's not a critical field (eg. it won't change any can-i-deploy results), the easiest way to reduce this contention
# is to just not update it if the row is locked, because if it is locked, the value of contract_data_updated_at is already
# going to be a date from a few seconds ago, which is perfectly fine for the purposes for which we are using the value.
# @param [Array<Object>] where each object MAY have a consumer and does have a provider (for Pactflow provider contract published there is no consumer)
def set_contract_data_updated_at_for_multiple_integrations(objects_with_consumer_and_provider)
consumer_and_provider_ids = objects_with_consumer_and_provider.collect{ | object | [object.consumer.id, object.provider.id] }.uniq
consumer_and_provider_ids = objects_with_consumer_and_provider.collect{ | object | { consumer_id: object.consumer&.id, provider_id: object.provider.id }.compact }.uniq
integration_ids_to_update = Integration
.select(:id)
.where(Sequel.|(*consumer_and_provider_ids))
.for_update_skip_locked_if_supported
Integration
.where([:consumer_id, :provider_id] => consumer_and_provider_ids)
.where(id: integration_ids_to_update)
.update(contract_data_updated_at: Sequel.datetime_class.now)
end
end

0 comments on commit a829402

Please sign in to comment.