pg_eventstore 1.0.3 → 1.0.4

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: d164756463155daaa363ac2c51d2b85097559d471912af64298e988d6a1831bb
4
- data.tar.gz: a3b727ae69c0cc4d897b9c1bc9ca4d2495677280f64f4af177ae790cefdfec1a
3
+ metadata.gz: d5a69951bc18bd74869eb843b9e017a1c1f238bfc17750400e30b99f7d7b9f2d
4
+ data.tar.gz: 56ce94941096227944e8d5f11b9c7f9ca2f1fd816ad477cb02abe58604945c47
5
5
  SHA512:
6
- metadata.gz: bc96f8a831df8271c74aa4ec9570016c740f454ce3b48cca41e070ec41166d4aea46e68d5630707c30c797aa4ec922f982c53c147c90e2e665eb7f14118ed84b
7
- data.tar.gz: c1f8daa5fb4ff0b243d3d34ae8846aef18d20dadf0284f7ac5e9d8c159f2f2fdfe622124c6186a4cc566a445c90228b27814ad93d7b4db3cdf98f793fc564670
6
+ metadata.gz: f2f682990d1450b874e3d1ad9cf68e3e1cf7dcd546c78bb101982c5a03c5f355f31101442b2dd9a2c59531535e02eff01b12b119c79beca331f7262e927ebe5c
7
+ data.tar.gz: 2b2624a388eba5772c6a3bdd601ffe6fbcdb3c08c0d3c4585c1dfcf03862cf94f19e39db453950294f1a5a01a35e5ee33c975632c7ba6a2c966fcb85ff6dbb5d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.0.4]
4
+ - Fix bug which caused slow Subscriptions to stop processing new events
5
+ - Optimize Subscriptions update queries
6
+
3
7
  ## [1.0.3]
4
8
  - Do no update `Subscription#last_chunk_fed_at` if the chunk is empty
5
9
 
@@ -203,4 +203,7 @@ module PgEventstore
203
203
  @event_types = event_types
204
204
  end
205
205
  end
206
+
207
+ class EmptyChunkFedError < Error
208
+ end
206
209
  end
@@ -101,6 +101,22 @@ module PgEventstore
101
101
  deserialize(updated_attrs)
102
102
  end
103
103
 
104
+ # @param subscriptions_set_id [Integer] SubscriptionsSet#id
105
+ # @param subscriptions_ids [Array<Integer>] Array of Subscription#id
106
+ # @return [Hash<Integer => Time>]
107
+ def ping_all(subscriptions_set_id, subscriptions_ids)
108
+ pg_result = connection.with do |conn|
109
+ sql = <<~SQL
110
+ UPDATE subscriptions SET updated_at = $1 WHERE locked_by = $2 AND id = ANY($3::int[])
111
+ RETURNING id, updated_at
112
+ SQL
113
+ conn.exec_params(sql, [Time.now.utc, subscriptions_set_id, subscriptions_ids])
114
+ end
115
+ pg_result.to_h do |attrs|
116
+ [attrs['id'], attrs['updated_at']]
117
+ end
118
+ end
119
+
104
120
  # @param query_options [Hash{Integer => Hash}] runner_id/query options association
105
121
  # @return [Hash{Integer => Hash}] runner_id/events association
106
122
  def subscriptions_events(query_options)
@@ -20,6 +20,8 @@ module PgEventstore
20
20
  # @param raw_events [Array<Hash>]
21
21
  # @return [void]
22
22
  def feed(raw_events)
23
+ raise EmptyChunkFedError.new("Empty chunk was fed!") if raw_events.empty?
24
+
23
25
  callbacks.run_callbacks(:feed, global_position(raw_events.last))
24
26
  @raw_events.push(*raw_events)
25
27
  end
@@ -69,11 +71,9 @@ module PgEventstore
69
71
  callbacks.run_callbacks(:change_state, ...)
70
72
  end
71
73
 
72
- # @param raw_event [Hash, nil]
73
- # @return [Integer, nil]
74
+ # @param raw_event [Hash]
75
+ # @return [Integer]
74
76
  def global_position(raw_event)
75
- return unless raw_event
76
-
77
77
  raw_event['link'] ? raw_event['link']['global_position'] : raw_event['global_position']
78
78
  end
79
79
  end
@@ -73,6 +73,25 @@ module PgEventstore
73
73
  # @return [Time]
74
74
  attribute(:updated_at)
75
75
 
76
+ class << self
77
+ # @param subscriptions_set_id [Integer] SubscriptionsSet#id
78
+ # @param subscriptions [Array<PgEventstoreSubscription>]
79
+ # @return [void]
80
+ def ping_all(subscriptions_set_id, subscriptions)
81
+ result = subscription_queries.ping_all(subscriptions_set_id, subscriptions.map(&:id))
82
+ subscriptions.each do |subscription|
83
+ next unless result[subscription.id]
84
+
85
+ subscription.assign_attributes(updated_at: result[subscription.id])
86
+ end
87
+ end
88
+
89
+ # @return [PgEventstore::SubscriptionQueries]
90
+ def subscription_queries
91
+ SubscriptionQueries.new(connection)
92
+ end
93
+ end
94
+
76
95
  def options=(val)
77
96
  @options = Utils.deep_transform_keys(val, &:to_sym)
78
97
  end
@@ -135,6 +154,7 @@ module PgEventstore
135
154
 
136
155
  private
137
156
 
157
+ # @return [void]
138
158
  def reset_runtime_attributes
139
159
  update(
140
160
  options: options,
@@ -151,8 +171,9 @@ module PgEventstore
151
171
  )
152
172
  end
153
173
 
174
+ # @return [PgEventstore::SubscriptionQueries]
154
175
  def subscription_queries
