pg_eventstore 1.13.4 → 2.0.0

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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +13 -0
  4. data/Dockerfile +3 -0
  5. data/README.md +20 -7
  6. data/db/migrations/10_setup_pg_cron.rb +23 -0
  7. data/db/migrations/11_add_events_link_global_position.sql +1 -0
  8. data/db/migrations/12_migrate_legacy_links.rb +83 -0
  9. data/db/migrations/13_remove_events_link_id.sql +6 -0
  10. data/db/migrations/14_remove_ids_events_id_index.sql +1 -0
  11. data/db/migrations/9_create_events_horizon.sql +21 -0
  12. data/docs/appending_events.md +1 -1
  13. data/docs/events_and_streams.md +1 -1
  14. data/docs/multiple_commands.md +16 -1
  15. data/lib/pg_eventstore/callbacks.rb +7 -5
  16. data/lib/pg_eventstore/cli/try_to_delete_subscriptions_set.rb +2 -2
  17. data/lib/pg_eventstore/client.rb +7 -5
  18. data/lib/pg_eventstore/commands/all_stream_read_grouped.rb +3 -3
  19. data/lib/pg_eventstore/commands/append.rb +3 -3
  20. data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +5 -2
  21. data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +1 -1
  22. data/lib/pg_eventstore/commands/link_to.rb +6 -6
  23. data/lib/pg_eventstore/commands/multiple.rb +2 -2
  24. data/lib/pg_eventstore/commands/regular_stream_read_grouped.rb +1 -1
  25. data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +1 -1
  26. data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +1 -1
  27. data/lib/pg_eventstore/connection.rb +1 -35
  28. data/lib/pg_eventstore/errors.rb +1 -1
  29. data/lib/pg_eventstore/event.rb +5 -5
  30. data/lib/pg_eventstore/extensions/options_extension.rb +40 -11
  31. data/lib/pg_eventstore/maintenance.rb +1 -1
  32. data/lib/pg_eventstore/queries/event_queries.rb +7 -7
  33. data/lib/pg_eventstore/queries/links_resolver.rb +6 -3
  34. data/lib/pg_eventstore/queries/partition_queries.rb +1 -1
  35. data/lib/pg_eventstore/queries/transaction_queries.rb +10 -4
  36. data/lib/pg_eventstore/query_builders/events_filtering.rb +2 -2
  37. data/lib/pg_eventstore/query_builders/partitions_filtering.rb +2 -2
  38. data/lib/pg_eventstore/stream.rb +1 -1
  39. data/lib/pg_eventstore/subscriptions/basic_runner.rb +4 -4
  40. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rb +1 -1
  41. data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rb +2 -11
  42. data/lib/pg_eventstore/subscriptions/events_processor.rb +1 -1
  43. data/lib/pg_eventstore/subscriptions/queries/subscription_command_queries.rb +5 -5
  44. data/lib/pg_eventstore/subscriptions/queries/subscription_queries.rb +2 -2
  45. data/lib/pg_eventstore/subscriptions/queries/subscription_service_queries.rb +78 -0
  46. data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rb +2 -2
  47. data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_queries.rb +1 -1
  48. data/lib/pg_eventstore/subscriptions/subscription.rb +7 -6
  49. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +2 -2
  50. data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +1 -3
  51. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +3 -16
  52. data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +10 -2
  53. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +11 -16
  54. data/lib/pg_eventstore/tasks/setup.rake +30 -31
  55. data/lib/pg_eventstore/utils.rb +8 -0
  56. data/lib/pg_eventstore/version.rb +1 -1
  57. data/lib/pg_eventstore/web/application.rb +5 -5
  58. data/lib/pg_eventstore/web/paginator/events_collection.rb +3 -3
  59. data/lib/pg_eventstore/web/paginator/helpers.rb +3 -3
  60. data/lib/pg_eventstore/web/subscriptions/helpers.rb +2 -2
  61. data/lib/pg_eventstore.rb +4 -4
  62. data/pg_eventstore.gemspec +1 -1
  63. data/sig/pg_eventstore/client.rbs +1 -1
  64. data/sig/pg_eventstore/commands/multiple.rbs +1 -1
  65. data/sig/pg_eventstore/event.rbs +4 -4
  66. data/sig/pg_eventstore/extensions/options_extension.rbs +9 -1
  67. data/sig/pg_eventstore/queries/event_queries.rbs +11 -11
  68. data/sig/pg_eventstore/queries/links_resolver.rbs +5 -5
  69. data/sig/pg_eventstore/queries/transaction_queries.rbs +2 -2
  70. data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rbs +0 -3
  71. data/sig/pg_eventstore/subscriptions/queries/subscription_queries.rbs +13 -13
  72. data/sig/pg_eventstore/subscriptions/queries/subscription_service_queries.rbs +19 -0
  73. data/sig/pg_eventstore/subscriptions/subscription_runner.rbs +0 -3
  74. data/sig/pg_eventstore/subscriptions/subscription_runners_feeder.rbs +10 -3
  75. data/sig/pg_eventstore/utils.rbs +10 -2
  76. metadata +11 -6
  77. data/lib/pg_eventstore/subscriptions/queries/service_queries.rb +0 -73
  78. data/lib/pg_eventstore/subscriptions/subscription_position_evaluation.rb +0 -195
  79. data/sig/pg_eventstore/subscriptions/queries/service_queries.rbs +0 -15
  80. data/sig/pg_eventstore/subscriptions/subscription_position_evaluation.rbs +0 -53
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
-
5
3
  module PgEventstore
