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
optional :page_token, type: String, desc: 'Name of branch to start the paginaition from'
end
get ':id/repository/branches' do
user_project.preload_protected_branches
repository = user_project.repository
branches_finder = BranchesFinder.new(repository, declared_params(include_missing: false))
branches = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(branches_finder)
merged_branch_names = repository.merged_branch_names(branches.map(&:name))
if Feature.enabled?(:api_caching_branches, user_project, type: :development, default_enabled: :yaml)
present_cached(
branches,
with: Entities::Branch,
current_user: current_user,
project: user_project,
merged_branch_names: merged_branch_names,
expires_in: 10.minutes,
cache_context: -> (branch) { [current_user&.cache_key, merged_branch_names.include?(branch.name)] }
)
else
present(
branches,
with: Entities::Branch,
current_user: current_user,
project: user_project,
merged_branch_names: merged_branch_names
)
ff_enabled = Feature.enabled?(:api_caching_rate_limit_branches, user_project, default_enabled: :yaml)
cache_action_if(ff_enabled, [user_project, :branches, current_user&.cache_key, params], expires_in: 30.seconds) do
user_project.preload_protected_branches
repository = user_project.repository
branches_finder = BranchesFinder.new(repository, declared_params(include_missing: false))
branches = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(branches_finder)
merged_branch_names = repository.merged_branch_names(branches.map(&:name))
if Feature.enabled?(:api_caching_branches, user_project, type: :development, default_enabled: :yaml)
present_cached(
branches,
with: Entities::Branch,
current_user: current_user,
project: user_project,
merged_branch_names: merged_branch_names,
expires_in: 10.minutes,
cache_context: -> (branch) { [current_user&.cache_key, merged_branch_names.include?(branch.name)] }
)
else
present(
branches,
with: Entities::Branch,
current_user: current_user,
project: user_project,
merged_branch_names: merged_branch_names
)
end
end
end
......
......@@ -63,6 +63,32 @@ module API
body Gitlab::Json::PrecompiledJson.new(json)
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
# 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