phobos_db_checkpoint 0.5.0 → 1.0.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
  SHA1:
3
- metadata.gz: 780d29aed6d459e239c93d0bd1982964d9ecaaf8
4
- data.tar.gz: b5e02880a25c59329ce422566e8d23acb59538c4
3
+ metadata.gz: 4b29e206cb7135f08b91717795503156869f4b7f
4
+ data.tar.gz: e5e38ea02d946d15df776e139de72943e85cbefc
5
5
  SHA512:
6
- metadata.gz: 042e4635cd5939215f3afbc3468c07a26efbe7c3998a9c5484d21595f2a12ccdf978b44aa16970f55b4e1b11bb9a90da86b755eac67f16f617571dbfed731818
7
- data.tar.gz: 7b24abd6b44be10026d371339f8b1dade902bb07dcec2ea99299661c8f5e3aa3c3c7c83b4fa215259aef5c6fb5ce1b0532e1a19446c633ff0169611e000b52c1
6
+ metadata.gz: a01c09788822ac5ecf0f11d412777b9a29274645ad46805ebb972da9e6469afec76402ea364d03bd61011ccab1d35912d4986dfa83550133eee757adc88c3125
7
+ data.tar.gz: f76968bd632cd8a0a5f98a39458d9d07af98a0cbba1c6596b9a015c7fb37e8821ac407cba2d6ec8e70b8f42f0f31c27d2095376f71a472772b4203b5bf05507c
data/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## 1.0.0 (2017-02-24)
8
+
9
+ - [feature] Introduce failures and failure handling in Handler. Add failures to the events API
10
+
7
11
  ## 0.5.0 (2016-12-28)
8
12
 
9
13
  - [feature] Add another instrumentation to wrap the entire around consume
