BigW Consortium Gitlab

Refactor runner attribute caching implementation

parent 28fd49c1
...@@ -5,7 +5,7 @@ module Ci ...@@ -5,7 +5,7 @@ module Ci
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
ONLINE_CONTACT_TIMEOUT = 1.hour ONLINE_CONTACT_TIMEOUT = 1.hour
UPDATE_DB_RUNNER_INFO_EVERY = 1.hour UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes
AVAILABLE_SCOPES = %w[specific shared active paused online].freeze AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level].freeze FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level].freeze
...@@ -68,12 +68,20 @@ module Ci ...@@ -68,12 +68,20 @@ module Ci
ONLINE_CONTACT_TIMEOUT.ago ONLINE_CONTACT_TIMEOUT.ago
end end
def cached_contacted_at def contacted_at
if runner_info_cache(:contacted_at) cached_attribute(:contacted_at) || read_attribute(:contacted_at)
Time.zone.parse(runner_info_cache(:contacted_at)) end
else
self.contacted_at def version
cached_attribute(:version) || read_attribute(:version)
end end
def revision
cached_attribute(:revision) || read_attribute(:revision)
end
def architecture
cached_attribute(:architecture) || read_attribute(:architecture)
end end
def set_default_values def set_default_values
...@@ -97,7 +105,7 @@ module Ci ...@@ -97,7 +105,7 @@ module Ci
end end
def online? def online?
contacted_at && cached_contacted_at > self.class.contact_time_deadline contacted_at && contacted_at > self.class.contact_time_deadline
end end
def status def status
...@@ -161,16 +169,16 @@ module Ci ...@@ -161,16 +169,16 @@ module Ci
ensure_runner_queue_value == value if value.present? ensure_runner_queue_value == value if value.present?
end end
def update_runner_info(params) def update_cached_info(values)
update_runner_info_cache(params) values = values.slice(:version, :revision, :platform, :architecture)
values[:contacted_at] = Time.now
# Use a 1h threshold to prevent beating DB updates. cache_attributes(values)
return unless self.contacted_at.nil? ||
(Time.now - self.contacted_at) >= UPDATE_DB_RUNNER_INFO_EVERY
self.contacted_at = Time.now if persist_cached_data?
self.assign_attributes(params) self.assign_attributes(values)
self.save if self.changed? self.save
end
end end
private private
...@@ -185,24 +193,33 @@ module Ci ...@@ -185,24 +193,33 @@ module Ci
"runner:build_queue:#{self.token}" "runner:build_queue:#{self.token}"
end end
def runner_info_redis_cache_key def cache_attribute_key(key)
"runner:info:#{self.id}" "runner:info:#{self.id}:#{key}"
end end
def update_runner_info_cache(params) def persist_cached_data?
Gitlab::Redis::SharedState.with do |redis| # Use a random threshold to prevent beating DB updates.
redis.set("#{runner_info_redis_cache_key}:contacted_at", Time.now) # It generates a distribution between [40m, 80m].
params && params.each do |key, value| contacted_at_max_age = UPDATE_DB_RUNNER_INFO_EVERY + Random.rand(UPDATE_DB_RUNNER_INFO_EVERY)
redis_key = "#{runner_info_redis_cache_key}:#{key}"
redis.set(redis_key, value) real_contacted_at = read_attribute(:contacted_at)
real_contacted_at.nil? ||
(Time.now - real_contacted_at) >= contacted_at_max_age
end end
def cached_attribute(key)
@cached_attributes = {}
@cached_attributes[key] ||= Gitlab::Redis::SharedState.with do |redis|
redis.get(cache_attribute_key(key))
end end
end end
def runner_info_cache(attribute) def cache_attributes(values)
Gitlab::Redis::SharedState.with do |redis| Gitlab::Redis::SharedState.with do |redis|
redis.get("#{runner_info_redis_cache_key}:#{attribute}") values.each do |key, value|
redis.set(cache_attribute_key(key), value, ex: 24.hours)
end
end end
end end
......
...@@ -20,7 +20,7 @@ module API ...@@ -20,7 +20,7 @@ module API
def authenticate_runner! def authenticate_runner!
forbidden! unless current_runner forbidden! unless current_runner
current_runner.update_runner_info(get_runner_version_from_params) current_runner.update_cached_info(get_runner_version_from_params)
end end
def current_runner def current_runner
......
...@@ -146,9 +146,10 @@ describe Ci::Runner do ...@@ -146,9 +146,10 @@ describe Ci::Runner do
end end
def stub_redis_runner_contacted_at(value) def stub_redis_runner_contacted_at(value)
redis = double Gitlab::Redis::SharedState.with do |redis|
allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis) cache_key = runner.send(:cache_attribute_key, :contacted_at)
allow(redis).to receive(:get).with("#{runner.send(:runner_info_redis_cache_key)}:contacted_at").and_return(value) allow(redis).to receive(:get).with(cache_key).and_return(value)
end
end end
end end
...@@ -393,10 +394,10 @@ describe Ci::Runner do ...@@ -393,10 +394,10 @@ describe Ci::Runner do
end end
end end
describe '#update_runner_info' do describe '#update_cached_info' do
let(:runner) { create(:ci_runner) } let(:runner) { create(:ci_runner) }
subject { runner.update_runner_info(name: 'testing_runner') } subject { runner.update_cached_info(architecture: '18-bit') }
context 'when database was updated recently' do context 'when database was updated recently' do
before do before do
...@@ -404,7 +405,7 @@ describe Ci::Runner do ...@@ -404,7 +405,7 @@ describe Ci::Runner do
end end
it 'updates cache' do it 'updates cache' do
expect_redis_update(:contacted_at, :name) expect_redis_update(:architecture, :contacted_at)
subject subject
end end
...@@ -416,26 +417,25 @@ describe Ci::Runner do ...@@ -416,26 +417,25 @@ describe Ci::Runner do
end end
it 'updates database' do it 'updates database' do
expect_redis_update(:contacted_at, :name) expect_redis_update(:architecture, :contacted_at)
expect { subject }.to change { runner.reload.contacted_at } expect { subject }.to change { runner.reload.read_attribute(:contacted_at) }
.and change { runner.reload.name } .and change { runner.reload.read_attribute(:architecture) }
end end
it 'updates cache' do it 'updates cache' do
expect_redis_update(:contacted_at, :name) expect_redis_update(:architecture, :contacted_at)
subject subject
end end
end end
def expect_redis_update(*params) def expect_redis_update(*params)
redis = double Gitlab::Redis::SharedState.with do |redis|
expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis)
params.each do |param| params.each do |param|
redis_key = "#{runner.send(:runner_info_redis_cache_key)}:#{param}" redis_key = runner.send(:cache_attribute_key, param)
expect(redis).to receive(:set).with(redis_key, anything) expect(redis).to receive(:set).with(redis_key, anything, any_args)
end
end 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