lowkiq 1.0.0 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -20,7 +20,7 @@ services:
20
20
  image: redis:5-alpine
21
21
 
22
22
  frontend:
23
- image: node
23
+ image: node:12.16.3
24
24
  ports:
25
25
  - "8081:8081"
26
26
  working_dir: /usr/src/app
@@ -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
@@ -3,7 +3,7 @@ module Lowkiq
3
3
  def extended(mod)
4
4
  @extended_modules ||= []
5
5
  @extended_modules << mod
6
- @extended_modules.sort_by! &:name
6
+ @extended_modules.sort_by!(&:name).uniq!
7
7
  end
8
8
 
9
9
  def extended_modules
@@ -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)| [Marshal.load_payload(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)| [Marshal.load_payload(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
@@ -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
@@ -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, Marshal.dump_payload(payload), nx: true
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
- data = nil
41
- tx = redis.watch @keys.ids_scored_by_perform_in_zset(shard) do
42
- ids = redis.zrangebyscore @keys.ids_scored_by_perform_in_zset(shard),
43
- 0, @timestamp.call,
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
- data = @fetch.fetch(redis, :pipelined, ids)
45
+ res = redis.multi do |redis|
46
+ redis.hset @keys.processing_length_by_shard_hash, shard, ids.length
52
47
 
53
- redis.multi do
54
- _delete redis, ids
55
- redis.set @keys.processing_key(shard), Marshal.dump_data(data)
56
- redis.hset @keys.processing_length_by_shard_hash, shard, data.length
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
- end until tx
67
+ processing_data_pipeline(redis, shard, ids)
68
+ end
59
69
 
60
- data
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
- batch.each do |job|
67
- id = job.fetch(:id)
68
- perform_in = job.fetch(:perform_in, @timestamp.call)
69
- retry_count = job.fetch(:retry_count, -1)
70
- payloads = job.fetch(:payloads).map do |(payload, score)|
71
- [score, Marshal.dump_payload(payload)]
72
- end
73
- error = job.fetch(:error, nil)
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
- shard = id_to_shard id
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 @keys.processing_key(shard)
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, length if result == :fail
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
- data = @pool.with do |redis|
106
- redis.get @keys.processing_key(shard)
107
- end
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
- Marshal.load_data data
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
- batch.each do |job|
116
- id = job.fetch(:id)
117
- payloads = job.fetch(:payloads).map do |(payload, score)|
118
- [score, Marshal.dump_payload(payload)]
119
- end
120
- error = job.fetch(:error, nil)
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, @timestamp.call, id
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
- _delete redis, ids
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 _delete(redis, ids)
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
- shard = id_to_shard id
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
@@ -17,6 +17,10 @@ module Lowkiq
17
17
  end
18
18
 
19
19
  def start
20
+ Lowkiq.server_redis_pool.with do |redis|
21
+ Script.load! redis
22
+ end
23
+
20
24
  @shard_handlers_by_thread.each do |handlers|
21
25
  handlers.each(&:restore)
22
26
  end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Lowkiq
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.5"
3
3
  end
@@ -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.16"
32
- spec.add_development_dependency "rake", "~> 10.0"
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.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: 2019-10-04 00:00:00.000000000 Z
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: '1.16'
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: '1.16'
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: '10.0'
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: '10.0'
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
- rubyforge_project:
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