perfectqueue 0.8.47 → 0.8.48
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/.travis.yml +3 -4
- data/ChangeLog +8 -0
- data/README.md +9 -1
- data/bin/stress +201 -0
- data/lib/perfectqueue/backend/rdb_compat.rb +13 -4
- data/lib/perfectqueue/version.rb +1 -1
- data/perfectqueue.gemspec +1 -0
- data/spec/supervisor_spec.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 56f251e9261948702f9f058179343788c08ea08e
|
4
|
+
data.tar.gz: 4bd8336b27bfc2097a22581120e872fe621d4c53
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a826da88811b6f511b4e4b0a6bdab9255a0a792848874b761fb9c2b6014fb3ac32f1c2a8231c8bdd54dfec82bb37fbd46b87664d392f829fb9fadfcb4388d3e
|
7
|
+
data.tar.gz: 4591e5a7f253502dafb1687ce57d572fa8b7742b2482be6938d41ce501d9e42f44cc4a2ca3e9a13a4a7de6f50f3518831b2bc9ba60bbac5ec29930ea67c36602
|
data/.travis.yml
CHANGED
data/ChangeLog
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
== 2016-07-07 version 0.8.48
|
2
|
+
|
3
|
+
* Drop 2.0.0 support #33
|
4
|
+
* Show taken time of delete and acquire (#33)
|
5
|
+
* To ease the migration delete v0.8.44 finished tasks too (#34)
|
6
|
+
* Add stress test tool (#35)
|
7
|
+
* Fix cleanup interval count (#36)
|
8
|
+
|
1
9
|
== 2016-04-15 version 0.8.47
|
2
10
|
|
3
11
|
* require libraries before fork to help CoW
|
data/README.md
CHANGED
@@ -189,6 +189,14 @@ additional configuration:
|
|
189
189
|
|
190
190
|
Not implemented yet.
|
191
191
|
|
192
|
+
## config/perfectqueue.yml Example
|
193
|
+
|
194
|
+
```
|
195
|
+
development:
|
196
|
+
type: rdb_compat
|
197
|
+
url: mysql2://root:@localhost:3306/perfectqueue
|
198
|
+
table: queues
|
199
|
+
```
|
192
200
|
|
193
201
|
## Command line management tool
|
194
202
|
|
@@ -241,7 +249,7 @@ options for run:
|
|
241
249
|
|
242
250
|
### force finish a tasks
|
243
251
|
|
244
|
-
$ perfectqueue
|
252
|
+
$ perfectqueue force_finish k2
|
245
253
|
|
246
254
|
### running a worker
|
247
255
|
|
data/bin/stress
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
3
|
+
require 'sequel'
|
4
|
+
require 'perfectqueue'
|
5
|
+
require 'perfectqueue/backend/rdb_compat'
|
6
|
+
require 'securerandom'
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
queue_config =
|
10
|
+
{
|
11
|
+
:type => 'rdb_compat',
|
12
|
+
:url => "mysql2://testuser:@localhost:3306/perfectqueue_test",
|
13
|
+
:table => 'test_stress',
|
14
|
+
:disable_resource_limit => true, # TODO backend-specific test cases
|
15
|
+
:cleanup_interval => 200,
|
16
|
+
insert_processes: 0,
|
17
|
+
worker_processes: 0,
|
18
|
+
}
|
19
|
+
opt = OptionParser.new
|
20
|
+
|
21
|
+
opt.on('--url URL', 'database url') {|v| queue_config[:url] = v }
|
22
|
+
opt.on('--table TABLE', 'table name') {|v| queue_config[:table] = v }
|
23
|
+
opt.on('--disable_resource_limit=true', TrueClass, 'use resource limit or not') {|v| queue_config[:disable_resource_limit] = v }
|
24
|
+
opt.on('--cleanup_interval SECOND', Integer, 'cleanup interval') {|v| queue_config[:cleanup_interval] = v }
|
25
|
+
opt.on('--insert_processes NUM', Integer, 'inserts') {|v| queue_config[:insert_processes] = v }
|
26
|
+
opt.on('--worker_processes NUM', Integer, 'workers') {|v| queue_config[:worker_processes] = v }
|
27
|
+
opt.on('--retention-time SECOND', Integer, 'retention time') {|v| queue_config[:retention_time] = v }
|
28
|
+
opt.parse!(ARGV)
|
29
|
+
|
30
|
+
module PerfectQueue
|
31
|
+
class Queue
|
32
|
+
def submit1000(data)
|
33
|
+
@client.submit1000(data)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
class Client
|
37
|
+
def submit1000(data)
|
38
|
+
@backend.submit1000(data)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
module Backend
|
42
|
+
class RDBCompatBackend
|
43
|
+
def submit1000(h)
|
44
|
+
rd = Random.new
|
45
|
+
i = 0
|
46
|
+
connect {
|
47
|
+
begin
|
48
|
+
begin
|
49
|
+
n = Process.clock_gettime(Process::CLOCK_REALTIME, :second)
|
50
|
+
submit0("import.1/main.action_#{n}_#{rd.hex(80)}", 'user02', h, now: n)
|
51
|
+
end while (i+=1) < 10000
|
52
|
+
end
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
# => Task
|
57
|
+
def submit0(key, type, data, options)
|
58
|
+
now = (options[:now] || Time.now).to_i
|
59
|
+
now = 1 if now < 1 # 0 means cancel requested
|
60
|
+
run_at = (options[:run_at] || now).to_i
|
61
|
+
user = options[:user]
|
62
|
+
user = user.to_s if user
|
63
|
+
max_running = options[:max_running]
|
64
|
+
data = data ? data.dup : {}
|
65
|
+
data['type'] = type
|
66
|
+
|
67
|
+
d = data.to_json
|
68
|
+
|
69
|
+
if options[:compression] == 'gzip'
|
70
|
+
require 'zlib'
|
71
|
+
require 'stringio'
|
72
|
+
io = StringIO.new
|
73
|
+
gz = Zlib::GzipWriter.new(io)
|
74
|
+
begin
|
75
|
+
gz.write(d)
|
76
|
+
ensure
|
77
|
+
gz.close
|
78
|
+
end
|
79
|
+
d = io.string
|
80
|
+
d.force_encoding('ASCII-8BIT') if d.respond_to?(:force_encoding)
|
81
|
+
d = Sequel::SQL::Blob.new(d)
|
82
|
+
end
|
83
|
+
|
84
|
+
@db[
|
85
|
+
"INSERT INTO `#{@table}` (id, timeout, data, created_at, resource, max_running) VALUES (?, ?, ?, ?, ?, ?)",
|
86
|
+
key, run_at, d, now, user, max_running
|
87
|
+
].insert
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
def insert1000(queue)
|
95
|
+
Process.setproctitle'bin/stress/insert'
|
96
|
+
while 1
|
97
|
+
t0 = t = Process.clock_gettime(Process::CLOCK_REALTIME, :second)
|
98
|
+
h = {"path":"in/1/main.action.e9f070b5bfea96442af13ce6acc36699_0f7ad8aee859867aae303190e372ec1e.msgpack.gz",
|
99
|
+
"size":11373,
|
100
|
+
"format":"msgpack.gz",
|
101
|
+
"account_id":1,
|
102
|
+
"start_at":nil,
|
103
|
+
"database":"main",
|
104
|
+
"table":"action",
|
105
|
+
"table_path":"action_20160516_d85364_e43b23f4",
|
106
|
+
"table_id":123456,
|
107
|
+
"user":1,
|
108
|
+
"c3769c43b07202bf6f456f4b99639a15":"1/main/action_20160516_d85364_e43b23f4",
|
109
|
+
"hogefuga":"e9f070b5bfea96442af13ce6acc36699",
|
110
|
+
"unique_id":"2d7325ef94e3eda8a20b65d33696d45a",
|
111
|
+
"hoge_callback":{"url":"f117c9682a9c5129275f39493599272f809fcf3064edbfacc1c0a5",
|
112
|
+
"headers":{"21a166dedd97bc":"TD1 4f8e44c0880086628ad7349e69d7d6ba19ce05ae"},
|
113
|
+
"params":{}},
|
114
|
+
"params":{}}
|
115
|
+
begin
|
116
|
+
queue.submit1000(h)
|
117
|
+
rescue Sequel::DatabaseError
|
118
|
+
p $!
|
119
|
+
sleep 5
|
120
|
+
end
|
121
|
+
t = Process.clock_gettime(Process::CLOCK_REALTIME, :second)
|
122
|
+
puts "#{__method__}#{Process.pid}: #{t-t0}sec for 1000 inserts\n"
|
123
|
+
end
|
124
|
+
rescue Interrupt
|
125
|
+
exit
|
126
|
+
end
|
127
|
+
|
128
|
+
def insert(queue)
|
129
|
+
while 1
|
130
|
+
rd = Random.new
|
131
|
+
i = 0
|
132
|
+
t0 = t = Process.clock_gettime(Process::CLOCK_REALTIME, :second)
|
133
|
+
h = {"path":"in/1/main.action.e9f070b5bfea96442af13ce6acc36699_0f7ad8aee859867aae303190e372ec1e.msgpack.gz",
|
134
|
+
"size":11373,
|
135
|
+
"format":"msgpack.gz",
|
136
|
+
"account_id":1,
|
137
|
+
"start_at":nil,
|
138
|
+
"database":"main",
|
139
|
+
"table":"action",
|
140
|
+
"table_path":"action_20160516_d85364_e43b23f4",
|
141
|
+
"table_id":123456,
|
142
|
+
"user":1,
|
143
|
+
"c3769c43b07202bf6f456f4b99639a15":"1/main/action_20160516_d85364_e43b23f4",
|
144
|
+
"hogefuga":"e9f070b5bfea96442af13ce6acc36699",
|
145
|
+
"unique_id":"2d7325ef94e3eda8a20b65d33696d45a",
|
146
|
+
"hoge_callback":{"url":"f117c9682a9c5129275f39493599272f809fcf3064edbfacc1c0a5",
|
147
|
+
"headers":{"21a166dedd97bc":"TD1 4f8e44c0880086628ad7349e69d7d6ba19ce05ae"},
|
148
|
+
"params":{}},
|
149
|
+
"params":{}}
|
150
|
+
begin
|
151
|
+
n = Process.clock_gettime(Process::CLOCK_REALTIME, :second)
|
152
|
+
queue.submit("import.1/main.action_#{n}_#{rd.hex(80)}", 'user02', h, now: t)
|
153
|
+
rescue
|
154
|
+
p $!
|
155
|
+
sleep 1
|
156
|
+
end while (i+=1) < 1000
|
157
|
+
t = Process.clock_gettime(Process::CLOCK_REALTIME, :second)
|
158
|
+
puts "#{__method__}#{Process.pid}: #{t-t0}sec for 1000 inserts\n"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def worker(queue)
|
163
|
+
while 1
|
164
|
+
i = 0
|
165
|
+
t0 = t = Process.clock_gettime(Process::CLOCK_REALTIME, :second)
|
166
|
+
begin
|
167
|
+
ary = queue.poll_multi(max_acquire: 11, now: t)
|
168
|
+
ary.each do |x|
|
169
|
+
x.finish!({})
|
170
|
+
end if ary
|
171
|
+
t = Process.clock_gettime(Process::CLOCK_REALTIME, :second)
|
172
|
+
rescue
|
173
|
+
p $!.class
|
174
|
+
sleep 1
|
175
|
+
end while (i+=1) < 100
|
176
|
+
puts "#{__method__}#{Process.pid}: #{t-t0}sec for 100 acquires\n"
|
177
|
+
end
|
178
|
+
rescue Interrupt
|
179
|
+
exit
|
180
|
+
end
|
181
|
+
|
182
|
+
pids = []
|
183
|
+
queue = PerfectQueue.open(queue_config)
|
184
|
+
#queue.client.init_database(:force => true)
|
185
|
+
queue.config[:insert_processes].times do
|
186
|
+
pids << fork { insert1000(queue) }
|
187
|
+
end
|
188
|
+
queue.config[:worker_processes].times do
|
189
|
+
#queue.client.backend.instance_variable_set(:@cleanup_interval_count, rand(200))
|
190
|
+
pids << fork { worker(queue) }
|
191
|
+
end
|
192
|
+
queue.close
|
193
|
+
|
194
|
+
trap (:INT) do
|
195
|
+
pids.each do |pid|
|
196
|
+
Process.kill(:INT, pid) rescue nil
|
197
|
+
Process.waitpid(pid) rescue nil
|
198
|
+
end
|
199
|
+
exit
|
200
|
+
end
|
201
|
+
sleep while 1
|
@@ -105,7 +105,10 @@ SQL
|
|
105
105
|
@prefetch_break_types = config[:prefetch_break_types] || []
|
106
106
|
|
107
107
|
@cleanup_interval = config[:cleanup_interval] || DEFAULT_DELETE_INTERVAL
|
108
|
-
|
108
|
+
# If cleanup_interval > max_request_per_child / max_acquire,
|
109
|
+
# some processes won't run DELETE query.
|
110
|
+
# (it's not an issue when there are enough workers)
|
111
|
+
@cleanup_interval_count = @cleanup_interval > 0 ? rand(@cleanup_interval) : 0
|
109
112
|
end
|
110
113
|
|
111
114
|
attr_reader :db
|
@@ -212,18 +215,22 @@ SQL
|
|
212
215
|
def acquire(alive_time, max_acquire, options)
|
213
216
|
now = (options[:now] || Time.now).to_i
|
214
217
|
next_timeout = now + alive_time
|
215
|
-
|
216
|
-
|
218
|
+
tasks = nil
|
219
|
+
t0 = nil
|
217
220
|
|
218
221
|
if @cleanup_interval_count <= 0
|
219
222
|
delete_timeout = now - DELETE_OFFSET
|
220
|
-
|
223
|
+
connect_locked {
|
224
|
+
t0=Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
225
|
+
@db["DELETE FROM `#{@table}` WHERE #{EVENT_HORIZON} < timeout && timeout <= ? AND created_at IS NULL", now].delete
|
221
226
|
@db["DELETE FROM `#{@table}` WHERE timeout <= ? AND created_at IS NULL", delete_timeout].delete
|
222
227
|
@cleanup_interval_count = @cleanup_interval
|
228
|
+
STDERR.puts"PQ:delete from #{@table}:%6f sec" % [Process.clock_gettime(Process::CLOCK_MONOTONIC)-t0]
|
223
229
|
}
|
224
230
|
end
|
225
231
|
|
226
232
|
connect_locked {
|
233
|
+
t0=Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
227
234
|
tasks = []
|
228
235
|
@db.fetch(@sql, now, now, max_acquire) {|row|
|
229
236
|
attributes = create_attributes(nil, row)
|
@@ -256,6 +263,8 @@ SQL
|
|
256
263
|
|
257
264
|
return tasks
|
258
265
|
}
|
266
|
+
ensure
|
267
|
+
STDERR.puts "PQ:acquire from #{@table}:%6f sec (%d tasks)" % [Process.clock_gettime(Process::CLOCK_MONOTONIC)-t0,tasks.size] if tasks
|
259
268
|
end
|
260
269
|
|
261
270
|
# => nil
|
data/lib/perfectqueue/version.rb
CHANGED
data/perfectqueue.gemspec
CHANGED
@@ -17,6 +17,7 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
18
|
gem.require_paths = ['lib']
|
19
19
|
|
20
|
+
gem.required_ruby_version = '>= 2.1'
|
20
21
|
gem.add_dependency "sequel", "~> 3.48.0"
|
21
22
|
gem.add_development_dependency "rake", "~> 0.9.2"
|
22
23
|
gem.add_development_dependency "rspec", "~> 3.3.0"
|
data/spec/supervisor_spec.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: perfectqueue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.48
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sadayuki Furuhashi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -84,6 +84,7 @@ description: Highly available distributed cron built on RDBMS
|
|
84
84
|
email: frsyuki@gmail.com
|
85
85
|
executables:
|
86
86
|
- perfectqueue
|
87
|
+
- stress
|
87
88
|
extensions: []
|
88
89
|
extra_rdoc_files: []
|
89
90
|
files:
|
@@ -96,6 +97,7 @@ files:
|
|
96
97
|
- README.md
|
97
98
|
- Rakefile
|
98
99
|
- bin/perfectqueue
|
100
|
+
- bin/stress
|
99
101
|
- circle.yml
|
100
102
|
- lib/perfectqueue.rb
|
101
103
|
- lib/perfectqueue/application.rb
|
@@ -163,7 +165,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
163
165
|
requirements:
|
164
166
|
- - ">="
|
165
167
|
- !ruby/object:Gem::Version
|
166
|
-
version: '
|
168
|
+
version: '2.1'
|
167
169
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
170
|
requirements:
|
169
171
|
- - ">="
|
@@ -200,4 +202,3 @@ test_files:
|
|
200
202
|
- spec/task_metadata_spec.rb
|
201
203
|
- spec/task_monitor_spec.rb
|
202
204
|
- spec/task_spec.rb
|
203
|
-
has_rdoc: false
|