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.
@@ -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