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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +61 -0
- data/lib/phobos_db_checkpoint.rb +4 -0
- data/lib/phobos_db_checkpoint/actions/retry_failure.rb +29 -0
- data/lib/phobos_db_checkpoint/errors.rb +8 -0
- data/lib/phobos_db_checkpoint/event.rb +1 -8
- data/lib/phobos_db_checkpoint/event_helper.rb +31 -0
- data/lib/phobos_db_checkpoint/events_api.rb +52 -9
- data/lib/phobos_db_checkpoint/failure.rb +40 -0
- data/lib/phobos_db_checkpoint/handler.rb +14 -1
- data/lib/phobos_db_checkpoint/version.rb +1 -1
- data/templates/migrate/phobos_02_create_failures.rb.erb +23 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b29e206cb7135f08b91717795503156869f4b7f
|
4
|
+
data.tar.gz: e5e38ea02d946d15df776e139de72943e85cbefc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
data/lib/phobos_db_checkpoint.rb
CHANGED
@@ -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
|
@@ -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
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
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
|
@@ -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.
|
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:
|
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.
|
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
|