redcord 0.1.0 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16cb660da0ea843f300ff18cc090ccfed7883c9d1ca48ef0bc5ed8d4e805854c
4
- data.tar.gz: f53002cb43b3e5286ba1f914c41ffd6b8a9722d2d33d9764deb7bcba88d2659c
3
+ metadata.gz: 134041206308ab5d1cb967dca5b5985b2bdd6b9c058601d7040d75d1119a2c98
4
+ data.tar.gz: d04f590786ab4cd9251ac32f5eea7d11e8103c448916c4aaee290db177370eca
5
5
  SHA512:
6
- metadata.gz: db443ffbf21066c549b30daed2ad44f836aa4469065b7bd8b6d0c13f45597bd7bdbf6c7ef4202ddf8103a1c2fbcfca28ca60f9f59b90584d0ec718d380d0f908
7
- data.tar.gz: 40ded5853307f453fe7c5ab2962e29cc081c05ab7f0c2cfddb898259e207ae9c343dfee6a67967449ff43c227c88db6f7a7922c425d8e6df92466b5ed778c5c2
6
+ metadata.gz: 374deab010a1f6b40e038adae19b4a7dee5485c5e17ff3a7e8fc92a4e839a2e9835166acd9b6369e7b628e206c1a280e943a28c040172d107c147d3be158fa8b
7
+ data.tar.gz: 28279ab008fcf6ba226d574250f882f35b05b69fc968794430c3d992c69fd686ec9c2f3fa7821aa6227426c7a027fda07c6e929624a533058c4a396c975f1140
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require 'connection_pool'
3
+ require_relative 'redis'
4
+
5
+ class Redcord::ConnectionPool
6
+ def initialize(pool_size:, timeout:, **client_options)
7
+ @connection_pool = ::ConnectionPool.new(size: pool_size, timeout: timeout) do
8
+ # Construct a new client every time the block gets called
9
+ Redcord::Redis.new(**client_options, logger: Redcord::Logger.proxy)
10
+ end
11
+ end
12
+
13
+ # Avoid method_missing when possible for better performance
14
+ methods = Set.new(Redcord::Redis.instance_methods(false) + Redis.instance_methods(false))
15
+ methods.each do |method_name|
16
+ define_method method_name do |*args, &blk|
17
+ @connection_pool.with do |redis|
18
+ redis.send(method_name, *args, &blk)
19
+ end
20
+ end
21
+ end
22
+
23
+ def method_missing(method_name, *args, &blk)
24
+ @connection_pool.with do |redis|
25
+ redis.send(method_name, *args, &blk)
26
+ end
27
+ end
28
+ end
data/lib/redcord/redis.rb CHANGED
@@ -18,24 +18,19 @@ class Redcord::Redis < Redis
18
18
  ).returns(String)
19
19
  end
20
20
  def create_hash_returning_id(key, args, ttl:, index_attrs:, range_index_attrs:, custom_index_attrs:, hash_tag: nil)
21
- Redcord::Base.trace(
22
- 'redcord_redis_create_hash_returning_id',
23
- model_name: key,
24
- ) do
25
- id = "#{SecureRandom.uuid}#{hash_tag}"
26
- custom_index_attrs_flat = custom_index_attrs.inject([]) do |result, (index_name, attrs)|
27
- result << index_name
28
- result << attrs.size
29
- result + attrs
30
- end
31
- run_script(
32
- :create_hash,
33
- keys: [id, hash_tag],
34
- argv: [key, ttl, index_attrs.size, range_index_attrs.size, custom_index_attrs_flat.size] +
35
- index_attrs + range_index_attrs + custom_index_attrs_flat + args.to_a.flatten,
36
- )
37
- id
21
+ id = "#{SecureRandom.uuid}#{hash_tag}"
22
+ custom_index_attrs_flat = custom_index_attrs.inject([]) do |result, (index_name, attrs)|
23
+ result << index_name
24
+ result << attrs.size
25
+ result + attrs
38
26
  end
27
+ run_script(
28
+ :create_hash,
29
+ keys: [id, hash_tag],
30
+ argv: [key, ttl, index_attrs.size, range_index_attrs.size, custom_index_attrs_flat.size] +
31
+ index_attrs + range_index_attrs + custom_index_attrs_flat + args.to_a.flatten,
32
+ )
33
+ id
39
34
  end
