Commit 139ae205 authored by Eugenia Grieff's avatar Eugenia Grieff

Clear cache for all keys

- Worker should clear public and total counts
- Add specs for method clear_all_cache_keys
parent 8be484f4
...@@ -22,11 +22,12 @@ module Groups ...@@ -22,11 +22,12 @@ module Groups
refresh_cache_over_threshold refresh_cache_over_threshold
end end
def refresh_cache_over_threshold def refresh_cache_over_threshold(key = nil)
key ||= cache_key
new_count = uncached_count new_count = uncached_count
if new_count > CACHED_COUNT_THRESHOLD if new_count > CACHED_COUNT_THRESHOLD
update_cache_for_key(cache_key) { new_count } update_cache_for_key(key) { new_count }
else else
delete_cache delete_cache
end end
...@@ -34,8 +35,10 @@ module Groups ...@@ -34,8 +35,10 @@ module Groups
new_count new_count
end end
def cache_key def cache_key(key_name = nil)
['groups', "#{issuable_key}_count_service", VERSION, group.id, cache_key_name] key_name ||= cache_key_name
['groups', "#{issuable_key}_count_service", VERSION, group.id, key_name]
end end
private private
......
...@@ -6,6 +6,12 @@ module Groups ...@@ -6,6 +6,12 @@ module Groups
PUBLIC_COUNT_KEY = 'group_public_open_issues_count' PUBLIC_COUNT_KEY = 'group_public_open_issues_count'
TOTAL_COUNT_KEY = 'group_total_open_issues_count' TOTAL_COUNT_KEY = 'group_total_open_issues_count'
def clear_all_cache_keys
[cache_key(PUBLIC_COUNT_KEY), cache_key(TOTAL_COUNT_KEY)].each do |key|
Rails.cache.delete(key)
end
end
private private
def cache_key_name def cache_key_name
...@@ -23,7 +29,14 @@ module Groups ...@@ -23,7 +29,14 @@ module Groups
end end
def relation_for_count def relation_for_count
IssuesFinder.new(user, group_id: group.id, state: 'opened', non_archived: true, include_subgroups: true, public_only: public_only?).execute IssuesFinder.new(
user,
group_id: group.id,
state: 'opened',
non_archived: true,
include_subgroups: true,
public_only: public_only?
).execute
end end
def issuable_key def issuable_key
......
...@@ -17,8 +17,8 @@ module Issuable ...@@ -17,8 +17,8 @@ module Issuable
set_update_params(type) set_update_params(type)
updated_issuables = update_issuables(type, ids) updated_issuables = update_issuables(type, ids)
if updated_issuables.present? && requires_count_cache_refresh?(type) if updated_issuables.present? && requires_count_cache_reset?(type)
schedule_group_issues_count_refresh(updated_issuables) schedule_group_issues_count_reset(updated_issuables)
end end
response_success(payload: { count: updated_issuables.size }) response_success(payload: { count: updated_issuables.size })
...@@ -86,15 +86,15 @@ module Issuable ...@@ -86,15 +86,15 @@ module Issuable
ServiceResponse.error(message: message, http_status: http_status) ServiceResponse.error(message: message, http_status: http_status)
end end
def requires_count_cache_refresh?(type) def requires_count_cache_reset?(type)
type.to_sym == :issue && params.include?(:state_event) type.to_sym == :issue && params.include?(:state_event)
end end
def schedule_group_issues_count_refresh(updated_issuables) def schedule_group_issues_count_reset(updated_issuables)
group_ids = updated_issuables.map(&:project).map(&:namespace_id) group_ids = updated_issuables.map(&:project).map(&:namespace_id)
return if group_ids.empty? return if group_ids.empty?
Issuables::RefreshGroupsIssueCounterWorker.perform_async(group_ids) Issuables::ClearGroupsIssueCounterWorker.perform_async(group_ids)
end end
end end
end end
......
...@@ -2110,7 +2110,8 @@ ...@@ -2110,7 +2110,8 @@
:weight: 1 :weight: 1
:idempotent: :idempotent:
:tags: [] :tags: []
- :name: issuables_refresh_groups_issue_counter - :name: issuables_clear_groups_issue_counter
:worker_name: Issuables::ClearGroupsIssueCounterWorker
:feature_category: :issue_tracking :feature_category: :issue_tracking
:has_external_dependencies: :has_external_dependencies:
:urgency: :low :urgency: :low
......
# frozen_string_literal: true # frozen_string_literal: true
module Issuables module Issuables
class RefreshGroupsIssueCounterWorker class ClearGroupsIssueCounterWorker
include ApplicationWorker include ApplicationWorker
idempotent! idempotent!
...@@ -15,14 +15,14 @@ module Issuables ...@@ -15,14 +15,14 @@ module Issuables
.new(Group.by_id(group_ids)) .new(Group.by_id(group_ids))
.base_and_ancestors .base_and_ancestors
refresh_cached_count(groups_with_ancestors) clear_cached_count(groups_with_ancestors)
end end
private private
def refresh_cached_count(groups) def clear_cached_count(groups)
groups.each do |group| groups.each do |group|
Groups::OpenIssuesCountService.new(group).refresh_cache_over_threshold Groups::OpenIssuesCountService.new(group).clear_all_cache_keys
end end
end end
end end
......
--- ---
title: Refresh group open issues count cache when bulk updating issue state title: Clear group open issues count cache when bulk updating issues state
merge_request: 56386 merge_request: 56386
author: author:
type: added type: added
...@@ -190,7 +190,7 @@ ...@@ -190,7 +190,7 @@
- 1 - 1
- - issuable_export_csv - - issuable_export_csv
- 1 - 1
- - issuables_refresh_groups_issue_counter - - issuables_clear_groups_issue_counter
- 1 - 1
- - issue_placement - - issue_placement
- 2 - 2
......
...@@ -2376,4 +2376,18 @@ RSpec.describe Group do ...@@ -2376,4 +2376,18 @@ RSpec.describe Group do
it_behaves_like 'model with Debian distributions' it_behaves_like 'model with Debian distributions'
end end
describe '.ids_with_disabled_email' do
let!(:parent_1) { create(:group, emails_disabled: true) }
let!(:child_1) { create(:group, parent: parent_1) }
let!(:parent_2) { create(:group, emails_disabled: false) }
let!(:child_2) { create(:group, parent: parent_2) }
let!(:other_group) { create(:group, emails_disabled: false) }
subject(:group_ids_where_email_is_disabled) { described_class.ids_with_disabled_email([child_1, child_2, other_group]) }
it { is_expected.to eq(Set.new([child_1.id])) }
end
end end
...@@ -57,4 +57,15 @@ RSpec.describe Groups::OpenIssuesCountService, :use_clean_rails_memory_store_cac ...@@ -57,4 +57,15 @@ RSpec.describe Groups::OpenIssuesCountService, :use_clean_rails_memory_store_cac
it_behaves_like 'a counter caching service with threshold' it_behaves_like 'a counter caching service with threshold'
end end
end end
describe '#clear_all_cache_keys' do
it 'calls `Rails.cache.delete` with the correct keys' do
expect(Rails.cache).to receive(:delete)
.with(['groups', 'open_issues_count_service', 1, group.id, described_class::PUBLIC_COUNT_KEY])
expect(Rails.cache).to receive(:delete)
.with(['groups', 'open_issues_count_service', 1, group.id, described_class::TOTAL_COUNT_KEY])
subject.clear_all_cache_keys
end
end
end end
...@@ -101,17 +101,17 @@ RSpec.describe Issuable::BulkUpdateService do ...@@ -101,17 +101,17 @@ RSpec.describe Issuable::BulkUpdateService do
end end
end end
shared_examples 'scheduling cached group count refresh' do shared_examples 'scheduling cached group count clear' do
it 'schedules worker' do it 'schedules worker' do
expect(Issuables::RefreshGroupsIssueCounterWorker).to receive(:perform_async) expect(Issuables::ClearGroupsIssueCounterWorker).to receive(:perform_async)
bulk_update(issuables, params) bulk_update(issuables, params)
end end
end end
shared_examples 'not scheduling cached group count refresh' do shared_examples 'not scheduling cached group count clear' do
it 'does not schedule worker' do it 'does not schedule worker' do
expect(Issuables::RefreshGroupsIssueCounterWorker).not_to receive(:perform_async) expect(Issuables::ClearGroupsIssueCounterWorker).not_to receive(:perform_async)
bulk_update(issuables, params) bulk_update(issuables, params)
end end
...@@ -148,7 +148,7 @@ RSpec.describe Issuable::BulkUpdateService do ...@@ -148,7 +148,7 @@ RSpec.describe Issuable::BulkUpdateService do
expect(project.issues.closed).not_to be_empty expect(project.issues.closed).not_to be_empty
end end
it_behaves_like 'scheduling cached group count refresh' do it_behaves_like 'scheduling cached group count clear' do
let(:issuables) { issues } let(:issuables) { issues }
let(:params) { { state_event: 'close' } } let(:params) { { state_event: 'close' } }
end end
...@@ -171,7 +171,7 @@ RSpec.describe Issuable::BulkUpdateService do ...@@ -171,7 +171,7 @@ RSpec.describe Issuable::BulkUpdateService do
expect(project.issues.opened).not_to be_empty expect(project.issues.opened).not_to be_empty
end end
it_behaves_like 'scheduling cached group count refresh' do it_behaves_like 'scheduling cached group count clear' do
let(:issuables) { issues } let(:issuables) { issues }
let(:params) { { state_event: 'reopen' } } let(:params) { { state_event: 'reopen' } }
end end
...@@ -258,7 +258,7 @@ RSpec.describe Issuable::BulkUpdateService do ...@@ -258,7 +258,7 @@ RSpec.describe Issuable::BulkUpdateService do
it_behaves_like 'updates milestones' it_behaves_like 'updates milestones'
it_behaves_like 'not scheduling cached group count refresh' do it_behaves_like 'not scheduling cached group count clear' do
let(:params) { { milestone_id: milestone.id } } let(:params) { { milestone_id: milestone.id } }
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Issuables::ClearGroupsIssueCounterWorker do
describe '#perform' do
let_it_be(:user) { create(:user) }
let_it_be(:parent_group) { create(:group) }
let_it_be(:root_group) { create(:group, parent: parent_group) }
let_it_be(:subgroup) { create(:group, parent: root_group) }
let(:count_service) { Groups::OpenIssuesCountService }
let(:instance1) { instance_double(count_service) }
let(:instance2) { instance_double(count_service) }
it_behaves_like 'an idempotent worker' do
let(:job_args) { [[root_group.id]] }
let(:exec_times) { IdempotentWorkerHelper::WORKER_EXEC_TIMES }
it 'clears the cached issue count in given groups and ancestors' do
expect(count_service).to receive(:new)
.exactly(exec_times).times.with(root_group).and_return(instance1)
expect(count_service).to receive(:new)
.exactly(exec_times).times.with(parent_group).and_return(instance2)
expect(count_service).not_to receive(:new).with(subgroup)
[instance1, instance2].all? do |instance|
expect(instance).to receive(:clear_all_cache_keys).exactly(exec_times).times
end
subject
end
end
it 'does not call count service or rise error when group_ids is empty' do
expect(count_service).not_to receive(:new)
expect(Gitlab::ErrorTracking).not_to receive(:log_exception)
described_class.new.perform([])
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Issuables::RefreshGroupsIssueCounterWorker do
describe '#perform' do
let_it_be(:user) { create(:user) }
let_it_be(:parent_group) { create(:group) }
let_it_be(:root_group) { create(:group, parent: parent_group) }
let_it_be(:subgroup) { create(:group, parent: root_group) }
let(:count_service) { Groups::OpenIssuesCountService }
let(:group_ids) { [root_group.id] }
context 'when group_ids is empty' do
let(:group_ids) { [] }
it 'does not call count service or rise error' do
expect(count_service).not_to receive(:new)
expect(Gitlab::ErrorTracking).not_to receive(:log_exception)
described_class.new.perform(group_ids)
end
end
context 'when updating cache' do
let(:instance1) { instance_double(count_service) }
let(:instance2) { instance_double(count_service) }
it_behaves_like 'an idempotent worker' do
let(:job_args) { [group_ids] }
let(:exec_times) { IdempotentWorkerHelper::WORKER_EXEC_TIMES }
it 'refreshes the issue count in given groups and ancestors' do
expect(count_service).to receive(:new)
.exactly(exec_times).times.with(root_group).and_return(instance1)
expect(count_service).to receive(:new)
.exactly(exec_times).times.with(parent_group).and_return(instance2)
expect(count_service).not_to receive(:new).with(subgroup)
[instance1, instance2].all? do |instance|
expect(instance).to receive(:refresh_cache_over_threshold).exactly(exec_times).times
end
subject
end
end
end
end
end
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