Class: Gitlab::RepositoryHashCache

Inherits:
Object
  • Object
show all
Defined in:
lib/gitlab/repository_hash_cache.rb

Constant Summary collapse

RepositoryHashCacheError =
Class.new(StandardError)
InvalidKeysProvidedError =
Class.new(RepositoryHashCacheError)
InvalidHashProvidedError =
Class.new(RepositoryHashCacheError)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(repository, extra_namespace: nil, expires_in: 1.day) ⇒ RepositoryHashCache

Returns a new instance of RepositoryHashCache.

Parameters:

  • repository (Repository)
  • extra_namespace (String) (defaults to: nil)
  • expires_in (Integer) (defaults to: 1.day)

    expiry time for hash store keys



20
21
22
23
24
25
26
# File 'lib/gitlab/repository_hash_cache.rb', line 20

def initialize(repository, extra_namespace: nil, expires_in: 1.day)
  @repository = repository
  @namespace = "#{repository.full_path}"
  @namespace += ":#{repository.project.id}" if repository.project
  @namespace = "#{@namespace}:#{extra_namespace}" if extra_namespace
  @expires_in = expires_in
end

Instance Attribute Details

#expires_inObject (readonly)

Returns the value of attribute expires_in.



11
12
13
# File 'lib/gitlab/repository_hash_cache.rb', line 11

def expires_in
  @expires_in
end

#namespaceObject (readonly)

Returns the value of attribute namespace.



11
12
13
# File 'lib/gitlab/repository_hash_cache.rb', line 11

def namespace
  @namespace
end

#repositoryObject (readonly)

Returns the value of attribute repository.



11
12
13
# File 'lib/gitlab/repository_hash_cache.rb', line 11

def repository
  @repository
end

Instance Method Details

#cache_key(type) ⇒ String

Parameters:

  • type (String)

Returns:

  • (String)


30
31
32
# File 'lib/gitlab/repository_hash_cache.rb', line 30

def cache_key(type)
  "#{type}:#{namespace}:hash"
end

#delete(*keys) ⇒ Integer

Returns the number of keys successfully deleted.

Parameters:

  • keys (String)

    one or multiple keys to delete

Returns:

  • (Integer)

    the number of keys successfully deleted



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/gitlab/repository_hash_cache.rb', line 36

def delete(*keys)
  return 0 if keys.empty?

  with do |redis|
    keys = keys.map { |key| cache_key(key) }

    Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
      if Gitlab::Redis::ClusterUtil.cluster?(redis)
        Gitlab::Redis::ClusterUtil.batch_unlink(keys, redis)
      else
        redis.unlink(*keys)
      end
    end
  end
end

#fetch_and_add_missing(key, h_keys) {|missing_keys, new_values| ... } ⇒ Hash

A variation on the ‘fetch` pattern of other cache stores. This method allows you to attempt to fetch a group of keys from the hash store, and for any keys that are missing values a block is then called to provide those values, which are then written back into Redis. Both sets of data are then combined and returned as one hash.

Parameters:

  • key (String)
  • h_keys (Array<String>)

    the keys to fetch or add to the cache

Yield Parameters:

  • missing_keys (Array<String>)

    the keys missing from the cache

  • new_values (Hash)

    the hash to be populated by the block

Returns:

  • (Hash)

    the amalgamated hash of cached and uncached values



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/gitlab/repository_hash_cache.rb', line 115

def fetch_and_add_missing(key, h_keys, &block)
  # Check the cache for all supplied keys
  cache_values = read_members(key, h_keys)

  # Find the results which returned nil (meaning they're not in the cache)
  missing = cache_values.select { |_, v| v.nil? }.keys

  if missing.any?
    new_values = {}

    # Run the block, which updates the new_values hash
    yield(missing, new_values)

    # Ensure all values are converted to strings, to ensure merging hashes
    # below returns standardised data.
    new_values = standardize_hash(new_values)

    # Write the new values to the hset
    write(key, new_values)

    # Merge the two sets of values to return a complete hash
    cache_values.merge!(new_values)
  end

  record_metrics(key, cache_values, missing)

  cache_values
end

#key?(key, h_key) ⇒ True, False

Check if the provided hash key exists in the hash.

Parameters:

  • key (String)
  • h_key (String)

    the key to check presence in Redis

Returns:

  • (True, False)


57
58
59
# File 'lib/gitlab/repository_hash_cache.rb', line 57

def key?(key, h_key)
  with { |redis| redis.hexists(cache_key(key), h_key) }
end

#read_members(key, hash_keys = []) ⇒ Hash

Read the values of a set of keys from the hash store, and return them as a hash of those keys and their values.

Parameters:

  • key (String)
  • hash_keys (Array<String>) (defaults to: [])

    an array of keys to retrieve from the store

Returns:

  • (Hash)

    a Ruby hash of the provided keys and their values from the store

Raises:



67
68
69
70
71
72
73
74
75
76
77
# File 'lib/gitlab/repository_hash_cache.rb', line 67

def read_members(key, hash_keys = [])
  raise InvalidKeysProvidedError unless hash_keys.is_a?(Array) && hash_keys.any?

  with do |redis|
    # Fetch an array of values for the supplied keys
    values = redis.hmget(cache_key(key), hash_keys)

    # Turn it back into a hash
    hash_keys.zip(values).to_h
  end
end

#write(key, hash) ⇒ Boolean

Write a hash to the store. All keys and values will be strings when stored.

Parameters:

  • key (String)
  • hash (Hash)

    the hash to be written to Redis

Returns:

  • (Boolean)

    whether all operations were successful or not

Raises:



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/gitlab/repository_hash_cache.rb', line 84

def write(key, hash)
  raise InvalidHashProvidedError unless hash.is_a?(Hash) && hash.any?

  full_key = cache_key(key)

  with do |redis|
    results = redis.pipelined do |pipeline|
      # Set each hash key to the provided value
      hash.each do |h_key, h_value|
        pipeline.hset(full_key, h_key, h_value)
      end

      # Update the expiry time for this hset
      pipeline.expire(full_key, expires_in)
    end

    results.all?
  end
end