6
4
  module Extensions
7
5
  # A very simple extension that implements a DSL for adding attr_accessors with default values,
@@ -72,16 +70,47 @@ module PgEventstore
72
70
  end
73
71
  end
74
72
 
75
- class Options < Set
76
- def add(option)
77
- @hash[option] = option
78
- self
73
+ class Options
74
+ include Enumerable
75
+
76
+ attr_reader :options
77
+ protected :options
78
+
79
+ # @param options [Array<PgEventstore::Extensions::OptionsExtension::Option>]
80
+ def initialize(options = [])
81
+ @options = options.to_h { [_1, true] }
79
82
  end
80
83
 
81
- # @param option [Symbol]
84
+ # @param option_name [Symbol]
82
85
  # @return [PgEventstore::Extensions::OptionsExtension::Option, nil]
83
- def [](option)
84
- @hash[Option.new(option)]
86
+ def [](option_name)
87
+ option = Option.new(option_name)
88
+ options.find { |key, _| key == option }&.dig(0)
89
+ end
90
+
91
+ # @param other [PgEventstore::Extensions::OptionsExtension::Options]
92
+ # @return [PgEventstore::Extensions::OptionsExtension::Options]
93
+ def +(other)
94
+ self.class.new(options.keys + other.options.keys)
95
+ end
96
+
97
+ # @param option [PgEventstore::Extensions::OptionsExtension::Option]
98
+ # @return [Boolean]
99
+ def include?(option)
100
+ options.key?(option)
101
+ end
102
+
103
+ # @return [Boolean]
104
+ def dup
105
+ self.class.new(options.keys)
106
+ end
107
+
108
+ def each(...)
109
+ options.keys.each(...)
110
+ end
111
+
112
+ def ==(other)
113
+ options.keys == other.options.keys
85
114
  end
86
115
  end
87
116
 
@@ -92,7 +121,7 @@ module PgEventstore
92
121
  # context of your object to determine the default value of the option
93
122
  # @return [Symbol]
94
123
  def option(opt_name, metadata: nil, &blk)
95
- self.options = (options + Options.new([Option.new(opt_name, metadata: metadata)])).freeze
124
+ self.options = (options + Options.new([Option.new(opt_name, metadata:)])).freeze
96
125
  warn_already_defined(opt_name)
97
126
  warn_already_defined(:"#{opt_name}=")
98
127
  define_method "#{opt_name}=" do |value|
@@ -112,7 +141,7 @@ module PgEventstore
112
141
 
113
142
  def inherited(klass)
114
143
  super
115
- klass.options = Options.new(options).freeze
144
+ klass.options = options.dup.freeze
116
145
  end
