ruby_event_store-outbox 0.0.2 → 0.0.7

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