perfectqueue 0.8.17 → 0.8.18

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