117
146
 
118
147
  private
@@ -25,7 +25,7 @@ module PgEventstore
25
25
  def delete_event(event, force: false)
26
26
  Commands::DeleteEvent.new(
27
27
  Queries.new(transactions: transaction_queries, maintenance: maintenance_queries)
28
- ).call(event, force: force)
28
+ ).call(event, force:)
29
29
  end
30
30
 
31
31
  private
@@ -41,18 +41,18 @@ module PgEventstore
41
41
  # Takes an array of potentially persisted events and loads their ids from db. Those ids can be later used to check
42
42
  # whether events are actually existing events.
43
43
  # @param events [Array<PgEventstore::Event>]
44
- # @return [Array<String>]
45
- def ids_from_db(events)
46
- sql_builder = SQLBuilder.new.from(Event::PRIMARY_TABLE_NAME).select('id')
44
+ # @return [Array<Integer>]
45
+ def global_positions_from_db(events)
46
+ sql_builder = SQLBuilder.new.from(Event::PRIMARY_TABLE_NAME).select('global_position')
47
47
  partition_attrs = events.map { |event| [event.stream&.context, event.stream&.stream_name, event.type] }.uniq
48
48
  partition_attrs.each do |context, stream_name, event_type|
49
49
  sql_builder.where_or('context = ? and stream_name = ? and type = ?', context, stream_name, event_type)
50
50
  end
51
- sql_builder.where('id = ANY(?::uuid[])', events.map(&:id))
51
+ sql_builder.where('global_position = ANY(?::bigint[])', events.map(&:global_position))
52
52
  raw_events = PgEventstore.connection.with do |conn|
53
53
  conn.exec_params(*sql_builder.to_exec_params)
54
54
  end.to_a
55
- raw_events.map { |attrs| attrs['id'] }
55
+ raw_events.map { |attrs| attrs['global_position'] }
56
56
  end
57
57
 
58
58
  # @param stream [PgEventstore::Stream]
@@ -84,7 +84,7 @@ module PgEventstore
84
84
  # @return [Array<PgEventstore::Event>]
85
85
  def insert(stream, events)
86
86
  sql_rows_for_insert, values = prepared_statements(stream, events)
87
- columns = %w[id data metadata stream_revision link_id link_partition_id type context stream_name stream_id]
87
+ columns = %w[id data metadata stream_revision link_global_position link_partition_id type context stream_name stream_id]
88
88
 
89
89
  sql = <<~SQL
