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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +13 -0
- data/Dockerfile +3 -0
- data/README.md +20 -7
- data/db/migrations/10_setup_pg_cron.rb +23 -0
- data/db/migrations/11_add_events_link_global_position.sql +1 -0
- data/db/migrations/12_migrate_legacy_links.rb +83 -0
- data/db/migrations/13_remove_events_link_id.sql +6 -0
- data/db/migrations/14_remove_ids_events_id_index.sql +1 -0
- data/db/migrations/9_create_events_horizon.sql +21 -0
- data/docs/appending_events.md +1 -1
- data/docs/events_and_streams.md +1 -1
- data/docs/multiple_commands.md +16 -1
- data/lib/pg_eventstore/callbacks.rb +7 -5
- data/lib/pg_eventstore/cli/try_to_delete_subscriptions_set.rb +2 -2
- data/lib/pg_eventstore/client.rb +7 -5
- data/lib/pg_eventstore/commands/all_stream_read_grouped.rb +3 -3
- data/lib/pg_eventstore/commands/append.rb +3 -3
- data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +5 -2
- data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +1 -1
- data/lib/pg_eventstore/commands/link_to.rb +6 -6
- data/lib/pg_eventstore/commands/multiple.rb +2 -2
- data/lib/pg_eventstore/commands/regular_stream_read_grouped.rb +1 -1
- data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +1 -1
- data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +1 -1
- data/lib/pg_eventstore/connection.rb +1 -35
- data/lib/pg_eventstore/errors.rb +1 -1
- data/lib/pg_eventstore/event.rb +5 -5
- data/lib/pg_eventstore/extensions/options_extension.rb +40 -11
- data/lib/pg_eventstore/maintenance.rb +1 -1
- data/lib/pg_eventstore/queries/event_queries.rb +7 -7
- data/lib/pg_eventstore/queries/links_resolver.rb +6 -3
- data/lib/pg_eventstore/queries/partition_queries.rb +1 -1
- data/lib/pg_eventstore/queries/transaction_queries.rb +10 -4
- data/lib/pg_eventstore/query_builders/events_filtering.rb +2 -2
- data/lib/pg_eventstore/query_builders/partitions_filtering.rb +2 -2
- data/lib/pg_eventstore/stream.rb +1 -1
- data/lib/pg_eventstore/subscriptions/basic_runner.rb +4 -4
- data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rb +1 -1
- data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rb +2 -11
- data/lib/pg_eventstore/subscriptions/events_processor.rb +1 -1
- data/lib/pg_eventstore/subscriptions/queries/subscription_command_queries.rb +5 -5
- data/lib/pg_eventstore/subscriptions/queries/subscription_queries.rb +2 -2
- data/lib/pg_eventstore/subscriptions/queries/subscription_service_queries.rb +78 -0
- data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rb +2 -2
- data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_queries.rb +1 -1
- data/lib/pg_eventstore/subscriptions/subscription.rb +7 -6
- data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +2 -2
- data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +1 -3
- data/lib/pg_eventstore/subscriptions/subscription_runner.rb +3 -16
- data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +10 -2
- data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +11 -16
- data/lib/pg_eventstore/tasks/setup.rake +30 -31
- data/lib/pg_eventstore/utils.rb +8 -0
- data/lib/pg_eventstore/version.rb +1 -1
- data/lib/pg_eventstore/web/application.rb +5 -5
- data/lib/pg_eventstore/web/paginator/events_collection.rb +3 -3
- data/lib/pg_eventstore/web/paginator/helpers.rb +3 -3
- data/lib/pg_eventstore/web/subscriptions/helpers.rb +2 -2
- data/lib/pg_eventstore.rb +4 -4
- data/pg_eventstore.gemspec +1 -1
- data/sig/pg_eventstore/client.rbs +1 -1
- data/sig/pg_eventstore/commands/multiple.rbs +1 -1
- data/sig/pg_eventstore/event.rbs +4 -4
- data/sig/pg_eventstore/extensions/options_extension.rbs +9 -1
- data/sig/pg_eventstore/queries/event_queries.rbs +11 -11
- data/sig/pg_eventstore/queries/links_resolver.rbs +5 -5
- data/sig/pg_eventstore/queries/transaction_queries.rbs +2 -2
- data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rbs +0 -3
- data/sig/pg_eventstore/subscriptions/queries/subscription_queries.rbs +13 -13
- data/sig/pg_eventstore/subscriptions/queries/subscription_service_queries.rbs +19 -0
- data/sig/pg_eventstore/subscriptions/subscription_runner.rbs +0 -3
- data/sig/pg_eventstore/subscriptions/subscription_runners_feeder.rbs +10 -3
- data/sig/pg_eventstore/utils.rbs +10 -2
- metadata +11 -6
- data/lib/pg_eventstore/subscriptions/queries/service_queries.rb +0 -73
- data/lib/pg_eventstore/subscriptions/subscription_position_evaluation.rb +0 -195
- data/sig/pg_eventstore/subscriptions/queries/service_queries.rbs +0 -15
- 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
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
84
|
+
# @param option_name [Symbol]
|
|
82
85
|
# @return [PgEventstore::Extensions::OptionsExtension::Option, nil]
|
|
83
|
-
def [](
|
|
84
|
-
|
|
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:
|
|
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 =
|
|
144
|
+
klass.options = options.dup.freeze
|
|
116
145
|
end
|
|
117
146
|
|
|
118
147
|
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<
|
|
45
|
-
def
|
|
46
|
-
sql_builder = SQLBuilder.new.from(Event::PRIMARY_TABLE_NAME).select('
|
|
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('
|
|
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['
|
|
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
|
|
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, :
|
|
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['
|
|
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['
|
|
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(
|
|
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
|
|
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, &
|
|
43
|
+
def pg_transaction(level, read_only, pg_connection, &)
|
|
42
44
|
pg_connection.transaction do
|
|
43
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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!
|
data/lib/pg_eventstore/stream.rb
CHANGED
|
@@ -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
|
|
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:
|
|
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, &
|
|
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(&
|
|
252
|
-
@mutex.synchronize(&
|
|
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:
|
|
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
|
|
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:
|
|
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,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
|
|
24
|
+
subscription_id:, subscriptions_set_id:, command_name:
|
|
25
25
|
)
|
|
26
26
|
next existing if existing
|
|
27
27
|
|
|
28
28
|
create(
|
|
29
|
-
subscription_id
|
|
30
|
-
subscriptions_set_id
|
|
31
|
-
command_name
|
|
32
|
-
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:
|
|
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
|
|
23
|
-
create(subscriptions_set_id
|
|
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:
|
|
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
|
|
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
|
|
133
|
-
self.locked_by = subscription_queries.lock!(id, lock_id, 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
|
|
176
|
+
options:,
|
|
177
177
|
restart_count: 0,
|
|
178
178
|
last_restarted_at: nil,
|
|
179
|
-
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
|
|
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.
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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?)
|
|
15
|
+
runners = runners.select(&:running?).select(&:time_to_feed?)
|
|
16
16
|
return if runners.empty?
|
|
17
17
|
|
|
18
|
-
|
|
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
|