40
35
 
41
36
  sig do
@@ -51,26 +46,21 @@ class Redcord::Redis < Redis
51
46
  ).void
52
47
  end
53
48
  def update_hash(model, id, args, ttl:, index_attrs:, range_index_attrs:, custom_index_attrs:, hash_tag:)
54
- Redcord::Base.trace(
55
- 'redcord_redis_update_hash',
56
- model_name: model,
57
- ) do
58
- custom_index_attrs_flat = custom_index_attrs.inject([]) do |result, (index_name, attrs)|
59
- if !(args.keys.to_set & attrs.to_set).empty?
60
- result << index_name
61
- result << attrs.size
62
- result + attrs
63
- else
64
- result
65
- end
49
+ custom_index_attrs_flat = custom_index_attrs.inject([]) do |result, (index_name, attrs)|
50
+ if !(args.keys.to_set & attrs.to_set).empty?
51
+ result << index_name
52
+ result << attrs.size
53
+ result + attrs
54
+ else
55
+ result
66
56
  end
67
- run_script(
68
- :update_hash,
69
- keys: [id, hash_tag],
70
- argv: [model, ttl, index_attrs.size, range_index_attrs.size, custom_index_attrs_flat.size] +
71
- index_attrs + range_index_attrs + custom_index_attrs_flat + args.to_a.flatten,
72
- )
73
57
  end
58
+ run_script(
59
+ :update_hash,
60
+ keys: [id, hash_tag],
61
+ argv: [model, ttl, index_attrs.size, range_index_attrs.size, custom_index_attrs_flat.size] +
62
+ index_attrs + range_index_attrs + custom_index_attrs_flat + args.to_a.flatten,
63
+ )
74
64
  end
75
65
 
76
66
  sig do
@@ -83,17 +73,12 @@ class Redcord::Redis < Redis
83
73
  ).returns(Integer)
84
74
  end
85
75
  def delete_hash(model, id, index_attrs:, range_index_attrs:, custom_index_attrs:)
86
- Redcord::Base.trace(
87
- 'redcord_redis_delete_hash',
88
- model_name: model,
89
- ) do
90
- custom_index_names = custom_index_attrs.keys
91
- run_script(
92
- :delete_hash,
93
- keys: [id, id.match(/\{.*\}$/)&.send(:[], 0)],
94
- argv: [model, index_attrs.size, range_index_attrs.size] + index_attrs + range_index_attrs + custom_index_names,
95
- )
96
- end
76
+ custom_index_names = custom_index_attrs.keys
77
+ run_script(
78
+ :delete_hash,
79
+ keys: [id, id.match(/\{.*\}$/)&.send(:[], 0)],
80
+ argv: [model, index_attrs.size, range_index_attrs.size] + index_attrs + range_index_attrs + custom_index_names,
81
+ )
97
82
  end
98
83
 
99
84
  sig do
@@ -118,22 +103,17 @@ class Redcord::Redis < Redis
118
103
  hash_tag: nil,
119
104
  custom_index_name: nil
120
105
  )
121
- Redcord::Base.trace(
122
- 'redcord_redis_find_by_attr',
123
- model_name: model,
124
- ) do
125
- conditions = flatten_with_partial_sort(query_conditions.clone, custom_index_attrs)
126
- res = run_script(
127
- :find_by_attr,
128
- keys: [hash_tag],
129
- argv: [model, custom_index_name, index_attrs.size, range_index_attrs.size, custom_index_attrs.size, conditions.size] +
130
- index_attrs + range_index_attrs + custom_index_attrs + conditions + select_attrs.to_a.flatten
131
- )
132
- # The Lua script will return this as a flattened array.
133
- # Convert the result into a hash of {id -> model hash}
134
- res_hash = res.each_slice(2)
135
- res_hash.map { |key, val| [key, val.each_slice(2).to_h] }.to_h
136
- end
106
+ conditions = flatten_with_partial_sort(query_conditions.clone, custom_index_attrs)
107
+ res = run_script(
108
+ :find_by_attr,
109
+ keys: [hash_tag],
110
+ argv: [model, custom_index_name, index_attrs.size, range_index_attrs.size, custom_index_attrs.size, conditions.size] +
111
+ index_attrs + range_index_attrs + custom_index_attrs + conditions + select_attrs.to_a.flatten
112
+ )
113
+ # The Lua script will return this as a flattened array.
114
+ # Convert the result into a hash of {id -> model hash}
115
+ res_hash = res.each_slice(2)
116
+ res_hash.map { |key, val| [key, val.each_slice(2).to_h] }.to_h
137
117
  end