90
90
  INSERT INTO events (#{columns.join(', ')})
@@ -129,7 +129,7 @@ module PgEventstore
129
129
  sql_rows_for_insert = events.map do |event|
130
130
  event = serializer.serialize(event)
131
131
  attributes = event.options_hash.slice(
132
- :id, :data, :metadata, :stream_revision, :link_id, :link_partition_id, :type
132
+ :id, :data, :metadata, :stream_revision, :link_global_position, :link_partition_id, :type
133
133
  )
134
134
 
135
135
  attributes = attributes.merge(stream.to_hash)
@@ -20,9 +20,9 @@ module PgEventstore
20
20
  link_events = raw_events.select { _1['link_partition_id'] }.group_by { _1['link_partition_id'] }
21
21
  return raw_events if link_events.empty?
22
22
 
23
- original_events = load_original_events(link_events).to_h { |attrs| [attrs['id'], attrs] }
23
+ original_events = load_original_events(link_events).to_h { |attrs| [attrs['global_position'], attrs] }
24
24
  raw_events.map do |attrs|
25
- original_event = original_events[attrs['link_id']]
25
+ original_event = original_events[attrs['link_global_position']]
26
26
  next attrs unless original_event
27
27
 
28
28
  original_event.merge('link' => attrs).merge(attrs.except(*original_event.keys))
@@ -37,7 +37,10 @@ module PgEventstore
37
37
  partitions = partition_queries.find_by_ids(link_events.keys)
38
38
  sql_builders = partitions.map do |partition|
39
39
  sql_builder = SQLBuilder.new.select('*').from(partition['table_name'])
40
- sql_builder.where('id = ANY(?::uuid[])', link_events[partition['id']].map { _1['link_id'] })
40
+ sql_builder.where(
41
+ 'global_position = ANY(?::bigint[])',
42
+ link_events[partition['id']].map { _1['link_global_position'] }
43
+ )
41
44
  end
42
45
  sql_builder = sql_builders[1..].each_with_object(sql_builders.first) do |builder, top_builder|
43
46
  top_builder.union(builder)
@@ -76,7 +76,7 @@ module PgEventstore
76
76
  # @return [Hash] partition attributes
77
77
  def create_event_type_partition(stream, event_type, stream_name_partition_name)
78
78
  attributes = {
79
- context: stream.context, stream_name: stream.stream_name, event_type: event_type,
79
+ context: stream.context, stream_name: stream.stream_name, event_type:,
80
80
  table_name: event_type_partition_name(stream, event_type)
81
81
  }
82
82
 
@@ -23,24 +23,30 @@ module PgEventstore
23
23
  end
24
24
 
25
25
  # @param level [Symbol] transaction isolation level
26
+ # @param read_only [Boolean] whether transaction is read-only
26
27
  # @return [void]
27
- def transaction(level = :serializable, &blk)
28
+ def transaction(level = :serializable, read_only: false, &blk)
28
29
  connection.with do |conn|
29
30
  # We are inside a transaction already - no need to start another one
30
31
  next yield if [PG::PQTRANS_ACTIVE, PG::PQTRANS_INTRANS].include?(conn.transaction_status)
31
32
 
32
- pg_transaction(ISOLATION_LEVELS[level], conn, &blk)
33
+ pg_transaction(ISOLATION_LEVELS[level], read_only, conn, &blk)
33
34
  end
34
35
  end
35
36
 
36
37
  private
37
38
 
38
39
  # @param level [String] PostgreSQL transaction isolation level
40
+ # @param read_only [Boolean]
39
41
  # @param pg_connection [PG::Connection]
40
42
  # @return [void]
41
- def pg_transaction(level, pg_connection, &_blk)
43
+ def pg_transaction(level, read_only, pg_connection, &)
42
44
  pg_connection.transaction do
43
- pg_connection.exec("SET TRANSACTION ISOLATION LEVEL #{level}")
45
+ if read_only
46
+ pg_connection.exec("SET TRANSACTION ISOLATION LEVEL #{level} READ ONLY")
47
+ else
48
+ pg_connection.exec("SET TRANSACTION ISOLATION LEVEL #{level}")
49
+ end
44
50
  yield
45
51
  end
46
52
  rescue PG::TRSerializationFailure, PG::TRDeadlockDetected
@@ -90,7 +90,7 @@ module PgEventstore
90
90
  stream_attrs in { context: String | NilClass => context }
91
91
  stream_attrs in { stream_name: String | NilClass => stream_name }
92
92
  stream_attrs in { stream_id: String | NilClass => stream_id }
93
- { context: context, stream_name: stream_name, stream_id: stream_id }
93
+ { context:, stream_name:, stream_id: }
94
94
  end
95
95
  streams || []
96
96
  end
@@ -111,7 +111,7 @@ module PgEventstore
111
111
  # @param stream_id [String, nil]
112
112
  # @return [void]
113
113
  def add_stream_attrs(context: nil, stream_name: nil, stream_id: nil)
114
- stream_attrs = { context: context, stream_name: stream_name, stream_id: stream_id }
114
+ stream_attrs = { context:, stream_name:, stream_id: }
115
115
  return unless correct_stream_filter?(stream_attrs)
116
116
 
117
117
  stream_attrs.compact!
@@ -24,7 +24,7 @@ module PgEventstore
24
24
  streams = streams&.map do |stream_attrs|
25
25
  stream_attrs in { context: String | NilClass => context }
26
26
  stream_attrs in { stream_name: String | NilClass => stream_name }
27
- { context: context, stream_name: stream_name }
27
+ { context:, stream_name: }
28
28
  end
29
29
  streams || []
30
30
  end
@@ -52,7 +52,7 @@ module PgEventstore
52
52
  # @param stream_name [String, nil]
53
53
  # @return [PgEventstore::SQLBuilder]
54
54
  def add_stream_attrs(context: nil, stream_name: nil)
55
- stream_attrs = { context: context, stream_name: stream_name }
55
+ stream_attrs = { context:, stream_name: }
56
56
  return @sql_builder unless self.class.correct_stream_filter?(stream_attrs)
57
57
 
58
58
  stream_attrs.compact!
@@ -67,7 +67,7 @@ module PgEventstore
67
67
  # @param keys [Array<Symbol>, nil]
68
68
  # @return [Hash<Symbol => String>]
69
69
  def deconstruct_keys(keys)
70
- hash = { context: context, stream_name: stream_name, stream_id: stream_id }
70
+ hash = { context:, stream_name:, stream_id: }
71
71
  return hash unless keys
72
72
 
73
73
  hash.slice(*keys)
@@ -46,7 +46,7 @@ module PgEventstore
46
46
  #
47
47
  # def initialize
48
48
  # @basic_runner = PgEventstore::BasicRunner.new(
49
- # run_interval: 1, async_shutdown_time: 2, recovery_strategies: recovery_strategies
49
+ # run_interval: 1, async_shutdown_time: 2, recovery_strategies:
50
50
  # )
51
51
  # @jobs_performed = 0
52
52
  # @jobs_limit = 3
@@ -224,7 +224,7 @@ module PgEventstore
224
224
 
225
225
  # @param state [Symbol]
226
226
  # @return [Object, nil] a result of evaluating of passed block
227
- def within_state(state, &_blk)
227
+ def within_state(state, &)
228
228
  synchronize do
229
229
  return unless @state.public_send("#{RunnerState::STATES.fetch(state)}?")
230
230
 
@@ -248,8 +248,8 @@ module PgEventstore
248
248
 
249
249
  private
250
250
 
251
- def synchronize(&blk)
252
- @mutex.synchronize(&blk)
251
+ def synchronize(&)
252
+ @mutex.synchronize(&)
253
253
  end
254
254
 
255
255
  # @return [void]
@@ -10,7 +10,7 @@ module PgEventstore
10
10
  # @param state [String]
11
11
  # @return [void]
12
12
  def update_subscriptions_set_state(subscriptions_set_lifecycle, state)
13
- subscriptions_set_lifecycle.persisted_subscriptions_set.update(state: state)
13
+ subscriptions_set_lifecycle.persisted_subscriptions_set.update(state:)
14
14
  end
15
15
 
16
16
  # @param subscriptions_lifecycle [PgEventstore::SubscriptionsLifecycle]
@@ -21,7 +21,7 @@ module PgEventstore
21
21
  def update_subscription_stats(subscription, stats, current_position)
22
22
  subscription.update(
23
23
  average_event_processing_time: stats.average_event_processing_time,
24
- current_position: current_position,
24
+ current_position:,
25
25
  total_processed_events: subscription.total_processed_events + 1
26
26
  )
27
27
  end
@@ -50,16 +50,7 @@ module PgEventstore
50
50
  # @param state [String]
51
51
  # @return [void]
52
52
  def update_subscription_state(subscription, state)
53
- subscription.update(state: state)
54
- end
55
-
56
- # @param subscription_position_evaluation [PgEventstore::SubscriptionPositionEvaluation]
57
- # @param state [String]
58
- # @return [void]
59
- def stop_position_evaluation(subscription_position_evaluation, state)
60
- return if state == 'running'
61
-
62
- subscription_position_evaluation.stop_evaluation
53
+ subscription.update(state:)
63
54
  end
64
55
  end
65
56
  end
@@ -21,7 +21,7 @@ module PgEventstore
21
21
  @basic_runner = BasicRunner.new(
22
22
  run_interval: 0,
23
23
  async_shutdown_time: graceful_shutdown_timeout,
24
- recovery_strategies: recovery_strategies
24
+ recovery_strategies:
25
25
  )
26
26
  attach_runner_callbacks
27
27
  end
@@ -21,15 +21,15 @@ module PgEventstore
21
21
  def find_or_create_by(subscription_id:, subscriptions_set_id:, command_name:, data:)
22
22
  transaction_queries.transaction do
23
23
  existing = find_by(
24
- subscription_id: subscription_id, subscriptions_set_id: subscriptions_set_id, command_name: command_name
24
+ subscription_id:, subscriptions_set_id:, command_name:
25
25
  )
26
26
  next existing if existing
27
27
 
28
28
  create(
29
- subscription_id: subscription_id,
30
- subscriptions_set_id: subscriptions_set_id,
31
- command_name: command_name,
32
- data: data
29
+ subscription_id:,
30
+ subscriptions_set_id:,
31
+ command_name:,
32
+ data:
33
33
  )
34
34
  end
35
35
  end
@@ -60,7 +60,7 @@ module PgEventstore
60
60
  # @return [Hash]
61
61
  # @raise [PgEventstore::RecordNotFound]
62
62
  def find!(id)
63
- find_by(id: id) || raise(RecordNotFound.new('subscriptions', id))
63
+ find_by(id:) || raise(RecordNotFound.new('subscriptions', id))
64
64
  end
65
65
 
66
66
  # @param attrs [Hash]
@@ -105,7 +105,7 @@ module PgEventstore
105
105
  updated_attrs
106
106
  end
107
107
 
108
- deserialize(updated_attrs)
108
+ deserialize(updated_attrs).slice(*attrs.keys)
109
109
  end
110
110
 
111
111
  # @param subscriptions_set_id [Integer] SubscriptionsSet#id
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ # @!visibility private
5
+ class SubscriptionServiceQueries
6
+ # @return [Integer]
7
+ DEFAULT_SAFE_POSITION = 0
8
+ private_constant :DEFAULT_SAFE_POSITION
9
+
10
+ # @!attribute connection
11
+ # @return [PgEventstore::Connection]
12
+ attr_reader :connection
13
+ private :connection
14
+
15
+ # @param connection [PgEventstore::Connection]
16
+ def initialize(connection)
17
+ @connection = connection
18
+ end
19
+
20
+ # @return [Integer]
21
+ def safe_global_position
22
+ result = transaction_queries.transaction(read_only: true) do
23
+ connection.with do |conn|
24
+ conn.exec(<<~SQL)
25
+ SELECT MAX(global_position) as global_position
26
+ FROM events_horizon
27
+ WHERE xact_id < pg_snapshot_xmin(pg_current_snapshot())
28
+ SQL
29
+ end
30
+ end
31
+
32
+ global_position = result.to_a.first&.dig('global_position')
33
+ return global_position if global_position
34
+
35
+ if global_position.nil? && !events_horizon_present?
36
+ init_events_horizon
37
+ return safe_global_position
38
+ end
39
+ # events_horizon table is not empty, but there is no safe position yet. Thus, we wait for the safe position by
40
+ # returning default value which will prevent from fetching events with gaps
41
+ DEFAULT_SAFE_POSITION
42
+ end
43
+
44
+ # @return [Boolean]
45
+ def events_horizon_present?
46
+ result = connection.with do |conn|
47
+ conn.exec(<<~SQL)
48
+ SELECT true as presence FROM events_horizon LIMIT 1
49
+ SQL
50
+ end
51
+ result.to_a.first&.dig('presence') || false
52
+ end
53
+
54
+ # @return [void]
55
+ def init_events_horizon
56
+ transaction_queries.transaction do
57
+ return if events_horizon_present?
58
+
59
+ max_pos = connection.with do |conn|
60
+ conn.exec('SELECT MAX(global_position) FROM events')
61
+ end
62
+ max_pos = max_pos.to_a.first&.dig('global_position') || DEFAULT_SAFE_POSITION
63
+ connection.with do |conn|
64
+ conn.exec_params(<<~SQL, [max_pos])
65
+ INSERT INTO events_horizon (global_position, xact_id) VALUES ($1, DEFAULT)
66
+ SQL
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ # @return [PgEventstore::TransactionQueries]
74
+ def transaction_queries
75
+ TransactionQueries.new(connection)
76
+ end
77
+ end
78
+ end
@@ -19,8 +19,8 @@ module PgEventstore
19
19
  # @return [PgEventstore::SubscriptionFeederCommands::Base]
20
20
  def find_or_create_by(subscriptions_set_id:, command_name:, data:)
21
21
  transaction_queries.transaction do
22
- find_by(subscriptions_set_id: subscriptions_set_id, command_name: command_name) ||
23
- create(subscriptions_set_id: subscriptions_set_id, command_name: command_name, data: data)
22
+ find_by(subscriptions_set_id:, command_name:) ||
23
+ create(subscriptions_set_id:, command_name:, data:)
24
24
  end
25
25
  end
26
26
 
@@ -62,7 +62,7 @@ module PgEventstore
62
62
  # @return [Hash]
63
63
  # @raise [PgEventstore::RecordNotFound]
64
64
  def find!(id)
65
- find_by(id: id) || raise(RecordNotFound.new('subscriptions_set', id))
65
+ find_by(id:) || raise(RecordNotFound.new('subscriptions_set', id))
66
66
  end
67
67
 
68
68
  # @param attrs [Hash]
@@ -113,7 +113,7 @@ module PgEventstore
113
113
  # @param attrs [Hash]
114
114
  # @return [Hash]
115
115
  def update(attrs)
116
- assign_attributes(subscription_queries.update(id, attrs: attrs, locked_by: locked_by))
116
+ assign_attributes(subscription_queries.update(id, attrs:, locked_by:))
117
117
  end
118
118
 
119
119
  # @param attrs [Hash]
@@ -129,8 +129,8 @@ module PgEventstore
129
129
  # @param force [Boolean]
130
130
  # @return [PgEventstore::Subscription]
131
131
  def lock!(lock_id, force: false)
132
- self.id = subscription_queries.find_or_create_by(set: set, name: name)[:id]
133
- self.locked_by = subscription_queries.lock!(id, lock_id, force: force)
132
+ self.id = subscription_queries.find_or_create_by(set:, name:)[:id]
133
+ self.locked_by = subscription_queries.lock!(id, lock_id, force:)
134
134
  reset_runtime_attributes
135
135
  self
136
136
  end
@@ -173,18 +173,19 @@ module PgEventstore
173
173
  # @return [void]
174
174
  def reset_runtime_attributes
175
175
  update(
176
- options: options,
176
+ options:,
177
177
  restart_count: 0,
178
178
  last_restarted_at: nil,
179
- max_restarts_number: max_restarts_number,
179
+ max_restarts_number:,
180
180
  chunk_query_interval: [chunk_query_interval, MIN_EVENTS_PULL_INTERVAL].max,
181
181
  last_chunk_fed_at: DEFAULT_TIMESTAMP,
182
182
  last_chunk_greatest_position: nil,
183
183
  last_error: nil,
184
184
  last_error_occurred_at: nil,
185
- time_between_restarts: time_between_restarts,
185
+ time_between_restarts:,
186
186
  state: RunnerState::STATES[:initial]
187
187
  )
188
+ reload
188
189
  end
189
190
 
190
191
  # @return [PgEventstore::SubscriptionQueries]
@@ -10,7 +10,7 @@ module PgEventstore
10
10
  # Determines how often to fetch events from the event store.
11
11
  # @see PgEventstore::Subscription::MIN_EVENTS_PULL_INTERVAL
12
12
  # @return [Float]
13
- EVENTS_PULL_INTERVAL = 0.1 # seconds
13
+ EVENTS_PULL_INTERVAL = 0.2 # seconds
14
14
  private_constant :EVENTS_PULL_INTERVAL
15
15
 
16
16
  attr_reader :config_name
@@ -122,7 +122,7 @@ module PgEventstore
122
122
  [
123
123
  RunnerRecoveryStrategies::RestoreConnection.new(config_name),
124
124
  RunnerRecoveryStrategies::RestoreSubscriptionFeeder.new(
125
- subscriptions_set_lifecycle: subscriptions_set_lifecycle
125
+ subscriptions_set_lifecycle:
126
126
  ),
127
127
  ]
128
128
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'benchmark'
4
-
5
3
  module PgEventstore
6
4
  # This class measures the performance of Subscription's handler and returns the average time required to process an
7
5
  # event.
@@ -23,7 +21,7 @@ module PgEventstore
23
21
  # @return [Object] the result of yielded block
24
22
  def track_exec_time
25
23
  result = nil
26
- time = Benchmark.realtime { result = yield }
24
+ time = Utils.benchmark { result = yield }
27
25
  synchronize do
28
26
  @timings.shift if @timings.size == TIMINGS_TO_KEEP
29
27
  @timings.push(time)
@@ -28,12 +28,10 @@ module PgEventstore
28
28
  # @param stats [PgEventstore::SubscriptionHandlerPerformance]
29
29
  # @param events_processor [PgEventstore::EventsProcessor]
30
30
  # @param subscription [PgEventstore::Subscription]
31
- # @param position_evaluation [PgEventstore::SubscriptionPositionEvaluation]
32
- def initialize(stats:, events_processor:, subscription:, position_evaluation:)
31
+ def initialize(stats:, events_processor:, subscription:)
33
32
  @stats = stats
34
33
  @events_processor = events_processor
35
34
  @subscription = subscription
36
- @position_evaluation = position_evaluation
37
35
 
38
36
  attach_callbacks
39
37
  end
@@ -42,19 +40,13 @@ module PgEventstore
42
40
  def next_chunk_query_opts
43
41
  @subscription.options.merge(
44
42
  from_position: next_chunk_global_position,
45
- max_count: estimate_events_number,
46
- to_position: @position_evaluation.last_safe_position
43
+ max_count: estimate_events_number
47
44
  )
48
45
  end
49
46
 
50
47
  # @return [Boolean]
51
48
  def time_to_feed?
52
- @subscription.last_chunk_fed_at + @subscription.chunk_query_interval <= Time.now.utc
53
- end
54
-
55
- # @return [Boolean]
56
- def next_chunk_safe?
57
- estimate_events_number == 0 ? false : @position_evaluation.evaluate(next_chunk_global_position).safe?
49
+ estimate_events_number > 0 && @subscription.last_chunk_fed_at + @subscription.chunk_query_interval <= Time.now.utc
58
50
  end
59
51
 
60
52
  private
@@ -108,11 +100,6 @@ module PgEventstore
108
100
  :change_state, :after,
109
101
  SubscriptionRunnerHandlers.setup_handler(:update_subscription_state, @subscription)
110
102
  )
