redcord 0.0.3 → 0.0.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 +4 -4
- data/lib/redcord.rb +1 -0
- data/lib/redcord/base.rb +13 -3
- data/lib/redcord/migration.rb +2 -0
- data/lib/redcord/migration/index.rb +68 -0
- data/lib/redcord/migration/ttl.rb +14 -2
- data/lib/redcord/redis_connection.rb +7 -0
- data/lib/redcord/server_scripts/create_hash_returning_id.erb.lua +0 -1
- data/lib/redcord/tasks/redis.rake +15 -0
- data/lib/redcord/vacuum_helper.rb +57 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85772724fda7e2a152acab1f04164ff1ee0146a15f13facebd7b5f7b18323aa9
|
4
|
+
data.tar.gz: e1527ca0cfe6e7cc0a0aef6346bdb055cd47e6e6d7ca22aa8488608a02e5eeaa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd8b0d84aecba33d41fdad600f00c74a2005c566f439914b3a72f920effc87d344b6974fc18a236d4972fb6d21efa607f43ee121da2d5c70cc6d772a5b80e5c7
|
7
|
+
data.tar.gz: c16a9836a3556bbfdf38b04e78e66827733c7e40cfb002ec2744c9748453968571c110dff24f00838b3b2dfa64b658b79c378c214dd82c936df0459c303ab269
|
data/lib/redcord.rb
CHANGED
data/lib/redcord/base.rb
CHANGED
@@ -56,9 +56,19 @@ module Redcord::Base
|
|
56
56
|
# coerced to the specified attribute types. Like ActiveRecord,
|
57
57
|
# Redcord manages the created_at and updated_at fields behind the
|
58
58
|
# scene.
|
59
|
-
|
60
|
-
|
61
|
-
attribute :updated_at, T.nilable(Time), index: true
|
59
|
+
prop :created_at, T.nilable(Time)
|
60
|
+
prop :updated_at, T.nilable(Time)
|
62
61
|
end
|
63
62
|
end
|
63
|
+
|
64
|
+
sig { returns(T::Array[T.class_of(Redcord::Base)]) }
|
65
|
+
def self.descendants
|
66
|
+
descendants = []
|
67
|
+
# TODO: Use T::Struct instead of Class
|
68
|
+
ObjectSpace.each_object(Class) do |klass|
|
69
|
+
descendants << klass if klass < self
|
70
|
+
end
|
71
|
+
descendants
|
72
|
+
end
|
73
|
+
|
64
74
|
end
|
data/lib/redcord/migration.rb
CHANGED
@@ -2,11 +2,13 @@
|
|
2
2
|
class Redcord::Migration
|
3
3
|
end
|
4
4
|
|
5
|
+
require 'redcord/migration/index'
|
5
6
|
require 'redcord/migration/ttl'
|
6
7
|
|
7
8
|
class Redcord::Migration
|
8
9
|
extend T::Sig
|
9
10
|
extend T::Helpers
|
11
|
+
include Redcord::Migration::Index
|
10
12
|
include Redcord::Migration::TTL
|
11
13
|
|
12
14
|
abstract!
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# typed: strict
|
4
|
+
|
5
|
+
module Redcord::Migration::Index
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { params(model: T.class_of(Redcord::Base), index_name: Symbol).void }
|
9
|
+
def remove_index(model, index_name)
|
10
|
+
if model.redis.sismember("#{model.model_key}:index_attrs", index_name)
|
11
|
+
_remove_index_from_attr_set(
|
12
|
+
model: model,
|
13
|
+
attr_set_name: 'index_attrs',
|
14
|
+
index_name: index_name,
|
15
|
+
)
|
16
|
+
|
17
|
+
model.redis.scan_each(match: "#{model.model_key}:#{index_name}:*") { |key| _del_set(model, key) }
|
18
|
+
elsif model.redis.sismember("#{model.model_key}:range_index_attrs", index_name)
|
19
|
+
_remove_index_from_attr_set(
|
20
|
+
model: model,
|
21
|
+
attr_set_name: 'range_index_attrs',
|
22
|
+
index_name: index_name,
|
23
|
+
)
|
24
|
+
|
25
|
+
attr_set = "#{model.model_key}:#{index_name}"
|
26
|
+
nil_attr_set = "#{attr_set}:"
|
27
|
+
|
28
|
+
_del_set(model, nil_attr_set)
|
29
|
+
_del_zset(model, attr_set)
|
30
|
+
else
|
31
|
+
raise(
|
32
|
+
Redcord::AttributeNotIndexed,
|
33
|
+
"#{index_name} is not an indexed attribute.",
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
sig {
|
39
|
+
params(
|
40
|
+
model: T.class_of(Redcord::Base),
|
41
|
+
attr_set_name: String,
|
42
|
+
index_name: Symbol,
|
43
|
+
).void
|
44
|
+
}
|
45
|
+
def _remove_index_from_attr_set(model:, attr_set_name:, index_name:)
|
46
|
+
model.redis.srem("#{model.model_key}:#{attr_set_name}", index_name)
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { params(model: T.class_of(Redcord::Base), key: String).void }
|
50
|
+
def _del_set(model, key)
|
51
|
+
# Use SPOP here to minimize blocking
|
52
|
+
loop do
|
53
|
+
break unless model.redis.spop(key)
|
54
|
+
end
|
55
|
+
|
56
|
+
model.redis.del(key)
|
57
|
+
end
|
58
|
+
|
59
|
+
sig { params(model: T.class_of(Redcord::Base), key: String).void }
|
60
|
+
def _del_zset(model, key)
|
61
|
+
# ZPOPMIN might not be avaliable on old redis servers
|
62
|
+
model.redis.zscan_each(match: key) do |id, _|
|
63
|
+
model.redis.zrem(key, id)
|
64
|
+
end
|
65
|
+
|
66
|
+
model.redis.del(key)
|
67
|
+
end
|
68
|
+
end
|
@@ -2,10 +2,22 @@
|
|
2
2
|
module Redcord::Migration::TTL
|
3
3
|
extend T::Sig
|
4
4
|
|
5
|
+
sig { params(model: T.class_of(Redcord::Base)).returns(T.untyped) }
|
6
|
+
def _get_ttl(model)
|
7
|
+
model.class_variable_get(:@@ttl) || -1
|
8
|
+
end
|
9
|
+
|
5
10
|
# This won't change ttl until we call update on a record
|
6
11
|
sig { params(model: T.class_of(Redcord::Base)).void }
|
7
12
|
def change_ttl_passive(model)
|
8
|
-
|
9
|
-
|
13
|
+
model.redis.set("#{model.model_key}:ttl", _get_ttl(model))
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { params(model: T.class_of(Redcord::Base)).void }
|
17
|
+
def change_ttl_active(model)
|
18
|
+
change_ttl_passive(model)
|
19
|
+
model.redis.scan_each(match: "#{model.model_key}:id:*") do |key|
|
20
|
+
model.redis.expire(key, _get_ttl(model))
|
21
|
+
end
|
10
22
|
end
|
11
23
|
end
|
@@ -60,7 +60,6 @@ if #index_attr_keys > 0 then
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
local range_index_attr_keys = redis.call('smembers', model .. ':range_index_attrs')
|
63
|
-
attrs_hash['id'] = id
|
64
63
|
if #range_index_attr_keys > 0 then
|
65
64
|
for _, attr_key in ipairs(range_index_attr_keys) do
|
66
65
|
add_id_to_range_index_attr(model, attr_key, attrs_hash[attr_key], id)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# typed: strict
|
2
2
|
require 'redcord/migration/version'
|
3
3
|
require 'redcord/migration/migrator'
|
4
|
+
require 'redcord/vacuum_helper'
|
4
5
|
|
5
6
|
db_namespace = namespace :redis do
|
6
7
|
task migrate: :environment do
|
@@ -31,4 +32,18 @@ db_namespace = namespace :redis do
|
|
31
32
|
migration_end = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
32
33
|
puts "\nFinished in #{(migration_end - migration_start).round(3)} seconds"
|
33
34
|
end
|
35
|
+
|
36
|
+
task :vacuum, [:model_name] => :environment do |t, args|
|
37
|
+
desc "Vacuum index attributes for stale ids on a Redcord model"
|
38
|
+
$stdout.sync = true
|
39
|
+
model_name = args[:model_name]
|
40
|
+
puts "Attempting to vacuum the index attributes of the Redcord model: #{model_name}"
|
41
|
+
vacuum_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
42
|
+
|
43
|
+
Redcord::VacuumHelper.vacuum(Object.const_get(args[:model_name]))
|
44
|
+
vacuum_end = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
45
|
+
puts "Finished vacuuming #{model_name} in #{(vacuum_end - vacuum_start).round(3)} seconds"
|
46
|
+
rescue NameError => e
|
47
|
+
raise StandardError.new("#{args[:model_name]} is not a valid Redcord model.")
|
48
|
+
end
|
34
49
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# typed: strict
|
4
|
+
|
5
|
+
module Redcord::VacuumHelper
|
6
|
+
extend T::Sig
|
7
|
+
extend T::Helpers
|
8
|
+
|
9
|
+
sig { params(model: T.class_of(Redcord::Base)).void }
|
10
|
+
def self.vacuum(model)
|
11
|
+
model.class_variable_get(:@@index_attributes).each do |index_attr|
|
12
|
+
puts "Vacuuming index attribute: #{index_attr}"
|
13
|
+
_vacuum_index_attribute(model, index_attr)
|
14
|
+
end
|
15
|
+
model.class_variable_get(:@@range_index_attributes).each do |range_index_attr|
|
16
|
+
puts "Vacuuming range index attribute: #{range_index_attr}"
|
17
|
+
_vacuum_range_index_attribute(model, range_index_attr)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { params(model: T.class_of(Redcord::Base), index_attr: Symbol).void }
|
22
|
+
def self._vacuum_index_attribute(model, index_attr)
|
23
|
+
# Scan through all index attribute values by matching on Redcord:Model:index_attr:*
|
24
|
+
model.redis.scan_each(match: "#{model.model_key}:#{index_attr}:*") do |key|
|
25
|
+
_remove_stale_ids_from_set(model, key)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { params(model: T.class_of(Redcord::Base), range_index_attr: Symbol).void }
|
30
|
+
def self._vacuum_range_index_attribute(model, range_index_attr)
|
31
|
+
range_index_set_key = "#{model.model_key}:#{range_index_attr}"
|
32
|
+
_remove_stale_ids_from_sorted_set(model, range_index_set_key)
|
33
|
+
|
34
|
+
# Handle nil values for range index attributes, which are stored in a normal
|
35
|
+
# set at Redcord:Model:range_index_attr:
|
36
|
+
range_index_set_nil_key = "#{range_index_set_key}:"
|
37
|
+
_remove_stale_ids_from_set(model, range_index_set_nil_key)
|
38
|
+
end
|
39
|
+
|
40
|
+
sig { params(model: T.class_of(Redcord::Base), set_key: String).void }
|
41
|
+
def self._remove_stale_ids_from_set(model, set_key)
|
42
|
+
model.redis.sscan_each(set_key) do |id|
|
43
|
+
if !model.redis.exists?("#{model.model_key}:id:#{id}")
|
44
|
+
model.redis.srem(set_key, id)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { params(model: T.class_of(Redcord::Base), sorted_set_key: String).void }
|
50
|
+
def self._remove_stale_ids_from_sorted_set(model, sorted_set_key)
|
51
|
+
model.redis.zscan_each(sorted_set_key) do |id, _|
|
52
|
+
if !model.redis.exists?("#{model.model_key}:id:#{id}")
|
53
|
+
model.redis.zrem(sorted_set_key, id)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
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.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chan Zuckerberg Initiative
|
@@ -165,6 +165,7 @@ files:
|
|
165
165
|
- lib/redcord/logger.rb
|
166
166
|
- lib/redcord/lua_script_reader.rb
|
167
167
|
- lib/redcord/migration.rb
|
168
|
+
- lib/redcord/migration/index.rb
|
168
169
|
- lib/redcord/migration/migrator.rb
|
169
170
|
- lib/redcord/migration/ttl.rb
|
170
171
|
- lib/redcord/migration/version.rb
|
@@ -184,6 +185,7 @@ files:
|
|
184
185
|
- lib/redcord/server_scripts/update_hash.erb.lua
|
185
186
|
- lib/redcord/tasks/redis.rake
|
186
187
|
- lib/redcord/tracer.rb
|
188
|
+
- lib/redcord/vacuum_helper.rb
|
187
189
|
homepage: https://github.com/chanzuckerberg/redis-record
|
188
190
|
licenses:
|
189
191
|
- MIT
|