govuk_message_queue_consumer 3.1.0 → 3.2.0

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
- 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