111
- # Prevent dangling position evaluation runner when subscription changes the state to something except 'running'
112
- @events_processor.define_callback(
113
- :change_state, :after,
114
- SubscriptionRunnerHandlers.setup_handler(:stop_position_evaluation, @position_evaluation)
115
- )
116
103
  end
117
104
  end
118
105
  end
@@ -12,10 +12,13 @@ module PgEventstore
12
12
  # @param runners [Array<PgEventstore::SubscriptionRunner>]
13
13
  # @return [void]
14
14
  def feed(runners)
15
- runners = runners.select(&:running?).select(&:time_to_feed?).select(&:next_chunk_safe?)
15
+ runners = runners.select(&:running?).select(&:time_to_feed?)
16
16
  return if runners.empty?
17
17
 
18
- runners_query_options = runners.to_h { |runner| [runner.id, runner.next_chunk_query_opts] }
18
+ safe_pos = subscription_service_queries.safe_global_position
19
+ runners_query_options = runners.to_h do |runner|
20
+ [runner.id, runner.next_chunk_query_opts.merge(to_position: safe_pos)]
21
+ end
19
22
  grouped_events = subscription_queries.subscriptions_events(runners_query_options)
20
23
 
21
24
  runners.each do |runner|
@@ -34,5 +37,10 @@ module PgEventstore
34
37
  def subscription_queries
35
38
  SubscriptionQueries.new(connection)
36
39
  end
40
+
41
+ # @return [PgEventstore::SubscriptionServiceQueries]
42
+ def subscription_service_queries
43
+ SubscriptionServiceQueries.new(connection)
44
+ end
37
45
  end
38
46
  end