138
118
 
139
119
  sig do
@@ -156,33 +136,28 @@ class Redcord::Redis < Redis
156
136
  hash_tag: nil,
157
137
  custom_index_name: nil
158
138
  )
159
- Redcord::Base.trace(
160
- 'redcord_redis_find_by_attr_count',
161
- model_name: model,
162
- ) do
163
- conditions = flatten_with_partial_sort(query_conditions.clone, custom_index_attrs)
164
- run_script(
165
- :find_by_attr_count,
166
- keys: [hash_tag],
167
- argv: [model, custom_index_name, index_attrs.size, range_index_attrs.size, custom_index_attrs.size] +
168
- index_attrs + range_index_attrs + custom_index_attrs + conditions
169
- )
170
- end
139
+ conditions = flatten_with_partial_sort(query_conditions.clone, custom_index_attrs)
140
+ run_script(
141
+ :find_by_attr_count,
142
+ keys: [hash_tag],
143
+ argv: [model, custom_index_name, index_attrs.size, range_index_attrs.size, custom_index_attrs.size] +
144
+ index_attrs + range_index_attrs + custom_index_attrs + conditions
145
+ )
171
146
  end
172
147
 
173
- def scan_each_shard(key, &blk)
148
+ def scan_each_shard(key, count: 1000, &blk)
174
149
  clients = instance_variable_get(:@client)
175
150
  &.instance_variable_get(:@node)
176
151
  &.instance_variable_get(:@clients)
177
152
  &.values
178
153
 
179
154
  if clients.nil?
180
- scan_each(match: key, &blk)
155
+ scan_each(match: key, count: count, &blk)
181
156
  else
182
157
  clients.each do |client|
183
158
  cursor = 0
184
159
  loop do
185
- cursor, keys = client.call([:scan, cursor, 'match', key])
160
+ cursor, keys = client.call([:scan, cursor, 'match', key, 'count', count])
186
161
  keys.each(&blk)
187
162
  break if cursor == "0"
188
163
  end
@@ -6,11 +6,14 @@ require 'rails'
6
6
 
7
7
  require 'redcord/lua_script_reader'
8
8
  require 'redcord/redis'
9
+ require 'redcord/connection_pool'
9
10
 
10
11
  module Redcord::RedisConnection
11
12
  extend T::Sig
12
13
  extend T::Helpers
13
14
 
15
+ RedcordClientType = T.type_alias { T.any(Redcord::Redis, Redcord::ConnectionPool) }
16
+
14
17
  @connections = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
15
18
  @procs_to_prepare = T.let([], T::Array[Proc])
16
19
 
@@ -29,17 +32,17 @@ module Redcord::RedisConnection
29
32
  (env_config[name.underscore] || env_config['default']).symbolize_keys
30
33
  end
31
34
 
32
- sig { returns(Redcord::Redis) }
35
+ sig { returns(RedcordClientType) }
33
36
  def redis
34
37
  Redcord::RedisConnection.connections[name.underscore] ||= prepare_redis!
35
38
  end
36
39
 
37
- sig { returns(Redcord::Redis) }
40
+ sig { returns(RedcordClientType) }
38
41
  def establish_connection
39
42
  Redcord::RedisConnection.connections[name.underscore] = prepare_redis!
40
43
  end
41
44
 
42
- sig { params(redis: Redis).returns(Redcord::Redis) }
45
+ sig { params(redis: Redis).returns(RedcordClientType) }
43
46
  def redis=(redis)
44
47
  Redcord::RedisConnection.connections[name.underscore] =
45
48
  prepare_redis!(redis)
@@ -50,20 +53,21 @@ module Redcord::RedisConnection
50
53
  # definitions in each Redis query.
