postburner 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71a903596aa63a03c214e5367c603b3941b8ee4d4f002383208534c31f2c4d7a
4
- data.tar.gz: a86e68e87419b3d40317b31beb76bf2ff3f0cf36915ae45e8a4f4cdd52422bf5
3
+ metadata.gz: 6faa3078340ee2bc8d90e931ee1db203114e32ff69b288ec69340e8b86a987cd
4
+ data.tar.gz: 0a2ddf78a320e9b01c26a0e3fbd3158b78d37a89bca04a5fdd8c64b70eada1a7
5
5
  SHA512:
6
- metadata.gz: 76590506bc73dc5f875551021183080e0693ce242278401519a0c0c77c36b4ea0861b96e738a0f5491aa6c6029217b5c6d52b5248fd6e963e65eedfe494df721
7
- data.tar.gz: 4b0cae0a80716e081a519d14b83a56dd545578ac3a45a9e59af7ca6429f610e93f2d180f3de0b57e512134190ab1e3c2f73c5ae2d0afedf6fb348468cdbff6f7
6
+ metadata.gz: 5253309eb7aff502623040d49e923faff7c91df6aac407ec7a4e306a8317b1363db43a4b0408eb20fbdad6ee017ad68489480ac50d7e2ac68a612a1337488457
7
+ data.tar.gz: 49d0253fe3ecde9cfd4de8b48a2ce95997245bea4310f7c751ccca3685fea8a42b29310a8058f222031ee8aa91f6ec61f990ec5c330157e39853c95830236cfa
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.4.0 - 2021-10-12
4
+
5
+ ### Added
6
+ - Change Job logging to persisted in perform.
7
+ - Update mailer job to use #with and support serializations.
8
+
9
+ ## v0.3.3 - 2021-06-15
10
+
11
+ ### Fixed
12
+ - re-raise exception after logging during perform.
13
+ - log but swallow post perform data update exceptions.
14
+ - only allow remove! if job still present.
15
+
16
+ ### Added
17
+ - Job#log! and Job#log_exception! that save the data immediately.
18
+
19
+ ## v0.3.2 - 2021-05-07
20
+
21
+ ### Fixed
22
+ - add processed_at guard w/ logs.
23
+
24
+ ## v0.3.1 - 2021-05-07
25
+
26
+ ### Fixed
27
+ - calling warning on Rails.logger.
28
+
3
29
  ## v0.3.0 - 2021-05-07
4
30
 
5
31
  ### Added
data/README.md CHANGED
@@ -1,29 +1,15 @@
1
1
  # Postburner
