perfectqueue 0.8.17 → 0.8.18

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -1,4 +1,12 @@
1
1
 
2
+ == 2012-09-04 version 0.8.18
3
+
4
+ * rdb_compat backend uses table lock to avoid dead locks with InnoDB
5
+ * rdb_compat backend uses bulk cleanup to improve throughput
6
+ * rdb_compat backend uses created_at in WHERE clause to work with partitioning
7
+ * rdb_compat backend supports disable_resource_limit to improve acquire() performance
8
+
9
+
2
10
  == 2012-09-03 version 0.8.17
3
11
 
4
12
  * Increased wait time before starting processors to 1 second at average and
@@ -43,7 +43,16 @@ module PerfectQueue
43
43
  # connection test
44
44
  }
45
45
 
46
- @sql = <<SQL
46
+ if config[:disable_resource_limit]
47
+ @sql = <<SQL
48
+ SELECT id, timeout, data, created_at, resource
49
+ FROM `#{@table}`
50
+ WHERE timeout <= ? AND timeout <= ? AND created_at IS NOT NULL
51
+ ORDER BY timeout ASC
52
+ LIMIT ?
53
+ SQL
54
+ else
55
+ @sql = <<SQL
47
56
  SELECT id, timeout, data, created_at, resource, max_running, max_running/running AS weight
48
57
  FROM `#{@table}`
49
58
  LEFT JOIN (
@@ -52,24 +61,37 @@ LEFT JOIN (
52
61
  WHERE timeout > ? AND created_at IS NOT NULL AND resource IS NOT NULL
53
62
  GROUP BY resource
54
63
  ) AS R ON resource = res
55
- WHERE timeout <= ? AND (max_running-running IS NULL OR max_running-running > 0)
64
+ WHERE timeout <= ? AND created_at IS NOT NULL AND (max_running-running IS NULL OR max_running-running > 0)
56
65
  ORDER BY weight IS NOT NULL, weight DESC, timeout ASC
57
- LIMIT #{MAX_SELECT_ROW}
66
+ LIMIT ?
58
67
  SQL
68
+ end
59
69
 
60
- # sqlite doesn't support SELECT ... FOR UPDATE but
61
- # sqlite doesn't need it because the db is not shared
62
- unless url.split('//',2)[0].to_s.include?('sqlite')
63
- @sql << 'FOR UPDATE'
70
+ case url.split('//',2)[0].to_s
71
+ when /sqlite/i
72
+ # sqlite always locks tables on BEGIN
73
+ @table_lock = nil
74
+ when /mysql/i
75
+ if config[:disable_resource_limit]
76
+ @table_lock = "LOCK TABLES `#{@table}` WRITE"
77
+ else
78
+ @table_lock = "LOCK TABLES `#{@table}` WRITE, `#{@table}` AS T WRITE"
79
+ end
80
+ else
81
+ @table_lock = "LOCK TABLE `#{@table}`"
64
82
  end
83
+
84
+ @prefetch_break_types = config[:prefetch_break_types] || []
85
+
86
+ @cleanup_interval = config[:cleanup_interval] || DEFAULT_DELETE_INTERVAL
87
+ @cleanup_interval_count = 0
65
88
  end
66
89
 
67
90
  attr_reader :db
68
91
 
69
- MAX_SELECT_ROW = 8
70
- MAX_RESOURCE = (ENV['PQ_MAX_RESOURCE'] || 4).to_i
71
92
  #KEEPALIVE = 10
72
93
  MAX_RETRY = 10
94
+ DEFAULT_DELETE_INTERVAL = 20
73
95
 
74
96
  def init_database(options)
75
97
  sql = %[
@@ -148,42 +170,49 @@ SQL
148
170
  now = (options[:now] || Time.now).to_i
149
171
  next_timeout = now + alive_time
150
172
 
151
- fetched = []
173
+ tasks = []
152
174
 
153
175
  connect {
154
- while true
155
- rows = 0
156
- begin
157
- @db.transaction do
158
- @db.fetch(@sql, now, now) {|row|
159
- unless row[:created_at]
160
- # finished task
161
- @db["DELETE FROM `#{@table}` WHERE id=?;", row[:id]].delete
162
-
163
- else
164
- ## optimistic lock is not needed because the row is locked for update
165
- #n = @db["UPDATE `#{@table}` SET timeout=? WHERE id=? AND timeout=?", timeout, row[:id], row[:timeout]].update
166
- n = @db["UPDATE `#{@table}` SET timeout=? WHERE id=?", next_timeout, row[:id]].update
167
- if n > 0
168
- attributes = create_attributes(nil, row)
169
- task_token = Token.new(row[:id])
170
- task = AcquiredTask.new(@client, row[:id], attributes, task_token)
171
- fetched.push task
172
- break if fetched.size >= max_acquire
173
- end
174
- end
175
-
176
- rows += 1
177
- }
176
+ if @cleanup_interval_count <= 0
177
+ @db["DELETE FROM `#{@table}` WHERE timeout <= ? AND created_at IS NULL", now].delete
178
+ @cleanup_interval_count = @cleanup_interval
179
+ end
180
+
181
+ @db.transaction do
182
+ if @table_lock
183
+ @db[@table_lock].update
184
+ end
185
+
186
+ tasks = []
187
+ @db.fetch(@sql, now, now, max_acquire) {|row|
188
+ attributes = create_attributes(nil, row)
189
+ task_token = Token.new(row[:id])
190
+ task = AcquiredTask.new(@client, row[:id], attributes, task_token)
191
+ tasks.push task
192
+
193
+ if @prefetch_break_types.include?(attributes[:type])
194
+ break
178
195
  end
179
- rescue
180
- raise if fetched.empty?
196
+ }
197
+
198
+ if tasks.empty?
199
+ return nil
181
200
  end
182
- unless fetched.empty?
183
- return fetched
201
+
202
+ sql = "UPDATE `#{@table}` SET timeout = ? WHERE id IN ("
203
+ params = [sql, next_timeout]
204
+ tasks.each {|t| params << t.key }
205
+ sql << (1..tasks.size).map { '?' }.join(',')
206
+ sql << ") AND created_at IS NOT NULL"
207
+
208
+ n = @db[*params].update
209
+ if n != tasks.size
210
+ # TODO table lock doesn't work. error?
184
211
  end
185
- break nil if rows < MAX_SELECT_ROW
212
+
213
+ @cleanup_interval_count -= 1
186
214
  end
215
+ return tasks
187
216
  }
188
217
  end
189
218
 
@@ -1,3 +1,3 @@
1
1
  module PerfectQueue
2
- VERSION = "0.8.17"
2
+ VERSION = "0.8.18"
3
3
  end
data/spec/spec_helper.rb CHANGED
@@ -25,7 +25,9 @@ module QueueTest
25
25
  :type => 'rdb_compat',
26
26
  :url => "sqlite://#{database_path}",
27
27
  :table => 'test_tasks',
28
- :processor_type => 'thread'
28
+ :processor_type => 'thread',
29
+ :cleanup_interval => 0, # for test
30
+ #:disable_resource_limit => true, # TODO backend-specific test cases
29
31
  }
30
32
  end
31
33
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perfectqueue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.17
4
+ version: 0.8.18
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-03 00:00:00.000000000 Z
12
+ date: 2012-10-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sequel