diff options
Diffstat (limited to 'app/models/ci/build_trace_chunks/redis_base.rb')
-rw-r--r-- | app/models/ci/build_trace_chunks/redis_base.rb | 90 |
1 files changed, 90 insertions, 0 deletions
diff --git a/app/models/ci/build_trace_chunks/redis_base.rb b/app/models/ci/build_trace_chunks/redis_base.rb new file mode 100644 index 00000000000..3b7a844d122 --- /dev/null +++ b/app/models/ci/build_trace_chunks/redis_base.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +module Ci + module BuildTraceChunks + class RedisBase + CHUNK_REDIS_TTL = 1.week + LUA_APPEND_CHUNK = <<~EOS + local key, new_data, offset = KEYS[1], ARGV[1], ARGV[2] + local length = new_data:len() + local expire = #{CHUNK_REDIS_TTL.seconds} + local current_size = redis.call("strlen", key) + offset = tonumber(offset) + + if offset == 0 then + -- overwrite everything + redis.call("set", key, new_data, "ex", expire) + return redis.call("strlen", key) + elseif offset > current_size then + -- offset range violation + return -1 + elseif offset + length >= current_size then + -- efficiently append or overwrite and append + redis.call("expire", key, expire) + return redis.call("setrange", key, offset, new_data) + else + -- append and truncate + local current_data = redis.call("get", key) + new_data = current_data:sub(1, offset) .. new_data + redis.call("set", key, new_data, "ex", expire) + return redis.call("strlen", key) + end + EOS + + def data(model) + with_redis do |redis| + redis.get(key(model)) + end + end + + def set_data(model, new_data) + with_redis do |redis| + redis.set(key(model), new_data, ex: CHUNK_REDIS_TTL) + end + end + + def append_data(model, new_data, offset) + with_redis do |redis| + redis.eval(LUA_APPEND_CHUNK, keys: [key(model)], argv: [new_data, offset]) + end + end + + def size(model) + with_redis do |redis| + redis.strlen(key(model)) + end + end + + def delete_data(model) + delete_keys([[model.build_id, model.chunk_index]]) + end + + def keys(relation) + relation.pluck(:build_id, :chunk_index) + end + + def delete_keys(keys) + return if keys.empty? + + keys = keys.map { |key| key_raw(*key) } + + with_redis do |redis| + # https://gitlab.com/gitlab-org/gitlab/-/issues/224171 + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + redis.del(keys) + end + end + end + + private + + def key(model) + key_raw(model.build_id, model.chunk_index) + end + + def key_raw(build_id, chunk_index) + "gitlab:ci:trace:#{build_id.to_i}:chunks:#{chunk_index.to_i}" + end + end + end +end |