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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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