51
54
  #
52
55
  # TODO: Replace this with Redcord migrations
53
- sig { params(client: T.nilable(Redis)).returns(Redcord::Redis) }
56
+ sig { params(client: T.nilable(Redis)).returns(RedcordClientType) }
54
57
  def prepare_redis!(client = nil)
55
- return client if client.is_a?(Redcord::Redis)
56
-
57
- client = Redcord::Redis.new(
58
- **(
59
- if client.nil?
60
- connection_config
61
- else
62
- client.instance_variable_get(:@options)
63
- end
64
- ),
65
- logger: Redcord::Logger.proxy,
66
- )
58
+ return client if client.is_a?(Redcord::Redis) || client.is_a?(Redcord::ConnectionPool)
59
+
60
+ options = client.nil? ? connection_config : client.instance_variable_get(:@options)
61
+ client =
62
+ if options[:pool]
63
+ Redcord::ConnectionPool.new(
64
+ pool_size: options[:pool],
65
+ timeout: options[:connection_timeout] || 1.0,
66
+ **options
67
+ )
68
+ else
69
+ Redcord::Redis.new(**options, logger: Redcord::Logger.proxy)
70
+ end
67
71
 
68
72
  client.ping
69
73
  client
@@ -73,7 +77,7 @@ module Redcord::RedisConnection
73
77
  module InstanceMethods
74
78
  extend T::Sig
75
79
 
76
- sig { returns(Redcord::Redis) }
80
+ sig { returns(RedcordClientType) }
77
81
  def redis
78
82
  self.class.redis
79
83
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  # typed: strict
4
4
 
5
+ require 'active_support'
5
6
  require 'active_support/core_ext/array'
6
7
  require 'active_support/core_ext/module'
7
8
 
@@ -88,16 +89,21 @@ class Redcord::Relation
88
89
 
89
90
  sig { returns(Integer) }
90
91
  def count
91
- model.validate_index_attributes(query_conditions.keys, custom_index_name: custom_index_name)
92
- redis.find_by_attr_count(
93
- model.model_key,
94
- extract_query_conditions!,
95
- index_attrs: model._script_arg_index_attrs,
96
- range_index_attrs: model._script_arg_range_index_attrs,
97
- custom_index_attrs: model._script_arg_custom_index_attrs[custom_index_name],
98
- hash_tag: extract_hash_tag!,
99
- custom_index_name: custom_index_name
100
- )
92
+ Redcord::Base.trace(
93
+ 'redcord_relation_count',
94
+ model_name: model.name,
95
+ ) do
96
+ model.validate_index_attributes(query_conditions.keys, custom_index_name: custom_index_name)
97
+ redis.find_by_attr_count(
98
+ model.model_key,
99
+ extract_query_conditions!,
100
+ index_attrs: model._script_arg_index_attrs,
101
+ range_index_attrs: model._script_arg_range_index_attrs,
102
+ custom_index_attrs: model._script_arg_custom_index_attrs[custom_index_name],
103
+ hash_tag: extract_hash_tag!,
104
+ custom_index_name: custom_index_name
105
+ )
106
+ end
101
107
  end
102
108
 
103
109
  sig { params(index_name: T.nilable(Symbol)).returns(Redcord::Relation) }
@@ -243,7 +249,7 @@ class Redcord::Relation
243
249
  end
244
250
  end
245
251
 
246
- sig { returns(Redcord::Redis) }
252
+ sig { returns(Redcord::RedisConnection::RedcordClientType) }
247
253
  def redis
248
254
  model.redis
249
255
  end
@@ -46,10 +46,11 @@ local attr_selection_pos = query_cond_pos + ARGV[6]
46
46
 
47
47
  -- Get all ids which have the corresponding attribute values.
48
48
  local ids_set = nil
49
+ local index_sets, range_index_sets = {}, {}
49
50
 
50
51
  -- If custom index name is empty -> use single-attribute indices
51
52
  if index_name == '' then
