`/api/:version/repos/:namespace/:project/branches` should not query the primary databases frequently
Even though being a GET read-only endpoint, /api/:version/repos/:namespace/:project/branches is one of the top endpoint that frequently queries the primary database. Around 500k (38%) requests to this endpoint get this problem over total 1.3M request. Somehow, all of the troublesome requests have JIRA DVCS user agent.
It turns out that in the API controller, we update the feature usage of the integrations:
# Blah blah
def update_project_feature_usage_for(project)
# Prevent errors on GitLab Geo not allowing
# UPDATE statements to happen in GET requests.
return if Gitlab::Database.read_only?
project.log_jira_dvcs_integration_usage(cloud: jira_cloud?)
end
get ':namespace/:project/branches' do
user_project = find_project_with_access(params)
update_project_feature_usage_for(user_project) # !!!
branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))
present paginate(branches), with: ::API::Github::Entities::Branch, project: user_project
end
# Blah blah
Inside ProjectFeatureUsage, we wrap around timestamp updates with a transaction. That makes the session sticky and redirects the following queries to the primary.
def log_jira_dvcs_integration_usage(cloud: true)
transaction(requires_new: true) do
save unless persisted?
touch(self.class.jira_dvcs_integration_field(cloud: cloud))
end
rescue ActiveRecord::RecordNotUnique
reset
retry
end
Solution
In !56849 (merged), we introduces ::Gitlab::Database::LoadBalancing::Session.without_sticky_writes to prevent session stickiness after a write. It actually solves this issue. We just need to wrap:
def update_project_feature_usage_for(project)
# Prevent errors on GitLab Geo not allowing
# UPDATE statements to happen in GET requests.
return if Gitlab::Database.read_only?
::Gitlab::Database::LoadBalancing::Session.without_sticky_writes do
project.log_jira_dvcs_integration_usage(cloud: jira_cloud?)
end
end
The transaction in the model looks redundant. It can be refactored as:
def log_jira_dvcs_integration_usage(cloud: true)
assign_attributes(
self.class.jira_dvcs_integration_field(cloud: cloud) => Time.now
)
save
rescue ActiveRecord::RecordNotUnique
reset
retry
end
