govuk_message_queue_consumer 3.1.0 → 3.2.0

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
- SHA1:
3
- metadata.gz: 48237ae34d62ad2c19bfa687518f0f83b8953855
4
- data.tar.gz: 3078b5856d7cbd3f1b3eeb2365f7a924337ba9bd
2
+ SHA256:
3
+ metadata.gz: 92d5ee51b1ed29b2d86c75475cf767c3cb978d19bfe17cc92cab3cadb9de4bce
4
+ data.tar.gz: 9f2667dff5e909beccace001dd9f2cef64911253f019ec86009ba210e722a69b
5
5
  SHA512:
6
- metadata.gz: a821d0c9b7c3d65de3d8aa88cafca57ac50ea3196f5add31a3108edfcfe90664f723ab4de9357f697e17512fead995fb0036a7be9f0270379908ee16769461e5
7
- data.tar.gz: 7b1c8112e94b10db93a0038377e980a06fbfe4acfbe8953308a1a90302e44150fd2972f6880533e8cddb952a30104c23a4040f7af8cb83b3949577c631e29171
6
+ metadata.gz: eedbec227fd052d8427c9b8de008550467b3bc10d57e88f0a2605c1a5053b76488bec2b9d4964aceb5bcda0d67953ba4ec6045ad10bee52224ce4ac7d041efb8
7
+ data.tar.gz: c7f8c8d4a6b8273a30f8483c88daf3d7f712b13ac933ee9c64b844f72e6713ba763c6fe019e61316562b989a21c390d096a38cb73ed1ebf22267fe6daa6ed659
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 3.2.0
2
+
3
+ - Add batch process capabilities
4
+ - Refactor `process_chain` to `message_consumer`
5
+
1
6
  # 3.1.0
2
7
 
3
8
  - Change Airbrake to GovukError
@@ -5,5 +5,7 @@ require 'govuk_message_queue_consumer/version'
5
5
  require 'govuk_message_queue_consumer/heartbeat_processor'
6
6
  require 'govuk_message_queue_consumer/json_processor'
7
7
  require 'govuk_message_queue_consumer/message'
8
+ require 'govuk_message_queue_consumer/message_consumer'
8
9
  require 'govuk_message_queue_consumer/consumer'
10
+ require 'govuk_message_queue_consumer/batch_consumer'
9
11
  require 'govuk_message_queue_consumer/rabbitmq_config'
@@ -0,0 +1,69 @@
1
+ module GovukMessageQueueConsumer
2
+ class BatchConsumer < Consumer
3
+ HANDLE_BATCHES = true
4
+ DEFAULT_BATCH_SIZE = 100
5
+ DEFAULT_BATCH_TIMEOUT = 5
6
+ # we want to increase the prefetch size here to the batch size
7
+ # so that we don't need to do multiple fetches for a batch.
8
+ NUMBER_OF_MESSAGES_TO_PREFETCH = DEFAULT_BATCH_SIZE
9
+
10
+ def initialize(options={})
11
+ opts = options.dup
12
+ @batch_size = opts.delete(:batch_size) || DEFAULT_BATCH_SIZE
13
+ @batch_timeout = opts.delete(:batch_timeout) || DEFAULT_BATCH_TIMEOUT
14
+ super(opts)
15
+ end
16
+
17
+ def run
18
+ @rabbitmq_connection.start
19
+ @running = true
20
+ while @running do
21
+ process_batch
22
+ end
23
+ end
24
+
25
+ # used for testing to stop processing in a different thread
26
+ def stop
27
+ @running = false
28
+ end
29
+
30
+ private
31
+
32
+ def process_batch
33
+ messages = []
34
+ with_timeout do
35
+ while messages.count < @batch_size do
36
+ delivery_info, headers, payload = queue.pop(manual_ack: true)
37
+ messages << Message.new(payload, headers, delivery_info) if payload
38
+ end
39
+ end
40
+
41
+ if messages.any?
42
+ @statsd_client.count("#{@queue_name}.started", messages.count)
43
+ @statsd_client.increment("#{@queue_name}.batch_started")
44
+ message_consumer.process(messages)
45
+
46
+ status_counts = messages.map(&:status).each_with_object(Hash.new(0)) { |s, h| h[s] += 1 }
47
+ status_counts.each do |status, count|
48
+ @statsd_client.count("#{@queue_name}.#{status}", count)
49
+ end
50
+ @statsd_client.increment("#{@queue_name}.batch_complete")
51
+ end
52
+ rescue Exception => e
53
+ @statsd_client.increment("#{@queue_name}.uncaught_exception")
54
+ GovukError.notify(e) if defined?(GovukError)
55
+ @logger.error "Uncaught exception in processor: \n\n #{e.class}: #{e.message}\n\n#{e.backtrace.join("\n")}"
56
+
57
+ exit(1) # Ensure rabbitmq requeues outstanding messages
58
+ end
59
+
60
+ def with_timeout
61
+ begin
62
+ Timeout.timeout(@batch_timeout) do
63
+ yield
64
+ end
65
+ rescue Timeout::Error
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,5 +1,6 @@
1
1
  module GovukMessageQueueConsumer
2
2
  class Consumer
3
+ HANDLE_BATCHES = false
3
4
  # Only fetch one message at a time on the channel.
4
5
  #
5
6
  # By default, queues will grab messages eagerly, which reduces latency.