52
- local index_sets, range_index_sets = unpack(validate_and_parse_query_conditions(
53
+ index_sets, range_index_sets = unpack(validate_and_parse_query_conditions(
53
54
  KEYS[1],
54
55
  model,
55
56
  to_set({unpack(ARGV, index_attr_pos, range_attr_pos - 1)}),
@@ -9,15 +9,15 @@ module Redcord::VacuumHelper
9
9
  sig { params(model: T.class_of(Redcord::Base)).void }
10
10
  def self.vacuum(model)
11
11
  model.class_variable_get(:@@index_attributes).each do |index_attr|
12
- puts "Vacuuming index attribute: #{index_attr}"
12
+ puts "Vacuuming index attribute: #{index_attr} for model: #{model.name}"
13
13
  _vacuum_index_attribute(model, index_attr)
14
14
  end
15
15
  model.class_variable_get(:@@range_index_attributes).each do |range_index_attr|
16
- puts "Vacuuming range index attribute: #{range_index_attr}"
16
+ puts "Vacuuming range index attribute: #{range_index_attr} for model: #{model.name}"
17
17
  _vacuum_range_index_attribute(model, range_index_attr)
18
18
  end
19
19
  model.class_variable_get(:@@custom_index_attributes).keys.each do |index_name|
20
- puts "Vacuuming custom index: #{index_name}"
20
+ puts "Vacuuming custom index: #{index_name} for model: #{model.name}"
21
21
  _vacuum_custom_index(model, index_name)
22
22
  end
23
23
  end
@@ -32,24 +32,27 @@ module Redcord::VacuumHelper
32
32
 
33
33
  sig { params(model: T.class_of(Redcord::Base), range_index_attr: Symbol).void }
34
34
  def self._vacuum_range_index_attribute(model, range_index_attr)
35
+ key_suffix = model.shard_by_attribute.nil? ? nil : '{*}'
35
36
  range_index_set_key = "#{model.model_key}:#{range_index_attr}"
36
37
  range_index_set_nil_key = "#{range_index_set_key}:"
37
38
 
38
39
  # Handle nil values for range index attributes, which are stored in a normal
39
40
  # set at Redcord:Model:range_index_attr:
40
- model.redis.scan_each_shard("#{range_index_set_nil_key}*") do |key|
41
+ model.redis.scan_each_shard("#{range_index_set_nil_key}#{key_suffix}") do |key|
41
42
  _remove_stale_ids_from_set(model, key)
42
43
  end
43
44
 
44
- model.redis.scan_each_shard("#{range_index_set_key}*") do |key|
45
+ model.redis.scan_each_shard("#{range_index_set_key}#{key_suffix}") do |key|
45
46
  _remove_stale_ids_from_sorted_set(model, key)
46
47
  end
47
48
  end
48
49
 
49
50
  sig { params(model: T.class_of(Redcord::Base), index_name: Symbol).void }
50
51
  def self._vacuum_custom_index(model, index_name)
52
+ key_suffix = model.shard_by_attribute.nil? ? nil : '{*}'
51
53
  custom_index_content_key = "#{model.model_key}:custom_index:#{index_name}_content"
52
- model.redis.scan_each_shard("#{custom_index_content_key}*") do |key|
54
+
55
+ model.redis.scan_each_shard("#{custom_index_content_key}#{key_suffix}") do |key|
53
56
  hash_tag = key.split(custom_index_content_key)[1] || ""
54
57
  _remove_stale_records_from_custom_index(model, hash_tag, index_name)
55
58
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redcord
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chan Zuckerberg Initiative
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: connection_pool
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 2.2.3
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 2.2.3
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: sorbet
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -162,6 +176,7 @@ files:
162
176
  - lib/redcord/attribute.rb
163
177
  - lib/redcord/base.rb
164
178
  - lib/redcord/configurations.rb
179
+ - lib/redcord/connection_pool.rb
165
180
  - lib/redcord/logger.rb
166
181
  - lib/redcord/lua_script_reader.rb
167
182
  - lib/redcord/migration.rb
@@ -205,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
205
220
  - !ruby/object:Gem::Version
206
221
  version: '0'
207
222
  requirements: []
208
- rubygems_version: 3.0.8
223
+ rubygems_version: 3.1.6
209
224
  signing_key:
210
225
  specification_version: 4
211
226
  summary: A Ruby ORM like Active Record, but for Redis