Class: Gitlab::Issues::Rebalancing::State

Inherits:
Object
  • Object
show all
Defined in:
lib/gitlab/issues/rebalancing/state.rb

Constant Summary collapse

REDIS_KEY_PREFIX =
"gitlab:issues-position-rebalances"
CONCURRENT_RUNNING_REBALANCES_KEY =
"#{REDIS_KEY_PREFIX}:running_rebalances"
RECENTLY_FINISHED_REBALANCE_PREFIX =
"#{REDIS_KEY_PREFIX}:recently_finished"
REDIS_EXPIRY_TIME =
10.days
MAX_NUMBER_OF_CONCURRENT_REBALANCES =
5
NAMESPACE =
1
PROJECT =
2

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root_namespace, projects) ⇒ State

Returns a new instance of State.



16
17
18
19
20
21
# File 'lib/gitlab/issues/rebalancing/state.rb', line 16

def initialize(root_namespace, projects)
  @root_namespace = root_namespace
  @projects = projects
  @rebalanced_container_type = @root_namespace.is_a?(Group) ? NAMESPACE : PROJECT
  @rebalanced_container_id = @rebalanced_container_type == NAMESPACE ? @root_namespace.id : projects.take.id # rubocop:disable CodeReuse/ActiveRecord
end

Class Method Details

.fetch_rebalancing_groups_and_projectsObject



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/gitlab/issues/rebalancing/state.rb', line 140

def self.fetch_rebalancing_groups_and_projects
  namespace_ids = []
  project_ids = []

  current_rebalancing_containers.each do |string|
    container_type, container_id = string.split('/', 2).map(&:to_i)

    case container_type
    when NAMESPACE
      namespace_ids << container_id
    when PROJECT
      project_ids << container_id
    end
  end

  [namespace_ids, project_ids]
end

.rebalance_recently_finished?(project_id, namespace_id) ⇒ Boolean

Returns:

  • (Boolean)


133
134
135
136
137
138
# File 'lib/gitlab/issues/rebalancing/state.rb', line 133

def self.rebalance_recently_finished?(project_id, namespace_id)
  container_id = project_id || namespace_id
  container_type = project_id.present? ? PROJECT : NAMESPACE

  Gitlab::Redis::SharedState.with { |redis| redis.get(recently_finished_key(container_type, container_id)) }
end

Instance Method Details

#cache_current_index(index) ⇒ Object



76
77
78
# File 'lib/gitlab/issues/rebalancing/state.rb', line 76

def cache_current_index(index)
  with_redis { |redis| redis.set(current_index_key, index, ex: REDIS_EXPIRY_TIME) }
end

#cache_current_project_id(project_id) ⇒ Object



84
85
86
# File 'lib/gitlab/issues/rebalancing/state.rb', line 84

def cache_current_project_id(project_id)
  with_redis { |redis| redis.set(current_project_key, project_id, ex: REDIS_EXPIRY_TIME) }
end

#cache_issue_ids(issue_ids) ⇒ Object



59
60
61
62
63
64
65
66
67
68
# File 'lib/gitlab/issues/rebalancing/state.rb', line 59

def cache_issue_ids(issue_ids)
  with_redis do |redis|
    values = issue_ids.map { |issue| [issue.relative_position, issue.id] }

    redis.multi do |multi|
      multi.zadd(issue_ids_key, values) unless values.blank?
      multi.expire(issue_ids_key, REDIS_EXPIRY_TIME)
    end
  end
end

#can_start_rebalance?Boolean

Returns:

  • (Boolean)


55
56
57
# File 'lib/gitlab/issues/rebalancing/state.rb', line 55

def can_start_rebalance?
  rebalance_in_progress? || concurrent_rebalance_within_limit?
end

#cleanup_cacheObject



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/gitlab/issues/rebalancing/state.rb', line 113