155
- SubscriptionQueries.new(self.class.connection)
176
+ self.class.subscription_queries
156
177
  end
157
178
  end
158
179
  end
@@ -24,7 +24,7 @@ module PgEventstore
24
24
  @commands_handler = CommandsHandler.new(@config_name, self, @runners)
25
25
  @basic_runner = BasicRunner.new(0.2, 0)
26
26
  @force_lock = false
27
- @refreshed_at = Time.at(0)
27
+ @subscriptions_pinged_at = Time.at(0)
28
28
  attach_runner_callbacks
29
29
  end
30
30
 
@@ -100,6 +100,7 @@ module PgEventstore
100
100
  @basic_runner.define_callback(:after_runner_died, :after, method(:restart_runner))
101
101
  @basic_runner.define_callback(:process_async, :before, method(:ping_subscriptions_set))
102
102
  @basic_runner.define_callback(:process_async, :before, method(:process_async))
103
+ @basic_runner.define_callback(:process_async, :after, method(:ping_subscriptions))
103
104
  @basic_runner.define_callback(:after_runner_stopped, :before, method(:after_runner_stopped))
104
105
  @basic_runner.define_callback(:before_runner_restored, :after, method(:update_runner_restarts))
105
106
  end
@@ -140,10 +141,25 @@ module PgEventstore
140
141
 
141
142
  # @return [void]
142
143
  def ping_subscriptions_set
143
- return unless subscriptions_set.updated_at > Time.now.utc - HEARTBEAT_INTERVAL
144
+ return if subscriptions_set.updated_at > Time.now.utc - HEARTBEAT_INTERVAL
144
145
 
145
146
  subscriptions_set.update(updated_at: Time.now.utc)
146
- @refreshed_at = Time.now.utc
147
+ end
148
+
149
+ # @return [void]
150
+ def ping_subscriptions
151
+ return if @subscriptions_pinged_at > Time.now.utc - HEARTBEAT_INTERVAL
152
+
153
+ runners = @runners.select do |runner|
154
+ next false unless runner.running?
155
+
156
+ runner.subscription.updated_at < Time.now.utc - HEARTBEAT_INTERVAL
157
+ end
158
+ unless runners.empty?
159
+ Subscription.using_connection(@config_name).ping_all(subscriptions_set.id, runners.map(&:subscription))
160
+ end
161
+
162
+ @subscriptions_pinged_at = Time.now.utc
147
163
  end
148
164
 
149
165
  # @return [void]
@@ -11,6 +11,7 @@ module PgEventstore
11
11
  extend Forwardable
12
12
 
13
13
  MAX_EVENTS_PER_CHUNK = 1_000
14
+ MIN_EVENTS_PER_CHUNK = 10
14
15
  INITIAL_EVENTS_PER_CHUNK = 10
15
16
 
16
17
  attr_reader :subscription
@@ -56,7 +57,10 @@ module PgEventstore
56
57
  return INITIAL_EVENTS_PER_CHUNK if @stats.average_event_processing_time.zero?
57
58
 
58
59
  events_per_chunk = (@subscription.chunk_query_interval / @stats.average_event_processing_time).round
59
- [[events_per_chunk, MAX_EVENTS_PER_CHUNK].min - @events_processor.events_left_in_chunk, 0].max
60
+ events_to_fetch = [events_per_chunk, MAX_EVENTS_PER_CHUNK].min - @events_processor.events_left_in_chunk
61
+ return 0 if events_to_fetch < 0 # We still have a lot of events in the chunk - no need to fetch more
62
+
63
+ [events_to_fetch, MIN_EVENTS_PER_CHUNK].max
60
64
  end
61
65
 
62
66
  # @return [void]
@@ -103,12 +107,9 @@ module PgEventstore
103
107
  @subscription.update(last_error: Utils.error_info(error), last_error_occurred_at: Time.now.utc)
104
108
  end
105
109
 
106
- # @param global_position [Integer, nil]
110
+ # @param global_position [Integer]
107
111
  # @return [void]
108
112
  def update_subscription_chunk_stats(global_position)
109
- # nil means subscriptions events query were executed, but there were no new events
110
- return @subscription.update(updated_at: Time.now.utc) if global_position.nil?
111
-
112
113
  @subscription.update(last_chunk_fed_at: Time.now.utc, last_chunk_greatest_position: global_position)
113
114
  end
114
115
 
@@ -17,8 +17,9 @@ module PgEventstore
17
17
 
18
18
  runners_query_options = runners.to_h { |runner| [runner.id, runner.next_chunk_query_opts] }
19
19
  grouped_events = subscription_queries.subscriptions_events(runners_query_options)
20
+
20
21
  runners.each do |runner|
21
- runner.feed(grouped_events[runner.id] || [])
22
+ runner.feed(grouped_events[runner.id]) if grouped_events[runner.id]
22
23
  end
23
24
  end
24
25
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgEventstore
4
- VERSION = "1.0.3"
4
+ VERSION = "1.0.4"
5
5
  end
@@ -43,7 +43,8 @@ module PgEventstore
43
43
  # @return [String] html status
44
44
  def colored_state(state, updated_at)
45
45
  if state == RunnerState::STATES[:running]
46
- if updated_at < Time.now.utc - SubscriptionFeeder::HEARTBEAT_INTERVAL
46
+ # -1 is added as a margin to prevent false-positive result
47
+ if updated_at < Time.now.utc - SubscriptionFeeder::HEARTBEAT_INTERVAL - 1
47
48
  title = <<~TEXT
48
49
  Something is wrong. Last update was more than #{SubscriptionFeeder::HEARTBEAT_INTERVAL} seconds \
49
50
  ago(#{updated_at}).
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_eventstore
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Dzyzenko
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-05-17 00:00:00.000000000 Z
11
+ date: 2024-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg