ruby_event_store-outbox 0.0.3 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89f971d2c4366655940f73a71fd28822d7b07351b3365b8b7fcef56c1f667c38
4
- data.tar.gz: 4cf6356f1b559d277b5ef17a39ef4158eea7e6e35ec2eba0705e985fd1555c56
3
+ metadata.gz: 9457e402dabad2f00ec38ce0a6aecb12dcfdd303614b0ed6c8e391214e2eece1
4
+ data.tar.gz: 8545183b1d4720f5674cfdd5cf385666f2cfcd38ed2cc9fdd4bf8aa79c750835
5
5
  SHA512:
6
- metadata.gz: f25f09627e62a3a856709c8dd331b43d48ca8d7001690704e91de7d488291765f013986785131fd8bb24c383b2cd207802a4b34f960d09375287388c27ddd29b
7
- data.tar.gz: 788200eaba625da46a762304071fe6a24699f23cc6da179069672afbb7ad1ab57e22c391308ca04e90e21a872b5b373abdb83d188c797cb8bd6463c513788171
6
+ metadata.gz: dc5d885558ac2c2d3b446cc1499a9ceb8aaa541b29c1b85e8019cd832ca2572d883c4fa165abbcedc9470f36f29667becc0b7b937c49fc00b3a3aed2714699db
7
+ data.tar.gz: 4371dcb52922553adebe91a4fb36472c27bb0a4df0a76ec6782aaf4c48b57e424d55e7f8ba262b5f694d8d5c32e6062bbc504f8ca5e2edfb04767d0278cccc95
@@ -1,15 +1,16 @@
1
1
  require "optparse"
2
2
  require "ruby_event_store/outbox/version"
3
3
  require "ruby_event_store/outbox/consumer"
4
+ require "ruby_event_store/outbox/metrics"
4
5
 
5
6
  module RubyEventStore
6
7
  module Outbox
7
8
  class CLI
8
- Options = Struct.new(:database_url, :redis_url, :log_level, :split_keys, :message_format)
9
+ Options = Struct.new(:database_url, :redis_url, :log_level, :split_keys, :message_format, :batch_size, :metrics_url)
9
10
 
10
11
  class Parser
11
12
  def self.parse(argv)
12
- options = Options.new(nil, nil, :warn, nil, nil)
13
+ options = Options.new(nil, nil, :warn, nil, nil, 100)
13
14
  OptionParser.new do |option_parser|
14
15
  option_parser.banner = "Usage: res_outbox [options]"
15
16
 
@@ -33,6 +34,14 @@ module RubyEventStore
33
34
  options.split_keys = split_keys if !split_keys.empty?
34
35
  end
35
36
 
37
+ option_parser.on("--batch-size BATCH_SIZE", Integer, "Amount of records fetched in one fetch. Bigger value means more duplicated messages when network problems occur.") do |batch_size|
38
+ options.batch_size = batch_size
39
+ end
40
+
41
+ option_parser.on("--metrics-url METRICS_URL", "URI to metrics collector") do |metrics_url|
42
+ options.metrics_url = metrics_url
43
+ end
44
+
36
45
  option_parser.on_tail("--version", "Show version") do
37
46
  puts VERSION
38
47
  exit
@@ -44,16 +53,28 @@ module RubyEventStore
44
53
 
45
54
  def run(argv)
46
55
  options = Parser.parse(argv)
56
+ outbox_consumer = build_consumer(options)
57
+ outbox_consumer.init
58
+ outbox_consumer.run
59
+ end
60
+
61
+ def build_consumer(options)
47
62
  logger = Logger.new(STDOUT, level: options.log_level, progname: "RES-Outbox")