def cleanup_cache
  value = "#{rebalanced_container_type}/#{rebalanced_container_id}"

  # The clean up is done sequentially to be compatible with Redis Cluster
  # Do not use a pipeline as it fans-out in a Redis-Cluster setting and forego ordering guarantees
  with_redis do |redis|
    # srem followed by .del(issue_ids_key) to ensure that any subsequent redis errors would
    # result in a no-op job retry since current_index_key still exists
    redis.srem?(CONCURRENT_RUNNING_REBALANCES_KEY, value)
    redis.del(issue_ids_key)

    # delete current_index_key to ensure that subsequent redis errors would
    # result in a fresh job retry
    redis.del(current_index_key)

    # setting recently_finished_key last after job details is cleaned up
    redis.set(self.class.recently_finished_key(rebalanced_container_type, rebalanced_container_id), true, ex: 1.hour)
  end
end

#concurrent_running_rebalances_countObject



34
35
36
# File 'lib/gitlab/issues/rebalancing/state.rb', line 34

def concurrent_running_rebalances_count
  with_redis { |redis| redis.scard(CONCURRENT_RUNNING_REBALANCES_KEY).to_i }
end

#get_cached_issue_ids(index, limit) ⇒ Object



70
71
72
73
74
# File 'lib/gitlab/issues/rebalancing/state.rb', line 70

def get_cached_issue_ids(index, limit)
  with_redis do |redis|
    redis.zrange(issue_ids_key, index, index + limit - 1)
  end
end

#get_current_indexObject



80
81
82
# File 'lib/gitlab/issues/rebalancing/state.rb', line 80

def get_current_index
  with_redis { |redis| redis.get(current_index_key).to_i }
end

#get_current_project_idObject



88
89
90
# File 'lib/gitlab/issues/rebalancing/state.rb', line 88

def get_current_project_id
  with_redis { |redis| redis.get(current_project_key) }
end

#issue_countObject



92
93
94
# File 'lib/gitlab/issues/rebalancing/state.rb', line 92

def issue_count
  @issue_count ||= with_redis { |redis| redis.zcard(issue_ids_key) }
end

#rebalance_in_progress?Boolean

Returns:

  • (Boolean)


38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/gitlab/issues/rebalancing/state.rb', line 38

def rebalance_in_progress?
  is_running = case rebalanced_container_type
               when NAMESPACE
                 namespace_ids = self.class.current_rebalancing_containers.filter_map { |string| string.split("#{NAMESPACE}/").second.to_i }
                 namespace_ids.include?(root_namespace.id)
               when PROJECT
                 project_ids = self.class.current_rebalancing_containers.filter_map { |string| string.split("#{PROJECT}/").second.to_i }
                 project_ids.include?(projects.take.id) # rubocop:disable CodeReuse/ActiveRecord
               else
                 false
               end

  refresh_keys_expiration if is_running

  is_running
end

#refresh_keys_expirationObject



100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/gitlab/issues/rebalancing/state.rb', line 100

def refresh_keys_expiration
  with_redis do |redis|
    Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
      redis.pipelined do |pipeline|
        pipeline.expire(issue_ids_key, REDIS_EXPIRY_TIME)
        pipeline.expire(current_index_key, REDIS_EXPIRY_TIME)
        pipeline.expire(current_project_key, REDIS_EXPIRY_TIME)
        pipeline.expire(CONCURRENT_RUNNING_REBALANCES_KEY, REDIS_EXPIRY_TIME)
      end
    end
  end
end

#remove_current_project_id_cacheObject



96
97
98
# File 'lib/gitlab/issues/rebalancing/state.rb', line 96

def remove_current_project_id_cache
  with_redis { |redis| redis.del(current_project_key) }
end

#track_new_running_rebalanceObject



23
24
25
26
27
28
29
30
31
32
# File 'lib/gitlab/issues/rebalancing/state.rb', line 23

def track_new_running_rebalance
  with_redis do |redis|
    redis.multi do |multi|
      # we trigger re-balance for namespaces(groups) or specific user project
      value = "#{rebalanced_container_type}/#{rebalanced_container_id}"
      multi.sadd?(CONCURRENT_RUNNING_REBALANCES_KEY, value)
      multi.expire(CONCURRENT_RUNNING_REBALANCES_KEY, REDIS_EXPIRY_TIME)
    end
  end
end