ruby_event_store-outbox 0.0.4 → 0.0.5
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 +4 -4
- data/lib/ruby_event_store/outbox/cli.rb +26 -7
- data/lib/ruby_event_store/outbox/consumer.rb +51 -10
- data/lib/ruby_event_store/outbox/metrics.rb +16 -0
- data/lib/ruby_event_store/outbox/metrics/influx.rb +33 -0
- data/lib/ruby_event_store/outbox/metrics/null.rb +10 -0
- data/lib/ruby_event_store/outbox/version.rb +1 -1
- metadata +4 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 4a53d0609414ba7c53da1a8602042de223631437392a9a29b0e3dbb3d2015a61
         | 
| 4 | 
            +
              data.tar.gz: fa4dde249eecb5701f311d91a772fc12394b5412c6af18649d5ba604b11c9567
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: f8d236f0e1cb2236f3740e7f92ea19662df5af2350e729266c50635208e3d714ccef6dfdd1f9080ac9237467a88be351e07f656cd5ae2361223f4eb6c96fb3cc
         | 
| 7 | 
            +
              data.tar.gz: eb6718caa012a1d6f5ed52374108c0a74f2f7faf73ddb8208d17c2f0bdb636712ee30249865b8eb39d410c1f273721e0495fa53d2b3f32d06a8f2be98eb4459f
         | 
| @@ -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 | 
            -
                     | 
| 49 | 
            -
                      options. | 
| 50 | 
            -
                      options. | 
| 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 | 
            -
                   | 
| 13 | 
            -
                     | 
| 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 | 
            -
                     | 
| 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  | 
| 50 | 
            +
                    raise "Unknown format" if configuration.message_format != SIDEKIQ5_FORMAT
         | 
| 20 51 | 
             
                    @message_format = SIDEKIQ5_FORMAT
         | 
| 21 52 |  | 
| 22 53 | 
             
                    @gracefully_shutting_down = false
         | 
| @@ -44,8 +75,11 @@ module RubyEventStore | |
| 44 75 | 
             
                    Record.transaction do
         | 
| 45 76 | 
             
                      records_scope = Record.lock.where(format: message_format, enqueued_at: nil)
         | 
| 46 77 | 
             
                      records_scope = records_scope.where(split_key: split_keys) if !split_keys.nil?
         | 
| 47 | 
            -
                      records = records_scope.order("id ASC").limit( | 
| 48 | 
            -
                       | 
| 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
         | 
| 49 83 |  | 
| 50 84 | 
             
                      now = @clock.now.utc
         | 
| 51 85 | 
             
                      failed_record_ids = []
         | 
| @@ -58,18 +92,25 @@ module RubyEventStore | |
| 58 92 | 
             
                        end
         | 
| 59 93 | 
             
                      end
         | 
| 60 94 |  | 
| 61 | 
            -
                       | 
| 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)
         | 
| 62 98 |  | 
| 63 99 | 
             
                      logger.info "Sent #{records.size} messages from outbox table"
         | 
| 64 | 
            -
                       | 
| 100 | 
            +
                      true
         | 
| 65 101 | 
             
                    end
         | 
| 66 102 | 
             
                  rescue ActiveRecord::Deadlocked
         | 
| 67 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")
         | 
| 68 109 | 
             
                    false
         | 
| 69 110 | 
             
                  end
         | 
| 70 111 |  | 
| 71 112 | 
             
                  private
         | 
| 72 | 
            -
                  attr_reader :split_keys, :logger, :message_format
         | 
| 113 | 
            +
                  attr_reader :split_keys, :logger, :message_format, :batch_size, :metrics
         | 
| 73 114 |  | 
| 74 115 | 
             
                  def handle_one_record(now, record)
         | 
| 75 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,33 @@ | |
| 1 | 
            +
            require 'influxdb'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RubyEventStore
         | 
| 4 | 
            +
              module Outbox
         | 
| 5 | 
            +
                module Metrics
         | 
| 6 | 
            +
                  class Influx
         | 
| 7 | 
            +
                    def initialize(url)
         | 
| 8 | 
            +
                      @influxdb_client = InfluxDB::Client.new(url: url, async: true, time_precision: 'ns')
         | 
| 9 | 
            +
                    end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    def write_point_queue(status:, enqueued: 0, failed: 0)
         | 
| 12 | 
            +
                      write_point("ruby_event_store.outbox.queue", {
         | 
| 13 | 
            +
                        values: {
         | 
| 14 | 
            +
                          enqueued: enqueued,
         | 
| 15 | 
            +
                          failed: failed,
         | 
| 16 | 
            +
                        },
         | 
| 17 | 
            +
                        tags: {
         | 
| 18 | 
            +
                          status: status,
         | 
| 19 | 
            +
                        }
         | 
| 20 | 
            +
                      })
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    def write_point(series, data)
         | 
| 24 | 
            +
                      data[:timestamp] ||= Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond).to_i
         | 
| 25 | 
            +
                      @influxdb_client.write_point(series, data)
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    private
         | 
| 29 | 
            +
                    attr_reader :influxdb_client
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: ruby_event_store-outbox
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0. | 
| 4 | 
            +
              version: 0.0.5
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Arkency
         | 
| @@ -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
         |