summaryrefslogtreecommitdiff
path: root/app/workers/issues
diff options
context:
space:
mode:
Diffstat (limited to 'app/workers/issues')
-rw-r--r--app/workers/issues/placement_worker.rb67
-rw-r--r--app/workers/issues/rebalancing_worker.rb51
-rw-r--r--app/workers/issues/reschedule_stuck_issue_rebalances_worker.rb36
3 files changed, 154 insertions, 0 deletions
diff --git a/app/workers/issues/placement_worker.rb b/app/workers/issues/placement_worker.rb
new file mode 100644
index 00000000000..ec29a754128
--- /dev/null
+++ b/app/workers/issues/placement_worker.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+module Issues
+ class PlacementWorker
+ include ApplicationWorker
+
+ data_consistency :always
+
+ sidekiq_options retry: 3
+
+ idempotent!
+ deduplicate :until_executed, including_scheduled: true
+ feature_category :team_planning
+ urgency :high
+ worker_resource_boundary :cpu
+ weight 2
+
+ # Move at most the most recent 100 issues
+ QUERY_LIMIT = 100
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def perform(issue_id, project_id = nil)
+ issue = find_issue(issue_id, project_id)
+ return unless issue
+
+ # Temporary disable moving null elements because of performance problems
+ # For more information check https://gitlab.com/gitlab-com/gl-infra/production/-/issues/4321
+ return if issue.blocked_for_repositioning?
+
+ # Move the oldest 100 unpositioned items to the end.
+ # This is to deal with out-of-order execution of the worker,
+ # while preserving creation order.
+ to_place = Issue
+ .relative_positioning_query_base(issue)
+ .with_null_relative_position
+ .order({ created_at: :asc }, { id: :asc })
+ .limit(QUERY_LIMIT + 1)
+ .to_a
+
+ leftover = to_place.pop if to_place.count > QUERY_LIMIT
+
+ Issue.move_nulls_to_end(to_place)
+ Issues::BaseService.new(project: nil).rebalance_if_needed(to_place.max_by(&:relative_position))
+ Issues::PlacementWorker.perform_async(nil, leftover.project_id) if leftover.present?
+ rescue RelativePositioning::NoSpaceLeft => e
+ Gitlab::ErrorTracking.log_exception(e, issue_id: issue_id, project_id: project_id)
+ Issues::RebalancingWorker.perform_async(nil, *root_namespace_id_to_rebalance(issue, project_id))
+ end
+
+ def find_issue(issue_id, project_id)
+ return Issue.id_in(issue_id).take if issue_id
+
+ project = Project.id_in(project_id).take
+ return unless project
+
+ project.issues.take
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ private
+
+ def root_namespace_id_to_rebalance(issue, project_id)
+ project_id = project_id.presence || issue.project_id
+ Project.find(project_id)&.self_or_root_group_ids
+ end
+ end
+end
diff --git a/app/workers/issues/rebalancing_worker.rb b/app/workers/issues/rebalancing_worker.rb
new file mode 100644
index 00000000000..466617d9fa1
--- /dev/null
+++ b/app/workers/issues/rebalancing_worker.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Issues
+ class RebalancingWorker
+ include ApplicationWorker
+
+ data_consistency :always
+
+ sidekiq_options retry: 3
+
+ idempotent!
+ urgency :low
+ feature_category :team_planning
+ deduplicate :until_executed, including_scheduled: true
+
+ def perform(ignore = nil, project_id = nil, root_namespace_id = nil)
+ # we need to have exactly one of the project_id and root_namespace_id params be non-nil
+ raise ArgumentError, "Expected only one of the params project_id: #{project_id} and root_namespace_id: #{root_namespace_id}" if project_id && root_namespace_id
+ return if project_id.nil? && root_namespace_id.nil?
+
+ # pull the projects collection to be rebalanced either the project if namespace is not a group(i.e. user namesapce)
+ # or the root namespace, this also makes the worker backward compatible with previous version where a project_id was
+ # passed as the param
+ projects_to_rebalance = projects_collection(project_id, root_namespace_id)
+
+ # something might have happened with the namespace between scheduling the worker and actually running it,
+ # maybe it was removed.
+ if projects_to_rebalance.blank?
+ Gitlab::ErrorTracking.log_exception(
+ ArgumentError.new("Projects to be rebalanced not found for arguments: project_id #{project_id}, root_namespace_id: #{root_namespace_id}"),
+ { project_id: project_id, root_namespace_id: root_namespace_id })
+
+ return
+ end
+
+ Issues::RelativePositionRebalancingService.new(projects_to_rebalance).execute
+ rescue Issues::RelativePositionRebalancingService::TooManyConcurrentRebalances => e
+ Gitlab::ErrorTracking.log_exception(e, root_namespace_id: root_namespace_id, project_id: project_id)
+ end
+
+ private
+
+ def projects_collection(project_id, root_namespace_id)
+ # we can have either project_id(older version) or project_id if project is part of a user namespace and not a group
+ # or root_namespace_id(newer version) never both.
+ return Project.id_in([project_id]) if project_id
+
+ Namespace.find_by_id(root_namespace_id)&.all_projects
+ end
+ end
+end
diff --git a/app/workers/issues/reschedule_stuck_issue_rebalances_worker.rb b/app/workers/issues/reschedule_stuck_issue_rebalances_worker.rb
new file mode 100644
index 00000000000..d1759589cc0
--- /dev/null
+++ b/app/workers/issues/reschedule_stuck_issue_rebalances_worker.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Issues
+ class RescheduleStuckIssueRebalancesWorker
+ include ApplicationWorker
+ include CronjobQueue
+
+ data_consistency :sticky
+
+ idempotent!
+ urgency :low
+ feature_category :team_planning
+ deduplicate :until_executed, including_scheduled: true
+
+ def perform
+ namespace_ids, project_ids = ::Gitlab::Issues::Rebalancing::State.fetch_rebalancing_groups_and_projects
+
+ return if namespace_ids.blank? && project_ids.blank?
+
+ namespaces = Namespace.id_in(namespace_ids)
+ projects = Project.id_in(project_ids)
+
+ IssueRebalancingWorker.bulk_perform_async_with_contexts(
+ namespaces,
+ arguments_proc: -> (namespace) { [nil, nil, namespace.id] },
+ context_proc: -> (namespace) { { namespace: namespace } }
+ )
+
+ IssueRebalancingWorker.bulk_perform_async_with_contexts(
+ projects,
+ arguments_proc: -> (project) { [nil, project.id, nil] },
+ context_proc: -> (project) { { project: project } }
+ )
+ end
+ end
+end