deimos-ruby 1.24.3 → 2.0.0.pre.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +0 -17
- data/.tool-versions +1 -0
- data/CHANGELOG.md +1 -1
- data/README.md +287 -498
- data/deimos-ruby.gemspec +4 -4
- data/docs/CONFIGURATION.md +133 -227
- data/docs/UPGRADING.md +237 -0
- data/lib/deimos/active_record_consume/batch_consumption.rb +28 -29
- data/lib/deimos/active_record_consume/message_consumption.rb +15 -21
- data/lib/deimos/active_record_consumer.rb +36 -26
- data/lib/deimos/active_record_producer.rb +28 -9
- data/lib/deimos/backends/base.rb +4 -35
- data/lib/deimos/backends/kafka.rb +6 -22
- data/lib/deimos/backends/kafka_async.rb +6 -22
- data/lib/deimos/backends/{db.rb → outbox.rb} +13 -9
- data/lib/deimos/config/configuration.rb +116 -385
- data/lib/deimos/consume/batch_consumption.rb +24 -124
- data/lib/deimos/consume/message_consumption.rb +36 -63
- data/lib/deimos/consumer.rb +16 -75
- data/lib/deimos/ext/consumer_route.rb +35 -0
- data/lib/deimos/ext/producer_middleware.rb +94 -0
- data/lib/deimos/ext/producer_route.rb +22 -0
- data/lib/deimos/ext/redraw.rb +29 -0
- data/lib/deimos/ext/routing_defaults.rb +72 -0
- data/lib/deimos/ext/schema_route.rb +70 -0
- data/lib/deimos/kafka_message.rb +2 -2
- data/lib/deimos/kafka_source.rb +2 -7
- data/lib/deimos/kafka_topic_info.rb +1 -1
- data/lib/deimos/logging.rb +71 -0
- data/lib/deimos/message.rb +2 -11
- data/lib/deimos/metrics/datadog.rb +40 -1
- data/lib/deimos/metrics/provider.rb +4 -4
- data/lib/deimos/producer.rb +39 -116
- data/lib/deimos/railtie.rb +6 -0
- data/lib/deimos/schema_backends/avro_base.rb +21 -21
- data/lib/deimos/schema_backends/avro_schema_registry.rb +1 -2
- data/lib/deimos/schema_backends/avro_validation.rb +2 -2
- data/lib/deimos/schema_backends/base.rb +19 -12
- data/lib/deimos/schema_backends/mock.rb +6 -1
- data/lib/deimos/schema_backends/plain.rb +47 -0
- data/lib/deimos/schema_class/base.rb +2 -2
- data/lib/deimos/schema_class/enum.rb +1 -1
- data/lib/deimos/schema_class/record.rb +2 -2
- data/lib/deimos/test_helpers.rb +95 -320
- data/lib/deimos/tracing/provider.rb +6 -6
- data/lib/deimos/transcoder.rb +88 -0
- data/lib/deimos/utils/db_poller/base.rb +16 -14
- data/lib/deimos/utils/db_poller/state_based.rb +3 -3
- data/lib/deimos/utils/db_poller/time_based.rb +4 -4
- data/lib/deimos/utils/db_poller.rb +1 -1
- data/lib/deimos/utils/deadlock_retry.rb +1 -1
- data/lib/deimos/utils/{db_producer.rb → outbox_producer.rb} +16 -47
- data/lib/deimos/utils/schema_class.rb +0 -7
- data/lib/deimos/version.rb +1 -1
- data/lib/deimos.rb +79 -26
- data/lib/generators/deimos/{db_backend_generator.rb → outbox_backend_generator.rb} +4 -4
- data/lib/generators/deimos/schema_class_generator.rb +0 -1
- data/lib/generators/deimos/v2/templates/karafka.rb.tt +149 -0
- data/lib/generators/deimos/v2_generator.rb +193 -0
- data/lib/tasks/deimos.rake +5 -7
- data/spec/active_record_batch_consumer_association_spec.rb +22 -13
- data/spec/active_record_batch_consumer_spec.rb +84 -65
- data/spec/active_record_consume/batch_consumption_spec.rb +10 -10
- data/spec/active_record_consume/batch_slicer_spec.rb +12 -12
- data/spec/active_record_consumer_spec.rb +29 -13
- data/spec/active_record_producer_spec.rb +36 -26
- data/spec/backends/base_spec.rb +0 -23
- data/spec/backends/kafka_async_spec.rb +1 -3
- data/spec/backends/kafka_spec.rb +1 -3
- data/spec/backends/{db_spec.rb → outbox_spec.rb} +14 -20
- data/spec/batch_consumer_spec.rb +66 -116
- data/spec/consumer_spec.rb +53 -147
- data/spec/deimos_spec.rb +10 -126
- data/spec/kafka_source_spec.rb +19 -52
- data/spec/karafka/karafka.rb +69 -0
- data/spec/karafka_config/karafka_spec.rb +97 -0
- data/spec/logging_spec.rb +25 -0
- data/spec/message_spec.rb +9 -9
- data/spec/producer_spec.rb +112 -254
- data/spec/rake_spec.rb +1 -3
- data/spec/schema_backends/avro_validation_spec.rb +1 -1
- data/spec/schemas/com/my-namespace/MySchemaWithTitle.avsc +22 -0
- data/spec/snapshots/consumers-no-nest.snap +49 -0
- data/spec/snapshots/consumers.snap +49 -0
- data/spec/snapshots/consumers_and_producers-no-nest.snap +49 -0
- data/spec/snapshots/consumers_and_producers.snap +49 -0
- data/spec/snapshots/consumers_circular-no-nest.snap +49 -0
- data/spec/snapshots/consumers_circular.snap +49 -0
- data/spec/snapshots/consumers_complex_types-no-nest.snap +49 -0
- data/spec/snapshots/consumers_complex_types.snap +49 -0
- data/spec/snapshots/consumers_nested-no-nest.snap +49 -0
- data/spec/snapshots/consumers_nested.snap +49 -0
- data/spec/snapshots/namespace_folders.snap +49 -0
- data/spec/snapshots/namespace_map.snap +49 -0
- data/spec/snapshots/producers_with_key-no-nest.snap +49 -0
- data/spec/snapshots/producers_with_key.snap +49 -0
- data/spec/spec_helper.rb +61 -29
- data/spec/utils/db_poller_spec.rb +49 -39
- data/spec/utils/{db_producer_spec.rb → outbox_producer_spec.rb} +17 -184
- metadata +58 -67
- data/lib/deimos/batch_consumer.rb +0 -7
- data/lib/deimos/config/phobos_config.rb +0 -164
- data/lib/deimos/instrumentation.rb +0 -95
- data/lib/deimos/monkey_patches/phobos_cli.rb +0 -35
- data/lib/deimos/utils/inline_consumer.rb +0 -158
- data/lib/deimos/utils/lag_reporter.rb +0 -186
- data/lib/deimos/utils/schema_controller_mixin.rb +0 -129
- data/spec/config/configuration_spec.rb +0 -329
- data/spec/kafka_listener_spec.rb +0 -55
- data/spec/phobos.bad_db.yml +0 -73
- data/spec/phobos.yml +0 -77
- data/spec/utils/inline_consumer_spec.rb +0 -31
- data/spec/utils/lag_reporter_spec.rb +0 -76
- data/spec/utils/platform_schema_validation_spec.rb +0 -0
- data/spec/utils/schema_controller_mixin_spec.rb +0 -84
- /data/lib/generators/deimos/{db_backend → outbox_backend}/templates/migration +0 -0
- /data/lib/generators/deimos/{db_backend → outbox_backend}/templates/rails3_migration +0 -0
data/docs/UPGRADING.md
CHANGED
@@ -1,5 +1,242 @@
|
|
1
1
|
# Upgrading Deimos
|
2
2
|
|
3
|
+
## Upgrading to 2.x
|
4
|
+
|
5
|
+
2.x is a major rewrite from 1.0. The underlying library has been changed from [Phobos](https://github.com/phobos/phobos) to [Karafka](https://karafka.io/). This change has given us an opportunity to fix some issues and deprecated code paths from version 1.0 as well as provide much more functionality by integrating more fully with the Karafka ecosystem.
|
6
|
+
|
7
|
+
For a deeper dive into the internal changes, please see [...]().
|
8
|
+
|
9
|
+
There are a number of breaking changes. We provide a `v2` generator to attempt to auto-fix many of these breaking changes automatically. To run the generator:
|
10
|
+
|
11
|
+
KARAFKA_BOOT_FILE=false rails g deimos:v2
|
12
|
+
|
13
|
+
### Running Deimos
|
14
|
+
|
15
|
+
Instead of running `rake deimos:start`, you now run your Kafka consumers the same way any Karafka consumers are run: `karafka server`.
|
16
|
+
|
17
|
+
### Configuration
|
18
|
+
|
19
|
+
In V1, Deimos configuration was all done in a single `Deimos.configure` block, including Kafka configs, consumers and producers:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
Deimos.configure do
|
23
|
+
producers.schema_namespace 'com.my-namespace'
|
24
|
+
kafka.seed_brokers ['my-broker:9092']
|
25
|
+
|
26
|
+
consumer do
|
27
|
+
class_name 'MyConsumer'
|
28
|
+
topic 'MyTopic'
|
29
|
+
session_timeout 30
|
30
|
+
schema 'MySchema'
|
31
|
+
key_config field: :id
|
32
|
+
namespace 'com.my-namespace'
|
33
|
+
end
|
34
|
+
|
35
|
+
producer do
|
36
|
+
class_name 'MyProducer'
|
37
|
+
topic 'MyTopic2'
|
38
|
+
schema 'MySchema2'
|
39
|
+
key_config none: true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
In V2, the `Deimos.configure` block now only takes Deimos-specific settings, and is **not** used to configure producers and consumers. Kafka settings now go in the Karafka `kafka` setting method, and producers and consumers use Karafka [routing](https://karafka.io/docs/Routing/). There are Deimos-specific extensions to routing to apply to consumers and producers, either via a `defaults` block (applying to all consumers and producers) or in individual `topic` blocks:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
Deimos.configure do
|
48
|
+
producers.schema_namespace 'com.my-namespace'
|
49
|
+
end
|
50
|
+
|
51
|
+
class KarafkaApp < Karafka::App
|
52
|
+
setup do |config|
|
53
|
+
config.kafka = {
|
54
|
+
"bootstrap.servers": "my-broker:9092"
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
routes.draw do
|
59
|
+
defaults do
|
60
|
+
namespace "com.my-namespace"
|
61
|
+
end
|
62
|
+
|
63
|
+
topic "MyTopic" do
|
64
|
+
# Karafka settings
|
65
|
+
consumer MyConsumer
|
66
|
+
kafka({"session.timeout.ms": 30_000})
|
67
|
+
# Deimos settings
|
68
|
+
schema "MySchema" # the res
|
69
|
+
key_config({field: id})
|
70
|
+
end
|
71
|
+
|
72
|
+
topic "MyTopic2" do
|
73
|
+
# these are all Deimos settings since Karafka doesn't actually do per-topic producer configs
|
74
|
+
producer_class MyProducer
|
75
|
+
schema 'MySchema2'
|
76
|
+
key_config none: true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
This configuration must be in a file called `karafka.rb` at the root of your application. The V2 generator will generate this file for you. Without the generator, if you have this file and start up your app with the old `Deimos.configure` code, you will get notifications of the correct places to put these settings.
|
83
|
+
|
84
|
+
|
85
|
+
### Removed deprecations
|
86
|
+
|
87
|
+
The following were deprecated in version 1.x and are removed in 2.0.
|
88
|
+
|
89
|
+
* The `kafka_producer` method for KafkaSource is no longer supported. Please use `kafka_producers`. (This is not addressed by the V2 generator.)
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
# before:
|
93
|
+
class MyRecord < ApplicationRecord
|
94
|
+
def kafka_producer
|
95
|
+
MyProducer
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# after:
|
100
|
+
class MyRecord < ApplicationRecord
|
101
|
+
def kafka_producers
|
102
|
+
[MyProducer]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
* The `record_attributes` method for ActiveRecordConsumer now must take two parameters, not one. (The V2 generator can fix this.)
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
# before:
|
111
|
+
class MyConsumer < Deimos::ActiveRecordConsumer
|
112
|
+
def record_attributes(payload)
|
113
|
+
# ...
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# after:
|
118
|
+
class MyConsumer < Deimos::ActiveRecordConsumer
|
119
|
+
def record_attributes(payload, key)
|
120
|
+
# ...
|
121
|
+
end
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
* The `BatchConsumer` class has been removed. Please use the `Consumer` class.
|
126
|
+
* You can no longer configure your application using a `phobos.yml` file. The V2 generator will not be able to work on apps using this approach.
|
127
|
+
* Removed `test_consume_invalid_message` and `test_consume_batch_invalid_message` test helpers. These did not serve a useful purpose.
|
128
|
+
* The following deprecated testing functions have been removed: `stub_producers_and_consumers!`, `stub_producer`, `stub_consumer`, `stub_batch_consumer`. These have not done anything in a long time.
|
129
|
+
|
130
|
+
### Major breaking changes
|
131
|
+
* Since Karafka only supports Ruby >= 3.0, that means Deimos also only supports those versions.
|
132
|
+
* Deimos no longer supports a separate logger from Karafka. When you configure a Karafka logger, Deimos will use that logger for all its logging. (Deimos logs will be prefixed with a `[Deimos]` tag.)
|
133
|
+
* The `:db` backend has been renamed to `:outbox`. All associated classes (like `DbProducer`) have likewise been renamed. The Rake task has also been renamed to `rake deimos:outbox`.
|
134
|
+
* The `SchemaControllerMixin` has been removed as there was no serious usage for it.
|
135
|
+
* `InlineConsumer` has been removed - Karafka Pro has an [Iterator API](https://karafka.io/docs/Pro-Iterator-API/) that does the same thing. There also has been no evidence that it was used (and was probably pretty buggy).
|
136
|
+
* The `:test` backend has been removed and the `Deimos::TestHelpers` module is now largely powered by [karafka-testing](https://github.com/karafka/karafka-testing/). This means that you can no longer use `Deimos::Backends::Test.sent_messages` - you need to use `Deimos::TestHelpers.sent_messages`. (The V2 generator should fix this.)
|
137
|
+
* Individual consumer and producer settings now live within Karafka route configuration. This means you can no longer call e.g. `consumer.schema` to retrieve this information, as settings are no longer stored directly on the consumer and producer objects (it is still available, but via different methods).
|
138
|
+
* Consumers should no longer define a `consume` method, as the semantics have changed with Karafka. Instead, you can define a `consume_message` or `consume_batch` method. Both of these methods now take Karafka `Message` objects instead of hashes. The V2 generator can handle translating this for you, but if you create new consumers, you should take advantage of the Karafka functionality and use it first-class.
|
139
|
+
* Phobos `delivery_method` is no longer relevant. Instead, specify an `each_message` setting for your consumer. If set to true, you should define a `consume_message` method. Otherwise, you should define a `consume_batch` method. (Note that this is the reverse from the previous default, which assumed `delivery_method: message`.) The V2 generator will create the correct setting for each consumer.
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
# before:
|
143
|
+
class MyConsumer < Deimos::Consumer
|
144
|
+
def consume(payload, metadata)
|
145
|
+
# payload and metadata are both hashes
|
146
|
+
end
|
147
|
+
|
148
|
+
# OR with delivery_method: inline_batch
|
149
|
+
def batch_consume(payloads, metadata)
|
150
|
+
# payloads is an array of hashes, metadata is a hash
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# now:
|
155
|
+
class MyConsumer < Deimos::Consumer
|
156
|
+
def consume_batch
|
157
|
+
payloads = messages.payloads # messages is an instance method and `payloads` will return the decoded hashes
|
158
|
+
end
|
159
|
+
|
160
|
+
# OR with batch(false)
|
161
|
+
def consume_message(message)
|
162
|
+
# message is a Karafka Message object
|
163
|
+
payload = message.payload
|
164
|
+
key = message.key # etc.
|
165
|
+
end
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
### Metrics
|
170
|
+
|
171
|
+
The following metrics have been **removed** in favor of Karafka's more robust [DataDog metrics](https://karafka.io/docs/Monitoring-and-Logging/#datadog-and-statsd-integration) and WaterDrop's [DataDog metrics](https://karafka.io/docs/WaterDrop-Monitoring-and-Logging/#datadog-and-statsd-integration):
|
172
|
+
* `consumer_lag` (use `consumer.lags`)
|
173
|
+
* `handler` (use `consumer.consumed.time_taken`)
|
174
|
+
* `publish` (use `produced_sync` and `produced_async`)
|
175
|
+
* `publish_error` (use `deliver.errors`)
|
176
|
+
|
177
|
+
You will need to manually add the DataDog MetricsListener as shown in the above pages.
|
178
|
+
|
179
|
+
The following metrics have been **renamed**:
|
180
|
+
|
181
|
+
* `db_producer.insert` -> `outbox.insert`
|
182
|
+
* `db_producer.process` -> `outbox.process`
|
183
|
+
|
184
|
+
### Instrumentation
|
185
|
+
|
186
|
+
Deimos's own instrumentation layer has been removed in favor of Karafka's. You can still subscribe to Deimos notifications - you simply do it via Karafka's monitor instead of Deimos's.
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
# before:
|
190
|
+
Deimos.subscribe('encode_messages') do |event|
|
191
|
+
# ...
|
192
|
+
end
|
193
|
+
|
194
|
+
# after:
|
195
|
+
Karafka.monitor.subscribe('deimos.encode_messages') do |event|
|
196
|
+
# ...
|
197
|
+
end
|
198
|
+
```
|
199
|
+
|
200
|
+
Note that Karafka's monitors do not support the legacy "splatted" subscribe:
|
201
|
+
```ruby
|
202
|
+
Deimos.subscribe("event") do |*args|
|
203
|
+
payload = ActiveSupport::Notifications::Event.new(*args).payload
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
207
|
+
The following instrumentation events have been **removed** in favor of Karafka's [events](https://karafka.io/docs/Monitoring-and-Logging/#subscribing-to-the-instrumentation-events):
|
208
|
+
|
209
|
+
* `produce_error` (use `error.occurred`)
|
210
|
+
|
211
|
+
The following events have been **renamed**:
|
212
|
+
* `encode_messages` -> `deimos.encode_message` (**note that only one message is instrumented at a time now**)
|
213
|
+
* `db_producer.produce` -> `deimos.outbox.produce`
|
214
|
+
* `batch_consumption.valid_records` -> `deimos.batch_consumption.valid_records`
|
215
|
+
* `batch_consumption.invalid_records` -> `deimos.batch_consumption.invalid_records`
|
216
|
+
|
217
|
+
### Additional breaking changes
|
218
|
+
* `key_config` now defaults to `{none: true}` instead of erroring out if not set.
|
219
|
+
* `fatal_error?` now receives a Karafka `messages` object instead of a payload hash or array of hashes.
|
220
|
+
* `watched_attributes` has been moved from the corresponding ActiveRecord class to the ActiveRecordProducer class. The object being watched is passed into the method.
|
221
|
+
* Removed `TestHelpers.full_integration_test!` and `kafka_test!` as Karafka does not currently support these use cases. If we need them back, we will need to put in changes to the testing library to support them.
|
222
|
+
* `test_consume_message` and `test_consume_batch` used to not fully validate schemas when using the `:avro_validation` backend. Now these are fully validated, which may cause test errors when upgrading.
|
223
|
+
|
224
|
+
### New functionality
|
225
|
+
|
226
|
+
* When setting up a Datadog metrics client, you can pass `:karafka_namespace`, `:karafka_distribution_mode`, or `:rd_kafka_metrics` tags to specify the Karafka settings for Datadog metrics.
|
227
|
+
- The `payload_log` setting now works for consumers as well as producers, as it is now a topic setting.
|
228
|
+
- You can publish messages **without a Deimos Producer class**. Karafka producers take a hash with `:message`, `:topic`, `:key`, `:headers` and `:partition_key` keys. As long as the topic is configured in `karafka.rb`, you don't need a special class to send the message. You can simply call `Karafka.producer.produce()`.
|
229
|
+
- The only features that are now available on the bare Producer (as opposed to ActiveRecordProducer) class are:
|
230
|
+
- Outbox backend
|
231
|
+
- Instance method to determine partition key (rather than passing it in)
|
232
|
+
- Using `Deimos.disable_producers`
|
233
|
+
- If you need these features, you must continue using a `Deimos::Producer`.
|
234
|
+
- You can now call `.produce(messages)` directly on a `Deimos::Producer` which allows for use of these features while still passing a Karafka message hash. This removes the need to add a `payload_key` key into your payload. This is now the recommended method to use in a Deimos Producer.
|
235
|
+
|
236
|
+
### New deprecations
|
237
|
+
* For testing, you no longer have to call `unit_test!` to get the right settings. It is handled automatically by Karafka. The only thing this method now does is set the schema backend to `:avro_validation`, and you can do that in a single line.
|
238
|
+
* The `skip_expectation` and `call_original` arguments to `test_consume_message` and `test_consume_batch` have been deprecated and no longer need to be provided. The assumption is that `call_original` is always true.
|
239
|
+
|
3
240
|
## Upgrading from < 1.5.0 to >= 1.5.0
|
4
241
|
|
5
242
|
If you are using Confluent's schema registry to Avro-encode your
|
@@ -4,6 +4,7 @@ require 'deimos/active_record_consume/batch_slicer'
|
|
4
4
|
require 'deimos/active_record_consume/batch_record'
|
5
5
|
require 'deimos/active_record_consume/batch_record_list'
|
6
6
|
require 'deimos/active_record_consume/mass_updater'
|
7
|
+
require 'deimos/consume/batch_consumption'
|
7
8
|
|
8
9
|
require 'deimos/utils/deadlock_retry'
|
9
10
|
require 'deimos/message'
|
@@ -14,28 +15,26 @@ module Deimos
|
|
14
15
|
# Methods for consuming batches of messages and saving them to the database
|
15
16
|
# in bulk ActiveRecord operations.
|
16
17
|
module BatchConsumption
|
18
|
+
include Deimos::Consume::BatchConsumption
|
19
|
+
|
17
20
|
# Handle a batch of Kafka messages. Batches are split into "slices",
|
18
21
|
# which are groups of independent messages that can be processed together
|
19
22
|
# in a single database operation.
|
20
23
|
# If two messages in a batch have the same key, we cannot process them
|
21
24
|
# in the same operation as they would interfere with each other. Thus
|
22
25
|
# they are split
|
23
|
-
# @param payloads [Array<Hash,Deimos::SchemaClass::Record>] Decoded payloads
|
24
|
-
# @param metadata [Hash] Information about batch, including keys.
|
25
26
|
# @return [void]
|
26
|
-
def consume_batch
|
27
|
-
|
28
|
-
zip(metadata[:keys]).
|
29
|
-
map { |p, k| Deimos::Message.new(p, nil, key: k) }
|
27
|
+
def consume_batch
|
28
|
+
deimos_messages = messages.map { |p| Deimos::Message.new(p.payload, key: p.key) }
|
30
29
|
|
31
|
-
tag =
|
30
|
+
tag = topic.name
|
32
31
|
Deimos.config.tracer.active_span.set_tag('topic', tag)
|
33
32
|
|
34
|
-
|
35
|
-
if @compacted
|
36
|
-
update_database(compact_messages(
|
33
|
+
Karafka.monitor.instrument('deimos.ar_consumer.consume_batch', {topic: tag}) do
|
34
|
+
if @compacted && deimos_messages.map(&:key).compact.any?
|
35
|
+
update_database(compact_messages(deimos_messages))
|
37
36
|
else
|
38
|
-
uncompacted_update(
|
37
|
+
uncompacted_update(deimos_messages)
|
39
38
|
end
|
40
39
|
end
|
41
40
|
end
|
@@ -68,11 +67,11 @@ module Deimos
|
|
68
67
|
if key.nil?
|
69
68
|
{}
|
70
69
|
elsif key.is_a?(Hash)
|
71
|
-
|
72
|
-
elsif self.
|
70
|
+
self.key_converter.convert(key)
|
71
|
+
elsif self.topic.key_config[:field].nil?
|
73
72
|
{ @klass.primary_key => key }
|
74
73
|
else
|
75
|
-
|
74
|
+
{ self.topic.key_config[:field].to_s => key }
|
76
75
|
end
|
77
76
|
end
|
78
77
|
|
@@ -154,10 +153,10 @@ module Deimos
|
|
154
153
|
record_list = build_records(messages)
|
155
154
|
invalid = filter_records(record_list)
|
156
155
|
if invalid.any?
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
156
|
+
Karafka.monitor.instrument('deimos.batch_consumption.invalid_records', {
|
157
|
+
records: invalid,
|
158
|
+
consumer: self.class
|
159
|
+
})
|
161
160
|
end
|
162
161
|
return if record_list.empty?
|
163
162
|
|
@@ -167,14 +166,14 @@ module Deimos
|
|
167
166
|
updater = MassUpdater.new(@klass,
|
168
167
|
key_col_proc: key_col_proc,
|
169
168
|
col_proc: col_proc,
|
170
|
-
replace_associations: self.
|
171
|
-
bulk_import_id_generator: self.
|
172
|
-
save_associations_first: self.
|
173
|
-
bulk_import_id_column: self.
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
169
|
+
replace_associations: self.replace_associations,
|
170
|
+
bulk_import_id_generator: self.bulk_import_id_generator,
|
171
|
+
save_associations_first: self.save_associations_first,
|
172
|
+
bulk_import_id_column: self.bulk_import_id_column)
|
173
|
+
Karafka.monitor.instrument('deimos.batch_consumption.valid_records', {
|
174
|
+
records: updater.mass_update(record_list),
|
175
|
+
consumer: self.class
|
176
|
+
})
|
178
177
|
end
|
179
178
|
|
180
179
|
# @param record_list [BatchRecordList]
|
@@ -205,14 +204,14 @@ module Deimos
|
|
205
204
|
attrs = attrs.merge(record_key(m.key))
|
206
205
|
next unless attrs
|
207
206
|
|
208
|
-
col = if @klass.column_names.include?(self.
|
209
|
-
self.
|
207
|
+
col = if @klass.column_names.include?(self.bulk_import_id_column.to_s)
|
208
|
+
self.bulk_import_id_column
|
210
209
|
end
|
211
210
|
|
212
211
|
BatchRecord.new(klass: @klass,
|
213
212
|
attributes: attrs,
|
214
213
|
bulk_import_column: col,
|
215
|
-
bulk_import_id_generator: self.
|
214
|
+
bulk_import_id_generator: self.bulk_import_id_generator)
|
216
215
|
end
|
217
216
|
BatchRecordList.new(records.compact)
|
218
217
|
end
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'deimos/consume/message_consumption'
|
4
|
+
|
3
5
|
module Deimos
|
4
6
|
module ActiveRecordConsume
|
5
7
|
# Methods for consuming individual messages and saving them to the database
|
6
8
|
# as ActiveRecord instances.
|
7
9
|
module MessageConsumption
|
10
|
+
include Deimos::Consume::MessageConsumption
|
8
11
|
# Find the record specified by the given payload and key.
|
9
12
|
# Default is to use the primary key column and the value of the first
|
10
13
|
# field in the key.
|
@@ -26,38 +29,29 @@ module Deimos
|
|
26
29
|
record[record.class.primary_key] = key
|
27
30
|
end
|
28
31
|
|
29
|
-
# @param
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
metadata: metadata
|
38
|
-
)
|
32
|
+
# @param message [Karafka::Messages::Message]
|
33
|
+
def consume_message(message)
|
34
|
+
unless self.process_message?(message)
|
35
|
+
Deimos::Logging.log_debug(
|
36
|
+
message: 'Skipping processing of message',
|
37
|
+
payload: message.payload.to_h,
|
38
|
+
metadata: Deimos::Logging.metadata_log_text(message.metadata)
|
39
|
+
)
|
39
40
|
return
|
40
41
|
end
|
41
42
|
|
42
|
-
key = metadata.with_indifferent_access[:key]
|
43
43
|
klass = self.class.config[:record_class]
|
44
|
-
record = fetch_record(klass,
|
45
|
-
if payload.nil?
|
44
|
+
record = fetch_record(klass, message.payload.to_h.with_indifferent_access, message.key)
|
45
|
+
if message.payload.nil?
|
46
46
|
destroy_record(record)
|
47
47
|
return
|
48
48
|
end
|
49
49
|
if record.blank?
|
50
50
|
record = klass.new
|
51
|
-
assign_key(record, payload, key)
|
51
|
+
assign_key(record, message.payload, message.key)
|
52
52
|
end
|
53
53
|
|
54
|
-
|
55
|
-
# TODO next major release we should deprecate this
|
56
|
-
attrs = if self.method(:record_attributes).parameters.size == 2
|
57
|
-
record_attributes(payload.with_indifferent_access, key)
|
58
|
-
else
|
59
|
-
record_attributes(payload.with_indifferent_access)
|
60
|
-
end
|
54
|
+
attrs = record_attributes((message.payload || {}).with_indifferent_access, message.key)
|
61
55
|
# don't use attributes= - bypass Rails < 5 attr_protected
|
62
56
|
attrs.each do |k, v|
|
63
57
|
record.send("#{k}=", v)
|
@@ -30,26 +30,6 @@ module Deimos
|
|
30
30
|
config[:record_class] = klass
|
31
31
|
end
|
32
32
|
|
33
|
-
# @return [String,nil]
|
34
|
-
def bulk_import_id_column
|
35
|
-
config[:bulk_import_id_column]
|
36
|
-
end
|
37
|
-
|
38
|
-
# @return [Proc]
|
39
|
-
def bulk_import_id_generator
|
40
|
-
config[:bulk_import_id_generator]
|
41
|
-
end
|
42
|
-
|
43
|
-
# @return [Boolean]
|
44
|
-
def replace_associations
|
45
|
-
config[:replace_associations]
|
46
|
-
end
|
47
|
-
|
48
|
-
# @return [Boolean]
|
49
|
-
def save_associations_first
|
50
|
-
config[:save_associations_first]
|
51
|
-
end
|
52
|
-
|
53
33
|
# @param val [Boolean] Turn pre-compaction of the batch on or off. If true,
|
54
34
|
# only the last message for each unique key in a batch is processed.
|
55
35
|
# @return [void]
|
@@ -62,18 +42,48 @@ module Deimos
|
|
62
42
|
def max_db_batch_size(limit)
|
63
43
|
config[:max_db_batch_size] = limit
|
64
44
|
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Boolean]
|
49
|
+
def replace_associations
|
50
|
+
self.topic.replace_associations
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String,nil]
|
54
|
+
def bulk_import_id_column
|
55
|
+
self.topic.bulk_import_id_column
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Proc]
|
59
|
+
def bulk_import_id_generator
|
60
|
+
topic.bulk_import_id_generator
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Boolean]
|
64
|
+
def save_associations_first
|
65
|
+
topic.save_associations_first
|
66
|
+
end
|
67
|
+
|
68
|
+
def key_decoder
|
69
|
+
self.topic.serializers[:key]&.backend
|
65
70
|
end
|
66
71
|
|
67
72
|
# Setup
|
68
73
|
def initialize
|
69
74
|
@klass = self.class.config[:record_class]
|
70
|
-
@
|
75
|
+
@compacted = self.class.config[:compacted] != false
|
76
|
+
end
|
71
77
|
|
72
|
-
|
73
|
-
|
74
|
-
|
78
|
+
def converter
|
79
|
+
decoder = self.topic.deserializers[:payload].backend
|
80
|
+
@converter ||= ActiveRecordConsume::SchemaModelConverter.new(decoder, @klass)
|
81
|
+
end
|
75
82
|
|
76
|
-
|
83
|
+
def key_converter
|
84
|
+
decoder = self.topic.deserializers[:key]&.backend
|
85
|
+
return nil if decoder.nil?
|
86
|
+
@key_converter ||= ActiveRecordConsume::SchemaModelConverter.new(decoder, @klass)
|
77
87
|
end
|
78
88
|
|
79
89
|
# Override this method (with `super`) if you want to add/change the default
|
@@ -82,7 +92,7 @@ module Deimos
|
|
82
92
|
# @param _key [String]
|
83
93
|
# @return [Hash]
|
84
94
|
def record_attributes(payload, _key=nil)
|
85
|
-
|
95
|
+
self.converter.convert(payload)
|
86
96
|
end
|
87
97
|
|
88
98
|
# Override this message to conditionally save records
|
@@ -18,9 +18,11 @@ module Deimos
|
|
18
18
|
# a record object, refetch the record to pass into the `generate_payload`
|
19
19
|
# method.
|
20
20
|
# @return [void]
|
21
|
-
def record_class(klass, refetch: true)
|
22
|
-
|
23
|
-
|
21
|
+
def record_class(klass=nil, refetch: true)
|
22
|
+
return @record_class if klass.nil?
|
23
|
+
|
24
|
+
@record_class = klass
|
25
|
+
@refetch_record = refetch
|
24
26
|
end
|
25
27
|
|
26
28
|
# @param record [ActiveRecord::Base]
|
@@ -34,14 +36,14 @@ module Deimos
|
|
34
36
|
# @param force_send [Boolean]
|
35
37
|
# @return [void]
|
36
38
|
def send_events(records, force_send: false)
|
37
|
-
primary_key =
|
39
|
+
primary_key = @record_class&.primary_key
|
38
40
|
messages = records.map do |record|
|
39
41
|
if record.respond_to?(:attributes)
|
40
42
|
attrs = record.attributes.with_indifferent_access
|
41
43
|
else
|
42
44
|
attrs = record.with_indifferent_access
|
43
|
-
if
|
44
|
-
record =
|
45
|
+
if @refetch_record && attrs[primary_key]
|
46
|
+
record = @record_class.find(attrs[primary_key])
|
45
47
|
end
|
46
48
|
end
|
47
49
|
generate_payload(attrs, record).with_indifferent_access
|
@@ -50,6 +52,15 @@ module Deimos
|
|
50
52
|
self.post_process(records)
|
51
53
|
end
|
52
54
|
|
55
|
+
def config
|
56
|
+
Deimos.karafka_configs.find { |t| t.producer_class == self }
|
57
|
+
end
|
58
|
+
|
59
|
+
def encoder
|
60
|
+
raise "No schema or namespace configured for #{self.name}" if config.nil?
|
61
|
+
config.deserializers[:payload].backend
|
62
|
+
end
|
63
|
+
|
53
64
|
# Generate the payload, given a list of attributes or a record..
|
54
65
|
# Can be overridden or added to by subclasses.
|
55
66
|
# @param attributes [Hash]
|
@@ -62,9 +73,9 @@ module Deimos
|
|
62
73
|
payload.delete_if do |k, _|
|
63
74
|
k.to_sym != :payload_key && !fields.map(&:name).include?(k)
|
64
75
|
end
|
65
|
-
return payload unless
|
76
|
+
return payload unless self.config.use_schema_classes
|
66
77
|
|
67
|
-
Utils::SchemaClass.instance(payload,
|
78
|
+
Utils::SchemaClass.instance(payload, encoder.schema, encoder.namespace)
|
68
79
|
end
|
69
80
|
|
70
81
|
# Query to use when polling the database with the DbPoller. Add
|
@@ -76,7 +87,7 @@ module Deimos
|
|
76
87
|
# than this value).
|
77
88
|
# @return [ActiveRecord::Relation]
|
78
89
|
def poll_query(time_from:, time_to:, column_name: :updated_at, min_id:)
|
79
|
-
klass =
|
90
|
+
klass = @record_class
|
80
91
|
table = ActiveRecord::Base.connection.quote_table_name(klass.table_name)
|
81
92
|
column = ActiveRecord::Base.connection.quote_column_name(column_name)
|
82
93
|
primary = ActiveRecord::Base.connection.quote_column_name(klass.primary_key)
|
@@ -95,6 +106,14 @@ module Deimos
|
|
95
106
|
def post_process(_records)
|
96
107
|
end
|
97
108
|
|
109
|
+
# Override this in active record producers to add
|
110
|
+
# non-schema fields to check for updates
|
111
|
+
# @param _record [ActiveRecord::Base]
|
112
|
+
# @return [Array<String>] fields to check for updates
|
113
|
+
def watched_attributes(_record)
|
114
|
+
self.encoder.schema_fields.map(&:name)
|
115
|
+
end
|
116
|
+
|
98
117
|
end
|
99
118
|
end
|
100
119
|
end
|
data/lib/deimos/backends/base.rb
CHANGED
@@ -6,10 +6,11 @@ module Deimos
|
|
6
6
|
class Base
|
7
7
|
class << self
|
8
8
|
# @param producer_class [Class<Deimos::Producer>]
|
9
|
-
# @param messages [Array<
|
9
|
+
# @param messages [Array<Hash>]
|
10
10
|
# @return [void]
|
11
11
|
def publish(producer_class:, messages:)
|
12
|
-
Deimos.
|
12
|
+
message = ::Deimos::Logging.messages_log_text(producer_class.karafka_config.payload_log, messages)
|
13
|
+
Deimos::Logging.log_info({message: 'Publishing Messages:'}.merge(message))
|
13
14
|
execute(producer_class: producer_class, messages: messages)
|
14
15
|
end
|
15
16
|
|
@@ -17,43 +18,11 @@ module Deimos
|
|
17
18
|
# @param messages [Array<Deimos::Message>]
|
18
19
|
# @return [void]
|
19
20
|
def execute(producer_class:, messages:)
|
20
|
-
raise
|
21
|
+
raise MissingImplementationError
|
21
22
|
end
|
22
23
|
|
23
24
|
private
|
24
25
|
|
25
|
-
def log_message(messages)
|
26
|
-
log_message = {
|
27
|
-
message: 'Publishing messages',
|
28
|
-
topic: messages.first&.topic
|
29
|
-
}
|
30
|
-
|
31
|
-
case Deimos.config.payload_log
|
32
|
-
when :keys
|
33
|
-
log_message.merge!(
|
34
|
-
payload_keys: messages.map(&:key)
|
35
|
-
)
|
36
|
-
when :count
|
37
|
-
log_message.merge!(
|
38
|
-
payloads_count: messages.count
|
39
|
-
)
|
40
|
-
when :headers
|
41
|
-
log_message.merge!(
|
42
|
-
payload_headers: messages.map(&:headers)
|
43
|
-
)
|
44
|
-
else
|
45
|
-
log_message.merge!(
|
46
|
-
payloads: messages.map do |message|
|
47
|
-
{
|
48
|
-
payload: message.payload,
|
49
|
-
key: message.key
|
50
|
-
}
|
51
|
-
end
|
52
|
-
)
|
53
|
-
end
|
54
|
-
|
55
|
-
log_message
|
56
|
-
end
|
57
26
|
end
|
58
27
|
end
|
59
28
|
end
|