deimos-ruby 1.8.0.pre.beta2 → 1.8.1.pre.beta1

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: 5c184b8f182ecfa304684f4e8e522e93c43c12fe50b34300326c2e1ef206b6d1
4
- data.tar.gz: 5861974e036c92470a4a801bed477e5cebefa679cc9996a5d6f0258eaa484150
3
+ metadata.gz: 335328a0ca5fb9147405485a66944ebe0d05af552ec09ec9605d76a7e7da7e9a
4
+ data.tar.gz: 438a9a91bda52be144d63867a4ae42dd1901cc7472f61232044aa934efebb464
5
5
  SHA512:
6
- metadata.gz: f19a799d333485b6461e2d8cdeaec5643ec6ddf8f6a2ce1725f3c39269cc193cc5b1bc1e81266b6a33af87ec5d09b3ac2f857b48da61c3baded00a98d6935624
7
- data.tar.gz: a08f6519a635e76944b3b7c2f08d5369b6f327e60e968391fcd5e09e703e8ed526d35d8e124f91cbff8c885fbf8d620f90e0a99b48e4289868a0c11925b43d51
6
+ metadata.gz: 3e01c5c9db93cb6e0f56dafa2f1b301c30b9cc11c397ca35f77b80cfe62bd9600eacb8774516ee1c81337193687944a8c7bec3cba400df6ea8b1c33af7e82c27
7
+ data.tar.gz: 9de8cea35db671c43123d096653a6423ccfcaad8a2b8e9478f787e879bd7fd50a626604b8626f79b5f8654c84130fded58385d6f75fe44e59e1bfb5a284e1096
@@ -5,6 +5,7 @@ AllCops:
5
5
  Exclude:
6
6
  - lib/deimos/monkey_patches/*.rb
7
7
  - vendor/**/*
8
+ - Guardfile
8
9
  NewCops: enable
9
10
 
10
11
  # class Plumbus
@@ -36,7 +37,7 @@ Layout/EmptyLinesAroundBlockBody:
36
37
  Enabled: false
37
38
 
38
39
  Layout/LineLength:
39
- Max: 100
40
+ Max: 120
40
41
  Severity: refactor
41
42
  Exclude:
42
43
  - 'spec/**/*'
@@ -77,13 +78,14 @@ Lint/UnusedMethodArgument:
77
78
 
78
79
  Metrics/AbcSize:
79
80
  Severity: refactor
80
- Max: 20
81
+ Max: 25
81
82
 
82
83
  Metrics/BlockLength:
83
- Severity: refactor
84
+ Enabled: false
84
85
 
85
86
  Metrics/ClassLength:
86
87
  Severity: refactor
88
+ Max: 500
87
89
 
88
90
  Metrics/CyclomaticComplexity:
89
91
  Severity: refactor
@@ -91,10 +93,11 @@ Metrics/CyclomaticComplexity:
91
93
 
92
94
  Metrics/MethodLength:
93
95
  Severity: refactor
94
- Max: 30
96
+ Max: 50
95
97
 
96
98
  Metrics/ModuleLength:
97
99
  Severity: refactor
100
+ Max: 200
98
101
 
99
102
  Metrics/ParameterLists:
100
103
  Max: 5
@@ -102,6 +105,7 @@ Metrics/ParameterLists:
102
105
 
103
106
  Metrics/PerceivedComplexity:
104
107
  Severity: refactor
108
+ Max: 10
105
109
 
106
110
  # Use alias_method instead of alias
107
111
  Style/Alias:
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## UNRELEASED
9
9
 
