Welcome to mirror list, hosted at ThFree Co, Russian Federation.

redis_cache_store.rb « patch « gitlab « lib - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 041cb2d44bda915307a839adfb20de246e4e79b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# frozen_string_literal: true

module Gitlab
  module Patch
    module RedisCacheStore
      PIPELINE_BATCH_SIZE = 100

      # We will try keep patched code explicit and matching the original signature in
      # https://github.com/rails/rails/blob/v6.1.7.2/activesupport/lib/active_support/cache/redis_cache_store.rb#L361
      def read_multi_mget(*names) # rubocop:disable Style/ArgumentsForwarding
        return super unless enable_rails_cache_pipeline_patch?
        return super unless use_patched_mget?

        patched_read_multi_mget(*names) # rubocop:disable Style/ArgumentsForwarding
      end

      # `delete_multi_entries` in Rails runs a multi-key `del` command
      # patch will run pipelined single-key `del` for Redis Cluster compatibility
      def delete_multi_entries(entries, **options)
        return super unless enable_rails_cache_pipeline_patch?

        delete_count = 0
        redis.with do |conn|
          entries.each_slice(PIPELINE_BATCH_SIZE) do |subset|
            delete_count += Gitlab::Redis::CrossSlot::Pipeline.new(conn).pipelined do |pipeline|
              subset.each { |entry| pipeline.del(entry) }
            end.sum
          end
        end
        delete_count
      end

      # Copied from https://github.com/rails/rails/blob/v6.1.6.1/activesupport/lib/active_support/cache/redis_cache_store.rb
      # re-implements `read_multi_mget` using a pipeline of `get`s rather than an `mget`
      #
      def patched_read_multi_mget(*names)
        options = names.extract_options!
        options = merged_options(options)
        return {} if names == []

        raw = options&.fetch(:raw, false)

        keys = names.map { |name| normalize_key(name, options) }

        values = failsafe(:patched_read_multi_mget, returning: {}) do
          redis.with do |c|
            if c.is_a?(Gitlab::Redis::MultiStore)
              c.with_readonly_pipeline { pipeline_mget(c, keys) }
            else
              pipeline_mget(c, keys)
            end
          end
        end

        names.zip(values).each_with_object({}) do |(name, value), results|
          if value # rubocop:disable Style/Next
            entry = deserialize_entry(value, raw: raw)
            unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
              results[name] = entry.value
            end
          end
        end
      end

      def pipeline_mget(conn, keys)
        keys.each_slice(PIPELINE_BATCH_SIZE).flat_map do |subset|
          Gitlab::Redis::CrossSlot::Pipeline.new(conn).pipelined do |p|
            subset.each { |key| p.get(key) }
          end
        end
      end

      private

      def enable_rails_cache_pipeline_patch?
        redis.with { |c| ::Gitlab::Redis::ClusterUtil.cluster?(c) }
      end

      # MultiStore reads ONLY from the default store (no fallback), hence we can use `mget`
      # if the default store is not a Redis::Cluster. We should do that as pipelining gets on a single redis is slow
      def use_patched_mget?
        redis.with do |conn|
          next true unless conn.is_a?(Gitlab::Redis::MultiStore)

          ::Gitlab::Redis::ClusterUtil.cluster?(conn.default_store)
        end
      end
    end
  end
end