redcord 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|