data/README.md CHANGED
@@ -14,6 +14,7 @@ Phobos DB Checkpoint is a plugin to [Phobos](https://github.com/klarna/phobos) a
14
14
  1. [Usage](#usage)
15
15
  1. [Setup](#setup)
16
16
  1. [Handler](#handler)
17
+ 1. [Failures](#failures)
17
18
  1. [Accessing the events](#accessing-the-events)
18
19
  1. [Events API](#events-api)
19
20
  1. [Instrumentation](#instrumentation)
@@ -119,6 +120,66 @@ If your handler returns anything different than an __ack__ it won't be saved to
119
120
 
120
121
  Note that the `PhobosDBCheckpoint::Handler` will automatically skip already handled events (i.e. duplicate Kafka messages).
121
122
 
123
+ #### <a name="failures"></a> Failures
124
+
125
+ If your handler fails during the process of consuming the event, the event will be processed again acknowledged or skipped. The default behavior of `Phobos` is to back off but keep retrying the same event forever, in order to guarantee messages are processed in the correct order. However, this blocking process could go on indefinitely, so in order to help you deal with this PhobosDBCheckpoint can (on an opt-in basis) mark them as permanently failed after a configurable number of attempts.
126
+
127
+ This configuration is set in Phobos configuration:
128
+
129
+ ```yml
130
+ db_checkpoint:
131
+ max_retries: 3
132
+ ```
133
+
134
+ The retry decision is driven by inspecting the retry counter in the Phobos metadata, and if not meeting the retry criteria it will result in creating a `Failure` record and then skipping the event. You can easily retry these events later by simply invoking `retry!` on them.
135
+
136
+ Optionally, by overriding the `retry_consume?` method you can take control over the conditions that apply for retrying consumption. Whenever these are not met, a failing event will be moved out of the queue and become a Failure.
137
+
138
+ The control is based on `payload` and `exception`:
139
+
140
+ ```ruby
141
+ class MyHandler
142
+ include PhobosDBCheckpoint::Handler
143
+
144
+ def retry_consume?(event, event_metadata, exception)
145
+ event_metadata[:retry_count] <= MyApp.config.max_retries
146
+ end
147
+ end
148
+ ```
149
+
150
+ ##### Failure details
151
+
152
+ Since PhobosDBCheckpoint does not know about the internals of your payload, for setting certain fields it is necessary to yield control back to the application.
153
+ In case you need to customize your failures, these are the methods you should implement in your handler:
154
+
155
+ ```ruby
156
+ class MyHandler
157
+ include PhobosDBCheckpoint::Handler
158
+ def entity_id(payload)
159
+ # Extract event id...
160
+ payload['my_payload']['my_event_id']
161
+ end
162
+
163
+ def entity_time(payload)
164
+ # Extract event time...
165
+ payload['my_payload']['my_event_time']
166
+ end
167
+
168
+ def event_type(payload)
169
+ # Extract event type...
170
+ payload['my_payload']['my_event_type']
171
+ end
172
+
173
+ def event_version(payload)
174
+ # Extract event version...
175
+ payload['my_payload']['my_event_version']
176
+ end
177
+ end
178
+ end
179
+ ```
180
+
181
+ This is completely optional, and if a method is not implemented, the corresponding value will simply be set to null.
182
+
122
183
  ### <a name="accessing-the-events">Accessing the events</a>
123
184
 
124
185
  `PhobosDBCheckpoint::Event` is a plain `ActiveRecord::Base` model, feel free to play with it.
@@ -3,10 +3,14 @@ require 'digest'
3
3
  require 'active_record'
4
4
  require 'phobos'
5
5
 
6
+ require 'phobos_db_checkpoint/event_helper'
6
7
  require 'phobos_db_checkpoint/version'
8
+ require 'phobos_db_checkpoint/errors'
7
9
  require 'phobos_db_checkpoint/event_actions'
8
10
  require 'phobos_db_checkpoint/event'
11
+ require 'phobos_db_checkpoint/failure'
9
12
  require 'phobos_db_checkpoint/handler'
13
+ require 'phobos_db_checkpoint/actions/retry_failure'
10
14
 
11
15
  module PhobosDBCheckpoint
12
16
  DEFAULT_DB_DIR = 'db'.freeze
@@ -0,0 +1,29 @@
1
+ module PhobosDBCheckpoint
2
+ class RetryFailure
3
+ def initialize(failure)
4
+ @failure = failure
5
+ end
6
+
7
+ def perform
8
+ result = retry_failure!
9
+ @failure.destroy
10
+ result
11
+ end
12
+
13
+ private
14
+
15
+ def retry_failure!
16
+ handler
17
+ .consume(
18
+ @failure.payload,
19
+ @failure.metadata.merge(retry_count: 0)
20
+ )
21
+ end
22
+
23
+ def handler
24
+ @failure
25
+ .configured_handler
26
+ .new
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ module PhobosDBCheckpoint
2
+ class PhobosDBCheckpointError < StandardError; end
3
+ class ListenerNotFoundError < PhobosDBCheckpointError
4
+ def initialize(group_id)
5
+ super("Phobos Listener not found for group id '#{group_id}'")
6
+ end
7
+ end
8
+ end
@@ -1,5 +1,6 @@
1
1
  module PhobosDBCheckpoint
2
2
  class Event < ActiveRecord::Base
3
+ include PhobosDBCheckpoint::EventHelper
3
4
  after_initialize :assign_checksum
4
5
 
5
6
  def exists?
@@ -14,14 +15,6 @@ module PhobosDBCheckpoint
14
15
  save!
15
16
  end
16
17
 
17
- def configured_handler
18
- Phobos
19
- .config
20
- .listeners
21
- .find { |listener| listener.group_id == self.group_id }
22
- &.handler
23
- end
24
-
25
18
  private
26
19
 
27
20
  def assign_checksum
@@ -0,0 +1,31 @@
1
+ module PhobosDBCheckpoint
2
+ module EventHelper
3
+ def configured_listener
4
+ listener = Phobos
5
+ .config
6
+ .listeners
7
+ .find { |l| l.group_id == self.group_id }
8
+
9
+ raise(ListenerNotFoundError, self.group_id) unless listener
10
+
11
+ listener
12
+ end
13
+
14
+ def configured_handler
15
+ configured_listener
16
+ .handler
17
+ .constantize
18
+ end
19
+
20
+ def method_missing(m, *args, &block)
21
+ rex = m.to_s.match /^fetch_(.+)/
22
+
23
+ if rex
24
+ handler = configured_handler.new
25
+ return handler.send(rex[1], payload) if handler.respond_to?(rex[1])
26
+ else
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -41,16 +41,23 @@ module PhobosDBCheckpoint
41
41
  post "/#{VERSION}/events/:id/retry" do
42
42
  content_type :json
43
43
  event = PhobosDBCheckpoint::Event.find(params['id'])
44
- handler_name = event.configured_handler
44
+ metadata = {
45
+ listener_id: 'events_api/retry',
46
+ group_id: event.group_id,
47
+ topic: event.topic,
48
+ retry_count: 0
49
+ }
45
50
 
46
- unless handler_name
47
- status 422
48
- return { error: true, message: 'no handler configured for this event' }.to_json
49
- end
50
-
51
- handler_class = handler_name.constantize
52
- metadata = { listener_id: 'events_api/retry', group_id: event.group_id, topic: event.topic, retry_count: 0 }
53
- event_action = handler_class.new.consume(event.payload, metadata)
51
+ event_action =
52
+ begin
53
+ event
54
+ .configured_handler
55
+ .new
56
+ .consume(event.payload, metadata)
57
+ rescue ListenerNotFoundError => e
58
+ status 422
59
+ return { error: true, message: e.message }.to_json
60
+ end
54
61
 
55
62
  { acknowledged: event_action.is_a?(PhobosDBCheckpoint::Ack) }.to_json
56
63
  end
@@ -72,5 +79,41 @@ module PhobosDBCheckpoint
72
79
  .offset(offset)
73
80
  .to_json
74
81
  end
82
+
83
+ get "/#{VERSION}/failures" do
84
+ content_type :json
85
+
86
+ limit = (params['limit'] || 20).to_i
87
+ offset = (params['offset'] || 0).to_i
88
+
89
+ query = PhobosDBCheckpoint::Failure
90
+ query = query.where(topic: params['topic']) if params['topic']
91
+ query = query.where(group_id: params['group_id']) if params['group_id']
92
+ query = query.where(entity_id: params['entity_id']) if params['entity_id']
93
+ query = query.where(event_type: params['event_type']) if params['event_type']
94
+
95
+ query
96
+ .order(event_time: :desc)
97
+ .limit(limit)
98
+ .offset(offset)
99
+ .to_json
100
+ end
101
+
102
+ post "/#{VERSION}/failures/:id/retry" do
103
+ content_type :json
104
+ failure = PhobosDBCheckpoint::Failure.find(params['id'])
105
+
106
+ failure_action =
107
+ begin
108
+ PhobosDBCheckpoint::RetryFailure
109
+ .new(failure)
110
+ .perform
111
+ rescue => e
112
+ status 422
113
+ return { error: true, message: e.message }.to_json
114
+ end
115
+
116
+ { acknowledged: failure_action.is_a?(PhobosDBCheckpoint::Ack) }.to_json
117
+ end
75
118
  end
76
119
  end
@@ -0,0 +1,40 @@
1
+ module PhobosDBCheckpoint
2
+ class Failure < ActiveRecord::Base
3
+ include PhobosDBCheckpoint::EventHelper
4
+
5
+ def self.record(event:, event_metadata:, exception: nil)
6
+ return if exists?(event_metadata[:checksum])
7
+
8
+ create do |record|
9
+ record.topic = event_metadata[:topic]
10
+ record.group_id = event_metadata[:group_id]
11
+ record.entity_id = event.fetch_entity_id
12
+ record.event_time = event.fetch_event_time
13
+ record.event_type = event.fetch_event_type
14
+ record.event_version = event.fetch_event_version
15
+ record.checksum = event_metadata[:checksum]
16
+ record.payload = event.payload
17
+ record.metadata = event_metadata
18
+ record.error_class = exception&.class&.name
19
+ record.error_message = exception&.message
20
+ record.error_backtrace = exception&.backtrace
21
+ end
22
+ end
23
+
24
+ def self.exists?(checksum)
25
+ where(checksum: checksum).exists?
26
+ end
27
+
28
+ def payload
29
+ attributes['payload'].deep_symbolize_keys
30
+ end
31
+
32
+ def metadata
33
+ attributes['metadata'].deep_symbolize_keys
34
+ end
35
+
36
+ def group_id
37
+ attributes['group_id'] || metadata[:group_id]
38
+ end
39
+ end
40
+ end
@@ -14,6 +14,11 @@ module PhobosDBCheckpoint
14
14
  include Phobos::Instrumentation
15
15
  include Phobos::Handler::ClassMethods
16
16
 
17
+ def retry_consume?(event, event_metadata, exception)
18
+ return true unless Phobos.config&.db_checkpoint&.max_retries
19
+ event_metadata[:retry_count] < Phobos.config&.db_checkpoint&.max_retries
20
+ end
21
+
17
22
  def around_consume(payload, metadata)
18
23
  event = PhobosDBCheckpoint::Event.new(
19
24
  topic: metadata[:topic],
@@ -31,7 +36,15 @@ module PhobosDBCheckpoint
31
36
  end
32
37
 
33
38
  event_action = instrument('db_checkpoint.event_action', event_metadata) do
34
- yield
39
+ begin
40
+ yield
41
+ rescue => e
42
+ if retry_consume?(event, event_metadata, e)
43
+ raise e
44
+ else
45
+ Failure.record(event: event, event_metadata: event_metadata, exception: e)
46
+ end
47
+ end
35
48
  end
36
49
 
37
50
  case event_action
@@ -1,3 +1,3 @@
1
1
  module PhobosDBCheckpoint
2
- VERSION = '0.5.0'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -0,0 +1,23 @@
1
+ class Phobos02CreateFailures < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def up
3
+ create_table :failures do |t|
4
+ t.timestamp :created_at, index: false
5
+ t.string :topic, index: true
6
+ t.string :group_id, index: true
7
+ t.string :entity_id, index: true
8
+ t.timestamp :event_time, index: true
9
+ t.string :event_type, index: true
10
+ t.string :event_version, index: true
11
+ t.string :checksum, index: true
12
+ t.json :payload, index: false
13
+ t.json :metadata, index: false
14
+ t.string :error_class, index: false
15
+ t.string :error_message, index: false
16
+ t.json :error_backtrace
17
+ end
18
+ end
19
+
20
+ def down
21
+ drop_table :failures
22
+ end
23
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phobos_db_checkpoint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Túlio Ornelas
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2016-12-28 00:00:00.000000000 Z
16
+ date: 2017-02-24 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: bundler
@@ -251,10 +251,14 @@ files:
251
251
  - bin/setup
252
252
  - circle.yml
253
253
  - lib/phobos_db_checkpoint.rb
254
+ - lib/phobos_db_checkpoint/actions/retry_failure.rb
254
255
  - lib/phobos_db_checkpoint/cli.rb
256
+ - lib/phobos_db_checkpoint/errors.rb
255
257
  - lib/phobos_db_checkpoint/event.rb
256
258
  - lib/phobos_db_checkpoint/event_actions.rb
259
+ - lib/phobos_db_checkpoint/event_helper.rb
257
260
  - lib/phobos_db_checkpoint/events_api.rb
261
+ - lib/phobos_db_checkpoint/failure.rb
258
262
  - lib/phobos_db_checkpoint/handler.rb
259
263
  - lib/phobos_db_checkpoint/middleware/database.rb
260
264
  - lib/phobos_db_checkpoint/middleware/logger.rb
@@ -264,6 +268,7 @@ files:
264
268
  - templates/config.ru
265
269
  - templates/database.yml.example
266
270
  - templates/migrate/phobos_01_create_events.rb.erb
271
+ - templates/migrate/phobos_02_create_failures.rb.erb
267
272
  - templates/new_migration.rb.erb
268
273
  - templates/phobos_boot.rb
269
274
  homepage: https://github.com/klarna/phobos_db_checkpoint
@@ -287,7 +292,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
287
292
  version: '0'
288
293
  requirements: []
289
294
  rubyforge_project:
290
- rubygems_version: 2.5.1
295
+ rubygems_version: 2.6.8
291
296
  signing_key:
292
297
  specification_version: 4
293
298
  summary: Phobos DB Checkpoint is a plugin to Phobos and is meant as a drop in replacement