lowkiq 1.0.0 → 1.0.5
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/Gemfile.lock +19 -19
- data/LICENSE.md +13 -3
- data/README.md +378 -308
- data/README.ru.md +645 -0
- data/docker-compose.yml +1 -1
- data/lib/lowkiq.rb +6 -2
- data/lib/lowkiq/extend_tracker.rb +1 -1
- data/lib/lowkiq/queue/fetch.rb +2 -2
- data/lib/lowkiq/queue/keys.rb +16 -4
- data/lib/lowkiq/queue/queue.rb +103 -55
- data/lib/lowkiq/script.rb +42 -0
- data/lib/lowkiq/server.rb +4 -0
- data/lib/lowkiq/shard_handler.rb +3 -3
- data/lib/lowkiq/version.rb +1 -1
- data/lowkiq.gemspec +3 -2
- metadata +12 -10
- data/lib/lowkiq/queue/marshal.rb +0 -23
data/docker-compose.yml
CHANGED
data/lib/lowkiq.rb
CHANGED
@@ -4,9 +4,11 @@ require "zlib"
|
|
4
4
|
require "json"
|
5
5
|
require "ostruct"
|
6
6
|
require "optparse"
|
7
|
+
require "digest"
|
7
8
|
|
8
9
|
require "lowkiq/version"
|
9
10
|
require "lowkiq/utils"
|
11
|
+
require "lowkiq/script"
|
10
12
|
|
11
13
|
require "lowkiq/extend_tracker"
|
12
14
|
require "lowkiq/option_parser"
|
@@ -19,7 +21,6 @@ require "lowkiq/schedulers/seq"
|
|
19
21
|
|
20
22
|
require "lowkiq/server"
|
21
23
|
|
22
|
-
require "lowkiq/queue/marshal"
|
23
24
|
require "lowkiq/queue/keys"
|
24
25
|
require "lowkiq/queue/fetch"
|
25
26
|
require "lowkiq/queue/queue"
|
@@ -40,7 +41,8 @@ module Lowkiq
|
|
40
41
|
:redis, :client_pool_size, :pool_timeout,
|
41
42
|
:server_middlewares, :on_server_init,
|
42
43
|
:build_scheduler, :build_splitter,
|
43
|
-
:last_words
|
44
|
+
:last_words,
|
45
|
+
:dump_payload, :load_payload
|
44
46
|
|
45
47
|
def server_redis_pool
|
46
48
|
@server_redis_pool ||= ConnectionPool.new(size: threads_per_node, timeout: pool_timeout, &redis)
|
@@ -108,4 +110,6 @@ module Lowkiq
|
|
108
110
|
self.build_scheduler = ->() { Lowkiq.build_lag_scheduler }
|
109
111
|
self.build_splitter = ->() { Lowkiq.build_default_splitter }
|
110
112
|
self.last_words = ->(ex) {}
|
113
|
+
self.dump_payload = ::Marshal.method :dump
|
114
|
+
self.load_payload = ::Marshal.method :load
|
111
115
|
end
|
data/lib/lowkiq/queue/fetch.rb
CHANGED
@@ -21,7 +21,7 @@ module Lowkiq
|
|
21
21
|
id: x[0],
|
22
22
|
perform_in: x[1][0],
|
23
23
|
retry_count: x[1][1],
|
24
|
-
payloads: x[1][2].map { |(payload, score)| [
|
24
|
+
payloads: x[1][2].map { |(payload, score)| [Lowkiq.load_payload.call(payload), score] },
|
25
25
|
error: x[1][3],
|
26
26
|
}.compact
|
27
27
|
end.compact
|
@@ -41,7 +41,7 @@ module Lowkiq
|
|
41
41
|
{
|
42
42
|
id: x[0],
|
43
43
|
updated_at: x[1][0],
|
44
|
-
payloads: x[1][1].map { |(payload, score)| [
|
44
|
+
payloads: x[1][1].map { |(payload, score)| [Lowkiq.load_payload.call(payload), score] },
|
45
45
|
error: x[1][2],
|
46
46
|
}.compact
|
47
47
|
end.compact
|
data/lib/lowkiq/queue/keys.rb
CHANGED
@@ -39,14 +39,26 @@ module Lowkiq
|
|
39
39
|
[@prefix, :errors].join(':')
|
40
40
|
end
|
41
41
|
|
42
|
-
def processing_key(shard)
|
43
|
-
[@prefix, :processing, shard].join(':')
|
44
|
-
end
|
45
|
-
|
46
42
|
def processing_length_by_shard_hash
|
47
43
|
[@prefix, :processing_length_by_shard].join(':')
|
48
44
|
end
|
49
45
|
|
46
|
+
def processing_ids_with_perform_in_hash(shard)
|
47
|
+
[@prefix, :processing, :ids_with_perform_in, shard].join(':')
|
48
|
+
end
|
49
|
+
|
50
|
+
def processing_ids_with_retry_count_hash(shard)
|
51
|
+
[@prefix, :processing, :ids_with_retry_count, shard].join(':')
|
52
|
+
end
|
53
|
+
|
54
|
+
def processing_payloads_zset(id)
|
55
|
+
[@prefix, :processing, :payloads, id].join(':')
|
56
|
+
end
|
57
|
+
|
58
|
+
def processing_errors_hash(shard)
|
59
|
+
[@prefix, :processing, :errors, shard].join(':')
|
60
|
+
end
|
61
|
+
|
50
62
|
def morgue_all_ids_lex_zset
|
51
63
|
[@prefix, :morgue, :all_ids_lex].join(':')
|
52
64
|
end
|
data/lib/lowkiq/queue/queue.rb
CHANGED
@@ -29,7 +29,7 @@ module Lowkiq
|
|
29
29
|
redis.zadd @keys.all_ids_scored_by_retry_count_zset, retry_count, id, nx: true
|
30
30
|
|
31
31
|
redis.zadd @keys.ids_scored_by_perform_in_zset(shard), perform_in, id, nx: true
|
32
|
-
redis.zadd @keys.payloads_zset(id), score,
|
32
|
+
redis.zadd @keys.payloads_zset(id), score, Lowkiq.dump_payload.call(payload), nx: true
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
@@ -37,44 +37,56 @@ module Lowkiq
|
|
37
37
|
|
38
38
|
def pop(shard, limit:)
|
39
39
|
@pool.with do |redis|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
limit: [0, limit]
|
45
|
-
|
46
|
-
if ids.empty?
|
47
|
-
redis.unwatch
|
48
|
-
return []
|
49
|
-
end
|
40
|
+
ids = redis.zrangebyscore @keys.ids_scored_by_perform_in_zset(shard),
|
41
|
+
0, @timestamp.call,
|
42
|
+
limit: [0, limit]
|
43
|
+
return [] if ids.empty?
|
50
44
|
|
51
|
-
|
45
|
+
res = redis.multi do |redis|
|
46
|
+
redis.hset @keys.processing_length_by_shard_hash, shard, ids.length
|
52
47
|
|
53
|
-
|
54
|
-
|
55
|
-
redis.
|
56
|
-
|
48
|
+
ids.each do |id|
|
49
|
+
redis.zrem @keys.all_ids_lex_zset, id
|
50
|
+
redis.zrem @keys.ids_scored_by_perform_in_zset(shard), id
|
51
|
+
|
52
|
+
Script.zremhset redis,
|
53
|
+
@keys.all_ids_scored_by_perform_in_zset,
|
54
|
+
@keys.processing_ids_with_perform_in_hash(shard),
|
55
|
+
id
|
56
|
+
Script.zremhset redis,
|
57
|
+
@keys.all_ids_scored_by_retry_count_zset,
|
58
|
+
@keys.processing_ids_with_retry_count_hash(shard),
|
59
|
+
id
|
60
|
+
redis.rename @keys.payloads_zset(id),
|
61
|
+
@keys.processing_payloads_zset(id)
|
62
|
+
Script.hmove redis,
|
63
|
+
@keys.errors_hash,
|
64
|
+
@keys.processing_errors_hash(shard),
|
65
|
+
id
|
57
66
|
end
|
58
|
-
|
67
|
+
processing_data_pipeline(redis, shard, ids)
|
68
|
+
end
|
59
69
|
|
60
|
-
|
70
|
+
res.shift 1 + ids.length * 6
|
71
|
+
processing_data_build res, ids
|
61
72
|
end
|
62
73
|
end
|
63
74
|
|
64
75
|
def push_back(batch)
|
65
76
|
@pool.with do |redis|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
77
|
+
timestamp = @timestamp.call
|
78
|
+
redis.multi do |redis|
|
79
|
+
batch.each do |job|
|
80
|
+
id = job.fetch(:id)
|
81
|
+
perform_in = job.fetch(:perform_in, timestamp)
|
82
|
+
retry_count = job.fetch(:retry_count, -1)
|
83
|
+
payloads = job.fetch(:payloads).map do |(payload, score)|
|
84
|
+
[score, Lowkiq.dump_payload.call(payload)]
|
85
|
+
end
|
86
|
+
error = job.fetch(:error, nil)
|
74
87
|
|
75
|
-
|
88
|
+
shard = id_to_shard id
|
76
89
|
|
77
|
-
redis.multi do
|
78
90
|
redis.zadd @keys.all_ids_lex_zset, 0, id
|
79
91
|
redis.zadd @keys.all_ids_scored_by_perform_in_zset, perform_in, id
|
80
92
|
redis.zadd @keys.all_ids_scored_by_retry_count_zset, retry_count, id
|
@@ -88,40 +100,52 @@ module Lowkiq
|
|
88
100
|
end
|
89
101
|
end
|
90
102
|
|
91
|
-
def ack(shard, result = nil)
|
103
|
+
def ack(shard, data, result = nil)
|
104
|
+
ids = data.map { |job| job[:id] }
|
105
|
+
length = ids.length
|
106
|
+
|
92
107
|
@pool.with do |redis|
|
93
|
-
length = redis.hget(@keys.processing_length_by_shard_hash, shard).to_i
|
94
108
|
redis.multi do
|
95
|
-
redis.del
|
109
|
+
redis.del @keys.processing_ids_with_perform_in_hash(shard)
|
110
|
+
redis.del @keys.processing_ids_with_retry_count_hash(shard)
|
111
|
+
redis.del @keys.processing_errors_hash(shard)
|
112
|
+
ids.each do |id|
|
113
|
+
redis.del @keys.processing_payloads_zset(id)
|
114
|
+
end
|
96
115
|
redis.hdel @keys.processing_length_by_shard_hash, shard
|
97
|
-
|
98
116
|
redis.incrby @keys.processed_key, length if result == :success
|
99
|
-
redis.incrby @keys.failed_key,
|
117
|
+
redis.incrby @keys.failed_key, length if result == :fail
|
100
118
|
end
|
101
119
|
end
|
102
120
|
end
|
103
121
|
|
104
122
|
def processing_data(shard)
|
105
|
-
|
106
|
-
redis.
|
107
|
-
|
108
|
-
return [] if data.nil?
|
123
|
+
@pool.with do |redis|
|
124
|
+
ids = redis.hkeys @keys.processing_ids_with_perform_in_hash(shard)
|
125
|
+
return [] if ids.empty?
|
109
126
|
|
110
|
-
|
127
|
+
res = redis.multi do |redis|
|
128
|
+
processing_data_pipeline redis, shard, ids
|
129
|
+
end
|
130
|
+
|
131
|
+
processing_data_build res, ids
|
132
|
+
end
|
111
133
|
end
|
112
134
|
|
113
135
|
def push_to_morgue(batch)
|
114
136
|
@pool.with do |redis|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
137
|
+
timestamp = @timestamp.call
|
138
|
+
redis.multi do
|
139
|
+
batch.each do |job|
|
140
|
+
id = job.fetch(:id)
|
141
|
+
payloads = job.fetch(:payloads).map do |(payload, score)|
|
142
|
+
[score, Lowkiq.dump_payload.call(payload)]
|
143
|
+
end
|
144
|
+
error = job.fetch(:error, nil)
|
145
|
+
|
121
146
|
|
122
|
-
redis.multi do
|
123
147
|
redis.zadd @keys.morgue_all_ids_lex_zset, 0, id
|
124
|
-
redis.zadd @keys.morgue_all_ids_scored_by_updated_at_zset,
|
148
|
+
redis.zadd @keys.morgue_all_ids_scored_by_updated_at_zset, timestamp, id
|
125
149
|
redis.zadd @keys.morgue_payloads_zset(id), payloads, nx: true
|
126
150
|
|
127
151
|
redis.hset @keys.morgue_errors_hash, id, error unless error.nil?
|
@@ -146,7 +170,15 @@ module Lowkiq
|
|
146
170
|
def delete(ids)
|
147
171
|
@pool.with do |redis|
|
148
172
|
redis.multi do
|
149
|
-
|
173
|
+
ids.each do |id|
|
174
|
+
shard = id_to_shard id
|
175
|
+
redis.zrem @keys.all_ids_lex_zset, id
|
176
|
+
redis.zrem @keys.all_ids_scored_by_perform_in_zset, id
|
177
|
+
redis.zrem @keys.all_ids_scored_by_retry_count_zset, id
|
178
|
+
redis.zrem @keys.ids_scored_by_perform_in_zset(shard), id
|
179
|
+
redis.del @keys.payloads_zset(id)
|
180
|
+
redis.hdel @keys.errors_hash, id
|
181
|
+
end
|
150
182
|
end
|
151
183
|
end
|
152
184
|
end
|
@@ -161,17 +193,33 @@ module Lowkiq
|
|
161
193
|
Zlib.crc32(id.to_s) % @shards_count
|
162
194
|
end
|
163
195
|
|
164
|
-
def
|
196
|
+
def processing_data_pipeline(redis, shard, ids)
|
197
|
+
redis.hgetall @keys.processing_ids_with_perform_in_hash(shard)
|
198
|
+
redis.hgetall @keys.processing_ids_with_retry_count_hash(shard)
|
199
|
+
redis.hgetall @keys.processing_errors_hash(shard)
|
200
|
+
|
165
201
|
ids.each do |id|
|
166
|
-
|
167
|
-
redis.zrem @keys.all_ids_lex_zset, id
|
168
|
-
redis.zrem @keys.all_ids_scored_by_perform_in_zset, id
|
169
|
-
redis.zrem @keys.all_ids_scored_by_retry_count_zset, id
|
170
|
-
redis.zrem @keys.ids_scored_by_perform_in_zset(shard), id
|
171
|
-
redis.del @keys.payloads_zset(id)
|
172
|
-
redis.hdel @keys.errors_hash, id
|
202
|
+
redis.zrange @keys.processing_payloads_zset(id), 0, -1, with_scores: true
|
173
203
|
end
|
174
204
|
end
|
205
|
+
|
206
|
+
def processing_data_build(arr, ids)
|
207
|
+
ids_with_perform_in = arr.shift
|
208
|
+
ids_with_retry_count = arr.shift
|
209
|
+
errors = arr.shift
|
210
|
+
payloads = arr
|
211
|
+
|
212
|
+
ids.zip(payloads).map do |(id, payloads)|
|
213
|
+
next if payloads.empty?
|
214
|
+
{
|
215
|
+
id: id,
|
216
|
+
perform_in: ids_with_perform_in[id].to_f,
|
217
|
+
retry_count: ids_with_retry_count[id].to_f,
|
218
|
+
payloads: payloads.map { |(payload, score)| [Lowkiq.load_payload.call(payload), score] },
|
219
|
+
error: errors[id]
|
220
|
+
}.compact
|
221
|
+
end.compact
|
222
|
+
end
|
175
223
|
end
|
176
224
|
end
|
177
225
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Lowkiq
|
2
|
+
module Script
|
3
|
+
module_function
|
4
|
+
|
5
|
+
ALL = {
|
6
|
+
hmove: <<-LUA,
|
7
|
+
local source = KEYS[1]
|
8
|
+
local destination = KEYS[2]
|
9
|
+
local key = ARGV[1]
|
10
|
+
local value = redis.call('hget', source, key)
|
11
|
+
if value then
|
12
|
+
redis.call('hdel', source, key)
|
13
|
+
redis.call('hset', destination, key, value)
|
14
|
+
end
|
15
|
+
LUA
|
16
|
+
zremhset: <<-LUA
|
17
|
+
local source = KEYS[1]
|
18
|
+
local destination = KEYS[2]
|
19
|
+
local member = ARGV[1]
|
20
|
+
local score = redis.call('zscore', source, member)
|
21
|
+
if score then
|
22
|
+
redis.call('zrem', source, member)
|
23
|
+
redis.call('hset', destination, member, score)
|
24
|
+
end
|
25
|
+
LUA
|
26
|
+
}.transform_values { |v| { sha: Digest::SHA1.hexdigest(v), source: v } }.freeze
|
27
|
+
|
28
|
+
def load!(redis)
|
29
|
+
ALL.each do |_, item|
|
30
|
+
redis.script(:load, item[:source])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def hmove(redis, source, destination, key)
|
35
|
+
redis.evalsha ALL[:hmove][:sha], keys: [source, destination], argv: [key]
|
36
|
+
end
|
37
|
+
|
38
|
+
def zremhset(redis, source, destination, member)
|
39
|
+
redis.evalsha ALL[:zremhset][:sha], keys: [source, destination], argv: [member]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/lowkiq/server.rb
CHANGED
data/lib/lowkiq/shard_handler.rb
CHANGED
@@ -31,7 +31,7 @@ module Lowkiq
|
|
31
31
|
@worker.perform batch
|
32
32
|
end
|
33
33
|
|
34
|
-
@queue.ack @shard_index, :success
|
34
|
+
@queue.ack @shard_index, data, :success
|
35
35
|
true
|
36
36
|
rescue => ex
|
37
37
|
fail! data, ex
|
@@ -39,7 +39,7 @@ module Lowkiq
|
|
39
39
|
|
40
40
|
@queue.push_back back
|
41
41
|
@queue.push_to_morgue morgue
|
42
|
-
@queue.ack @shard_index, :fail
|
42
|
+
@queue.ack @shard_index, data, :fail
|
43
43
|
false
|
44
44
|
end
|
45
45
|
end
|
@@ -48,7 +48,7 @@ module Lowkiq
|
|
48
48
|
data = @queue.processing_data @shard_index
|
49
49
|
return if data.nil?
|
50
50
|
@queue.push_back data
|
51
|
-
@queue.ack @shard_index
|
51
|
+
@queue.ack @shard_index, data
|
52
52
|
end
|
53
53
|
|
54
54
|
private
|
data/lib/lowkiq/version.rb
CHANGED
data/lowkiq.gemspec
CHANGED
@@ -11,6 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.summary = %q{Lowkiq}
|
12
12
|
spec.description = %q{Lowkiq}
|
13
13
|
spec.homepage = "https://github.com/bia-technologies/lowkiq"
|
14
|
+
spec.licenses = ['LGPL', 'EULA']
|
14
15
|
|
15
16
|
# Specify which files should be added to the gem when it is released.
|
16
17
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -28,8 +29,8 @@ Gem::Specification.new do |spec|
|
|
28
29
|
spec.add_dependency "connection_pool", "~> 2.2", ">= 2.2.2"
|
29
30
|
spec.add_dependency "rack", ">= 1.5.0"
|
30
31
|
|
31
|
-
spec.add_development_dependency "bundler", "~> 1.
|
32
|
-
spec.add_development_dependency "rake", "~>
|
32
|
+
spec.add_development_dependency "bundler", "~> 2.1.0"
|
33
|
+
spec.add_development_dependency "rake", "~> 12.3.0"
|
33
34
|
spec.add_development_dependency "rspec", "~> 3.0"
|
34
35
|
spec.add_development_dependency "rspec-mocks", "~> 3.8"
|
35
36
|
spec.add_development_dependency "rack-test", "~> 1.1"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lowkiq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mikhail Kuzmin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -70,28 +70,28 @@ dependencies:
|
|
70
70
|
requirements:
|
71
71
|
- - "~>"
|
72
72
|
- !ruby/object:Gem::Version
|
73
|
-
version:
|
73
|
+
version: 2.1.0
|
74
74
|
type: :development
|
75
75
|
prerelease: false
|
76
76
|
version_requirements: !ruby/object:Gem::Requirement
|
77
77
|
requirements:
|
78
78
|
- - "~>"
|
79
79
|
- !ruby/object:Gem::Version
|
80
|
-
version:
|
80
|
+
version: 2.1.0
|
81
81
|
- !ruby/object:Gem::Dependency
|
82
82
|
name: rake
|
83
83
|
requirement: !ruby/object:Gem::Requirement
|
84
84
|
requirements:
|
85
85
|
- - "~>"
|
86
86
|
- !ruby/object:Gem::Version
|
87
|
-
version:
|
87
|
+
version: 12.3.0
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
90
|
version_requirements: !ruby/object:Gem::Requirement
|
91
91
|
requirements:
|
92
92
|
- - "~>"
|
93
93
|
- !ruby/object:Gem::Version
|
94
|
-
version:
|
94
|
+
version: 12.3.0
|
95
95
|
- !ruby/object:Gem::Dependency
|
96
96
|
name: rspec
|
97
97
|
requirement: !ruby/object:Gem::Requirement
|
@@ -148,6 +148,7 @@ files:
|
|
148
148
|
- Gemfile.lock
|
149
149
|
- LICENSE.md
|
150
150
|
- README.md
|
151
|
+
- README.ru.md
|
151
152
|
- Rakefile
|
152
153
|
- assets/app.js
|
153
154
|
- bin/console
|
@@ -161,7 +162,6 @@ files:
|
|
161
162
|
- lib/lowkiq/queue/actions.rb
|
162
163
|
- lib/lowkiq/queue/fetch.rb
|
163
164
|
- lib/lowkiq/queue/keys.rb
|
164
|
-
- lib/lowkiq/queue/marshal.rb
|
165
165
|
- lib/lowkiq/queue/queries.rb
|
166
166
|
- lib/lowkiq/queue/queue.rb
|
167
167
|
- lib/lowkiq/queue/queue_metrics.rb
|
@@ -169,6 +169,7 @@ files:
|
|
169
169
|
- lib/lowkiq/redis_info.rb
|
170
170
|
- lib/lowkiq/schedulers/lag.rb
|
171
171
|
- lib/lowkiq/schedulers/seq.rb
|
172
|
+
- lib/lowkiq/script.rb
|
172
173
|
- lib/lowkiq/server.rb
|
173
174
|
- lib/lowkiq/shard_handler.rb
|
174
175
|
- lib/lowkiq/splitters/by_node.rb
|
@@ -181,7 +182,9 @@ files:
|
|
181
182
|
- lib/lowkiq/worker.rb
|
182
183
|
- lowkiq.gemspec
|
183
184
|
homepage: https://github.com/bia-technologies/lowkiq
|
184
|
-
licenses:
|
185
|
+
licenses:
|
186
|
+
- LGPL
|
187
|
+
- EULA
|
185
188
|
metadata: {}
|
186
189
|
post_install_message:
|
187
190
|
rdoc_options: []
|
@@ -198,8 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
198
201
|
- !ruby/object:Gem::Version
|
199
202
|
version: '0'
|
200
203
|
requirements: []
|
201
|
-
|
202
|
-
rubygems_version: 2.7.7
|
204
|
+
rubygems_version: 3.1.2
|
203
205
|
signing_key:
|
204
206
|
specification_version: 4
|
205
207
|
summary: Lowkiq
|