2
- An ActiveRecord layer on top of Backburner for inspecting and auditing the
3
- queue, especially for delayed jobs. It isn't meant to be fast, but safe.
2
+ An ActiveRecord layer on top of [Backburner](https://github.com/nesquena/backburner) for inspecting and auditing the
3
+ queue, especially for delayed jobs. It isn't meant to be outperform other queues, but be safe (and inspectable).
4
4
 
5
- It is meant to be complementary to Backburner. Use Backburner as the default
5
+ It is meant to be complementary to [Backburner](https://github.com/nesquena/backburner). Use Backburner as the default
6
6
  ActiveJob processor for mailers, active storage, and the like. Use a
7
- `Postburner::Job` for things that you want to track.
7
+ `Postburner::Job` for things that you want to track. See [Comparison to Backburner](#comparison-to-backburner) for more.
8
8
 
9
- Comes with a mountable interface that can be password protected with whatever
10
- authentication you use in your project.
11
-
12
- Also meant to be a replacement/upgrade for [Que](https://github.com/que-rb/que).
13
- If you need something faster, but backed with ACID compliance, check out Que.
14
- There are some additional features in Postburner such as retained jobs
15
- after processing, stats, per job logging, etc.
16
-
17
- Compared to plain [Backburner](https://github.com/nesquena/backburner),
18
- Postburner adds:
19
- - Database Jobs for inspection, linking, auditing, removal (and deletion)
20
- - Direct access to associated [Beanstalk](https://beanstalkd.github.io/) (via [beaneater](https://github.com/beanstalkd/beaneater))
21
- - Job Statistics (lag, attempts, logs, tracked errors)
22
- - Convenience methods to clear tubes, stats, and connections for Beanstalk.
23
-
24
- Otherwise, Postburner tries to be a super simple layer on `Backburner::Queue`
25
- and `ActiveRecord`. Every tool with either of those are avilabel in
26
- `Postburner::Job`s.
9
+ Postburner meant to be a replacement/upgrade for [Que](https://github.com/que-rb/que).
10
+ If you need something faster, check out Que - we love it! Postburner is built for a slightly different purpose: to be
11
+ simple, safe, and inspectable. All of which Que has (or can be added), but are included by default with some architecture
12
+ differences. See [Comparison to Que](#comparison-to-que) for more.
27
13
 
28
14
  ## Usage
29
15
 
@@ -33,51 +19,240 @@ class RunDonation < Postburner::Job
33
19
  queue_priority 0 # 0 is highest priority
34
20
  queue_max_job_retries 0 # don't retry
35
21
 
36
- def process(args)
22
+ def perform(args)
37
23
  # do long tasks here
38
24
  # also have access to self.args
39
25
  end
40
26
  end
41
27
 
42
- RunDonation.create!(args: {donation_id: 123}).queue!
28
+ # RunDonation#create! is the `ActiveRecord` method, so it returns a model,
29
+ # you can manipulate, add columns to the table to, reference with foreign
30
+ # keys, etc.
31
+ job = RunDonation.create!(args: {donation_id: 123})
32
+ # NOTE Make sure use use an after_commit or after_save_commit, etc to avoid
33
+ # any race conditions of Postburner trying to process before a required
34
+ # database mutation is commited.
35
+ job.queue!
43
36
  => {:status=>"INSERTED", :id=>"1139"}
37
+
38
+ # queue for later with `:at`
44
39
  RunDonation.create!(args: {donation_id: 123}).queue! at: Time.zone.now + 2.days
45
40
  => {:status=>"INSERTED", :id=>"1140"}
46
41
 
47
- # `delay` takes priority over `at`
42
+ # queue for later with `:delay`
43
+ #
44
+ # `:delay` takes priority over `:at`takes priority over `:at` because the
45
+ # beanstalkd protocol uses uses `delay`
48
46
  RunDonation.create!(args: {donation_id: 123}).queue! delay: 1.hour
49
47
  => {:status=>"INSERTED", :id=>"1141"}
50
48
  ```
51
49
 
50
+ ### [Beaneater](https://github.com/beanstalkd/beaneater) and [beanstalkd](https://raw.githubusercontent.com/beanstalkd/beanstalkd/master/doc/protocol.txt) attributes and methods
51
+ ```ruby
52
+ # get the beanstalkd job id
53
+ job.bkid
54
+ => 1104
55
+
56
+ # get the beaneater job, call any beaneater methods on this object
57
+ job.beanstalk_job
58
+
59
+ # get the beanstald stats
60
+ job.beanstalk_job.stats
61
+
62
+ # kick the beanstalk job
63
+ job.kick!
64
+
65
+ # delete beankstalkd job, retain the job model
66
+ job.delete!
67
+
68
+ # delete beankstalkd job, retain the job model, but set `removed_at` on model.
69
+ job.remove!
70
+
71
+ # or simply remove the model, which will clean up the beanstalkd job in a before_destroy hook
72
+ job.destroy # OR job.destroy!
73
+
74
+ # get a cached Backburner connection and inspect it (or even use it directly)
75
+ c = Postburner.connection
76
+ c.beanstalk.tubes.to_a
77
+ c.beanstalk.tubes.to_a.map{|t| c.tubes[t.name].peek(:buried)}
78
+ c.beanstalk.tubes['ss.development.caching'].stats
79
+ c.beanstalk.tubes['ss.development.caching'].peek(:buried).kick
80
+ c.beanstalk.tubes['ss.development.caching'].kick(3)
81
+ c.close
82
+
83
+ # automatically close
84
+ Postburner.connected do |connection|
85
+ # do stuff with connection
86
+ end
87
+ ```
88
+
89
+ Read about the [beanstalkd protocol](https://raw.githubusercontent.com/beanstalkd/beanstalkd/master/doc/protocol.txt).
90
+
91
+ ### Basic model fields
92
+ ```ruby
93
+ # ActiveRecord primary key
94
+ job.id
95
+
96
+ # int id of the beankstalkd job
97
+ job.bkid
98
+
99
+ # string uuid
100
+ job.sid
101
+
102
+ # ActiveRecord STI job subtype
103
+ job.type
104
+
105
+ # jsonb arguments for use in job
106
+ job.args
107
+
108
+ # time job should run - not intended to be changed
109
+ # TODO run_at should be readonly after create
110
+ job.run_at
111
+ ```
112
+
113
+ ### Job statistics
114
+ ```ruby
115
+ # when job was inserted into beankstalkd
116
+ job.queued_at
117
+
118
+ # last time attempted
119
+ job.attempting_at
120
+
121
+ # last time processing started
122
+ job.processing_at
123
+
124
+ # when completed
125
+ job.processed_at
126
+
127
+ # when removed, may be nil
128
+ job.removed_at
129
+
130
+ # lag in ms from run_at/queued_at to attempting_at
131
+ job.lag
132
+
133
+ # duration of processing in ms
134
+ job.duration
135
+
136
+ # number of attempts
137
+ job.attempt_count
138
+
139
+ # number of errors (length of errata)
140
+ job.error_count
141
+
142
+ # number of log entries (length of logs)
143
+ job.log_count
144
+
145
+ # array of attempting_at times
146
+ job.attempts
147
+
148
+ # array of errors
149
+ job.errata
150
+
151
+ # array of log messages
152
+ job.logs
153
+ ```
154
+
155
+ ### Job logging and exceptions
156
+
157
+ Optionally, you can:
158
+ 1. Add log messages to the job during processing to `logs`
159
+ 1. Add log your own exceptions to `errata`
160
+
161
+ ```ruby
162
+ class RunDonation < Postburner::Job
163
+ queue 'critical'
164
+ queue_priority 0 # 0 is highest priority
165
+ queue_max_job_retries 0 # don't retry
166
+
167
+ def perform(args)
168
+ # log at task, defaults to `:info`, but `:debug`, `:warning`, `:error`
169
+ log "Log bad condition...", level: :error
170
+
171
+ begin
172
+ # danger
173
+ rescue Exception => e
174
+ log_exception e
175
+ end
176
+ end
177
+ end
178
+ ```
179
+
180
+ ### Optionally, mount the engine
181
+
182
+ ```ruby
183
+ mount Postburner::Engine => "/postburner"
184
+
185
+ # mount only for development inspection
186
+ mount Postburner::Engine => "/postburner" if Rails.env.development?
187
+ ```
188
+
189
+ [Open the controller](https://guides.rubyonrails.org/engines.html#overriding-models-and-controllers)
190
+ to add your own authentication or changes - or just create your own routes, controllers, and views.
191
+
192
+ [Override the views](https://guides.rubyonrails.org/engines.html#overriding-views) to make them
193
+ prettier - or follow the suggestion above and use your own.
194
+
52
195
  ## Installation
53
196
 
197
+ First [install beanstalkd](https://beanstalkd.github.io/download.html). On Debian-based systems, that goes like this:
198
+ ```bash
199
+ sudo apt-get install beanstalkd
200
+ ```
201
+
202
+ Then add to your Gemfile.
54
203
  ```ruby
55
- # in Gemfile
56
204
  gem 'postburner'
57
205
  ```
58
206
 
207
+ Install...
59
208
  ```bash
60
209
  $ bundle
210
+ ```
61
211
 
212
+ After bundling, install the migration.
213
+ ```
62
214
  # install migration, possible to edit and add attributes or indexes as needed.
63
215
  $ bundle exec rails g postburner:install
64
216
  ```
65
217
 
66
- Add a `config/initializers/backburner.rb` with option found [here](https://github.com/nesquena/backburner#configuration).
218
+ Inspect `XXX_create_postburner_jobs.rb`. (The template is [here](-/blob/master/lib/generators/postburner/install/templates/migrations/create_postburner_jobs.rb.erb)).The required attributes are in there, but add more if you would like to use them,
219
+ or do it in a separate migration. By default, several indexes are added
67
220
 
68
- Set `Backburner` for `ActiveJob`
221
+ Because `Postburner` is built on `Backburner`, add a `config/initializers/backburner.rb` with option found [here](https://github.com/nesquena/backburner#configuration).
222
+ ```ruby
223
+ Backburner.configure do |config|
224
+ config.beanstalk_url = "beanstalk://127.0.0.1"
225
+ config.tube_namespace = "some.app.production"
226
+ config.namespace_separator = "."
227
+ config.on_error = lambda { |e| puts e }
228
+ config.max_job_retries = 3 # default 0 retries
229
+ config.retry_delay = 2 # default 5 seconds
230
+ config.retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) }
231
+ config.default_priority = 65536
232
+ config.respond_timeout = 120
233
+ config.default_worker = Backburner::Workers::Simple
234
+ config.logger = Logger.new(STDOUT)
235
+ config.primary_queue = "backburner-jobs"
236
+ config.priority_labels = { :custom => 50, :useless => 1000 }
237
+ config.reserve_timeout = nil
238
+ config.job_serializer_proc = lambda { |body| JSON.dump(body) }
239
+ config.job_parser_proc = lambda { |body| JSON.parse(body) }
240
+ end
241
+ ```
242
+
243
+ Finally, set `Backburner` for `ActiveJob`
69
244
  ```
70
245
  # config/application.rb
71
246
  config.active_job.queue_adapter = :backburner
72
247
  ```
73
248
 
74
249
  Postburner may later provide an adapter, but we recommend using `Postburner::Job` classes
75
- directyly.
250
+ directly.
76
251
 
77
252
  Add jobs to `app/jobs/`. There currently is no generator.
253
+
78
254
  ```ruby
79
255
  # app/jobs/run_donation.rb
80
-
81
256
  class RunDonation < Postburner::Job
82
257
  queue 'critical'
83
258
  queue_priority 0 # 0 is highest priority
@@ -88,6 +263,34 @@ class RunDonation < Postburner::Job
88
263
  # also have access to self.args
89
264
  end
90
265
  end
266
+ ```
267
+
268
+ ### Comparison to Backburner
269
+
270
+ Compared to plain [Backburner](https://github.com/nesquena/backburner),
271
+ Postburner adds:
272
+ - Database Jobs for inspection, linking, auditing, removal (and deletion)
273
+ - Direct access to associated [Beanstalk](https://beanstalkd.github.io/) (via [beaneater](https://github.com/beanstalkd/beaneater))
274
+ - Job Statistics (lag, attempts, logs, tracked errors)
275
+ - Convenience methods to clear tubes, stats, and connections for Beanstalk.
276
+
277
+ Otherwise, Postburner tries to be a super simple layer on `Backburner::Queue`
278
+ and `ActiveRecord`. Every tool with either of those are available in
279
+ `Postburner::Job`s.
280
+
281
+ Comes with a mountable interface that can be password protected with whatever
282
+ authentication you use in your project.
283
+
284
+ ### Comparison to Que
285
+
286
+ Postburner meant to be a replacement/upgrade for [Que](https://github.com/que-rb/que).
287
+ However, if you need something faster and backed with ACID compliance, check out Que.
288
+
289
+ Postburner has some additional features such as retained jobs after processing,
290
+ stats, per job logging, etc.
291
+
292
+ Postburner is meant to be simpler than `Que`. Que is incredible, but jobs should
293
+ be simple so the logic and history can be transparent.
91
294
 
92
295
  ## Contributing
93
296
  Submit a pull request. Follow conventions of the project. Be nice.
@@ -102,7 +305,6 @@ Submit a pull request. Follow conventions of the project. Be nice.
102
305
  - Job generator
103
306
  - Build file in app/jobs
104
307
  - Inherit from Postburner::Job
105
- - Job generator
106
308
  - Add before/after/around hooks
107
309
  - Add destroy, and remove actions on show page
108
310
  - Clear tubes.
@@ -112,5 +314,23 @@ Submit a pull request. Follow conventions of the project. Be nice.
112
314
  - Add logging with Job.args in backburner logs
113
315
  - MAYBE - ActiveJob adapter
114
316
 
317
+
318
+ ### Running in Development
319
+
320
+ ```
321
+ cd test/dummy
322
+ bundle exec backburner
323
+ ```
324
+
325
+ ### Releasing
326
+
327
+ 1. `gem bump -v minor -t`
328
+ Where <minor> can be: major|minor|patch|pre|release or a version number
329
+ 2. Edit `CHANGELOG.md` with details from `git log --oneline`
330
+ 3. `git commit --amend`
331
+ 4. `gem release -k nac -p`
332
+ Where <nac> is an authorized key with push capabilities.
333
+
334
+
115
335
  ## License
116
336
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -37,7 +37,7 @@ module Postburner
37
37
  job = self.find(id)
38
38
  job.perform!(job.args)
39
39
  rescue ActiveRecord::RecordNotFound => e
40
- Rails.logger.warning <<-MSG
40
+ Rails.logger.warn <<-MSG
41
41
  [Postburner::Job] [#{id}] Not Found.
42
42
  MSG
43
43
  end
@@ -55,45 +55,52 @@ module Postburner
55
55
  )
56
56
 
57
57
  begin
58
+ if self.processed_at.present?
59
+ self.log! "Already Processed", level: :error
60
+ self.delete!
61
+ return
62
+ end
63
+
58
64
  if self.removed_at.present?
59
- self.log "Removed", level: :error
60
- update_column :logs, self.logs
65
+ self.log! "Removed", level: :error
61
66
  return
62
67
  end
63
68
 
64
69
  if self.run_at && self.run_at > Time.zone.now
65
70
  response = self.insert! delay: self.run_at - Time.zone.now
66
- self.log "PREMATURE; RE-INSERTED: #{response}"
67
- update_column :logs, self.logs
71
+ self.log! "PREMATURE; RE-INSERTED: #{response}"
68
72
  return
69
73
  end
70
74
 
71
- self.log('START')
75
+ self.log!('START')
72
76
 
73
77
  self.perform(args)
74
78
 
75
- self.log('DONE')
79
+ self.log!('DONE')
80
+
81
+ begin
82
+ now = Time.zone.now
83
+ _duration = (now - self.processing_at) * 1000 rescue nil
84
+ self.update_columns(
85
+ processed_at: now,
86
+ duration: _duration,
87
+ errata: self.errata,
88
+ error_count: self.errata.length,
89
+ logs: self.logs,
90
+ log_count: self.logs.length,
91
+ )
92
+ rescue Exception => e
93
+ self.log_exception!(e)
94
+ self.log! '[Postburner] Could not set data after processing.'
95
+ # TODO README doesn't retry if Postburner is to blame
96
+ end
76
97
 
77
- rescue Exception => e
78
- self.log_exception(e)
98
+ rescue Exception => exception
99
+ self.log_exception!(exception)
100
+ self.log! '[Postburner] Exception raised during perform prevented completion.'
101
+ raise exception
79
102
  end
80
103
 
81
- begin
82
- now = Time.zone.now
83
- _duration = (now - self.processing_at) * 1000 rescue nil
84
- self.update_columns(
85
- processed_at: now,
86
- duration: _duration,
87
- errata: self.errata,
88
- error_count: self.errata.length,
89
- logs: self.logs,
90
- log_count: self.logs.length,
91
- )
92
- rescue Exception => e
93
- raise e if Rails.env.development? || Rails.env.production?
94
- self.log_exception(e)
95
- update_column :errata, self.errata
96
- end
97
104
  end
98
105
 
99
106
  def delete!
@@ -115,9 +122,12 @@ module Postburner
115
122
  end
116
123
 
117
124
  def remove!
118
- return false if self.attempts.any?
119
- self.delete!
120
- self.update_column(removed_at: Time.zone.now)
125
+ if self.beanstalk_job
126
+ self.delete!
127
+ self.update_column(:removed_at, Time.zone.now)
128
+ else
129
+ false
130
+ end
121
131
  end
122
132
 
123
133
  def beanstalk_job
@@ -145,6 +155,11 @@ module Postburner
145
155
  ]
146
156
  end
147
157
 
158
+ def log_exception!(exception)
159
+ self.log_exception(exception)
160
+ self.update_column :errata, self.errata
161
+ end
162
+
148
163
  def log(message, options={})
149
164
  options[:level] ||= :info
150
165
  options[:level] = :error unless LOG_LEVELS.member?(options[:level])
@@ -156,6 +171,11 @@ module Postburner
156
171
  ]
157
172
  end
158
173
 
174
+ def log!(message, options={})
175
+ self.log(message, options)
176
+ self.update_column :logs, self.logs
177
+ end
178
+
159
179
  private
160
180
 
161
181
  def insert!(options={})
@@ -1,13 +1,13 @@
1
1
  module Postburner
2
2
  # Send a mailer, tracked.
3
3
  #
4
- # Postburner::Mailer.deliver(UserMailer, :welcome, user_id: 1)
5
- # Postburner::Mailer.deliver(UserMailer, :welcome, user_id: 1).queue! at: Time.zone.now + 1.day
6
- # Postburner::Mailer.deliver(UserMailer, :welcome, user_id: 1).queue! delay: 5.minutes
4
+ # Postburner::Mailer.deliver(UserMailer, :welcome).with(user_id: 1)
5
+ # Postburner::Mailer.deliver(UserMailer, :welcome).with(user_id: 1).queue! at: Time.zone.now + 1.day
6
+ # Postburner::Mailer.deliver(UserMailer, :welcome).with(user_id: 1).queue! delay: 5.minutes
7
7
  class Mailer < Job
8
8
  #queue 'mailers'
9
9
 
10
- def self.deliver!(mailer, action)
10
+ def self.deliver(mailer, action)
11
11
  job = self.create!(
12
12
  args: {
13
13
  mailer: mailer.to_s,
@@ -17,9 +17,32 @@ module Postburner
17
17
  job
18
18
  end
19
19
 
20
- def process(args)
21
- cls = args['mailer'].constantize
22
- cls.send(args['action']).deliver_now
20
+ # Similar to ActionMailer #with - set the parameters
21
+ #
22
+ def with(params={})
23
+ self.args.merge!(
24
+ params: ActiveJob::Arguments.serialize(params)
25
+ )
26
+ self.save!
27
+ self
28
+ end
29
+
30
+ # Build the mail but don't send.
31
+ #
32
+ def assemble(args=nil)
33
+ _args = args || self.args
34
+
35
+ cls = _args['mailer'].constantize
36
+
37
+ mail = cls.
38
+ with(ActiveJob::Arguments.deserialize(_args['params']).to_h).
39
+ send(_args['action'])
40
+
41
+ mail
42
+ end
43
+
44
+ def perform(args)
45
+ self.assemble(args).deliver_now
23
46
  end
24
47
  end
25
48
  end
@@ -1,3 +1,3 @@
1
1
  module Postburner
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postburner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-07 00:00:00.000000000 Z
11
+ date: 2021-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -128,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
128
  - !ruby/object:Gem::Version
129
129
  version: '0'
130
130
  requirements: []
131
- rubygems_version: 3.1.4
131
+ rubygems_version: 3.1.6
132
132
  signing_key:
133
133
  specification_version: 4
134
134
  summary: Postgres backed beanstalkd queue via backburner