pg_eventstore 1.0.2 → 1.0.4

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: df0f1cb9814509329a52545776c5f3b3445b98f436cba8491c607ff6a4aaeea2
4
- data.tar.gz: 6044ef3293026dc4f4e952d5591ed660006cd79b14ab14d744b150f0d3e3a6ad
3
+ metadata.gz: d5a69951bc18bd74869eb843b9e017a1c1f238bfc17750400e30b99f7d7b9f2d
4
+ data.tar.gz: 56ce94941096227944e8d5f11b9c7f9ca2f1fd816ad477cb02abe58604945c47
5
5
  SHA512:
6
- metadata.gz: e66202ec3ff994a858fda5266d230d0a47eb12a6fbc921f602f4c73f09df7a5a34e8c3681f1a0889b58a42e82f1a32a59d8e1d73a376af51930774d675c1a4f9
7
- data.tar.gz: efe0cdbbf6e0fbb123ac767c7bf9e355d15b6159279cb2b2fbca29a15660128a6ecd3b799ca501b83f19f8aefa9904446b64f62fb3f8cd7f200bf5d562c569a0
6
+ metadata.gz: f2f682990d1450b874e3d1ad9cf68e3e1cf7dcd546c78bb101982c5a03c5f355f31101442b2dd9a2c59531535e02eff01b12b119c79beca331f7262e927ebe5c
7
+ data.tar.gz: 2b2624a388eba5772c6a3bdd601ffe6fbcdb3c08c0d3c4585c1dfcf03862cf94f19e39db453950294f1a5a01a35e5ee33c975632c7ba6a2c966fcb85ff6dbb5d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
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
+
7
+ ## [1.0.3]
8
+ - Do no update `Subscription#last_chunk_fed_at` if the chunk is empty
9
+
3
10
  ## [1.0.2]
4
11
  - UI: Fix opening of SubscriptionsSet tab of non-existing SubscriptionsSet
5
12
 
@@ -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,10 +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
- global_position ||= @subscription.last_chunk_greatest_position
110
113
  @subscription.update(last_chunk_fed_at: Time.now.utc, last_chunk_greatest_position: global_position)
111
114
  end
112
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.2"
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.2
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-13 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