Commit 84cb8798 authored by Igor Drozdov's avatar Igor Drozdov

Merge branch '208738-improve-performance-of-branches-list-api-when-under-load' into 'master'

Use native Gitaly pagination for Branch list API

See merge request gitlab-org/gitlab!35819
parents da361a35 e596e619
...@@ -5,11 +5,15 @@ class BranchesFinder < GitRefsFinder ...@@ -5,11 +5,15 @@ class BranchesFinder < GitRefsFinder
super(repository, params) super(repository, params)
end end
def execute def execute(gitaly_pagination: false)
branches = repository.branches_sorted_by(sort) if gitaly_pagination && names.blank? && search.blank?
branches = by_search(branches) repository.branches_sorted_by(sort, pagination_params)
branches = by_names(branches) else
branches branches = repository.branches_sorted_by(sort)
branches = by_search(branches)
branches = by_names(branches)
branches
end
end end
private private
...@@ -18,6 +22,18 @@ class BranchesFinder < GitRefsFinder ...@@ -18,6 +22,18 @@ class BranchesFinder < GitRefsFinder
@params[:names].presence @params[:names].presence
end end
def per_page
@params[:per_page].presence
end
def page_token
"#{Gitlab::Git::BRANCH_REF_PREFIX}#{@params[:page_token]}" if @params[:page_token]
end
def pagination_params
{ limit: per_page, page_token: page_token }
end
def by_names(branches) def by_names(branches)
return branches unless names return branches unless names
......
...@@ -713,8 +713,8 @@ class Repository ...@@ -713,8 +713,8 @@ class Repository
"#{name}-#{highest_branch_id + 1}" "#{name}-#{highest_branch_id + 1}"
end end
def branches_sorted_by(value) def branches_sorted_by(sort_by, pagination_params = nil)
raw_repository.local_branches(sort_by: value) raw_repository.local_branches(sort_by: sort_by, pagination_params: pagination_params)
end end
def tags_sorted_by(value) def tags_sorted_by(value)
......
---
title: Use native Gitaly pagination for Branch list API
merge_request: 35819
author:
type: changed
...@@ -32,14 +32,21 @@ module API ...@@ -32,14 +32,21 @@ module API
params do params do
use :pagination use :pagination
use :filter_params use :filter_params
optional :page_token, type: String, desc: 'Name of branch to start the paginaition from'
end end
get ':id/repository/branches' do get ':id/repository/branches' do
user_project.preload_protected_branches user_project.preload_protected_branches
repository = user_project.repository repository = user_project.repository
branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute if Feature.enabled?(:branch_list_keyset_pagination, user_project)
branches = paginate(::Kaminari.paginate_array(branches)) branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute(gitaly_pagination: true)
else
branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute
branches = paginate(::Kaminari.paginate_array(branches))
end
merged_branch_names = repository.merged_branch_names(branches.map(&:name)) merged_branch_names = repository.merged_branch_names(branches.map(&:name))
present( present(
......
...@@ -127,9 +127,9 @@ module Gitlab ...@@ -127,9 +127,9 @@ module Gitlab
end end
end end
def local_branches(sort_by: nil) def local_branches(sort_by: nil, pagination_params: nil)
wrapped_gitaly_errors do wrapped_gitaly_errors do
gitaly_ref_client.local_branches(sort_by: sort_by) gitaly_ref_client.local_branches(sort_by: sort_by, pagination_params: pagination_params)
end end
end end
......
...@@ -110,8 +110,8 @@ module Gitlab ...@@ -110,8 +110,8 @@ module Gitlab
branch_names.count branch_names.count
end end
def local_branches(sort_by: nil) def local_branches(sort_by: nil, pagination_params: nil)
request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo) request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo, pagination_params: pagination_params)
request.sort_by = sort_by_param(sort_by) if sort_by request.sort_by = sort_by_param(sort_by) if sort_by
response = GitalyClient.call(@storage, :ref_service, :find_local_branches, request, timeout: GitalyClient.fast_timeout) response = GitalyClient.call(@storage, :ref_service, :find_local_branches, request, timeout: GitalyClient.fast_timeout)
consume_find_local_branches_response(response) consume_find_local_branches_response(response)
......
This diff is collapsed.
...@@ -17,6 +17,7 @@ RSpec.describe API::Branches do ...@@ -17,6 +17,7 @@ RSpec.describe API::Branches do
before do before do
project.add_maintainer(user) project.add_maintainer(user)
project.repository.add_branch(user, 'ends-with.txt', branch_sha) project.repository.add_branch(user, 'ends-with.txt', branch_sha)
stub_feature_flags(branch_list_keyset_pagination: false)
end end
describe "GET /projects/:id/repository/branches" do describe "GET /projects/:id/repository/branches" do
...@@ -29,16 +30,6 @@ RSpec.describe API::Branches do ...@@ -29,16 +30,6 @@ RSpec.describe API::Branches do
end end
end end
it 'returns the repository branches' do
get api(route, current_user), params: { per_page: 100 }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/branches')
expect(response).to include_pagination_headers
branch_names = json_response.map { |x| x['name'] }
expect(branch_names).to match_array(project.repository.branch_names)
end
def check_merge_status(json_response) def check_merge_status(json_response)
merged, unmerged = json_response.partition { |branch| branch['merged'] } merged, unmerged = json_response.partition { |branch| branch['merged'] }
merged_branches = merged.map { |branch| branch['name'] } merged_branches = merged.map { |branch| branch['name'] }
...@@ -47,22 +38,107 @@ RSpec.describe API::Branches do ...@@ -47,22 +38,107 @@ RSpec.describe API::Branches do
expect(project.repository.merged_branch_names(unmerged_branches)).to be_empty expect(project.repository.merged_branch_names(unmerged_branches)).to be_empty
end end
it 'determines only a limited number of merged branch names' do context 'with branch_list_keyset_pagination feature off' do
expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original context 'with legacy pagination params' do
it 'returns the repository branches' do
get api(route, current_user), params: { per_page: 100 }
get api(route, current_user), params: { per_page: 2 } expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/branches')
expect(response).to include_pagination_headers
branch_names = json_response.map { |x| x['name'] }
expect(branch_names).to match_array(project.repository.branch_names)
end
expect(response).to have_gitlab_http_status(:ok) it 'determines only a limited number of merged branch names' do
expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
get api(route, current_user), params: { per_page: 2 }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq 2
check_merge_status(json_response)
end
check_merge_status(json_response) it 'merge status matches reality on paginated input' do
expected_first_branch_name = project.repository.branches_sorted_by('name')[20].name
get api(route, current_user), params: { per_page: 20, page: 2 }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq 20
expect(json_response.first['name']).to eq(expected_first_branch_name)
check_merge_status(json_response)
end
end
context 'with gitaly pagination params ' do
it 'merge status matches reality on paginated input' do
expected_first_branch_name = project.repository.branches_sorted_by('name').first.name
get api(route, current_user), params: { per_page: 20, page_token: 'feature' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq 20
expect(json_response.first['name']).to eq(expected_first_branch_name)
check_merge_status(json_response)
end
end
end end
it 'merge status matches reality on paginated input' do context 'with branch_list_keyset_pagination feature on' do
get api(route, current_user), params: { per_page: 20, page: 2 } before do
stub_feature_flags(branch_list_keyset_pagination: true)
end
expect(response).to have_gitlab_http_status(:ok) context 'with gitaly pagination params ' do
it 'returns the repository branches' do
get api(route, current_user), params: { per_page: 100 }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/branches')
branch_names = json_response.map { |x| x['name'] }
expect(branch_names).to match_array(project.repository.branch_names)
end
it 'determines only a limited number of merged branch names' do
expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
get api(route, current_user), params: { per_page: 2 }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq 2
check_merge_status(json_response)
end
check_merge_status(json_response) it 'merge status matches reality on paginated input' do
expected_first_branch_name = project.repository.branches_sorted_by('name').drop_while { |b| b.name <= 'feature' }.first.name
get api(route, current_user), params: { per_page: 20, page_token: 'feature' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq 20
expect(json_response.first['name']).to eq(expected_first_branch_name)
check_merge_status(json_response)
end
end
context 'with legacy pagination params' do
it 'ignores legacy pagination params' do
expected_first_branch_name = project.repository.branches_sorted_by('name').first.name
get api(route, current_user), params: { per_page: 20, page: 2 }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first['name']).to eq(expected_first_branch_name)
check_merge_status(json_response)
end
end
end end
context 'when repository is disabled' do context 'when repository is disabled' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment