sequent 7.1.1 → 8.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/bin/sequent +6 -107
- data/db/sequent_8_migration.sql +120 -0
- data/db/sequent_pgsql.sql +416 -0
- data/db/sequent_schema.rb +11 -57
- data/db/sequent_schema_indexes.sql +37 -0
- data/db/sequent_schema_partitions.sql +34 -0
- data/db/sequent_schema_tables.sql +74 -0
- data/lib/sequent/cli/app.rb +132 -0
- data/lib/sequent/cli/sequent_8_migration.rb +180 -0
- data/lib/sequent/configuration.rb +11 -8
- data/lib/sequent/core/aggregate_repository.rb +2 -2
- data/lib/sequent/core/aggregate_root.rb +32 -9
- data/lib/sequent/core/aggregate_snapshotter.rb +8 -6
- data/lib/sequent/core/command_record.rb +27 -18
- data/lib/sequent/core/command_service.rb +2 -2
- data/lib/sequent/core/event_publisher.rb +1 -1
- data/lib/sequent/core/event_record.rb +37 -17
- data/lib/sequent/core/event_store.rb +101 -119
- data/lib/sequent/core/helpers/array_with_type.rb +1 -1
- data/lib/sequent/core/helpers/association_validator.rb +2 -2
- data/lib/sequent/core/helpers/attribute_support.rb +8 -8
- data/lib/sequent/core/helpers/equal_support.rb +3 -3
- data/lib/sequent/core/helpers/message_matchers/has_attrs.rb +2 -0
- data/lib/sequent/core/helpers/message_router.rb +2 -2
- data/lib/sequent/core/helpers/param_support.rb +1 -3
- data/lib/sequent/core/helpers/pgsql_helpers.rb +32 -0
- data/lib/sequent/core/helpers/string_support.rb +1 -1
- data/lib/sequent/core/helpers/string_to_value_parsers.rb +1 -1
- data/lib/sequent/core/persistors/active_record_persistor.rb +1 -1
- data/lib/sequent/core/persistors/replay_optimized_postgres_persistor.rb +3 -4
- data/lib/sequent/core/projector.rb +1 -1
- data/lib/sequent/core/snapshot_record.rb +44 -0
- data/lib/sequent/core/snapshot_store.rb +105 -0
- data/lib/sequent/core/stream_record.rb +10 -15
- data/lib/sequent/dry_run/read_only_replay_optimized_postgres_persistor.rb +1 -1
- data/lib/sequent/dry_run/view_schema.rb +2 -3
- data/lib/sequent/generator/project.rb +5 -7
- data/lib/sequent/generator/template_aggregate/template_aggregate/commands.rb +2 -0
- data/lib/sequent/generator/template_aggregate/template_aggregate/events.rb +2 -0
- data/lib/sequent/generator/template_aggregate/template_aggregate/template_aggregate.rb +2 -0
- data/lib/sequent/generator/template_aggregate/template_aggregate/template_aggregate_command_handler.rb +2 -0
- data/lib/sequent/generator/template_aggregate/template_aggregate.rb +2 -0
- data/lib/sequent/generator/template_project/Gemfile +7 -5
- data/lib/sequent/generator/template_project/Rakefile +4 -2
- data/lib/sequent/generator/template_project/app/projectors/post_projector.rb +2 -0
- data/lib/sequent/generator/template_project/app/records/post_record.rb +2 -0
- data/lib/sequent/generator/template_project/config/initializers/sequent.rb +3 -8
- data/lib/sequent/generator/template_project/db/migrations.rb +3 -3
- data/lib/sequent/generator/template_project/lib/post/commands.rb +2 -0
- data/lib/sequent/generator/template_project/lib/post/events.rb +2 -0
- data/lib/sequent/generator/template_project/lib/post/post.rb +2 -0
- data/lib/sequent/generator/template_project/lib/post/post_command_handler.rb +2 -0
- data/lib/sequent/generator/template_project/lib/post.rb +2 -0
- data/lib/sequent/generator/template_project/my_app.rb +2 -1
- data/lib/sequent/generator/template_project/spec/app/projectors/post_projector_spec.rb +2 -0
- data/lib/sequent/generator/template_project/spec/lib/post/post_command_handler_spec.rb +9 -2
- data/lib/sequent/generator/template_project/spec/spec_helper.rb +4 -7
- data/lib/sequent/generator.rb +1 -1
- data/lib/sequent/internal/aggregate_type.rb +12 -0
- data/lib/sequent/internal/command_type.rb +12 -0
- data/lib/sequent/internal/event_type.rb +12 -0
- data/lib/sequent/internal/internal.rb +14 -0
- data/lib/sequent/internal/partitioned_aggregate.rb +26 -0
- data/lib/sequent/internal/partitioned_command.rb +16 -0
- data/lib/sequent/internal/partitioned_event.rb +29 -0
- data/lib/sequent/migrations/grouper.rb +90 -0
- data/lib/sequent/migrations/sequent_schema.rb +2 -1
- data/lib/sequent/migrations/view_schema.rb +76 -77
- data/lib/sequent/rake/migration_tasks.rb +49 -24
- data/lib/sequent/sequent.rb +1 -0
- data/lib/sequent/support/database.rb +20 -16
- data/lib/sequent/test/time_comparison.rb +1 -1
- data/lib/sequent/util/timer.rb +1 -1
- data/lib/version.rb +1 -1
- metadata +102 -21
- data/lib/sequent/generator/template_project/db/sequent_schema.rb +0 -52
- data/lib/sequent/generator/template_project/ruby-version +0 -1
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'bundler/setup'
|
2
4
|
Bundler.setup
|
3
5
|
|
@@ -8,12 +10,7 @@ require 'database_cleaner'
|
|
8
10
|
|
9
11
|
require_relative '../my_app'
|
10
12
|
|
11
|
-
|
12
|
-
Sequent::Support::Database.establish_connection(db_config)
|
13
|
-
|
14
|
-
Sequent::Support::Database.drop_schema!(Sequent.configuration.view_schema_name)
|
15
|
-
|
16
|
-
Sequent::Migrations::ViewSchema.new(db_config: db_config).create_view_tables
|
13
|
+
Sequent::Test::DatabaseHelpers.maintain_test_database_schema(env: 'test')
|
17
14
|
|
18
15
|
module DomainTests
|
19
16
|
def self.included(base)
|
@@ -23,7 +20,7 @@ end
|
|
23
20
|
|
24
21
|
RSpec.configure do |config|
|
25
22
|
config.include Sequent::Test::CommandHandlerHelpers
|
26
|
-
config.include DomainTests, file_path: /spec\/lib/
|
23
|
+
config.include DomainTests, file_path: %r{/spec\/lib/}
|
27
24
|
|
28
25
|
# Domain tests run with a clean sequent configuration and the in memory FakeEventStore
|
29
26
|
config.around :each, :domain_tests do |example|
|
data/lib/sequent/generator.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'aggregate_type'
|
4
|
+
require_relative 'command_type'
|
5
|
+
require_relative 'event_type'
|
6
|
+
require_relative 'partitioned_aggregate'
|
7
|
+
require_relative 'partitioned_command'
|
8
|
+
require_relative 'partitioned_event'
|
9
|
+
|
10
|
+
module Sequent
|
11
|
+
module Internal
|
12
|
+
end
|
13
|
+
private_constant :Internal
|
14
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
require_relative '../application_record'
|
5
|
+
|
6
|
+
module Sequent
|
7
|
+
module Internal
|
8
|
+
class PartitionedAggregate < Sequent::ApplicationRecord
|
9
|
+
self.table_name = :aggregates
|
10
|
+
self.primary_key = %i[aggregate_id]
|
11
|
+
|
12
|
+
belongs_to :aggregate_type
|
13
|
+
if Gem.loaded_specs['activerecord'].version < Gem::Version.create('7.2')
|
14
|
+
has_many :partitioned_events,
|
15
|
+
inverse_of: :partitioned_aggregate,
|
16
|
+
primary_key: %w[events_partition_key aggregate_id],
|
17
|
+
query_constraints: %w[partition_key aggregate_id]
|
18
|
+
else
|
19
|
+
has_many :partitioned_events,
|
20
|
+
inverse_of: :partitioned_aggregate,
|
21
|
+
primary_key: %i[events_partition_key aggregate_id],
|
22
|
+
foreign_key: %i[partition_key aggregate_id]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
require_relative '../application_record'
|
5
|
+
|
6
|
+
module Sequent
|
7
|
+
module Internal
|
8
|
+
class PartitionedCommand < Sequent::ApplicationRecord
|
9
|
+
self.table_name = :commands
|
10
|
+
|
11
|
+
belongs_to :command_type
|
12
|
+
has_many :partitioned_events,
|
13
|
+
inverse_of: :partitioned_command
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
require_relative '../application_record'
|
5
|
+
|
6
|
+
module Sequent
|
7
|
+
module Internal
|
8
|
+
class PartitionedEvent < Sequent::ApplicationRecord
|
9
|
+
self.table_name = :events
|
10
|
+
self.primary_key = %i[partition_key aggregate_id sequence_number]
|
11
|
+
|
12
|
+
belongs_to :event_type
|
13
|
+
belongs_to :partitioned_command,
|
14
|
+
inverse_of: :partitioned_events,
|
15
|
+
foreign_key: :command_id
|
16
|
+
if Gem.loaded_specs['activerecord'].version < Gem::Version.create('7.2')
|
17
|
+
belongs_to :partitioned_aggregate,
|
18
|
+
inverse_of: :partitioned_events,
|
19
|
+
primary_key: %w[partition_key aggregate_id],
|
20
|
+
query_constraints: %w[events_partition_key aggregate_id]
|
21
|
+
else
|
22
|
+
belongs_to :partitioned_aggregate,
|
23
|
+
inverse_of: :partitioned_events,
|
24
|
+
primary_key: %w[partition_key aggregate_id],
|
25
|
+
foreign_key: %w[events_partition_key aggregate_id]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sequent
|
4
|
+
module Migrations
|
5
|
+
module Grouper
|
6
|
+
GroupEndpoint = Data.define(:partition_key, :aggregate_id) do
|
7
|
+
def <=>(other)
|
8
|
+
return unless other.is_a?(self.class)
|
9
|
+
|
10
|
+
[partition_key, aggregate_id] <=> [other.partition_key, other.aggregate_id]
|
11
|
+
end
|
12
|
+
|
13
|
+
include Comparable
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"(#{partition_key}, #{aggregate_id})"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Generate approximately equally sized groups based on the
|
21
|
+
# events partition keys and the number of events per partition
|
22
|
+
# key. Each group is defined by a lower bound (partition-key,
|
23
|
+
# aggregate-id) and upper bound (partition-key, aggregate-id)
|
24
|
+
# (inclusive).
|
25
|
+
#
|
26
|
+
# For splitting a partition into equal sized groups the
|
27
|
+
# assumption is made that aggregate-ids and their events are
|
28
|
+
# equally distributed.
|
29
|
+
def self.group_partitions(partitions, target_group_size)
|
30
|
+
return [] unless partitions.present?
|
31
|
+
|
32
|
+
partitions = partitions.sort.map do |key, count|
|
33
|
+
PartitionData.new(key:, original_size: count, remaining_size: count, lower_bound: 0)
|
34
|
+
end
|
35
|
+
|
36
|
+
partition = partitions.shift
|
37
|
+
current_start = GroupEndpoint.new(partition.key, LOWEST_UUID)
|
38
|
+
current_size = 0
|
39
|
+
|
40
|
+
result = []
|
41
|
+
while partition.present?
|
42
|
+
if current_size + partition.remaining_size < target_group_size
|
43
|
+
current_size += partition.remaining_size
|
44
|
+
if partitions.empty?
|
45
|
+
result << (current_start..GroupEndpoint.new(partition.key, HIGHEST_UUID))
|
46
|
+
break
|
47
|
+
end
|
48
|
+
partition = partitions.shift
|
49
|
+
elsif current_size + partition.remaining_size == target_group_size
|
50
|
+
result << (current_start..GroupEndpoint.new(partition.key, HIGHEST_UUID))
|
51
|
+
|
52
|
+
partition = partitions.shift
|
53
|
+
break unless partition
|
54
|
+
|
55
|
+
current_start = GroupEndpoint.new(partition.key, LOWEST_UUID)
|
56
|
+
current_size = 0
|
57
|
+
else
|
58
|
+
taken = target_group_size - current_size
|
59
|
+
upper_bound = partition.lower_bound + (UUID_COUNT * taken / partition.original_size)
|
60
|
+
|
61
|
+
result << (current_start..GroupEndpoint.new(partition.key, number_to_uuid(upper_bound - 1)))
|
62
|
+
|
63
|
+
remaining_size = partition.remaining_size - taken
|
64
|
+
partition = partition.with(remaining_size:, lower_bound: upper_bound)
|
65
|
+
current_start = GroupEndpoint.new(partition.key, number_to_uuid(upper_bound))
|
66
|
+
current_size = 0
|
67
|
+
end
|
68
|
+
end
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
72
|
+
PartitionData = Data.define(:key, :original_size, :remaining_size, :lower_bound)
|
73
|
+
|
74
|
+
def self.number_to_uuid(number)
|
75
|
+
fail ArgumentError, number unless (0..UUID_COUNT - 1).include? number
|
76
|
+
|
77
|
+
s = format('%032x', number)
|
78
|
+
"#{s[0..7]}-#{s[8..11]}-#{s[12..15]}-#{s[16..19]}-#{s[20..]}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.uuid_to_number(uuid)
|
82
|
+
Integer(uuid.gsub('-', ''), 16)
|
83
|
+
end
|
84
|
+
|
85
|
+
UUID_COUNT = 2**128
|
86
|
+
LOWEST_UUID = number_to_uuid(0)
|
87
|
+
HIGHEST_UUID = number_to_uuid(UUID_COUNT - 1)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -19,7 +19,8 @@ module Sequent
|
|
19
19
|
Sequent::Support::Database.establish_connection(db_config)
|
20
20
|
|
21
21
|
event_store_schema = Sequent.configuration.event_store_schema_name
|
22
|
-
|
22
|
+
event_records_table = Sequent.configuration.event_record_class.table_name
|
23
|
+
schema_exists = Sequent::Support::Database.schema_exists?(event_store_schema, event_records_table)
|
23
24
|
|
24
25
|
FAIL_IF_EXISTS.call(event_store_schema) if schema_exists && fail_if_exists
|
25
26
|
return if schema_exists
|
@@ -8,11 +8,12 @@ require_relative '../support/database'
|
|
8
8
|
require_relative '../sequent'
|
9
9
|
require_relative '../util/timer'
|
10
10
|
require_relative '../util/printer'
|
11
|
-
require_relative '
|
11
|
+
require_relative 'projectors'
|
12
12
|
require_relative 'planner'
|
13
13
|
require_relative 'executor'
|
14
14
|
require_relative 'sql'
|
15
15
|
require_relative 'versions'
|
16
|
+
require_relative 'grouper'
|
16
17
|
|
17
18
|
module Sequent
|
18
19
|
module Migrations
|
@@ -60,16 +61,6 @@ module Sequent
|
|
60
61
|
#
|
61
62
|
# end
|
62
63
|
class ViewSchema
|
63
|
-
# Corresponds with the index on aggregate_id column in the event_records table
|
64
|
-
#
|
65
|
-
# Since we replay in batches of the first 3 chars of the uuid we created an index on
|
66
|
-
# these 3 characters. Hence the name ;-)
|
67
|
-
#
|
68
|
-
# This also means that the online replay is divided up into 16**3 groups
|
69
|
-
# This might seem a lot for starting event store, but when you will get more
|
70
|
-
# events, you will see that this is pretty good partitioned.
|
71
|
-
LENGTH_OF_SUBSTRING_INDEX_ON_AGGREGATE_ID_IN_EVENT_STORE = 3
|
72
|
-
|
73
64
|
include Sequent::Util::Timer
|
74
65
|
include Sequent::Util::Printer
|
75
66
|
include Sql
|
@@ -143,11 +134,10 @@ module Sequent
|
|
143
134
|
# Utility method that replays events for all managed_tables from all Sequent::Core::Projector's
|
144
135
|
#
|
145
136
|
# This method is mainly useful in test scenario's or development tasks
|
146
|
-
def replay_all!
|
137
|
+
def replay_all!
|
147
138
|
replay!(
|
148
139
|
Sequent.configuration.online_replay_persistor_class.new,
|
149
140
|
projectors: Core::Migratable.projectors,
|
150
|
-
groups: groups(group_exponent: group_exponent),
|
151
141
|
)
|
152
142
|
end
|
153
143
|
|
@@ -205,7 +195,6 @@ module Sequent
|
|
205
195
|
if plan.projectors.any?
|
206
196
|
replay!(
|
207
197
|
Sequent.configuration.online_replay_persistor_class.new,
|
208
|
-
groups: groups,
|
209
198
|
maximum_xact_id_exclusive: Versions.running.first.xmin_xact_id,
|
210
199
|
)
|
211
200
|
end
|
@@ -216,17 +205,15 @@ module Sequent
|
|
216
205
|
Versions.end_online!(Sequent.new_version)
|
217
206
|
end
|
218
207
|
Sequent.logger.info("Done migrate_online for version #{Sequent.new_version}")
|
219
|
-
rescue ConcurrentMigration
|
220
|
-
# Do not rollback the migration when this is a concurrent migration
|
221
|
-
|
222
|
-
|
223
|
-
# Do not rollback the migration when since there is nothing to rollback
|
208
|
+
rescue ConcurrentMigration, InvalidMigrationDefinition
|
209
|
+
# ConcurrentMigration: Do not rollback the migration when this is a concurrent migration
|
210
|
+
# as the other one is running
|
211
|
+
# InvalidMigrationDefinition: Do not rollback the migration when since there is nothing to rollback
|
224
212
|
raise
|
225
213
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
226
214
|
rollback_migration
|
227
215
|
raise e
|
228
216
|
end
|
229
|
-
|
230
217
|
##
|
231
218
|
# Last part of a view schema migration
|
232
219
|
#
|
@@ -259,7 +246,6 @@ module Sequent
|
|
259
246
|
if plan.projectors.any?
|
260
247
|
replay!(
|
261
248
|
Sequent.configuration.offline_replay_persistor_class.new,
|
262
|
-
groups: groups(group_exponent: 1),
|
263
249
|
minimum_xact_id_inclusive: Versions.running.first.xmin_xact_id,
|
264
250
|
)
|
265
251
|
end
|
@@ -309,18 +295,33 @@ module Sequent
|
|
309
295
|
|
310
296
|
def replay!(
|
311
297
|
replay_persistor,
|
312
|
-
groups:,
|
313
298
|
projectors: plan.projectors,
|
314
299
|
minimum_xact_id_inclusive: nil,
|
315
300
|
maximum_xact_id_exclusive: nil
|
316
301
|
)
|
317
|
-
|
302
|
+
event_types = projectors.flat_map { |projector| projector.message_mapping.keys }.uniq.map(&:name)
|
303
|
+
group_target_size = Sequent.configuration.replay_group_target_size
|
304
|
+
event_type_ids = Internal::EventType.where(type: event_types).pluck(:id)
|
305
|
+
|
306
|
+
partitions_query = Internal::PartitionedEvent.where(event_type_id: event_type_ids)
|
307
|
+
partitions_query = xact_id_filter(partitions_query, minimum_xact_id_inclusive, maximum_xact_id_exclusive)
|
308
|
+
|
309
|
+
partitions = partitions_query.group(:partition_key).order(:partition_key).count
|
310
|
+
event_count = partitions.values.sum
|
311
|
+
|
312
|
+
groups = Sequent::Migrations::Grouper.group_partitions(partitions, group_target_size)
|
313
|
+
|
314
|
+
if groups.empty?
|
315
|
+
groups = [nil..nil]
|
316
|
+
else
|
317
|
+
groups.prepend(nil..groups.first.begin)
|
318
|
+
groups.append(groups.last.end..nil)
|
319
|
+
end
|
318
320
|
|
319
321
|
with_sequent_config(replay_persistor, projectors) do
|
320
|
-
logger.info
|
322
|
+
logger.info "Start replaying #{event_count} events in #{groups.size} groups"
|
321
323
|
|
322
|
-
time("#{groups.size} groups replayed") do
|
323
|
-
event_types = projectors.flat_map { |projector| projector.message_mapping.keys }.uniq.map(&:name)
|
324
|
+
time("#{event_count} events in #{groups.size} groups replayed") do
|
324
325
|
disconnect!
|
325
326
|
|
326
327
|
@connected = false
|
@@ -328,24 +329,23 @@ module Sequent
|
|
328
329
|
result = Parallel.map_with_index(
|
329
330
|
groups,
|
330
331
|
in_processes: Sequent.configuration.number_of_replay_processes,
|
331
|
-
) do |
|
332
|
+
) do |group, index|
|
332
333
|
@connected ||= establish_connection
|
333
334
|
msg = <<~EOS.chomp
|
334
|
-
Group
|
335
|
+
Group #{group} (#{index + 1}/#{groups.size}) replayed
|
335
336
|
EOS
|
336
337
|
time(msg) do
|
337
338
|
replay_events(
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
maximum_xact_id_exclusive,
|
339
|
+
-> {
|
340
|
+
event_stream(group, event_type_ids, minimum_xact_id_inclusive, maximum_xact_id_exclusive)
|
341
|
+
},
|
342
342
|
replay_persistor,
|
343
343
|
&on_progress
|
344
344
|
)
|
345
345
|
end
|
346
346
|
nil
|
347
347
|
rescue StandardError => e
|
348
|
-
logger.error "Replaying failed for
|
348
|
+
logger.error "Replaying failed for group: #{group}"
|
349
349
|
logger.error '+++++++++++++++ ERROR +++++++++++++++'
|
350
350
|
recursively_print(e)
|
351
351
|
raise Parallel::Kill # immediately kill all sub-processes
|
@@ -357,19 +357,14 @@ module Sequent
|
|
357
357
|
end
|
358
358
|
|
359
359
|
def replay_events(
|
360
|
-
|
361
|
-
event_types,
|
362
|
-
minimum_xact_id_inclusive,
|
363
|
-
maximum_xact_id_exclusive,
|
360
|
+
get_events,
|
364
361
|
replay_persistor,
|
365
362
|
&on_progress
|
366
363
|
)
|
367
364
|
Sequent.configuration.event_store.replay_events_from_cursor(
|
368
365
|
block_size: 1000,
|
369
|
-
get_events
|
370
|
-
|
371
|
-
},
|
372
|
-
on_progress: on_progress,
|
366
|
+
get_events:,
|
367
|
+
on_progress:,
|
373
368
|
)
|
374
369
|
|
375
370
|
replay_persistor.commit
|
@@ -387,30 +382,6 @@ module Sequent
|
|
387
382
|
Versions.rollback!(Sequent.new_version)
|
388
383
|
end
|
389
384
|
|
390
|
-
def groups(group_exponent: 3, limit: nil, offset: nil)
|
391
|
-
number_of_groups = 16**group_exponent
|
392
|
-
groups = groups_of_aggregate_id_prefixes(number_of_groups)
|
393
|
-
groups = groups.drop(offset) unless offset.nil?
|
394
|
-
groups = groups.take(limit) unless limit.nil?
|
395
|
-
groups
|
396
|
-
end
|
397
|
-
|
398
|
-
def groups_of_aggregate_id_prefixes(number_of_groups)
|
399
|
-
all_prefixes = (0...16**LENGTH_OF_SUBSTRING_INDEX_ON_AGGREGATE_ID_IN_EVENT_STORE).to_a.map do |i|
|
400
|
-
i.to_s(16)
|
401
|
-
end
|
402
|
-
all_prefixes = all_prefixes.map { |s| s.length == 3 ? s : "#{'0' * (3 - s.length)}#{s}" }
|
403
|
-
|
404
|
-
logger.info "Number of groups #{number_of_groups}"
|
405
|
-
|
406
|
-
logger.debug "Prefixes: #{all_prefixes.length}"
|
407
|
-
if number_of_groups > all_prefixes.length
|
408
|
-
fail "Can not have more groups #{number_of_groups} than number of prefixes #{all_prefixes.length}"
|
409
|
-
end
|
410
|
-
|
411
|
-
all_prefixes.each_slice(all_prefixes.length / number_of_groups).to_a
|
412
|
-
end
|
413
|
-
|
414
385
|
def in_view_schema(&block)
|
415
386
|
Sequent::Support::Database.with_schema_search_path(view_schema, db_config, &block)
|
416
387
|
end
|
@@ -451,27 +422,55 @@ module Sequent
|
|
451
422
|
Sequent::Configuration.restore(old_config)
|
452
423
|
end
|
453
424
|
|
454
|
-
def event_stream(
|
455
|
-
fail ArgumentError, '
|
425
|
+
def event_stream(group, event_type_ids, minimum_xact_id_inclusive, maximum_xact_id_exclusive)
|
426
|
+
fail ArgumentError, 'group is mandatory' if group.nil?
|
456
427
|
|
457
|
-
event_stream =
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
428
|
+
event_stream = Internal::PartitionedEvent
|
429
|
+
.joins('JOIN event_types ON events.event_type_id = event_types.id')
|
430
|
+
.where(
|
431
|
+
event_type_id: event_type_ids,
|
432
|
+
)
|
433
|
+
if group.begin && group.end
|
434
|
+
event_stream = event_stream.where(
|
435
|
+
'(events.partition_key, events.aggregate_id) BETWEEN (?, ?) AND (?, ?)',
|
436
|
+
group.begin.partition_key,
|
437
|
+
group.begin.aggregate_id,
|
438
|
+
group.end.partition_key,
|
439
|
+
group.end.aggregate_id,
|
440
|
+
)
|
441
|
+
elsif group.end
|
442
|
+
event_stream = event_stream.where(
|
443
|
+
'(events.partition_key, events.aggregate_id) < (?, ?)',
|
444
|
+
group.end.partition_key,
|
445
|
+
group.end.aggregate_id,
|
446
|
+
)
|
447
|
+
elsif group.begin
|
462
448
|
event_stream = event_stream.where(
|
449
|
+
'(events.partition_key, events.aggregate_id) > (?, ?)',
|
450
|
+
group.begin.partition_key,
|
451
|
+
group.begin.aggregate_id,
|
452
|
+
)
|
453
|
+
end
|
454
|
+
event_stream = xact_id_filter(event_stream, minimum_xact_id_inclusive, maximum_xact_id_exclusive)
|
455
|
+
event_stream
|
456
|
+
.order('events.partition_key', 'events.aggregate_id', 'events.sequence_number')
|
457
|
+
.select('event_types.type AS event_type, enrich_event_json(events) AS event_json')
|
458
|
+
end
|
459
|
+
|
460
|
+
def xact_id_filter(events_query, minimum_xact_id_inclusive, maximum_xact_id_exclusive)
|
461
|
+
if minimum_xact_id_inclusive && maximum_xact_id_exclusive
|
462
|
+
events_query.where(
|
463
463
|
'xact_id >= ? AND xact_id < ?',
|
464
464
|
minimum_xact_id_inclusive,
|
465
465
|
maximum_xact_id_exclusive,
|
466
466
|
)
|
467
467
|
elsif minimum_xact_id_inclusive
|
468
|
-
|
468
|
+
events_query.where('xact_id >= ?', minimum_xact_id_inclusive)
|
469
469
|
elsif maximum_xact_id_exclusive
|
470
|
-
|
470
|
+
events_query.where('xact_id IS NULL OR xact_id < ?', maximum_xact_id_exclusive)
|
471
|
+
else
|
472
|
+
events_query
|
471
473
|
end
|
472
|
-
event_stream
|
473
|
-
.order('aggregate_id ASC, sequence_number ASC')
|
474
|
-
.select('id, event_type, event_json, sequence_number')
|
475
474
|
end
|
476
475
|
|
477
476
|
## shortcut methods
|
@@ -125,6 +125,11 @@ module Sequent
|
|
125
125
|
end
|
126
126
|
end
|
127
127
|
|
128
|
+
desc 'Aborts if a migration is pending'
|
129
|
+
task abort_if_pending_migrations: [:create_and_migrate_sequent_view_schema] do
|
130
|
+
abort if Sequent.new_version != Sequent::Migrations::Versions.current_version
|
131
|
+
end
|
132
|
+
|
128
133
|
desc <<-EOS
|
129
134
|
Shows the current status of the migrations
|
130
135
|
EOS
|
@@ -192,18 +197,15 @@ module Sequent
|
|
192
197
|
|
193
198
|
Pass a regular expression as parameter to select the projectors to run, otherwise all projectors are selected.
|
194
199
|
EOS
|
195
|
-
task :dryrun, %i[regex
|
200
|
+
task :dryrun, %i[regex group_target_size] => ['sequent:init', :init] do |_task, args|
|
196
201
|
ensure_sequent_env_set!
|
197
202
|
|
198
203
|
db_config = Sequent::Support::Database.read_config(@env)
|
199
204
|
view_schema = Sequent::DryRun::ViewSchema.new(db_config: db_config)
|
200
205
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
limit: args[:limit]&.to_i,
|
205
|
-
offset: args[:offset]&.to_i,
|
206
|
-
)
|
206
|
+
Sequent.configuration.replay_group_target_size = group_target_size
|
207
|
+
|
208
|
+
view_schema.migrate_dryrun(regex: args[:regex])
|
207
209
|
end
|
208
210
|
end
|
209
211
|
|
@@ -213,31 +215,54 @@ module Sequent
|
|
213
215
|
EOS
|
214
216
|
task :init
|
215
217
|
|
216
|
-
task :
|
217
|
-
|
218
|
-
|
218
|
+
task :connect, ['sequent:init', :init, :set_env_var] do
|
219
|
+
ensure_sequent_env_set!
|
220
|
+
|
221
|
+
Sequent.configuration.command_handlers << Sequent::Core::AggregateSnapshotter.new
|
222
|
+
|
223
|
+
db_config = Sequent::Support::Database.read_config(@env)
|
224
|
+
Sequent::Support::Database.establish_connection(db_config)
|
225
|
+
end
|
226
|
+
|
227
|
+
desc <<~EOS
|
228
|
+
Takes up-to `limit` snapshots, starting with the highest priority aggregates (based on snapshot outdated time and number of events)
|
229
|
+
EOS
|
230
|
+
task :take_snapshots, %i[limit] => :connect do |_t, args|
|
231
|
+
limit = args['limit']&.to_i
|
219
232
|
|
220
|
-
unless
|
233
|
+
unless limit
|
221
234
|
fail ArgumentError,
|
222
|
-
'usage rake sequent:snapshots:
|
235
|
+
'usage rake sequent:snapshots:take_snapshots[limit]'
|
223
236
|
end
|
224
|
-
|
237
|
+
|
238
|
+
aggregate_ids = Sequent.configuration.event_store.select_aggregates_for_snapshotting(limit:)
|
239
|
+
|
240
|
+
Sequent.logger.info "Taking #{aggregate_ids.size} snapshots"
|
241
|
+
aggregate_ids.each do |aggregate_id|
|
242
|
+
Sequent.command_service.execute_commands(Sequent::Core::TakeSnapshot.new(aggregate_id:))
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
desc <<~EOS
|
247
|
+
Takes a new snapshot for the aggregate specified by `aggregate_id`
|
248
|
+
EOS
|
249
|
+
task :take_snapshot, %i[aggregate_id] => :connect do |_t, args|
|
250
|
+
aggregate_id = args['aggregate_id']
|
251
|
+
|
252
|
+
unless aggregate_id
|
225
253
|
fail ArgumentError,
|
226
|
-
'usage rake sequent:snapshots:
|
254
|
+
'usage rake sequent:snapshots:take_snapshot[aggregate_id]'
|
227
255
|
end
|
228
256
|
|
229
|
-
|
230
|
-
UPDATE #{Sequent.configuration.stream_record_class} SET snapshot_threshold = #{threshold.to_i} WHERE aggregate_type = '#{aggregate_type}'
|
231
|
-
EOS
|
257
|
+
Sequent.command_service.execute_commands(Sequent::Core::TakeSnapshot.new(aggregate_id:))
|
232
258
|
end
|
233
259
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
Sequent.logger.info "Deleted #{result.cmd_tuples} aggregate snapshots from the event store"
|
260
|
+
desc <<~EOS
|
261
|
+
Delete all aggregate snapshots, which can negatively impact performance of a running system.
|
262
|
+
EOS
|
263
|
+
task delete_all: :connect do
|
264
|
+
Sequent.configuration.event_store.delete_all_snapshots
|
265
|
+
Sequent.logger.info 'Deleted all aggregate snapshots from the event store'
|
241
266
|
end
|
242
267
|
end
|
243
268
|
end
|
data/lib/sequent/sequent.rb
CHANGED