sequent 7.1.1 → 8.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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