@@ -32,7 +33,7 @@ module GovukMessageQueueConsumer
32
33
  begin
33
34
  message = Message.new(payload, headers, delivery_info)
34
35
  @statsd_client.increment("#{@queue_name}.started")
35
- processor_chain.process(message)
36
+ message_consumer.process(message)
36
37
  @statsd_client.increment("#{@queue_name}.#{message.status}")
37
38
  rescue Exception => e
38
39
  @statsd_client.increment("#{@queue_name}.uncaught_exception")
@@ -48,15 +49,25 @@ module GovukMessageQueueConsumer
48
49
  class NullStatsd
49
50
  def increment(_key)
50
51
  end
52
+
53
+ def count(_key, _volume)
54
+ end
51
55
  end
52
56
 
53
- def processor_chain
54
- @processor_chain ||= HeartbeatProcessor.new(JSONProcessor.new(@processor))
57
+ def message_consumer
58
+ @message_consumer ||= MessageConsumer.new(
59
+ processors: [
60
+ HeartbeatProcessor.new,
61
+ JSONProcessor.new,
62
+ @processor,
63
+ ],
64
+ handle_batches: self.class::HANDLE_BATCHES,
65
+ )
55
66
  end
56
67
 
57
68
  def queue
58
69
  @queue ||= begin
59
- channel.prefetch(NUMBER_OF_MESSAGES_TO_PREFETCH)
70
+ channel.prefetch(self.class::NUMBER_OF_MESSAGES_TO_PREFETCH)
60
71
  channel.queue(@queue_name, no_declare: true)
61
72
  end
62
73
  end
@@ -1,16 +1,13 @@
1
1
  module GovukMessageQueueConsumer
2
2
  class HeartbeatProcessor
3
- def initialize(next_processor)
4
- @next_processor = next_processor
5
- end
6
-
7
3
  def process(message)
8
4
  # Ignore heartbeat messages
9
5
  if message.headers.content_type == "application/x-heartbeat"
10
6
  message.ack
11
- else
12
- @next_processor.process(message)
7
+ return false
13
8
  end
9
+
10
+ true
14
11
  end
15
12
  end
16
13
  end
@@ -2,19 +2,16 @@ module GovukMessageQueueConsumer
2
2
  class JSONProcessor
3
3
  JSON_FORMAT = "application/json".freeze
4
4
 
5
- def initialize(next_processor)
6
- @next_processor = next_processor
7
- end
8
-
9
5
  def process(message)
10
6
  if message.headers.content_type == JSON_FORMAT
11
7
  message.payload = JSON.parse(message.payload)
12
8
  end
13
9
 
14
- @next_processor.process(message)
10
+ true
15
11
  rescue JSON::ParserError => e
16
12
  GovukError.notify(e) if defined?(GovukError)
17
13
  message.discard
14
+ false
18
15
  end
19
16
  end
20
17
  end
@@ -0,0 +1,27 @@
1
+ module GovukMessageQueueConsumer
2
+ class MessageConsumer
3
+ def initialize(processors:, handle_batches:)
4
+ @processors = processors
5
+ @handle_batches = handle_batches
6
+ end
7
+
8
+ def process(records)
9
+ @processors.inject(Array(records)) do |remaining_records, processor|
10
+ if handles_batches?(processor)
11
+ processor.process(remaining_records)
12
+ else
13
+ remaining_records.select { |record| processor.process(record) }
14
+ end
15
+ end
16
+ end
17
+
18
+ def handles_batches?(processor)
19
+ case processor
20
+ when HeartbeatProcessor,JSONProcessor
21
+ false
22
+ else
23
+ @handle_batches
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,3 +1,3 @@
1
1
  module GovukMessageQueueConsumer
2
- VERSION = '3.1.0'
2
+ VERSION = '3.2.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: govuk_message_queue_consumer
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GOV.UK Dev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-27 00:00:00.000000000 Z
11
+ date: 2018-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -66,6 +66,34 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bunny-mock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry-byebug
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
69
97
  description: Avoid writing boilerplate code in order to consume messages from an AMQP
70
98
  message queue. Plug in queue configuration, and how to process each message.
71
99
  email:
@@ -78,10 +106,12 @@ files:
78
106
  - LICENCE
79
107
  - README.md
80
108
  - lib/govuk_message_queue_consumer.rb
109
+ - lib/govuk_message_queue_consumer/batch_consumer.rb
81
110
  - lib/govuk_message_queue_consumer/consumer.rb
82
111
  - lib/govuk_message_queue_consumer/heartbeat_processor.rb
83
112
  - lib/govuk_message_queue_consumer/json_processor.rb
84
113
  - lib/govuk_message_queue_consumer/message.rb
114
+ - lib/govuk_message_queue_consumer/message_consumer.rb
85
115
  - lib/govuk_message_queue_consumer/rabbitmq_config.rb
86
116
  - lib/govuk_message_queue_consumer/test_helpers.rb
87
117
  - lib/govuk_message_queue_consumer/test_helpers/mock_message.rb
@@ -106,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
136
  version: '0'
107
137
  requirements: []
108
138
  rubyforge_project:
109
- rubygems_version: 2.6.13
139
+ rubygems_version: 2.7.3
110
140
  signing_key:
111
141
  specification_version: 4
112
142
  summary: AMQP message queue consumption with GOV.UK conventions