Commit 6d9eb344 authored by Robert May's avatar Robert May

Rate-limited action caching for branches API

parent aa389f6a
---
name: api_caching_rate_limit_branches
introduced_by_url:
rollout_issue_url:
milestone: '13.12'
type: development
group: group::source code
default_enabled: false
...@@ -38,33 +38,37 @@ module API ...@@ -38,33 +38,37 @@ module API
optional :page_token, type: String, desc: 'Name of branch to start the paginaition from' 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 ff_enabled = Feature.enabled?(:api_caching_rate_limit_branches, user_project, default_enabled: :yaml)
repository = user_project.repository cache_action_if(ff_enabled, [user_project, :branches, current_user&.cache_key, params], expires_in: 30.seconds) do
user_project.preload_protected_branches
branches_finder = BranchesFinder.new(repository, declared_params(include_missing: false))
branches = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(branches_finder) repository = user_project.repository
merged_branch_names = repository.merged_branch_names(branches.map(&:name)) branches_finder = BranchesFinder.new(repository, declared_params(include_missing: false))
branches = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(branches_finder)
if Feature.enabled?(:api_caching_branches, user_project, type: :development, default_enabled: :yaml)
present_cached( merged_branch_names = repository.merged_branch_names(branches.map(&:name))
branches,
with: Entities::Branch, if Feature.enabled?(:api_caching_branches, user_project, type: :development, default_enabled: :yaml)
current_user: current_user, present_cached(
project: user_project, branches,
merged_branch_names: merged_branch_names, with: Entities::Branch,
expires_in: 10.minutes, current_user: current_user,
cache_context: -> (branch) { [current_user&.cache_key, merged_branch_names.include?(branch.name)] } project: user_project,
) merged_branch_names: merged_branch_names,
else expires_in: 10.minutes,
present( cache_context: -> (branch) { [current_user&.cache_key, merged_branch_names.include?(branch.name)] }
branches, )
with: Entities::Branch, else
current_user: current_user, present(
project: user_project, branches,
merged_branch_names: merged_branch_names with: Entities::Branch,
) current_user: current_user,
project: user_project,
merged_branch_names: merged_branch_names
)
end
end end
end end
......
...@@ -63,6 +63,32 @@ module API ...@@ -63,6 +63,32 @@ module API
body Gitlab::Json::PrecompiledJson.new(json) body Gitlab::Json::PrecompiledJson.new(json)
end end
# Action caching implementation
#
# This allows you to wrap an entire API endpoint call in a cache, useful
# for short TTL caches to effectively rate-limit an endpoint.
#
# @param key [Object] any object that can be converted into a cache key
# @param expires_in [ActiveSupport::Duration, Integer] an expiry time for the cache entry
# @return [Gitlab::Json::PrecompiledJson]
def cache_action(key, **cache_opts)
json = cache.fetch(key, **cache_opts) do
Gitlab::Json.dump(yield.as_json)
end
body Gitlab::Json::PrecompiledJson.new(json)
end
def cache_action_if(conditional, *opts)
if conditional
cache_action(*opts) do
yield
end
else
yield
end
end
private private
# Optionally uses a `Proc` to add context to a cache key # Optionally uses a `Proc` to add context to a cache key
......
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