48
- outbox_consumer = RubyEventStore::Outbox::Consumer.new(
49
- options.message_format,
50
- options.split_keys,
63
+ # ActiveRecord::Base.logger = logger if options.log_level == :debug
64
+ # ActiveSupport::LogSubscriber.colorize_logging = false
65
+ consumer_configuration = Consumer::Configuration.new(
66
+ split_keys: options.split_keys,
67
+ message_format: options.message_format,
68
+ batch_size: options.batch_size,
51
69
  database_url: options.database_url,
52
70
  redis_url: options.redis_url,
71
+ )
72
+ metrics = Metrics.from_url(options.metrics_url)
73
+ outbox_consumer = RubyEventStore::Outbox::Consumer.new(
74
+ options,
53
75
  logger: logger,
76
+ metrics: metrics,
54
77
  )
55
- outbox_consumer.init
56
- outbox_consumer.run
57
78
  end
58
79
  end
59
80
  end
@@ -9,14 +9,46 @@ module RubyEventStore
9
9
  class Consumer
10
10
  SLEEP_TIME_WHEN_NOTHING_TO_DO = 0.1
11
11
 
12
- def initialize(format, split_keys, database_url:, redis_url:, clock: Time, logger:)
13
- @split_keys = split_keys
12
+ class Configuration
13
+ def initialize(
14
+ split_keys:,
15
+ message_format:,
16
+ batch_size:,
17
+ database_url:,
18
+ redis_url:
19
+ )
20
+ @split_keys = split_keys
21
+ @message_format = message_format
22
+ @batch_size = batch_size || 100
23
+ @database_url = database_url
24
+ @redis_url = redis_url
25
+ freeze
26
+ end
27
+
28
+ def with(overriden_options)
29
+ self.class.new(
30
+ split_keys: overriden_options.fetch(:split_keys, split_keys),
31
+ message_format: overriden_options.fetch(:message_format, message_format),
32
+ batch_size: overriden_options.fetch(:batch_size, batch_size),
33
+ database_url: overriden_options.fetch(:database_url, database_url),
34
+ redis_url: overriden_options.fetch(:redis_url, redis_url),
35
+ )
36
+ end
37
+
38
+ attr_reader :split_keys, :message_format, :batch_size, :database_url, :redis_url
39
+ end
40
+
41
+ def initialize(configuration, clock: Time, logger:, metrics:)
42
+ @split_keys = configuration.split_keys
14
43
  @clock = clock
15
- @redis = Redis.new(url: redis_url)
44
+ @redis = Redis.new(url: configuration.redis_url)
16
45
  @logger = logger
17
- ActiveRecord::Base.establish_connection(database_url) unless ActiveRecord::Base.connected?
46
+ @metrics = metrics
47
+ @batch_size = configuration.batch_size
48
+ ActiveRecord::Base.establish_connection(configuration.database_url) unless ActiveRecord::Base.connected?
49
+ ActiveRecord::Base.connection.execute("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;")
18
50
 
19
- raise "Unknown format" if format != SIDEKIQ5_FORMAT
51
+ raise "Unknown format" if configuration.message_format != SIDEKIQ5_FORMAT
20
52
  @message_format = SIDEKIQ5_FORMAT
21
53
 
22
54
  @gracefully_shutting_down = false
@@ -44,8 +76,11 @@ module RubyEventStore
44
76
  Record.transaction do
45
77
  records_scope = Record.lock.where(format: message_format, enqueued_at: nil)
46
78
  records_scope = records_scope.where(split_key: split_keys) if !split_keys.nil?
47
- records = records_scope.order("id ASC").limit(100)
48
- return false if records.empty?
79
+ records = records_scope.order("id ASC").limit(batch_size).to_a
80
+ if records.empty?
81
+ metrics.write_point_queue(status: "ok")
82
+ return false
83
+ end
49
84
 
50
85
  now = @clock.now.utc
51
86
  failed_record_ids = []
@@ -58,23 +93,25 @@ module RubyEventStore
58
93
  end
59
94
  end
60
95
 
61
- update_scope = if failed_record_ids.empty?
62
- records
63
- else
64
- records.where("id NOT IN (?)", failed_record_ids)
65
- end
66
- update_scope.update_all(enqueued_at: now)
96
+ updated_record_ids = records.map(&:id) - failed_record_ids
97
+ Record.where(id: updated_record_ids).update_all(enqueued_at: now)
98
+ metrics.write_point_queue(status: "ok", enqueued: updated_record_ids.size, failed: failed_record_ids.size)
67
99
 
68
100
  logger.info "Sent #{records.size} messages from outbox table"
69
- return true
101
+ true
70
102
  end
71
103
  rescue ActiveRecord::Deadlocked
72
104
  logger.warn "Outbox fetch deadlocked"
105
+ metrics.write_point_queue(status: "deadlocked")
106
+ false
107
+ rescue ActiveRecord::LockWaitTimeout
108
+ logger.warn "Outbox fetch lock timeout"
109
+ metrics.write_point_queue(status: "lock_timeout")
73
110
  false
74
111
  end
75
112
 
76
113
  private
77
- attr_reader :split_keys, :logger, :message_format
114
+ attr_reader :split_keys, :logger, :message_format, :batch_size, :metrics
78
115
 
79
116
  def handle_one_record(now, record)
80
117
  hash_payload = JSON.parse(record.payload)
@@ -0,0 +1,16 @@
1
+ require "ruby_event_store/outbox/metrics/null"
2
+ require "ruby_event_store/outbox/metrics/influx"
3
+
4
+ module RubyEventStore
5
+ module Outbox
6
+ module Metrics
7
+ def self.from_url(metrics_url)
8
+ if metrics_url.nil?
9
+ Null.new
10
+ else
11
+ Influx.new(metrics_url)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,41 @@
1
+ require 'influxdb'
2
+
3
+ module RubyEventStore
4
+ module Outbox
5
+ module Metrics
6
+ class Influx
7
+ def initialize(url)
8
+ uri = URI.parse(url)
9
+ params = CGI.parse(uri.query || "")
10
+ options = {
11
+ url: url,
12
+ async: true,
13
+ time_precision: 'ns',
14
+ }
15
+ options[:username] = params["username"]&.first if params["username"].present?
16
+ options[:password] = params["password"]&.first if params["password"].present?
17
+ @influxdb_client = InfluxDB::Client.new(**options)
18
+ end
19
+
20
+ def write_point_queue(status:, enqueued: 0, failed: 0)
21
+ write_point("ruby_event_store.outbox.queue", {
22
+ values: {
23
+ enqueued: enqueued,
24
+ failed: failed,
25
+ },
26
+ tags: {
27
+ status: status,
28
+ }
29
+ })
30
+ end
31
+
32
+ def write_point(series, data)
33
+ data[:timestamp] ||= Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond).to_i
34
+ @influxdb_client.write_point(series, data)
35
+ end
36
+
37
+ attr_reader :influxdb_client
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,10 @@
1
+ module RubyEventStore
2
+ module Outbox
3
+ module Metrics
4
+ class Null
5
+ def write_point_queue(status:, enqueued: 0, failed: 0)
6
+ end
7
+ end
8
+ end
9
+ end
10
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyEventStore
4
4
  module Outbox
5
- VERSION = "0.0.3"
5
+ VERSION = "0.0.8"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_event_store-outbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arkency
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-24 00:00:00.000000000 Z
11
+ date: 2020-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby_event_store
@@ -53,6 +53,9 @@ files:
53
53
  - lib/ruby_event_store/outbox.rb
54
54
  - lib/ruby_event_store/outbox/cli.rb
55
55
  - lib/ruby_event_store/outbox/consumer.rb
56
+ - lib/ruby_event_store/outbox/metrics.rb
57
+ - lib/ruby_event_store/outbox/metrics/influx.rb
58
+ - lib/ruby_event_store/outbox/metrics/null.rb
56
59
  - lib/ruby_event_store/outbox/record.rb
57
60
  - lib/ruby_event_store/outbox/sidekiq5_format.rb
58
61
  - lib/ruby_event_store/outbox/sidekiq_message_handler.rb