10
+ ## 1.8.1-beta1 - 2020-07-22
11
+
12
+ ### Fixes :wrench:
13
+ - Retry deleting messages without resending the batch if the
14
+ delete fails (fixes [#34](https://github.com/flipp-oss/deimos/issues/34))
15
+ - Delete messages in batches rather than all at once to
16
+ cut down on the chance of a deadlock.
17
+
18
+ ### Features :star:
19
+ - Add `last_processed_at` to `kafka_topic_info` to ensure
20
+ that wait metrics are accurate in cases where records
21
+ get created with an old `created_at` time (e.g. for
22
+ long-running transactions).
23
+ - Add generator for ActiveRecord models and migrations (fixes [#6](https://github.com/flipp-oss/deimos/issues/6))
24
+
10
25
  ## 1.8.0-beta2 - 2020-07-08
11
26
 
12
27
  ### Fixes :wrench:
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- deimos-ruby (1.8.0.pre.beta1)
4
+ deimos-ruby (1.8.1.pre.beta1)
5
5
  avro_turf (~> 0.11)
6
6
  phobos (~> 1.9)
7
7
  ruby-kafka (~> 0.7)
@@ -10,72 +10,86 @@ PATH
10
10
  GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
- actioncable (5.2.4.2)
14
- actionpack (= 5.2.4.2)
13
+ actioncable (6.0.3.2)
14
+ actionpack (= 6.0.3.2)
15
15
  nio4r (~> 2.0)
16
16
  websocket-driver (>= 0.6.1)
17
- actionmailer (5.2.4.2)
18
- actionpack (= 5.2.4.2)
19
- actionview (= 5.2.4.2)
20
- activejob (= 5.2.4.2)
17
+ actionmailbox (6.0.3.2)
18
+ actionpack (= 6.0.3.2)
19
+ activejob (= 6.0.3.2)
20
+ activerecord (= 6.0.3.2)
21
+ activestorage (= 6.0.3.2)
22
+ activesupport (= 6.0.3.2)
23
+ mail (>= 2.7.1)
24
+ actionmailer (6.0.3.2)
25
+ actionpack (= 6.0.3.2)
26
+ actionview (= 6.0.3.2)
27
+ activejob (= 6.0.3.2)
21
28
  mail (~> 2.5, >= 2.5.4)
22
29
  rails-dom-testing (~> 2.0)
23
- actionpack (5.2.4.2)
24
- actionview (= 5.2.4.2)
25
- activesupport (= 5.2.4.2)
30
+ actionpack (6.0.3.2)
31
+ actionview (= 6.0.3.2)
32
+ activesupport (= 6.0.3.2)
26
33
  rack (~> 2.0, >= 2.0.8)
27
34
  rack-test (>= 0.6.3)
28
35
  rails-dom-testing (~> 2.0)
29
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
30
- actionview (5.2.4.2)
31
- activesupport (= 5.2.4.2)
36
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
37
+ actiontext (6.0.3.2)
38
+ actionpack (= 6.0.3.2)
39
+ activerecord (= 6.0.3.2)
40
+ activestorage (= 6.0.3.2)
41
+ activesupport (= 6.0.3.2)
42
+ nokogiri (>= 1.8.5)
43
+ actionview (6.0.3.2)
44
+ activesupport (= 6.0.3.2)
32
45
  builder (~> 3.1)
33
46
  erubi (~> 1.4)
34
47
  rails-dom-testing (~> 2.0)
35
- rails-html-sanitizer (~> 1.0, >= 1.0.3)
36
- activejob (5.2.4.2)
37
- activesupport (= 5.2.4.2)
48
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
49
+ activejob (6.0.3.2)
50
+ activesupport (= 6.0.3.2)
38
51
  globalid (>= 0.3.6)
39
- activemodel (5.2.4.2)
40
- activesupport (= 5.2.4.2)
41
- activerecord (5.2.4.2)
42
- activemodel (= 5.2.4.2)
43
- activesupport (= 5.2.4.2)
44
- arel (>= 9.0)
45
- activerecord-import (1.0.4)
52
+ activemodel (6.0.3.2)
53
+ activesupport (= 6.0.3.2)
54
+ activerecord (6.0.3.2)
55
+ activemodel (= 6.0.3.2)
56
+ activesupport (= 6.0.3.2)
57
+ activerecord-import (1.0.5)
46
58
  activerecord (>= 3.2)
47
- activestorage (5.2.4.2)
48
- actionpack (= 5.2.4.2)
49
- activerecord (= 5.2.4.2)
59
+ activestorage (6.0.3.2)
60
+ actionpack (= 6.0.3.2)
61
+ activejob (= 6.0.3.2)
62
+ activerecord (= 6.0.3.2)
50
63
  marcel (~> 0.3.1)
51
- activesupport (5.2.4.2)
64
+ activesupport (6.0.3.2)
52
65
  concurrent-ruby (~> 1.0, >= 1.0.2)
53
66
  i18n (>= 0.7, < 2)
54
67
  minitest (~> 5.1)
55
68
  tzinfo (~> 1.1)
56
- arel (9.0.0)
57
- ast (2.4.0)
69
+ zeitwerk (~> 2.2, >= 2.2.2)
70
+ ast (2.4.1)
58
71
  avro (1.9.2)
59
72
  multi_json
60
73
  avro_turf (0.11.0)
61
74
  avro (>= 1.7.7, < 1.10)
62
75
  excon (~> 0.45)
63
76
  builder (3.2.4)
64
- coderay (1.1.2)
77
+ coderay (1.1.3)
65
78
  concurrent-ruby (1.1.6)
66
79
  concurrent-ruby-ext (1.1.6)
67
80
  concurrent-ruby (= 1.1.6)
68
81
  crass (1.0.6)
69
82
  database_cleaner (1.8.5)
70
- ddtrace (0.35.1)
83
+ ddtrace (0.37.0)
71
84
  msgpack
72
- diff-lcs (1.3)
73
- digest-crc (0.5.1)
74
- dogstatsd-ruby (4.8.0)
85
+ diff-lcs (1.4.4)
86
+ digest-crc (0.6.1)
87
+ rake (~> 13.0)
88
+ dogstatsd-ruby (4.8.1)
75
89
  erubi (1.9.0)
76
- excon (0.73.0)
90
+ excon (0.75.0)
77
91
  exponential-backoff (0.0.4)
78
- ffi (1.12.2)
92
+ ffi (1.13.1)
79
93
  formatador (0.2.5)
80
94
  globalid (0.4.2)
81
95
  activesupport (>= 4.2.0)
@@ -96,20 +110,19 @@ GEM
96
110
  guard-rubocop (1.3.0)
97
111
  guard (~> 2.0)
98
112
  rubocop (~> 0.20)
99
- i18n (1.8.2)
113
+ i18n (1.8.4)
100
114
  concurrent-ruby (~> 1.0)
101
- jaro_winkler (1.5.4)
102
115
  listen (3.2.1)
103
116
  rb-fsevent (~> 0.10, >= 0.10.3)
104
117
  rb-inotify (~> 0.9, >= 0.9.10)
105
118
  little-plugger (1.1.4)
106
- logging (2.2.2)
119
+ logging (2.3.0)
107
120
  little-plugger (~> 1.1)
108
- multi_json (~> 1.10)
109
- loofah (2.5.0)
121
+ multi_json (~> 1.14)
122
+ loofah (2.6.0)
110
123
  crass (~> 1.0.2)
111
124
  nokogiri (>= 1.5.9)
112
- lumberjack (1.2.4)
125
+ lumberjack (1.2.6)
113
126
  mail (2.7.1)
114
127
  mini_mime (>= 0.1.1)
115
128
  marcel (0.3.3)
@@ -118,20 +131,20 @@ GEM
118
131
  mimemagic (0.3.5)
119
132
  mini_mime (1.0.2)
120
133
  mini_portile2 (2.4.0)
121
- minitest (5.14.0)
134
+ minitest (5.14.1)
122
135
  msgpack (1.3.3)
123
- multi_json (1.14.1)
136
+ multi_json (1.15.0)
124
137
  mysql2 (0.5.3)
125
138
  nenv (0.3.0)
126
139
  nio4r (2.5.2)
127
- nokogiri (1.10.9)
140
+ nokogiri (1.10.10)
128
141
  mini_portile2 (~> 2.4.0)
129
142
  notiffany (0.1.3)
130
143
  nenv (~> 0.1)
131
144
  shellany (~> 0.0)
132
- parallel (1.19.1)
133
- parser (2.7.1.2)
134
- ast (~> 2.4.0)
145
+ parallel (1.19.2)
146
+ parser (2.7.1.4)
147
+ ast (~> 2.4.1)
135
148
  pg (1.2.3)
136
149
  phobos (1.9.0)
137
150
  activesupport (>= 3.0.0)
@@ -147,35 +160,38 @@ GEM
147
160
  rack (2.2.3)
148
161
  rack-test (1.1.0)
149
162
  rack (>= 1.0, < 3)
150
- rails (5.2.4.2)
151
- actioncable (= 5.2.4.2)
152
- actionmailer (= 5.2.4.2)
153
- actionpack (= 5.2.4.2)
154
- actionview (= 5.2.4.2)
155
- activejob (= 5.2.4.2)
156
- activemodel (= 5.2.4.2)
157
- activerecord (= 5.2.4.2)
158
- activestorage (= 5.2.4.2)
159
- activesupport (= 5.2.4.2)
163
+ rails (6.0.3.2)
164
+ actioncable (= 6.0.3.2)
165
+ actionmailbox (= 6.0.3.2)
166
+ actionmailer (= 6.0.3.2)
167
+ actionpack (= 6.0.3.2)
168
+ actiontext (= 6.0.3.2)
169
+ actionview (= 6.0.3.2)
170
+ activejob (= 6.0.3.2)
171
+ activemodel (= 6.0.3.2)
172
+ activerecord (= 6.0.3.2)
173
+ activestorage (= 6.0.3.2)
174
+ activesupport (= 6.0.3.2)
160
175
  bundler (>= 1.3.0)
161
- railties (= 5.2.4.2)
176
+ railties (= 6.0.3.2)
162
177
  sprockets-rails (>= 2.0.0)
163
178
  rails-dom-testing (2.0.3)
164
179
  activesupport (>= 4.2.0)
165
180
  nokogiri (>= 1.6)
166
181
  rails-html-sanitizer (1.3.0)
167
182
  loofah (~> 2.3)
168
- railties (5.2.4.2)
169
- actionpack (= 5.2.4.2)
170
- activesupport (= 5.2.4.2)
183
+ railties (6.0.3.2)
184
+ actionpack (= 6.0.3.2)
185
+ activesupport (= 6.0.3.2)
171
186
  method_source
172
187
  rake (>= 0.8.7)
173
- thor (>= 0.19.0, < 2.0)
188
+ thor (>= 0.20.3, < 2.0)
174
189
  rainbow (3.0.0)
175
190
  rake (13.0.1)
176
191
  rb-fsevent (0.10.4)
177
192
  rb-inotify (0.10.1)
178
193
  ffi (~> 1.0)
194
+ regexp_parser (1.7.1)
179
195
  rexml (3.2.4)
180
196
  rspec (3.9.0)
181
197
  rspec-core (~> 3.9.0)
@@ -183,7 +199,7 @@ GEM
183
199
  rspec-mocks (~> 3.9.0)
184
200
  rspec-core (3.9.2)
185
201
  rspec-support (~> 3.9.3)
186
- rspec-expectations (3.9.1)
202
+ rspec-expectations (3.9.2)
187
203
  diff-lcs (>= 1.2.0, < 2.0)
188
204
  rspec-support (~> 3.9.0)
189
205
  rspec-mocks (3.9.1)
@@ -192,16 +208,19 @@ GEM
192
208
  rspec-support (3.9.3)
193
209
  rspec_junit_formatter (0.4.1)
194
210
  rspec-core (>= 2, < 4, != 2.12.0)
195
- rubocop (0.82.0)
196
- jaro_winkler (~> 1.5.1)
211
+ rubocop (0.88.0)
197
212
  parallel (~> 1.10)
198
- parser (>= 2.7.0.1)
213
+ parser (>= 2.7.1.1)
199
214
  rainbow (>= 2.2.2, < 4.0)
215
+ regexp_parser (>= 1.7)
200
216
  rexml
217
+ rubocop-ast (>= 0.1.0, < 1.0)
201
218
  ruby-progressbar (~> 1.7)
202
219
  unicode-display_width (>= 1.4.0, < 2.0)
203
- rubocop-rspec (1.39.0)
204
- rubocop (>= 0.68.1)
220
+ rubocop-ast (0.2.0)
221
+ parser (>= 2.7.0.1)
222
+ rubocop-rspec (1.42.0)
223
+ rubocop (>= 0.87.0)
205
224
  ruby-kafka (0.7.10)
206
225
  digest-crc
207
226
  ruby-progressbar (1.10.1)
@@ -209,7 +228,7 @@ GEM
209
228
  sigurd (0.0.1)
210
229
  concurrent-ruby (~> 1)
211
230
  exponential-backoff
212
- sprockets (4.0.0)
231
+ sprockets (4.0.2)
213
232
  concurrent-ruby (~> 1.0)
214
233
  rack (> 1, < 3)
215
234
  sprockets-rails (3.2.1)
@@ -222,15 +241,16 @@ GEM
222
241
  tzinfo (1.2.7)
223
242
  thread_safe (~> 0.1)
224
243
  unicode-display_width (1.7.0)
225
- websocket-driver (0.7.1)
244
+ websocket-driver (0.7.3)
226
245
  websocket-extensions (>= 0.1.0)
227
246
  websocket-extensions (0.1.5)
247
+ zeitwerk (2.4.0)
228
248
 
229
249
  PLATFORMS
230
250
  ruby
231
251
 
232
252
  DEPENDENCIES
233
- activerecord (~> 5.2)
253
+ activerecord (~> 6)
234
254
  activerecord-import
235
255
  avro (~> 1.9)
236
256
  database_cleaner (~> 1.7)
@@ -242,7 +262,7 @@ DEPENDENCIES
242
262
  guard-rubocop (~> 1)
243
263
  mysql2 (~> 0.5)
244
264
  pg (~> 1.1)
245
- rails (~> 5.2, >= 5.2.4.2)
265
+ rails (~> 6)
246
266
  rake (~> 13)
247
267
  rspec (~> 3)
248
268
  rspec_junit_formatter (~> 0.3)
data/README.md CHANGED
@@ -557,6 +557,29 @@ class MyConsumer < Deimos::ActiveRecordConsumer
557
557
  end
558
558
  ```
559
559
 
560
+ #### Generating Tables and Models
561
+
562
+ Deimos provides a generator that takes an existing schema and generates a
563
+ database table based on its fields. By default, any complex sub-types (such as
564
+ records or arrays) are turned into JSON (if supported) or string columns.
565
+
566
+ Before running this migration, you must first copy the schema into your repo
567
+ in the correct path (in the example above, you would need to have a file
568
+ `{SCHEMA_ROOT}/com/my-namespace/MySchema.avsc`).
569
+
570
+ To generate a model and migration, run the following:
571
+
572
+ rails g deimos:active_record TABLE_NAME FULL_SCHEMA_NAME
573
+
574
+ Example:
575
+
576
+ rails g deimos:active_record my_table com.my-namespace.MySchema
577
+
578
+ ...would generate:
579
+
580
+ db/migrate/1234_create_my_table.rb
581
+ app/models/my_table.rb
582
+
560
583
  #### Batch Consumers
561
584
 
562
585
  Deimos also provides a batch consumption mode for `ActiveRecordConsumer` which
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.add_runtime_dependency('ruby-kafka', '~> 0.7')
24
24
  spec.add_runtime_dependency('sigurd', '0.0.1')
25
25
 
26
- spec.add_development_dependency('activerecord', '~> 5.2')
26
+ spec.add_development_dependency('activerecord', '~> 6')
27
27
  spec.add_development_dependency('activerecord-import')
28
28
  spec.add_development_dependency('avro', '~> 1.9')
29
29
  spec.add_development_dependency('database_cleaner', '~> 1.7')
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.add_development_dependency('guard-rubocop', '~> 1')
35
35
  spec.add_development_dependency('mysql2', '~> 0.5')
36
36
  spec.add_development_dependency('pg', '~> 1.1')
37
- spec.add_development_dependency('rails', '~> 5.2', '>= 5.2.4.2')
37
+ spec.add_development_dependency('rails', '~> 6')
38
38
  spec.add_development_dependency('rake', '~> 13')
39
39
  spec.add_development_dependency('rspec', '~> 3')
40
40
  spec.add_development_dependency('rspec_junit_formatter', '~>0.3')
@@ -28,9 +28,9 @@ if defined?(ActiveRecord)
28
28
  require 'deimos/kafka_source'
29
29
  require 'deimos/kafka_topic_info'
30
30
  require 'deimos/backends/db'
31
- require 'sigurd/signal_handler.rb'
32
- require 'sigurd/executor.rb'
33
- require 'deimos/utils/db_producer.rb'
31
+ require 'sigurd/signal_handler'
32
+ require 'sigurd/executor'
33
+ require 'deimos/utils/db_producer'
34
34
  require 'deimos/utils/db_poller'
35
35
  end
36
36
 
@@ -13,7 +13,7 @@ module Deimos
13
13
  def lock(topic, lock_id)
14
14
  # Try to create it - it's fine if it already exists
15
15
  begin
16
- self.create(topic: topic)
16
+ self.create(topic: topic, last_processed_at: Time.zone.now)
17
17
  rescue ActiveRecord::RecordNotUnique
18
18
  # continue on
19
19
  end
@@ -52,7 +52,26 @@ module Deimos
52
52
  # @param lock_id [String]
53
53
  def clear_lock(topic, lock_id)
54
54
  self.where(topic: topic, locked_by: lock_id).
55
- update_all(locked_by: nil, locked_at: nil, error: false, retries: 0)
55
+ update_all(locked_by: nil,
56
+ locked_at: nil,
57
+ error: false,
58
+ retries: 0,
59
+ last_processed_at: Time.zone.now)
60
+ end
61
+
62
+ # Update all topics that aren't currently locked and have no messages
63
+ # waiting. It's OK if some messages get inserted in the middle of this
64
+ # because the point is that at least within a few milliseconds of each
65
+ # other, it wasn't locked and had no messages, meaning the topic
66
+ # was in a good state.
67
+ # @param except_topics [Array<String>] the list of topics we've just
68
+ # realized had messages in them, meaning all other topics were empty.
69
+ def ping_empty_topics(except_topics)
70
+ records = KafkaTopicInfo.where(locked_by: nil).
71
+ where('topic not in(?)', except_topics)
72
+ records.each do |info|
73
+ info.update_attribute(:last_processed_at, Time.zone.now)
74
+ end
56
75
  end
57
76
 
58
77
  # The producer calls this if it gets an error sending messages. This
@@ -33,6 +33,30 @@ module Deimos
33
33
  decode(payload, schema: @key_schema['name'])[field_name]
34
34
  end
35
35
 
36
+ # :nodoc:
37
+ def sql_type(field)
38
+ type = field.type.type
39
+ return type if %w(array map record).include?(type)
40
+
41
+ if type == :union
42
+ non_null = field.type.schemas.reject { |f| f.type == :null }
43
+ if non_null.size > 1
44
+ warn("WARNING: #{field.name} has more than one non-null type. Picking the first for the SQL type.")
45
+ end
46
+ return non_null.first.type
47
+ end
48
+ return type.to_sym if %w(float boolean).include?(type)
49
+ return :integer if type == 'int'
50
+ return :bigint if type == 'long'
51
+
52
+ if type == 'double'
53
+ warn('Avro `double` type turns into SQL `float` type. Please ensure you have the correct `limit` set.')
54
+ return :float
55
+ end
56
+
57
+ :string
58
+ end
59
+
36
60
  # @override
37
61
  def coerce_field(field, value)
38
62
  AvroSchemaCoercer.new(avro_schema).coerce_type(field.type, value)
@@ -40,7 +64,10 @@ module Deimos
40
64
 
41
65
  # @override
42
66
  def schema_fields
43
- avro_schema.fields.map { |field| SchemaField.new(field.name, field.type) }
67
+ avro_schema.fields.map do |field|
68
+ enum_values = field.type.type == 'enum' ? field.type.symbols : []
69
+ SchemaField.new(field.name, field.type, enum_values)
70
+ end
44
71
  end
45
72
 
46
73
  # @override
@@ -3,13 +3,15 @@
3
3
  module Deimos
4
4
  # Represents a field in the schema.
5
5
  class SchemaField
6
- attr_accessor :name, :type
6
+ attr_accessor :name, :type, :enum_values
7
7
 
8
8
  # @param name [String]
9
9
  # @param type [Object]
10
- def initialize(name, type)
10
+ # @param enum_values [Array<String>]
11
+ def initialize(name, type, enum_values=[])
11
12
  @name = name
12
13
  @type = type
14
+ @enum_values = enum_values
13
15
  end
14
16
  end
15
17
 
@@ -109,6 +111,17 @@ module Deimos
109
111
  raise NotImplementedError
110
112
  end
111
113
 
114
+ # Given a field definition, return the SQL type that might be used in
115
+ # ActiveRecord table creation - e.g. for Avro, a `long` type would
116
+ # return `:bigint`. There are also special values that need to be returned:
117
+ # `:array`, `:map` and `:record`, for types representing those structures.
118
+ # `:enum` is also recognized.
119
+ # @param field [SchemaField]
120
+ # @return [Symbol]
121
+ def sql_type(field)
122
+ raise NotImplementedError
123
+ end
124
+
112
125
  # Encode a message key. To be defined by subclass.
113
126
  # @param key [String|Hash] the value to use as the key.
114
127
  # @param key_id [Symbol|String] the field name of the key.
@@ -9,6 +9,8 @@ module Deimos
9
9
  attr_accessor :id, :current_topic
10
10
 
11
11
  BATCH_SIZE = 1000
12
+ DELETE_BATCH_SIZE = 10
13
+ MAX_DELETE_ATTEMPTS = 3
12
14
 
13
15
  # @param logger [Logger]
14
16
  def initialize(logger=Logger.new(STDOUT))
@@ -48,6 +50,7 @@ module Deimos
48
50
  topics = retrieve_topics
49
51
  @logger.info("Found topics: #{topics}")
50
52
  topics.each(&method(:process_topic))
53
+ KafkaTopicInfo.ping_empty_topics(topics)
51
54
  sleep(0.5)
52
55
  end
53
56
 
@@ -87,13 +90,13 @@ module Deimos
87
90
  begin
88
91
  produce_messages(compacted_messages.map(&:phobos_message))
89
92
  rescue Kafka::BufferOverflow, Kafka::MessageSizeTooLarge, Kafka::RecordListTooLarge
93
+ delete_messages(messages)
90
94
  @logger.error('Message batch too large, deleting...')
91
95
  @logger.error(Deimos::KafkaMessage.decoded(messages))
92
- Deimos::KafkaMessage.where(id: messages.map(&:id)).delete_all
93
96
  raise
94
97
  end
95
98
  end
96
- Deimos::KafkaMessage.where(id: messages.map(&:id)).delete_all
99
+ delete_messages(messages)
97
100
  Deimos.config.metrics&.increment(
98
101
  'db_producer.process',
99
102
  tags: %W(topic:#{@current_topic}),
@@ -106,6 +109,27 @@ module Deimos
106
109
  true
107
110
  end
108
111
 
112
+ # @param messages [Array<Deimos::KafkaMessage>]
113
+ def delete_messages(messages)
114
+ attempts = 1
115
+ begin
116
+ messages.in_groups_of(DELETE_BATCH_SIZE, false).each do |batch|
117
+ Deimos::KafkaMessage.where(topic: batch.first.topic,
118
+ id: batch.map(&:id)).
119
+ delete_all
120
+ end
121
+ rescue StandardError => e
122
+ if (e.message =~ /Lock wait/i || e.message =~ /Lost connection/i) &&
123
+ attempts <= MAX_DELETE_ATTEMPTS
124
+ attempts += 1
125
+ ActiveRecord::Base.connection.verify!
126
+ sleep(1)
127
+ retry
128
+ end
129
+ raise
130
+ end
131
+ end
132
+
109
133
  # @return [Array<Deimos::KafkaMessage>]
110
134
  def retrieve_messages
111
135
  KafkaMessage.where(topic: @current_topic).order(:id).limit(BATCH_SIZE)
@@ -126,19 +150,33 @@ module Deimos
126
150
  metrics = Deimos.config.metrics
127
151
  return unless metrics
128
152
 
153
+ topics = KafkaTopicInfo.select(%w(topic last_processed_at))
129
154
  messages = Deimos::KafkaMessage.
130
155
  select('count(*) as num_messages, min(created_at) as earliest, topic').
131
- group(:topic)
132
- if messages.none?
133
- metrics.gauge('pending_db_messages_max_wait', 0)
134
- end
135
- messages.each do |record|
136
- earliest = record.earliest
137
- # SQLite gives a string here
138
- earliest = Time.zone.parse(earliest) if earliest.is_a?(String)
139
-
140
- time_diff = Time.zone.now - earliest
141
- metrics.gauge('pending_db_messages_max_wait', time_diff,
156
+ group(:topic).
157
+ index_by(&:topic)
158
+ topics.each do |record|
159
+ message_record = messages[record.topic]
160
+ # We want to record the last time we saw any activity, meaning either
161
+ # the oldest message, or the last time we processed, whichever comes
162
+ # last.
163
+ if message_record
164
+ record_earliest = record.earliest
165
+ # SQLite gives a string here
166
+ if record_earliest.is_a?(String)
167
+ record_earliest = Time.zone.parse(record_earliest)
168
+ end
169
+
170
+ earliest = [record.last_processed_at, record_earliest].max
171
+ time_diff = Time.zone.now - earliest
172
+ metrics.gauge('pending_db_messages_max_wait', time_diff,
173
+ tags: ["topic:#{record.topic}"])
174
+ else
175
+ # no messages waiting
176
+ metrics.gauge('pending_db_messages_max_wait', 0,
177
+ tags: ["topic:#{record.topic}"])
178
+ end
179
+ metrics.gauge('pending_db_messages_count', message_record&.num_messages || 0,
142
180
  tags: ["topic:#{record.topic}"])
143
181
  end
144
182
  end
@@ -174,11 +212,11 @@ module Deimos
174
212
  end
175
213
 
176
214
  @logger.error("Got error #{e.class.name} when publishing #{batch.size} in groups of #{batch_size}, retrying...")
177
- if batch_size < 10
178
- batch_size = 1
179
- else
180
- batch_size /= 10
181
- end
215
+ batch_size = if batch_size < 10
216
+ 1
217
+ else
218
+ (batch_size / 10).to_i
219
+ end
182
220
  shutdown_producer
183
221
  retry
184
222
  end
@@ -187,7 +225,7 @@ module Deimos
187
225
  # @param batch [Array<Deimos::KafkaMessage>]
188
226
  # @return [Array<Deimos::KafkaMessage>]
189
227
  def compact_messages(batch)
190
- return batch unless batch.first&.key.present?
228
+ return batch if batch.first&.key.blank?
191
229
 
192
230
  topic = batch.first.topic
193
231
  return batch if config.compact_topics != :all &&
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Deimos
4
- VERSION = '1.8.0-beta2'
4
+ VERSION = '1.8.1-beta1'
5
5
  end
@@ -0,0 +1,28 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
+ def up
3
+ if table_exists?(:<%= table_name %>)
4
+ warn "<%= table_name %> already exists, exiting"
5
+ return
6
+ end
7
+ create_table :<%= table_name %> do |t|
8
+ <%- fields.each do |key| -%>
9
+ <%- next if %w(id message_id timestamp).include?(key.name) -%>
10
+ <%- sql_type = schema_base.sql_type(key)
11
+ if %w(record array map).include?(sql_type)
12
+ conn = ActiveRecord::Base.connection
13
+ sql_type = conn.respond_to?(:supports_json?) && conn.supports_json? ? :json : :string
14
+ end
15
+ -%>
16
+ t.<%= sql_type %> :<%= key.name %>
17
+ <%- end -%>
18
+ end
19
+
20
+ # TODO add indexes as necessary
21
+ end
22
+
23
+ def down
24
+ return unless table_exists?(:<%= table_name %>)
25
+ drop_table :<%= table_name %>
26
+ end
27
+
28
+ end
@@ -0,0 +1,5 @@
1
+ class <%= table_name.classify %> < ApplicationRecord
2
+ <%- fields.select { |f| f.enum_values.any? }.each do |field| -%>
3
+ enum <%= field.name %>: {<%= field.enum_values.map { |v| "#{v}: '#{v}'"}.join(', ') %>}
4
+ <% end -%>
5
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/active_record/migration'
5
+ require 'rails/version'
6
+
7
+ # Generates a new consumer.
8
+ module Deimos
9
+ module Generators
10
+ # Generator for ActiveRecord model and migration.
11
+ class ActiveRecordGenerator < Rails::Generators::Base
12
+ include Rails::Generators::Migration
13
+ if Rails.version < '4'
14
+ extend(ActiveRecord::Generators::Migration)
15
+ else
16
+ include ActiveRecord::Generators::Migration
17
+ end
18
+ source_root File.expand_path('active_record/templates', __dir__)
19
+
20
+ argument :table_name, desc: 'The table to create.', required: true
21
+ argument :full_schema, desc: 'The fully qualified schema name.', required: true
22
+
23
+ no_commands do
24
+
25
+ # @return [String]
26
+ def db_migrate_path
27
+ if defined?(Rails.application) && Rails.application
28
+ paths = Rails.application.config.paths['db/migrate']
29
+ paths.respond_to?(:to_ary) ? paths.to_ary.first : paths.to_a.first
30
+ else
31
+ 'db/migrate'
32
+ end
33
+ end
34
+
35
+ # @return [String]
36
+ def migration_version
37
+ "[#{ActiveRecord::Migration.current_version}]"
38
+ rescue StandardError
39
+ ''
40
+ end
41
+
42
+ # @return [String]
43
+ def table_class
44
+ self.table_name.classify
45
+ end
46
+
47
+ # @return [String]
48
+ def schema
49
+ last_dot = self.full_schema.rindex('.')
50
+ self.full_schema[last_dot + 1..-1]
51
+ end
52
+
53
+ # @return [String]
54
+ def namespace
55
+ last_dot = self.full_schema.rindex('.')
56
+ self.full_schema[0...last_dot]
57
+ end
58
+
59
+ # @return [Deimos::SchemaBackends::Base]
60
+ def schema_base
61
+ @schema_base ||= Deimos.schema_backend_class.new(schema: schema, namespace: namespace)
62
+ end
63
+
64
+ # @return [Array<SchemaField>]
65
+ def fields
66
+ schema_base.schema_fields
67
+ end
68
+
69
+ end
70
+
71
+ desc 'Generate migration for a table based on an existing schema.'
72
+ # :nodoc:
73
+ def generate
74
+ migration_template('migration.rb', "db/migrate/create_#{table_name.underscore}.rb")
75
+ template('model.rb', "app/models/#{table_name.underscore}.rb")
76
+ end
77
+ end
78
+ end
79
+ end
@@ -16,6 +16,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
16
16
  t.datetime :locked_at
17
17
  t.boolean :error, null: false, default: false
18
18
  t.integer :retries, null: false, default: 0
19
+ t.datetime :last_processed_at
19
20
  end
20
21
  add_index :kafka_topic_info, :topic, unique: true
21
22
  add_index :kafka_topic_info, [:locked_by, :error]
@@ -16,6 +16,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
16
16
  t.datetime :locked_at
17
17
  t.boolean :error, null: false, default: false
18
18
  t.integer :retries, null: false, default: 0
19
+ t.datetime :last_processed_at
19
20
  end
20
21
  add_index :kafka_topic_info, :topic, unique: true
21
22
  add_index :kafka_topic_info, [:locked_by, :error]
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'generators/deimos/active_record_generator'
4
+
5
+ RSpec.describe Deimos::Generators::ActiveRecordGenerator do
6
+
7
+ after(:each) do
8
+ FileUtils.rm_rf('db') if File.exist?('db')
9
+ FileUtils.rm_rf('app') if File.exist?('app')
10
+ end
11
+
12
+ it 'should generate a migration' do
13
+ expect(Dir['db/migrate/*.rb']).to be_empty
14
+ expect(Dir['app/models/*.rb']).to be_empty
15
+ described_class.start(['generated_table', 'com.my-namespace.Generated'])
16
+ files = Dir['db/migrate/*.rb']
17
+ expect(files.length).to eq(1)
18
+ results = <<~MIGRATION
19
+ class CreateGeneratedTable < ActiveRecord::Migration[6.0]
20
+ def up
21
+ if table_exists?(:generated_table)
22
+ warn "generated_table already exists, exiting"
23
+ return
24
+ end
25
+ create_table :generated_table do |t|
26
+ t.string :a_string
27
+ t.integer :a_int
28
+ t.bigint :a_long
29
+ t.float :a_float
30
+ t.float :a_double
31
+ t.string :an_enum
32
+ t.json :an_array
33
+ t.json :a_map
34
+ t.json :a_record
35
+ end
36
+
37
+ # TODO add indexes as necessary
38
+ end
39
+
40
+ def down
41
+ return unless table_exists?(:generated_table)
42
+ drop_table :generated_table
43
+ end
44
+
45
+ end
46
+ MIGRATION
47
+ expect(File.read(files[0])).to eq(results)
48
+ model = <<~MODEL
49
+ class GeneratedTable < ApplicationRecord
50
+ enum an_enum: {sym1: 'sym1', sym2: 'sym2'}
51
+ end
52
+ MODEL
53
+ expect(File.read('app/models/generated_table.rb')).to eq(model)
54
+ end
55
+
56
+ end
@@ -37,22 +37,45 @@ each_db_config(Deimos::KafkaTopicInfo) do
37
37
  end
38
38
 
39
39
  specify '#clear_lock' do
40
- described_class.create!(topic: 'my-topic', locked_by: 'abc',
41
- locked_at: 10.seconds.ago, error: true, retries: 1)
42
- described_class.create!(topic: 'my-topic2', locked_by: 'def',
43
- locked_at: 10.seconds.ago, error: true, retries: 1)
44
- described_class.clear_lock('my-topic', 'abc')
45
- expect(described_class.count).to eq(2)
46
- record = described_class.first
47
- expect(record.locked_by).to eq(nil)
48
- expect(record.locked_at).to eq(nil)
49
- expect(record.error).to eq(false)
50
- expect(record.retries).to eq(0)
51
- record = described_class.last
52
- expect(record.locked_by).not_to eq(nil)
53
- expect(record.locked_at).not_to eq(nil)
54
- expect(record.error).not_to eq(false)
55
- expect(record.retries).not_to eq(0)
40
+ freeze_time do
41
+ Deimos::KafkaTopicInfo.create!(topic: 'my-topic', locked_by: 'abc',
42
+ locked_at: 10.seconds.ago, error: true, retries: 1,
43
+ last_processed_at: 20.seconds.ago)
44
+ Deimos::KafkaTopicInfo.create!(topic: 'my-topic2', locked_by: 'def',
45
+ locked_at: 10.seconds.ago, error: true, retries: 1,
46
+ last_processed_at: 20.seconds.ago)
47
+ Deimos::KafkaTopicInfo.clear_lock('my-topic', 'abc')
48
+ expect(Deimos::KafkaTopicInfo.count).to eq(2)
49
+ record = Deimos::KafkaTopicInfo.first
50
+ expect(record.locked_by).to eq(nil)
51
+ expect(record.locked_at).to eq(nil)
52
+ expect(record.error).to eq(false)
53
+ expect(record.retries).to eq(0)
54
+ expect(record.last_processed_at.to_s).to eq(Time.zone.now.to_s)
55
+ record = Deimos::KafkaTopicInfo.last
56
+ expect(record.locked_by).not_to eq(nil)
57
+ expect(record.locked_at).not_to eq(nil)
58
+ expect(record.error).not_to eq(false)
59
+ expect(record.retries).not_to eq(0)
60
+ expect(record.last_processed_at.to_s).to eq(20.seconds.ago.to_s)
61
+ end
62
+ end
63
+
64
+ specify '#ping_empty_topics' do
65
+ freeze_time do
66
+ old_time = 1.hour.ago.to_s
67
+ t1 = Deimos::KafkaTopicInfo.create!(topic: 'topic1', last_processed_at: old_time)
68
+ t2 = Deimos::KafkaTopicInfo.create!(topic: 'topic2', last_processed_at: old_time)
69
+ t3 = Deimos::KafkaTopicInfo.create!(topic: 'topic3', last_processed_at: old_time,
70
+ locked_by: 'me', locked_at: 1.minute.ago)
71
+
72
+ expect(Deimos::KafkaTopicInfo.count).to eq(3)
73
+ Deimos::KafkaTopicInfo.all.each { |t| expect(t.last_processed_at.to_s).to eq(old_time) }
74
+ Deimos::KafkaTopicInfo.ping_empty_topics(%w(topic1))
75
+ expect(t1.reload.last_processed_at.to_s).to eq(old_time) # was passed as an exception
76
+ expect(t2.reload.last_processed_at.to_s).to eq(Time.zone.now.to_s)
77
+ expect(t3.reload.last_processed_at.to_s).to eq(old_time) # is locked
78
+ end
56
79
  end
57
80
 
58
81
  specify '#register_error' do
@@ -0,0 +1,71 @@
1
+ {
2
+ "namespace": "com.my-namespace",
3
+ "name": "Generated",
4
+ "type": "record",
5
+ "doc": "Test schema",
6
+ "fields": [
7
+ {
8
+ "name": "a_string",
9
+ "type": "string"
10
+ },
11
+ {
12
+ "name": "a_int",
13
+ "type": "int"
14
+ },
15
+ {
16
+ "name": "a_long",
17
+ "type": "long"
18
+ },
19
+ {
20
+ "name": "a_float",
21
+ "type": "float"
22
+ },
23
+ {
24
+ "name": "a_double",
25
+ "type": "double"
26
+ },
27
+ {
28
+ "name": "an_enum",
29
+ "type": {
30
+ "type": "enum",
31
+ "name": "AnEnum",
32
+ "symbols": ["sym1", "sym2"]
33
+ }
34
+ },
35
+ {
36
+ "name": "an_array",
37
+ "type": {
38
+ "type": "array",
39
+ "items": "int"
40
+ }
41
+ },
42
+ {
43
+ "name": "a_map",
44
+ "type": {
45
+ "type": "map",
46
+ "values": "string"
47
+ }
48
+ },
49
+ {
50
+ "name": "timestamp",
51
+ "type": "string"
52
+ },
53
+ {
54
+ "name": "message_id",
55
+ "type": "string"
56
+ },
57
+ {
58
+ "name": "a_record",
59
+ "type": {
60
+ "type": "record",
61
+ "name": "ARecord",
62
+ "fields": [
63
+ {
64
+ "name": "a_record_field",
65
+ "type": "string"
66
+ }
67
+ ]
68
+ }
69
+ }
70
+ ]
71
+ }
@@ -32,6 +32,7 @@ module TestRunners
32
32
  # Test runner
33
33
  class TestRunner
34
34
  attr_accessor :id, :started, :stopped, :should_error
35
+
35
36
  # :nodoc:
36
37
  def initialize(id=nil)
37
38
  @id = id
@@ -16,11 +16,13 @@ each_db_config(Deimos::Utils::DbProducer) do
16
16
 
17
17
  before(:each) do
18
18
  stub_const('Deimos::Utils::DbProducer::BATCH_SIZE', 2)
19
+ stub_const('Deimos::Utils::DbProducer::DELETE_BATCH_SIZE', 1)
19
20
  end
20
21
 
21
22
  specify '#process_next_messages' do
22
23
  expect(producer).to receive(:retrieve_topics).and_return(%w(topic1 topic2))
23
24
  expect(producer).to receive(:process_topic).twice
25
+ expect(Deimos::KafkaTopicInfo).to receive(:ping_empty_topics).with(%w(topic1 topic2))
24
26
  expect(producer).to receive(:sleep).with(0.5)
25
27
  producer.process_next_messages
26
28
  end
@@ -40,6 +42,9 @@ each_db_config(Deimos::Utils::DbProducer) do
40
42
  Deimos::KafkaMessage.create!(topic: 'topic1',
41
43
  message: 'blah',
42
44
  key: "key#{i}")
45
+ Deimos::KafkaMessage.create!(topic: 'topic2',
46
+ message: 'blah',
47
+ key: "key#{i}")
43
48
  end
44
49
  stub_const('Deimos::Utils::DbProducer::BATCH_SIZE', 5)
45
50
  producer.current_topic = 'topic1'
@@ -280,6 +285,12 @@ each_db_config(Deimos::Utils::DbProducer) do
280
285
  message: "mess#{i}",
281
286
  partition_key: "key#{i}"
282
287
  )
288
+ Deimos::KafkaMessage.create!(
289
+ id: i,
290
+ topic: 'my-topic2',
291
+ message: "mess#{i}",
292
+ partition_key: "key#{i}"
293
+ )
283
294
  end
284
295
 
285
296
  expect(Deimos::KafkaTopicInfo).to receive(:lock).
@@ -288,9 +299,60 @@ each_db_config(Deimos::Utils::DbProducer) do
288
299
  expect(producer).to receive(:retrieve_messages).and_return(messages)
289
300
  expect(Deimos::KafkaTopicInfo).to receive(:register_error)
290
301
 
302
+ expect(Deimos::KafkaMessage.count).to eq(8)
303
+ producer.process_topic('my-topic')
291
304
  expect(Deimos::KafkaMessage.count).to eq(4)
305
+ end
306
+
307
+ it 'should retry deletes and not re-publish' do
308
+ messages = (1..4).map do |i|
309
+ Deimos::KafkaMessage.create!(
310
+ id: i,
311
+ topic: 'my-topic',
312
+ message: "mess#{i}",
313
+ partition_key: "key#{i}"
314
+ )
315
+ end
316
+ (5..8).each do |i|
317
+ Deimos::KafkaMessage.create!(
318
+ id: i,
319
+ topic: 'my-topic2',
320
+ message: "mess#{i}",
321
+ partition_key: "key#{i}"
322
+ )
323
+ end
324
+
325
+ raise_error = true
326
+ expect(Deimos::KafkaMessage).to receive(:where).exactly(5).times.and_wrap_original do |m, *args|
327
+ if raise_error
328
+ raise_error = false
329
+ raise 'Lock wait timeout'
330
+ end
331
+ m.call(*args)
332
+ end
333
+
334
+ expect(Deimos::KafkaTopicInfo).to receive(:lock).
335
+ with('my-topic', 'abc').and_return(true)
336
+ expect(producer).to receive(:retrieve_messages).ordered.and_return(messages)
337
+ expect(producer).to receive(:retrieve_messages).ordered.and_return([])
338
+ expect(phobos_producer).to receive(:publish_list).once.with(messages.map(&:phobos_message))
339
+
340
+ expect(Deimos::KafkaMessage.count).to eq(8)
292
341
  producer.process_topic('my-topic')
293
- expect(Deimos::KafkaMessage.count).to eq(0)
342
+ expect(Deimos::KafkaMessage.count).to eq(4)
343
+ end
344
+
345
+ it 'should re-raise misc errors on delete' do
346
+ messages = (1..3).map do |i|
347
+ Deimos::KafkaMessage.create!(
348
+ id: i,
349
+ topic: 'my-topic',
350
+ message: "mess#{i}",
351
+ partition_key: "key#{i}"
352
+ )
353
+ end
354
+ expect(Deimos::KafkaMessage).to receive(:where).once.and_raise('OH NOES')
355
+ expect { producer.delete_messages(messages) }.to raise_exception('OH NOES')
294
356
  end
295
357
 
296
358
  end
@@ -309,21 +371,34 @@ each_db_config(Deimos::Utils::DbProducer) do
309
371
  Deimos::KafkaMessage.create!(topic: "topic#{i}", message: nil,
310
372
  created_at: (1 + i).minute.ago)
311
373
  end
374
+ Deimos::KafkaTopicInfo.create!(topic: 'topic1',
375
+ last_processed_at: 6.minutes.ago)
376
+ Deimos::KafkaTopicInfo.create!(topic: 'topic2',
377
+ last_processed_at: 3.minutes.ago)
378
+ Deimos::KafkaTopicInfo.create!(topic: 'topic3',
379
+ last_processed_at: 5.minutes.ago)
312
380
  allow(Deimos.config.metrics).to receive(:gauge)
313
381
  producer.send_pending_metrics
314
- expect(Deimos.config.metrics).to have_received(:gauge).twice
382
+ expect(Deimos.config.metrics).to have_received(:gauge).exactly(6).times
383
+ # topic1 has the earliest message 4 minutes ago and last processed 6
384
+ # minutes ago, so the most amount of time we've seen nothing is 4 minutes
315
385
  expect(Deimos.config.metrics).to have_received(:gauge).
316
386
  with('pending_db_messages_max_wait', 4.minutes.to_i, tags: ['topic:topic1'])
387
+ # topic2 has earliest message 5 minutes ago and last processed 3 minutes
388
+ # ago, so we should give it 3 minutes
389
+ expect(Deimos.config.metrics).to have_received(:gauge).
390
+ with('pending_db_messages_max_wait', 3.minutes.to_i, tags: ['topic:topic2'])
391
+ # topic3 has no messages, so should get 0
317
392
  expect(Deimos.config.metrics).to have_received(:gauge).
318
- with('pending_db_messages_max_wait', 5.minutes.to_i, tags: ['topic:topic2'])
393
+ with('pending_db_messages_max_wait', 0, tags: ['topic:topic3'])
394
+ expect(Deimos.config.metrics).to have_received(:gauge).
395
+ with('pending_db_messages_count', 3, tags: ['topic:topic1'])
396
+ expect(Deimos.config.metrics).to have_received(:gauge).
397
+ with('pending_db_messages_count', 3, tags: ['topic:topic2'])
398
+ expect(Deimos.config.metrics).to have_received(:gauge).
399
+ with('pending_db_messages_count', 0, tags: ['topic:topic3'])
319
400
  end
320
401
  end
321
-
322
- it 'should send 0 if no messages' do
323
- expect(Deimos.config.metrics).to receive(:gauge).
324
- with('pending_db_messages_max_wait', 0)
325
- producer.send_pending_metrics
326
- end
327
402
  end
328
403
 
329
404
  example 'Full integration test' do
@@ -403,5 +478,4 @@ each_db_config(Deimos::Utils::DbProducer) do
403
478
  }
404
479
  ])
405
480
  end
406
-
407
481
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deimos-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0.pre.beta2
4
+ version: 1.8.1.pre.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Orner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-08 00:00:00.000000000 Z
11
+ date: 2020-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: avro_turf
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '5.2'
75
+ version: '6'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '5.2'
82
+ version: '6'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: activerecord-import
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -226,20 +226,14 @@ dependencies:
226
226
  requirements:
227
227
  - - "~>"
228
228
  - !ruby/object:Gem::Version
229
- version: '5.2'
230
- - - ">="
231
- - !ruby/object:Gem::Version
232
- version: 5.2.4.2
229
+ version: '6'
233
230
  type: :development
234
231
  prerelease: false
235
232
  version_requirements: !ruby/object:Gem::Requirement
236
233
  requirements:
237
234
  - - "~>"
238
235
  - !ruby/object:Gem::Version
239
- version: '5.2'
240
- - - ">="
241
- - !ruby/object:Gem::Version
242
- version: 5.2.4.2
236
+ version: '6'
243
237
  - !ruby/object:Gem::Dependency
244
238
  name: rake
245
239
  requirement: !ruby/object:Gem::Requirement
@@ -406,6 +400,9 @@ files:
406
400
  - lib/deimos/utils/inline_consumer.rb
407
401
  - lib/deimos/utils/lag_reporter.rb
408
402
  - lib/deimos/version.rb
403
+ - lib/generators/deimos/active_record/templates/migration.rb.tt
404
+ - lib/generators/deimos/active_record/templates/model.rb.tt
405
+ - lib/generators/deimos/active_record_generator.rb
409
406
  - lib/generators/deimos/db_backend/templates/migration
410
407
  - lib/generators/deimos/db_backend/templates/rails3_migration
411
408
  - lib/generators/deimos/db_backend_generator.rb
@@ -427,6 +424,7 @@ files:
427
424
  - spec/config/configuration_spec.rb
428
425
  - spec/consumer_spec.rb
429
426
  - spec/deimos_spec.rb
427
+ - spec/generators/active_record_generator_spec.rb
430
428
  - spec/handlers/my_batch_consumer.rb
431
429
  - spec/handlers/my_consumer.rb
432
430
  - spec/kafka_source_spec.rb
@@ -441,6 +439,7 @@ files:
441
439
  - spec/schema_backends/avro_schema_registry_spec.rb
442
440
  - spec/schema_backends/avro_validation_spec.rb
443
441
  - spec/schema_backends/base_spec.rb
442
+ - spec/schemas/com/my-namespace/Generated.avsc
444
443
  - spec/schemas/com/my-namespace/MySchema-key.avsc
445
444
  - spec/schemas/com/my-namespace/MySchema.avsc
446
445
  - spec/schemas/com/my-namespace/MySchemaCompound-key.avsc
@@ -499,6 +498,7 @@ test_files:
499
498
  - spec/config/configuration_spec.rb
500
499
  - spec/consumer_spec.rb
501
500
  - spec/deimos_spec.rb
501
+ - spec/generators/active_record_generator_spec.rb
502
502
  - spec/handlers/my_batch_consumer.rb
503
503
  - spec/handlers/my_consumer.rb
504
504
  - spec/kafka_source_spec.rb
@@ -513,6 +513,7 @@ test_files:
513
513
  - spec/schema_backends/avro_schema_registry_spec.rb
514
514
  - spec/schema_backends/avro_validation_spec.rb
515
515
  - spec/schema_backends/base_spec.rb
516
+ - spec/schemas/com/my-namespace/Generated.avsc
516
517
  - spec/schemas/com/my-namespace/MySchema-key.avsc
517
518
  - spec/schemas/com/my-namespace/MySchema.avsc
518
519
  - spec/schemas/com/my-namespace/MySchemaCompound-key.avsc