ruby_event_store-outbox 0.0.2 → 0.0.7

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: '08a5fc74b45a962065d79e01a6347919882bd97903ed30d5c722069d3e7ee617'
4
- data.tar.gz: 4238ad6ce88f09744ee98feb3b513ec6e3903db92579494109f0575098f23be5
3
+ metadata.gz: c8e2e739e68444fa47e69b09357b61b1d7b7136db13663028b97eaff9c8f3375
4
+ data.tar.gz: fbf657458847ca98c43698c4b40acea4993e0a9c752f788e112490e190d2415b
5
5
  SHA512:
6
- metadata.gz: 7939074f5b2fc85d9f4795103e0aaf38a59ca8e10b27f746a265b611fa4eda84a23806b1009845ce918dc61669bce4f5887d71c0139f152e4b04a9e7102a1fd9
7
- data.tar.gz: 7c6f7168c15b2c81cc462e63dd528ef1415b690eae8b3f49344bd2fdb8681338721ef2a2c0b511d75c265c1590031d799d268417ab69694714624fe0246e1e8a
6
+ metadata.gz: a2a08df4a60ba2e339e2914c20fe4a5638f18b20a55a580e2b048758bf273d20fbff1a05fe0eb183d38d625ce6ce94a2e8cc034a1136320ade6bd0e3adeec165
7
+ data.tar.gz: 8c6114269f96e992a134b5b731a7be3ba870c4a57d75bfc74129cd385b5100c93d450a9574a53fd3d1bf0489801a0839fc9d3fc9e35237d96672dc4c62baf0da
@@ -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,26 @@ 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
+ consumer_configuration = Consumer::Configuration.new(
64
+ split_keys: options.split_keys,
65
+ message_format: options.message_format,
66
+ batch_size: options.batch_size,
51
67
  database_url: options.database_url,
52
68
  redis_url: options.redis_url,
69
+ )
70
+ metrics = Metrics.from_url(options.metrics_url)
71
+ outbox_consumer = RubyEventStore::Outbox::Consumer.new(
72
+ options,
53
73
  logger: logger,
74
+ metrics: metrics,
54
75
  )
55
- outbox_consumer.init
56
- outbox_consumer.run
57
76
  end
58
77
  end
59
78
  end
@@ -9,14 +9,45 @@ 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?
18
49
 
19
- raise "Unknown format" if format != SIDEKIQ5_FORMAT
50
+ raise "Unknown format" if configuration.message_format != SIDEKIQ5_FORMAT
20
51
  @message_format = SIDEKIQ5_FORMAT
21
52
 
22
53
  @gracefully_shutting_down = false
@@ -32,7 +63,10 @@ module RubyEventStore
32
63
  def run
33
64
  while !@gracefully_shutting_down do
34
65
  was_something_changed = one_loop
35
- sleep SLEEP_TIME_WHEN_NOTHING_TO_DO if !was_something_changed
66
+ if !was_something_changed
67
+ STDOUT.flush
68
+ sleep SLEEP_TIME_WHEN_NOTHING_TO_DO
69
+ end
36
70
  end
37
71
  logger.info "Gracefully shutting down"
38
72
  end
@@ -41,8 +75,11 @@ module RubyEventStore
41
75
  Record.transaction do
42
76
  records_scope = Record.lock.where(format: message_format, enqueued_at: nil)
43
77
  records_scope = records_scope.where(split_key: split_keys) if !split_keys.nil?
44
- records = records_scope.order("id ASC").limit(100)
45
- return false if records.empty?
78
+ records = records_scope.order("id ASC").limit(batch_size).to_a
79
+ if records.empty?
80
+ metrics.write_point_queue(status: "ok")
81
+ return false
82
+ end
46
83
 
47
84
  now = @clock.now.utc
48
85
  failed_record_ids = []
@@ -55,20 +92,25 @@ module RubyEventStore
55
92
  end
56
93
  end
57
94
 
58
- update_scope = if failed_record_ids.empty?
59
- records
60
- else
61
- records.where("id NOT IN (?)", failed_record_ids)
62
- end
63
- update_scope.update_all(enqueued_at: now)
95
+ updated_record_ids = records.map(&:id) - failed_record_ids
96
+ Record.where(id: updated_record_ids).update_all(enqueued_at: now)
97
+ metrics.write_point_queue(status: "ok", enqueued: updated_record_ids.size, failed: failed_record_ids.size)
64
98
 
65
99
  logger.info "Sent #{records.size} messages from outbox table"
66
- return true
100
+ true
67
101
  end
102
+ rescue ActiveRecord::Deadlocked
103
+ logger.warn "Outbox fetch deadlocked"
104
+ metrics.write_point_queue(status: "deadlocked")
105
+ false
106
+ rescue ActiveRecord::LockWaitTimeout
107
+ logger.warn "Outbox fetch lock timeout"
108
+ metrics.write_point_queue(status: "lock_timeout")
109
+ false
68
110
  end
69
111
 
70
112
  private
71
- attr_reader :split_keys, :logger, :message_format
113
+ attr_reader :split_keys, :logger, :message_format, :batch_size, :metrics
72
114
 
73
115
  def handle_one_record(now, record)
74
116
  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
@@ -0,0 +1,19 @@
1
+ require "sidekiq"
2
+
3
+ module RubyEventStore
4
+ module Outbox
5
+ class SidekiqMessageHandler
6
+ def initialize
7
+ @sidekiq = Sidekiq::Client.new(Sidekiq.redis_pool)
8
+ end
9
+
10
+ def init
11
+ end
12
+
13
+ def handle_one_record(now, record)
14
+ hash_payload = JSON.parse(record.payload)
15
+ @sidekiq.__send__(:raw_push, [hash_payload])
16
+ end
17
+ end
18
+ end
19
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyEventStore
4
4
  module Outbox
5
- VERSION = "0.0.2"
5
+ VERSION = "0.0.7"
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.2
4
+ version: 0.0.7
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-12 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,8 +53,12 @@ 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
61
+ - lib/ruby_event_store/outbox/sidekiq_message_handler.rb
58
62
  - lib/ruby_event_store/outbox/sidekiq_scheduler.rb
59
63
  - lib/ruby_event_store/outbox/version.rb
60
64
  homepage: https://railseventstore.org