pg_eventstore 2.0.0 → 3.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 +10 -1
- data/CHANGELOG.md +186 -20
- data/README.md +25 -15
- data/db/migrations/10_setup_pg_cron.rb +1 -23
- data/db/migrations/15_create_streams_global_index.sql +17 -0
- data/db/migrations/16_populate_streams_global_index.rb +150 -0
- data/db/migrations/17_create_events_global_index.sql +62 -0
- data/db/migrations/18_populate_events_global_index.rb +124 -0
- data/db/migrations/19_populate_events_global_index_p2.rb +25 -0
- data/db/migrations/20_migrate_subscriptions_current_position.sql +19 -0
- data/db/migrations/21_create_partitions_stats.sql +2 -0
- data/db/migrations/22_drop_streams_sql_view.sql +2 -0
- data/db/migrations/23_adjust_partitions_indexes.sql +5 -0
- data/db/migrations/24_drop_legacy_events_indexes.sql +3 -0
- data/db/migrations/25_add_parent_partitions_info_to_partitions.sql +16 -0
- data/db/migrations/26_change_events_stream_revision_type.rb +35 -0
- data/db/migrations/27_drop_events_horizon.sql +3 -0
- data/db/migrations/28_create_maintenance_tasks.sql +8 -0
- data/db/migrations/29_create_event_markers.sql +35 -0
- data/db/migrations/30_improve_partitions_search_in_admin_ui.rb +24 -0
- data/db/migrations/31_drop_events_id_default_value.sql +1 -0
- data/db/migrations/32_create_parittions_triggers.sql +101 -0
- data/docs/AGENTS.fragment.md +696 -0
- data/docs/admin_ui.md +3 -1
- data/docs/appending_events.md +110 -49
- data/docs/configuration.md +69 -36
- data/docs/event_tracing.md +55 -0
- data/docs/events_and_streams.md +54 -30
- data/docs/how_it_works.md +57 -16
- data/docs/linking_events.md +2 -1
- data/docs/maintenance.md +14 -5
- data/docs/multiple_commands.md +17 -8
- data/docs/reading_events.md +208 -43
- data/docs/reading_streams.md +50 -0
- data/docs/replication.md +99 -0
- data/docs/subscriptions.md +165 -35
- data/docs/writing_middleware.md +25 -8
- data/ext/pg_eventstore_ext/extconf.rb +5 -0
- data/ext/pg_eventstore_ext/pg_eventstore_ext.c +74 -0
- data/lib/pg_eventstore/async_runner.rb +63 -0
- data/lib/pg_eventstore/basic_config.rb +15 -0
- data/lib/pg_eventstore/chunks/chunk.rb +31 -0
- data/lib/pg_eventstore/chunks/read_api_events_index_chunk.rb +83 -0
- data/lib/pg_eventstore/chunks/replica_events_index_chunk.rb +35 -0
- data/lib/pg_eventstore/chunks/repository.rb +73 -0
- data/lib/pg_eventstore/chunks/subscription_checkpoint_chunk.rb +48 -0
- data/lib/pg_eventstore/chunks/subscription_events_index_chunk.rb +99 -0
- data/lib/pg_eventstore/chunks.rb +14 -0
- data/lib/pg_eventstore/client.rb +163 -50
- data/lib/pg_eventstore/commands/append.rb +62 -43
- data/lib/pg_eventstore/commands/delete_event.rb +7 -8
- data/lib/pg_eventstore/commands/delete_stream.rb +3 -3
- data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +9 -7
- data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +12 -7
- data/lib/pg_eventstore/commands/link_to.rb +5 -5
- data/lib/pg_eventstore/commands/read.rb +23 -5
- data/lib/pg_eventstore/commands/read_grouped.rb +46 -0
- data/lib/pg_eventstore/commands/read_streams.rb +18 -0
- data/lib/pg_eventstore/commands/read_streams_paginated.rb +56 -0
- data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +4 -4
- data/lib/pg_eventstore/commands/revision_check/current_revision.rb +51 -0
- data/lib/pg_eventstore/commands/revision_check/event_type_revisions_comparison.rb +46 -0
- data/lib/pg_eventstore/commands/revision_check/expected_revision.rb +135 -0
- data/lib/pg_eventstore/commands/revision_check/stream_revision_check.rb +98 -0
- data/lib/pg_eventstore/commands/revision_check/stream_revision_comparison.rb +36 -0
- data/lib/pg_eventstore/commands/stream_revision.rb +14 -0
- data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +4 -4
- data/lib/pg_eventstore/commands.rb +4 -2
- data/lib/pg_eventstore/config.rb +30 -9
- data/lib/pg_eventstore/connection.rb +21 -2
- data/lib/pg_eventstore/errors.rb +136 -10
- data/lib/pg_eventstore/event.rb +34 -2
- data/lib/pg_eventstore/event_deserializer.rb +5 -4
- data/lib/pg_eventstore/event_global_index.rb +118 -0
- data/lib/pg_eventstore/event_marker.rb +16 -0
- data/lib/pg_eventstore/event_marker_index.rb +25 -0
- data/lib/pg_eventstore/event_serializer.rb +6 -0
- data/lib/pg_eventstore/extensions/acts_as_configurable.rb +88 -0
- data/lib/pg_eventstore/extensions/options_defaults.rb +25 -0
- data/lib/pg_eventstore/extensions/options_extension.rb +2 -2
- data/lib/pg_eventstore/feature_marker.rb +18 -0
- data/lib/pg_eventstore/maintenance.rb +11 -6
- data/lib/pg_eventstore/middleware/event_tracing.rb +53 -0
- data/lib/pg_eventstore/partition.rb +6 -0
- data/lib/pg_eventstore/pg_connection.rb +70 -14
- data/lib/pg_eventstore/queries/event_marker_queries.rb +74 -0
- data/lib/pg_eventstore/queries/event_queries.rb +7 -93
- data/lib/pg_eventstore/queries/events_global_index_queries.rb +131 -0
- data/lib/pg_eventstore/queries/index_filtering_queries.rb +179 -0
- data/lib/pg_eventstore/queries/links_resolver.rb +4 -4
- data/lib/pg_eventstore/queries/maintenance_queries.rb +159 -42
- data/lib/pg_eventstore/queries/partition_queries.rb +45 -103
- data/lib/pg_eventstore/queries/replica_queries.rb +110 -0
- data/lib/pg_eventstore/queries/streams_global_index_queries.rb +163 -0
- data/lib/pg_eventstore/queries/transaction_queries.rb +52 -10
- data/lib/pg_eventstore/queries.rb +29 -1
- data/lib/pg_eventstore/query_builders/basic_filtering.rb +38 -5
- data/lib/pg_eventstore/query_builders/event_markers_filtering.rb +47 -0
- data/lib/pg_eventstore/query_builders/event_markers_index_filtering.rb +275 -0
- data/lib/pg_eventstore/query_builders/event_subscription_positions_filtering.rb +37 -0
- data/lib/pg_eventstore/query_builders/events_filtering.rb +15 -164
- data/lib/pg_eventstore/query_builders/events_global_index_filtering.rb +227 -0
- data/lib/pg_eventstore/query_builders/filters/collection.rb +324 -0
- data/lib/pg_eventstore/query_builders/filters/event_type_filter.rb +37 -0
- data/lib/pg_eventstore/query_builders/filters/filter_row.rb +54 -0
- data/lib/pg_eventstore/query_builders/filters/marker_filter.rb +20 -0
- data/lib/pg_eventstore/query_builders/filters/marker_filter_row.rb +49 -0
- data/lib/pg_eventstore/query_builders/filters/stream_filter.rb +48 -0
- data/lib/pg_eventstore/query_builders/index_based_events_filtering.rb +260 -0
- data/lib/pg_eventstore/query_builders/partitions_filtering.rb +175 -51
- data/lib/pg_eventstore/query_builders/read_cursor/stream_cursor.rb +122 -0
- data/lib/pg_eventstore/query_builders/streams_global_index_filtering.rb +83 -0
- data/lib/pg_eventstore/query_builders/subscription_events_filtering.rb +79 -0
- data/lib/pg_eventstore/query_strategy/async.rb +64 -0
- data/lib/pg_eventstore/query_strategy/foreground.rb +25 -0
- data/lib/pg_eventstore/query_strategy.rb +19 -0
- data/lib/pg_eventstore/raw_event.rb +46 -0
- data/lib/pg_eventstore/rspec/test_helpers.rb +21 -8
- data/lib/pg_eventstore/sql_builder.rb +87 -8
- data/lib/pg_eventstore/stream.rb +13 -16
- data/lib/pg_eventstore/stream_global_index.rb +28 -0
- data/lib/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rb +5 -17
- data/lib/pg_eventstore/subscriptions/callback_handlers/events_subscription_position_worker_handlers.rb +36 -0
- data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rb +12 -0
- data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rb +21 -7
- data/lib/pg_eventstore/subscriptions/events_processor.rb +20 -18
- data/lib/pg_eventstore/subscriptions/events_processor_consumer/multiple.rb +63 -0
- data/lib/pg_eventstore/subscriptions/events_processor_consumer/replica.rb +68 -0
- data/lib/pg_eventstore/subscriptions/events_processor_consumer/single.rb +62 -0
- data/lib/pg_eventstore/subscriptions/events_processor_consumer.rb +44 -0
- data/lib/pg_eventstore/subscriptions/events_subscription_position_worker.rb +68 -0
- data/lib/pg_eventstore/subscriptions/queries/event_subscription_position_queries.rb +177 -0
- data/lib/pg_eventstore/subscriptions/queries/subscription_queries.rb +78 -54
- data/lib/pg_eventstore/subscriptions/replica_subscription_handler.rb +140 -0
- data/lib/pg_eventstore/subscriptions/replica_subscription_runner.rb +9 -0
- data/lib/pg_eventstore/subscriptions/subscription.rb +2 -1
- data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/collection.rb +36 -0
- data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/index_read_strategy.rb +82 -0
- data/lib/pg_eventstore/subscriptions/subscription_feed_strategies/replication_strategy.rb +75 -0
- data/lib/pg_eventstore/subscriptions/subscription_feed_strategy.rb +31 -0
- data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +24 -0
- data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +4 -2
- data/lib/pg_eventstore/subscriptions/subscription_runner.rb +20 -9
- data/lib/pg_eventstore/subscriptions/subscription_runner_commands/reset_position.rb +1 -1
- data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +9 -18
- data/lib/pg_eventstore/subscriptions/subscriptions_lifecycle.rb +0 -12
- data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +74 -14
- data/lib/pg_eventstore/{subscriptions/synchronized_array.rb → synchronized_array.rb} +15 -0
- data/lib/pg_eventstore/tasks/setup.rake +2 -2
- data/lib/pg_eventstore/utils.rb +31 -0
- data/lib/pg_eventstore/version.rb +1 -1
- data/lib/pg_eventstore/web/application.rb +100 -16
- data/lib/pg_eventstore/web/paginator/base_collection.rb +5 -1
- data/lib/pg_eventstore/web/paginator/event_types_collection.rb +14 -4
- data/lib/pg_eventstore/web/paginator/events_collection.rb +29 -33
- data/lib/pg_eventstore/web/paginator/helpers.rb +16 -9
- data/lib/pg_eventstore/web/paginator/markers_collection.rb +48 -0
- data/lib/pg_eventstore/web/paginator/stream_contexts_collection.rb +13 -2
- data/lib/pg_eventstore/web/paginator/stream_ids_collection.rb +21 -11
- data/lib/pg_eventstore/web/paginator/stream_names_collection.rb +18 -4
- data/lib/pg_eventstore/web/paginator/streams_collection.rb +95 -0
- data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +49 -22
- data/lib/pg_eventstore/web/subscriptions/set_collection.rb +1 -1
- data/lib/pg_eventstore/web/subscriptions/subscriptions.rb +1 -1
- data/lib/pg_eventstore/web/subscriptions/with_state/set_collection.rb +1 -1
- data/lib/pg_eventstore/web/subscriptions/with_state/subscriptions.rb +1 -1
- data/lib/pg_eventstore/web/views/home/dashboard.erb +11 -9
- data/lib/pg_eventstore/web/views/home/partials/event_filter.erb +18 -5
- data/lib/pg_eventstore/web/views/home/partials/events.erb +38 -1
- data/lib/pg_eventstore/web/views/home/partials/markers_filter.erb +18 -0
- data/lib/pg_eventstore/web/views/layouts/application.erb +6 -0
- data/lib/pg_eventstore/web/views/streams/index.erb +110 -0
- data/lib/pg_eventstore/web/views/streams/partials/streams.erb +11 -0
- data/lib/pg_eventstore/web/views/subscriptions/index.erb +4 -1
- data/lib/pg_eventstore/web.rb +2 -0
- data/lib/pg_eventstore.rb +23 -60
- data/pg_eventstore.gemspec +6 -4
- data/sig/interfaces/_raw_events_handler.rbs +3 -0
- data/sig/interfaces/subscription_handler.rbs +1 -1
- data/sig/pg/result.rbs +9 -0
- data/sig/pg_eventstore/async_runner.rbs +13 -0
- data/sig/pg_eventstore/basic_config.rbs +9 -0
- data/sig/pg_eventstore/chunks/chunk.rbs +13 -0
- data/sig/pg_eventstore/chunks/read_api_events_index_chunk.rbs +30 -0
- data/sig/pg_eventstore/chunks/replica_events_index_chunk.rbs +11 -0
- data/sig/pg_eventstore/chunks/repository.rbs +22 -0
- data/sig/pg_eventstore/chunks/subscription_checkpoint_chunk.rbs +18 -0
- data/sig/pg_eventstore/chunks/subscription_events_index_chunk.rbs +37 -0
- data/sig/pg_eventstore/client.rbs +40 -42
- data/sig/pg_eventstore/commands/append.rbs +11 -25
- data/sig/pg_eventstore/commands/event_modifiers/prepare_link_event.rbs +9 -12
- data/sig/pg_eventstore/commands/event_modifiers/prepare_regular_event.rbs +5 -4
- data/sig/pg_eventstore/commands/link_to.rbs +6 -11
- data/sig/pg_eventstore/commands/read.rbs +2 -5
- data/sig/pg_eventstore/commands/read_grouped.rbs +7 -0
- data/sig/pg_eventstore/commands/read_streams.rbs +7 -0
- data/sig/pg_eventstore/commands/read_streams_paginated.rbs +16 -0
- data/sig/pg_eventstore/commands/regular_stream_read_paginated.rbs +9 -17
- data/sig/pg_eventstore/commands/revision_check/current_revision.rbs +25 -0
- data/sig/pg_eventstore/commands/revision_check/event_type_revisions_comparison.rbs +14 -0
- data/sig/pg_eventstore/commands/revision_check/expected_revision.rbs +67 -0
- data/sig/pg_eventstore/commands/revision_check/stream_revision_check.rbs +20 -0
- data/sig/pg_eventstore/commands/revision_check/stream_revision_comparison.rbs +9 -0
- data/sig/pg_eventstore/commands/stream_revision.rbs +7 -0
- data/sig/pg_eventstore/commands/system_stream_read_paginated.rbs +9 -14
- data/sig/pg_eventstore/config.rbs +15 -6
- data/sig/pg_eventstore/connection.rbs +11 -18
- data/sig/pg_eventstore/errors.rbs +64 -34
- data/sig/pg_eventstore/event.rbs +16 -2
- data/sig/pg_eventstore/event_deserializer.rbs +5 -10
- data/sig/pg_eventstore/event_global_index.rbs +60 -0
- data/sig/pg_eventstore/event_marker.rbs +9 -0
- data/sig/pg_eventstore/event_marker_index.rbs +12 -0
- data/sig/pg_eventstore/extensions/acts_as_configurable.rbs +29 -0
- data/sig/pg_eventstore/extensions/options_defaults.rbs +7 -0
- data/sig/pg_eventstore/feature_marker.rbs +10 -0
- data/sig/pg_eventstore/maintenance.rbs +12 -8
- data/sig/pg_eventstore/middleware/event_trace.rbs +14 -0
- data/sig/pg_eventstore/partition.rbs +2 -4
- data/sig/pg_eventstore/pg_connection.rbs +8 -0
- data/sig/pg_eventstore/queries/event_marker_queries.rbs +21 -0
- data/sig/pg_eventstore/queries/event_queries.rbs +4 -36
- data/sig/pg_eventstore/queries/events_global_index_queries.rbs +37 -0
- data/sig/pg_eventstore/queries/index_filtering_queries.rbs +50 -0
- data/sig/pg_eventstore/queries/links_resolver.rbs +5 -9
- data/sig/pg_eventstore/queries/maintenance_queries.rbs +12 -9
- data/sig/pg_eventstore/queries/partition_queries.rbs +32 -67
- data/sig/pg_eventstore/queries/replica_queries.rbs +31 -0
- data/sig/pg_eventstore/queries/streams_global_index_queries.rbs +39 -0
- data/sig/pg_eventstore/queries/transaction_queries.rbs +2 -0
- data/sig/pg_eventstore/queries.rbs +11 -21
- data/sig/pg_eventstore/query_builders/basic_filtering.rbs +10 -3
- data/sig/pg_eventstore/query_builders/event_markers_filtering.rbs +17 -0
- data/sig/pg_eventstore/query_builders/event_markers_index_filtering.rbs +69 -0
- data/sig/pg_eventstore/query_builders/event_subscription_positions_filtering.rbs +15 -0
- data/sig/pg_eventstore/query_builders/events_filtering_query.rbs +9 -51
- data/sig/pg_eventstore/query_builders/events_global_index_filtering.rbs +68 -0
- data/sig/pg_eventstore/query_builders/filters/collection.rbs +84 -0
- data/sig/pg_eventstore/query_builders/filters/event_type_filter.rbs +19 -0
- data/sig/pg_eventstore/query_builders/filters/filter_row.rbs +21 -0
- data/sig/pg_eventstore/query_builders/filters/marker_filter.rbs +13 -0
- data/sig/pg_eventstore/query_builders/filters/marker_filter_row.rbs +19 -0
- data/sig/pg_eventstore/query_builders/filters/stream_filter.rbs +24 -0
- data/sig/pg_eventstore/query_builders/index_based_events_filtering.rbs +60 -0
- data/sig/pg_eventstore/query_builders/partitions_filtering.rbs +29 -10
- data/sig/pg_eventstore/query_builders/read_cursor/stream_cursor.rbs +47 -0
- data/sig/pg_eventstore/query_builders/streams_global_index_filtering.rbs +27 -0
- data/sig/pg_eventstore/query_builders/subscription_events_filtering.rbs +26 -0
- data/sig/pg_eventstore/query_strategy/async.rbs +17 -0
- data/sig/pg_eventstore/query_strategy/foreground.rbs +11 -0
- data/sig/pg_eventstore/query_strategy.rbs +7 -0
- data/sig/pg_eventstore/raw_event.rbs +20 -0
- data/sig/pg_eventstore/sql_builder.rbs +32 -10
- data/sig/pg_eventstore/stream.rbs +10 -18
- data/sig/pg_eventstore/stream_global_index.rbs +14 -0
- data/sig/pg_eventstore/subscriptions/callback_handlers/events_processor_handlers.rbs +5 -4
- data/sig/pg_eventstore/subscriptions/callback_handlers/events_subscription_position_worker_handlers.rbs +9 -0
- data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rbs +17 -12
- data/sig/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rbs +17 -6
- data/sig/pg_eventstore/subscriptions/event_subscription_position_queries.rbs +30 -0
- data/sig/pg_eventstore/subscriptions/events_processor.rbs +12 -5
- data/sig/pg_eventstore/subscriptions/events_processor_consumer/multiple.rbs +17 -0
- data/sig/pg_eventstore/subscriptions/events_processor_consumer/replica.rbs +17 -0
- data/sig/pg_eventstore/subscriptions/events_processor_consumer/single.rbs +17 -0
- data/sig/pg_eventstore/subscriptions/events_processor_consumer.rbs +14 -0
- data/sig/pg_eventstore/subscriptions/events_subscription_position_worker.rbs +28 -0
- data/sig/pg_eventstore/subscriptions/queries/subscription_queries.rbs +7 -39
- data/sig/pg_eventstore/subscriptions/replica_subscription_handler.rbs +33 -0
- data/sig/pg_eventstore/subscriptions/replica_subscription_runner.rbs +4 -0
- data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/collection.rbs +14 -0
- data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/index_read_strategy.rbs +23 -0
- data/sig/pg_eventstore/subscriptions/subscription_feed_strategy/replication_strategy.rbs +23 -0
- data/sig/pg_eventstore/subscriptions/subscription_feed_strategy.rbs +11 -0
- data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +13 -10
- data/sig/pg_eventstore/subscriptions/subscription_handler_performance.rbs +1 -1
- data/sig/pg_eventstore/subscriptions/subscription_runner.rbs +13 -2
- data/sig/pg_eventstore/subscriptions/subscription_runners_feeder.rbs +1 -8
- data/sig/pg_eventstore/subscriptions/subscriptions_lifecycle.rbs +4 -6
- data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +29 -43
- data/sig/pg_eventstore/synchronized_array.rbs +4 -0
- data/sig/pg_eventstore/utils.rbs +10 -11
- data/sig/pg_eventstore/web/application.rbs +13 -3
- data/sig/pg_eventstore/web/paginator/base_collection.rbs +2 -0
- data/sig/pg_eventstore/web/paginator/event_types_collection.rbs +4 -0
- data/sig/pg_eventstore/web/paginator/events_collection.rbs +11 -11
- data/sig/pg_eventstore/web/paginator/helpers.rbs +6 -15
- data/sig/pg_eventstore/web/paginator/markers_collection.rbs +13 -0
- data/sig/pg_eventstore/web/paginator/stream_contexts_collection.rbs +4 -0
- data/sig/pg_eventstore/web/paginator/stream_names_collection.rbs +4 -0
- data/sig/pg_eventstore/web/paginator/streams_collection.rbs +30 -0
- data/sig/pg_eventstore.rbs +8 -26
- metadata +175 -20
- data/Dockerfile +0 -3
- data/lib/pg_eventstore/commands/all_stream_read_grouped.rb +0 -69
- data/lib/pg_eventstore/commands/regular_stream_read_grouped.rb +0 -31
- data/lib/pg_eventstore/subscriptions/queries/subscription_service_queries.rb +0 -78
- data/lib/pg_eventstore/web/views/home/partials/system_stream_filter.erb +0 -15
- data/sig/pg_eventstore/commands/all_stream_read_grouped.rbs +0 -16
- data/sig/pg_eventstore/commands/regular_stream_read_grouped.rbs +0 -8
- data/sig/pg_eventstore/subscriptions/queries/subscription_service_queries.rbs +0 -19
- data/sig/pg_eventstore/subscriptions/synchronized_array.rbs +0 -4
- /data/sig/interfaces/{_raw_event_handler.rbs → raw_event_handler.rbs} +0 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PgEventstore
|
|
4
|
+
# @!visibility private
|
|
5
|
+
class EventSubscriptionPositionQueries
|
|
6
|
+
# Max number of events_global_index records to update
|
|
7
|
+
# @return [Integer]
|
|
8
|
+
MAX_INDEX_RECORDS_TO_UPDATE_SUBSCRIPTION_POSITION = 100_000
|
|
9
|
+
# @return [Integer]
|
|
10
|
+
ASSIGN_SUBSCRIPTION_POSITIONS_LOCK_ID = 462_315_339_855_922
|
|
11
|
+
# @return [Integer]
|
|
12
|
+
REINDEX_PERIOD = 24 * 60 * 60 # 1 day
|
|
13
|
+
# Do not reindex if index size is under this value
|
|
14
|
+
# @return [Integer]
|
|
15
|
+
UNPROCESSED_POSITIONS_INDEX_SIZE_THRESHOLD = 500 * 1_024 * 1_024 # 500 MB
|
|
16
|
+
# @return [String]
|
|
17
|
+
UNPROCESSED_POSITIONS_INDEX_NAME = 'idx_event_subscription_positions_unprocessed_gposition'
|
|
18
|
+
# @return [Integer]
|
|
19
|
+
UNPROCESSED_POSITIONS_LOCK_EXPIRES_IN = 5 * 60 # 5 minutes
|
|
20
|
+
|
|
21
|
+
# @!attribute connection
|
|
22
|
+
# @return [PgEventstore::Connection]
|
|
23
|
+
attr_reader :connection
|
|
24
|
+
private :connection
|
|
25
|
+
|
|
26
|
+
# @param connection [PgEventstore::Connection]
|
|
27
|
+
def initialize(connection)
|
|
28
|
+
@connection = connection
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param raw_events [Array<Hash>]
|
|
32
|
+
# @return [void]
|
|
33
|
+
def create_unprocessed_positions(raw_events)
|
|
34
|
+
values = raw_events.map { "(#{_1['global_position']})" }.join(', ')
|
|
35
|
+
connection.with do |conn|
|
|
36
|
+
conn.exec(<<~SQL)
|
|
37
|
+
INSERT INTO event_subscription_positions_unprocessed ("global_position") VALUES #{values};
|
|
38
|
+
SQL
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @param events [Array<PgEventstore::Event>]
|
|
43
|
+
# @return [Hash<Integer, Integer>]
|
|
44
|
+
def subscription_positions_from_db(events)
|
|
45
|
+
filtering = QueryBuilders::EventSubscriptionPositionsFiltering.new
|
|
46
|
+
filtering.by_global_positions(events.map(&:global_position))
|
|
47
|
+
connection.with do |conn|
|
|
48
|
+
conn.exec_params(*filtering.to_exec_params).to_h do |attrs|
|
|
49
|
+
[attrs['global_position'], attrs['subscription_position']]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [Integer, nil]
|
|
55
|
+
def max_subscription_position
|
|
56
|
+
filtering = QueryBuilders::EventSubscriptionPositionsFiltering.new
|
|
57
|
+
filtering.max_subscription_position
|
|
58
|
+
connection.with do |conn|
|
|
59
|
+
conn.exec_params(*filtering.to_exec_params).first['max_subscription_position']
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @return [Integer, nil] number of updated records
|
|
64
|
+
def assign_subscription_position
|
|
65
|
+
transaction_queries.transaction(:read_committed) do
|
|
66
|
+
connection.with do |conn|
|
|
67
|
+
locked = conn.exec_params(
|
|
68
|
+
'select pg_try_advisory_xact_lock($1::bigint) as locked',
|
|
69
|
+
[ASSIGN_SUBSCRIPTION_POSITIONS_LOCK_ID]
|
|
70
|
+
).first['locked']
|
|
71
|
+
return unless locked
|
|
72
|
+
|
|
73
|
+
new_positions = conn.exec_params(<<~SQL, [MAX_INDEX_RECORDS_TO_UPDATE_SUBSCRIPTION_POSITION])
|
|
74
|
+
select global_position
|
|
75
|
+
from event_subscription_positions_unprocessed
|
|
76
|
+
order by global_position
|
|
77
|
+
limit $1
|
|
78
|
+
SQL
|
|
79
|
+
return 0 if new_positions.ntuples == 0
|
|
80
|
+
|
|
81
|
+
affected_positions = new_positions.map { _1['global_position'] }
|
|
82
|
+
new_positions_values = affected_positions.map { "(#{_1})" }.join(', ')
|
|
83
|
+
|
|
84
|
+
conn.exec(<<~SQL)
|
|
85
|
+
insert into event_subscription_positions (global_position) values #{new_positions_values}
|
|
86
|
+
SQL
|
|
87
|
+
affected_positions = new_positions.map { _1['global_position'] }
|
|
88
|
+
conn.exec(<<~SQL).cmd_tuples
|
|
89
|
+
delete from event_subscription_positions_unprocessed
|
|
90
|
+
where global_position in (#{affected_positions.join(',')})
|
|
91
|
+
SQL
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# There is no concept of periodic maintenance tasks yet. Simply hardcode it for now and abstract when needed.
|
|
97
|
+
# @return [Time, nil]
|
|
98
|
+
def reindex_unprocessed_positions
|
|
99
|
+
task_name = 'reindex_unprocessed_positions'
|
|
100
|
+
task = connection.with do |conn|
|
|
101
|
+
conn.exec_params(
|
|
102
|
+
'select * from maintenance_tasks where task_name = $1',
|
|
103
|
+
[task_name]
|
|
104
|
+
).first
|
|
105
|
+
end
|
|
106
|
+
if task && task['performed_at'] && task['performed_at'] + REINDEX_PERIOD > Time.now
|
|
107
|
+
return task['performed_at'] + REINDEX_PERIOD
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if task
|
|
111
|
+
locked =
|
|
112
|
+
if task['locked_at']
|
|
113
|
+
if task['locked_at'] + UNPROCESSED_POSITIONS_LOCK_EXPIRES_IN > Time.now
|
|
114
|
+
# the task is locked by someone else. Retry later
|
|
115
|
+
return task['locked_at'] + UNPROCESSED_POSITIONS_LOCK_EXPIRES_IN
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
connection.with do |conn|
|
|
119
|
+
conn.exec_params(
|
|
120
|
+
'update maintenance_tasks set locked_at = $1 where task_name = $2 and locked_at = $3',
|
|
121
|
+
[Time.now.utc, task_name, task['locked_at']]
|
|
122
|
+
)
|
|
123
|
+
end.cmd_tuples == 1
|
|
124
|
+
else
|
|
125
|
+
connection.with do |conn|
|
|
126
|
+
conn.exec_params(
|
|
127
|
+
'update maintenance_tasks set locked_at = $1 where task_name = $2 and locked_at is null',
|
|
128
|
+
[Time.now.utc, task_name]
|
|
129
|
+
)
|
|
130
|
+
end.cmd_tuples == 1
|
|
131
|
+
end
|
|
132
|
+
return unless locked
|
|
133
|
+
else
|
|
134
|
+
connection.with do |conn|
|
|
135
|
+
conn.exec_params(
|
|
136
|
+
'insert into maintenance_tasks (task_name, locked_at) values ($1, $2) returning *',
|
|
137
|
+
[task_name, Time.now.utc]
|
|
138
|
+
).first
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
index_name = UNPROCESSED_POSITIONS_INDEX_NAME
|
|
143
|
+
if index_size(index_name) > UNPROCESSED_POSITIONS_INDEX_SIZE_THRESHOLD
|
|
144
|
+
connection.with do |conn|
|
|
145
|
+
conn.exec("reindex index concurrently #{index_name}")
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
connection.with do |conn|
|
|
149
|
+
conn.exec_params(
|
|
150
|
+
'update maintenance_tasks set performed_at = $1, locked_at = null where task_name = $2',
|
|
151
|
+
[Time.now.utc, task_name]
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
Time.now.utc + REINDEX_PERIOD
|
|
155
|
+
rescue PG::UniqueViolation
|
|
156
|
+
# concurrent process has created the record before us
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @param index_name [String]
|
|
160
|
+
# @return [Integer]
|
|
161
|
+
def index_size(index_name)
|
|
162
|
+
connection.with do |conn|
|
|
163
|
+
conn.exec_params(
|
|
164
|
+
'select pg_relation_size(c.oid) as size from pg_class c where c.relname = $1',
|
|
165
|
+
[index_name]
|
|
166
|
+
).first['size'] || 0
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
# @return [PgEventstore::TransactionQueries]
|
|
173
|
+
def transaction_queries
|
|
174
|
+
TransactionQueries.new(connection)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -9,8 +9,10 @@ module PgEventstore
|
|
|
9
9
|
private :connection
|
|
10
10
|
|
|
11
11
|
# @param connection [PgEventstore::Connection]
|
|
12
|
-
|
|
12
|
+
# @param query_strategy [PgEventstore::QueryStrategy]
|
|
13
|
+
def initialize(connection, query_strategy)
|
|
13
14
|
@connection = connection
|
|
15
|
+
@query_strategy = query_strategy
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
# @param attrs [Hash]
|
|
@@ -25,9 +27,7 @@ module PgEventstore
|
|
|
25
27
|
# @return [Hash, nil]
|
|
26
28
|
def find_by(attrs)
|
|
27
29
|
builder = find_by_attrs_builder(attrs).limit(1)
|
|
28
|
-
pg_result =
|
|
29
|
-
conn.exec_params(*builder.to_exec_params)
|
|
30
|
-
end
|
|
30
|
+
pg_result = @query_strategy.exec_params(*builder.to_exec_params)
|
|
31
31
|
return if pg_result.ntuples == 0
|
|
32
32
|
|
|
33
33
|
deserialize(pg_result.to_a.first)
|
|
@@ -37,9 +37,7 @@ module PgEventstore
|
|
|
37
37
|
# @return [Array<Hash>]
|
|
38
38
|
def find_all(attrs)
|
|
39
39
|
builder = find_by_attrs_builder(attrs)
|
|
40
|
-
pg_result =
|
|
41
|
-
conn.exec_params(*builder.to_exec_params)
|
|
42
|
-
end
|
|
40
|
+
pg_result = @query_strategy.exec_params(*builder.to_exec_params)
|
|
43
41
|
return [] if pg_result.ntuples == 0
|
|
44
42
|
|
|
45
43
|
pg_result.map(&method(:deserialize))
|
|
@@ -50,9 +48,7 @@ module PgEventstore
|
|
|
50
48
|
def set_collection(state = nil)
|
|
51
49
|
builder = SQLBuilder.new.from('subscriptions').select('set').group('set').order('set ASC')
|
|
52
50
|
builder.where('state = ?', state) if state
|
|
53
|
-
raw_subscriptions =
|
|
54
|
-
conn.exec_params(*builder.to_exec_params)
|
|
55
|
-
end
|
|
51
|
+
raw_subscriptions = @query_strategy.exec_params(*builder.to_exec_params)
|
|
56
52
|
raw_subscriptions.map { |attrs| attrs['set'] }
|
|
57
53
|
end
|
|
58
54
|
|
|
@@ -71,12 +67,67 @@ module PgEventstore
|
|
|
71
67
|
VALUES (#{Utils.positional_vars(attrs.values)})
|
|
72
68
|
RETURNING *
|
|
73
69
|
SQL
|
|
74
|
-
pg_result =
|
|
75
|
-
conn.exec_params(sql, attrs.values)
|
|
76
|
-
end
|
|
70
|
+
pg_result = @query_strategy.exec_params(sql, attrs.values)
|
|
77
71
|
deserialize(pg_result.to_a.first)
|
|
78
72
|
end
|
|
79
73
|
|
|
74
|
+
# @param id [Integer] Subscription#id
|
|
75
|
+
# @param options [Hash]
|
|
76
|
+
# @option options [Hash] :filter
|
|
77
|
+
# @param locked_by [Integer] SubscriptionSet#id
|
|
78
|
+
# @return [void]
|
|
79
|
+
def create_or_replace_table_function(id, options, locked_by)
|
|
80
|
+
filter_collection = QueryBuilders::Filters::Collection.from_options(options)
|
|
81
|
+
builder = QueryBuilders::IndexBasedEventsFiltering.sql_builder_for_subscriptions(filter_collection.collection)
|
|
82
|
+
function_name = QueryBuilders::SubscriptionEventsFiltering.new(id).to_table_name
|
|
83
|
+
transaction_queries.transaction(:read_committed) do
|
|
84
|
+
connection.with do |conn|
|
|
85
|
+
attrs = conn.exec_params('select * from subscriptions where id = $1 for update', [id]).to_a.first
|
|
86
|
+
unless attrs['locked_by'] == locked_by
|
|
87
|
+
# Subscription is force-locked by someone else. We have to roll back such transaction
|
|
88
|
+
raise(WrongLockIdError.new(attrs['set'], attrs['name'], attrs['locked_by']))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
compiled = conn.compile(*builder.to_exec_params)
|
|
92
|
+
compiled = <<~SQL
|
|
93
|
+
create or replace function #{function_name}(
|
|
94
|
+
from_position bigint,
|
|
95
|
+
to_position bigint,
|
|
96
|
+
max_count int
|
|
97
|
+
)
|
|
98
|
+
returns table (
|
|
99
|
+
global_position bigint,
|
|
100
|
+
event_type_partition_id bigint,
|
|
101
|
+
subscription_position bigint
|
|
102
|
+
)
|
|
103
|
+
language plpgsql
|
|
104
|
+
stable
|
|
105
|
+
parallel safe
|
|
106
|
+
as $$
|
|
107
|
+
declare
|
|
108
|
+
from_gpos bigint;
|
|
109
|
+
to_gpos bigint;
|
|
110
|
+
begin
|
|
111
|
+
with candidates as (
|
|
112
|
+
select esp.global_position
|
|
113
|
+
from event_subscription_positions esp
|
|
114
|
+
where esp.subscription_position >= from_position
|
|
115
|
+
and esp.subscription_position <= to_position
|
|
116
|
+
)
|
|
117
|
+
/* + 0 here is to keep PostgreSQL from trying to optimize the query by picking wrong index */
|
|
118
|
+
select coalesce(min(candidates.global_position + 0), 0), coalesce(max(candidates.global_position + 0), 0)
|
|
119
|
+
into from_gpos, to_gpos
|
|
120
|
+
from candidates;
|
|
121
|
+
|
|
122
|
+
return query #{compiled};
|
|
123
|
+
end;
|
|
124
|
+
$$;
|
|
125
|
+
SQL
|
|
126
|
+
conn.exec(compiled)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
80
131
|
# @param id [Integer]
|
|
81
132
|
# @param attrs [Hash]
|
|
82
133
|
# @param locked_by [Integer, nil]
|
|
@@ -91,9 +142,7 @@ module PgEventstore
|
|
|
91
142
|
sql =
|
|
92
143
|
"UPDATE subscriptions SET #{attrs_sql} WHERE id = $#{attrs.keys.size + 1} RETURNING *"
|
|
93
144
|
updated_attrs = transaction_queries.transaction(:read_committed) do
|
|
94
|
-
pg_result =
|
|
95
|
-
conn.exec_params(sql, [*attrs.values, id])
|
|
96
|
-
end
|
|
145
|
+
pg_result = @query_strategy.exec_params(sql, [*attrs.values, id])
|
|
97
146
|
raise(RecordNotFound.new('subscriptions', id)) if pg_result.ntuples == 0
|
|
98
147
|
|
|
99
148
|
updated_attrs = pg_result.to_a.first
|
|
@@ -112,34 +161,16 @@ module PgEventstore
|
|
|
112
161
|
# @param subscriptions_ids [Array<Integer>] Array of Subscription#id
|
|
113
162
|
# @return [Hash<Integer => Time>]
|
|
114
163
|
def ping_all(subscriptions_set_id, subscriptions_ids)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
conn.exec_params(sql, [Time.now.utc, subscriptions_set_id, subscriptions_ids])
|
|
121
|
-
end
|
|
164
|
+
sql = <<~SQL
|
|
165
|
+
UPDATE subscriptions SET updated_at = $1 WHERE locked_by = $2 AND id = ANY($3::int[])
|
|
166
|
+
RETURNING id, updated_at
|
|
167
|
+
SQL
|
|
168
|
+
pg_result = @query_strategy.exec_params(sql, [Time.now.utc, subscriptions_set_id, subscriptions_ids])
|
|
122
169
|
pg_result.to_h do |attrs|
|
|
123
170
|
[attrs['id'], attrs['updated_at']]
|
|
124
171
|
end
|
|
125
172
|
end
|
|
126
173
|
|
|
127
|
-
# @param query_options [Hash{Integer => Hash}] runner_id/query options association
|
|
128
|
-
# @return [Hash{Integer => Array<Hash>}] runner_id/events association
|
|
129
|
-
def subscriptions_events(query_options)
|
|
130
|
-
return {} if query_options.empty?
|
|
131
|
-
|
|
132
|
-
final_builder = SQLBuilder.union_builders(query_options.map { |id, opts| query_builder(id, opts) })
|
|
133
|
-
raw_events = connection.with do |conn|
|
|
134
|
-
conn.exec_params(*final_builder.to_exec_params)
|
|
135
|
-
end.to_a
|
|
136
|
-
raw_events.group_by { _1['runner_id'] }.to_h do |runner_id, runner_raw_events|
|
|
137
|
-
next [runner_id, runner_raw_events] unless query_options[runner_id][:resolve_link_tos]
|
|
138
|
-
|
|
139
|
-
[runner_id, links_resolver.resolve(runner_raw_events)]
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
174
|
# @param id [Integer] subscription's id
|
|
144
175
|
# @param lock_id [Integer] id of the subscriptions set which reserves the subscription
|
|
145
176
|
# @param force [Boolean] whether to lock the subscription despite on #locked_by value
|
|
@@ -155,9 +186,7 @@ module PgEventstore
|
|
|
155
186
|
raise SubscriptionAlreadyLockedError.new(attrs[:set], attrs[:name], attrs[:locked_by])
|
|
156
187
|
end
|
|
157
188
|
|
|
158
|
-
|
|
159
|
-
conn.exec_params('UPDATE subscriptions SET locked_by = $1 WHERE id = $2', [lock_id, id])
|
|
160
|
-
end
|
|
189
|
+
@query_strategy.exec_params('UPDATE subscriptions SET locked_by = $1 WHERE id = $2', [lock_id, id])
|
|
161
190
|
end
|
|
162
191
|
lock_id
|
|
163
192
|
end
|
|
@@ -165,22 +194,17 @@ module PgEventstore
|
|
|
165
194
|
# @param id [Integer]
|
|
166
195
|
# @return [void]
|
|
167
196
|
def delete(id)
|
|
168
|
-
|
|
169
|
-
|
|
197
|
+
function_name = QueryBuilders::SubscriptionEventsFiltering.new(id).to_table_name
|
|
198
|
+
transaction_queries.transaction(:read_committed) do
|
|
199
|
+
connection.with do |conn|
|
|
200
|
+
conn.exec_params('delete from subscriptions where id = $1', [id])
|
|
201
|
+
conn.exec("drop function if exists #{function_name}")
|
|
202
|
+
end
|
|
170
203
|
end
|
|
171
204
|
end
|
|
172
205
|
|
|
173
206
|
private
|
|
174
207
|
|
|
175
|
-
# @param id [Integer] runner id
|
|
176
|
-
# @param options [Hash] query options
|
|
177
|
-
# @return [PgEventstore::SQLBuilder]
|
|
178
|
-
def query_builder(id, options)
|
|
179
|
-
builder = PgEventstore::QueryBuilders::EventsFiltering.subscriptions_events_filtering(options).to_sql_builder
|
|
180
|
-
builder.where('global_position <= ?', options[:to_position]) if options[:to_position]
|
|
181
|
-
builder.select("#{id} as runner_id")
|
|
182
|
-
end
|
|
183
|
-
|
|
184
208
|
# @return [PgEventstore::TransactionQueries]
|
|
185
209
|
def transaction_queries
|
|
186
210
|
TransactionQueries.new(connection)
|
|
@@ -188,7 +212,7 @@ module PgEventstore
|
|
|
188
212
|
|
|
189
213
|
# @return [PgEventstore::LinksResolver]
|
|
190
214
|
def links_resolver
|
|
191
|
-
LinksResolver.new(connection)
|
|
215
|
+
LinksResolver.new(connection, @query_strategy)
|
|
192
216
|
end
|
|
193
217
|
|
|
194
218
|
# @param hash [Hash]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PgEventstore
|
|
4
|
+
class ReplicaSubscriptionHandler
|
|
5
|
+
def initialize(config_name, replica_config_name)
|
|
6
|
+
@replica_config_name = replica_config_name
|
|
7
|
+
@config_name = config_name
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# @param indexes [Array<PgEventstore::EventGlobalIndex::SubscriptionRepr>]
|
|
11
|
+
# @return [void]
|
|
12
|
+
def call(indexes)
|
|
13
|
+
indexes = reject_already_processed(indexes)
|
|
14
|
+
runner = AsyncRunner.new
|
|
15
|
+
objects_to_migrate = {}
|
|
16
|
+
runner.async do
|
|
17
|
+
objects_to_migrate[:raw_events] = source_replica_queries.load_events(indexes)
|
|
18
|
+
end
|
|
19
|
+
runner.async do
|
|
20
|
+
objects_to_migrate[:markers_index] = source_replica_queries.load_event_markers_index(indexes)
|
|
21
|
+
objects_to_migrate[:markers] = source_replica_queries.load_markers(objects_to_migrate[:markers_index])
|
|
22
|
+
end
|
|
23
|
+
runner.async do
|
|
24
|
+
objects_to_migrate[:events_index] = source_replica_queries.load_events_global_index(indexes)
|
|
25
|
+
objects_to_migrate[:streams_index] = source_replica_queries.load_streams_global_index(
|
|
26
|
+
objects_to_migrate[:events_index]
|
|
27
|
+
)
|
|
28
|
+
objects_to_migrate[:partitions] = source_replica_queries.load_partitions(objects_to_migrate[:events_index])
|
|
29
|
+
end
|
|
30
|
+
runner.run
|
|
31
|
+
|
|
32
|
+
sql = []
|
|
33
|
+
sql << records_to_sql(
|
|
34
|
+
QueryBuilders::PartitionsFiltering::TABLE_NAME,
|
|
35
|
+
Partition.options.map(&:name),
|
|
36
|
+
objects_to_migrate[:partitions].map(&:options_hash),
|
|
37
|
+
on_conflict: 'on conflict do nothing'
|
|
38
|
+
)
|
|
39
|
+
sql << records_to_sql(
|
|
40
|
+
Event::PRIMARY_TABLE_NAME,
|
|
41
|
+
RawEvent.options.map(&:name),
|
|
42
|
+
objects_to_migrate[:raw_events].map(&:options_hash)
|
|
43
|
+
)
|
|
44
|
+
sql << records_to_sql(
|
|
45
|
+
QueryBuilders::EventsGlobalIndexFiltering::PRIMARY_TABLE_NAME,
|
|
46
|
+
EventGlobalIndex.options.map(&:name),
|
|
47
|
+
objects_to_migrate[:events_index].map(&:options_hash)
|
|
48
|
+
)
|
|
49
|
+
sql << records_to_sql(
|
|
50
|
+
QueryBuilders::StreamsGlobalIndexFiltering::PRIMARY_TABLE_NAME,
|
|
51
|
+
StreamGlobalIndex.options.map(&:name),
|
|
52
|
+
objects_to_migrate[:streams_index].map(&:options_hash),
|
|
53
|
+
on_conflict: 'on conflict (id) do update set stream_revision = EXCLUDED.stream_revision'
|
|
54
|
+
)
|
|
55
|
+
sql << records_to_sql(
|
|
56
|
+
QueryBuilders::EventSubscriptionPositionsFiltering::PRIMARY_TABLE_NAME,
|
|
57
|
+
%i[global_position subscription_position],
|
|
58
|
+
indexes.map(&:to_subscription_position_attrs)
|
|
59
|
+
)
|
|
60
|
+
if objects_to_migrate[:markers_index].any?
|
|
61
|
+
sql << records_to_sql(
|
|
62
|
+
QueryBuilders::EventMarkersIndexFiltering::PRIMARY_TABLE_NAME,
|
|
63
|
+
EventMarkerIndex.options.map(&:name),
|
|
64
|
+
objects_to_migrate[:markers_index].map(&:options_hash)
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
if objects_to_migrate[:markers].any?
|
|
68
|
+
sql << records_to_sql(
|
|
69
|
+
QueryBuilders::EventMarkersFiltering::PRIMARY_TABLE_NAME,
|
|
70
|
+
EventMarker.options.map(&:name),
|
|
71
|
+
objects_to_migrate[:markers].map(&:options_hash),
|
|
72
|
+
on_conflict: 'on conflict do nothing'
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
sql = sql.join("\n")
|
|
76
|
+
replica_transaction_queries.transaction(:read_committed) do
|
|
77
|
+
replica_connection.with do |conn|
|
|
78
|
+
conn.exec(sql)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
# @param indexes [Array<PgEventstore::EventGlobalIndex::SubscriptionRepr>]
|
|
86
|
+
# @return [Array<PgEventstore::EventGlobalIndex::SubscriptionRepr>]
|
|
87
|
+
def reject_already_processed(indexes)
|
|
88
|
+
existing_positions = destination_replica_queries.load_subscription_positions(indexes)
|
|
89
|
+
return indexes if existing_positions.empty?
|
|
90
|
+
|
|
91
|
+
indexes.reject do |index|
|
|
92
|
+
existing_positions.include?(index.subscription_position)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @param table_name [String]
|
|
97
|
+
# @param attribute_names [Array<Symbol>]
|
|
98
|
+
# @param attributes_collection [Array<Hash<Symbol, Object>>]
|
|
99
|
+
# @param on_conflict [String, nil]
|
|
100
|
+
# @return [String]
|
|
101
|
+
def records_to_sql(table_name, attribute_names, attributes_collection, on_conflict: nil)
|
|
102
|
+
sql_values = attributes_collection.map do |attributes|
|
|
103
|
+
values = attribute_names.map do |attribute_name|
|
|
104
|
+
connection.with do |conn|
|
|
105
|
+
conn.prepared_value(attributes[attribute_name])
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
"(#{values.join(', ')})"
|
|
109
|
+
end
|
|
110
|
+
sql_values = sql_values.join(', ')
|
|
111
|
+
attributes = attribute_names.join(', ')
|
|
112
|
+
"insert into #{table_name} (#{attributes}) values #{sql_values} #{on_conflict if on_conflict};"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @return [PgEventstore::Connection]
|
|
116
|
+
def connection
|
|
117
|
+
PgEventstore.connection(@config_name)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @return [PgEventstore::Connection]
|
|
121
|
+
def replica_connection
|
|
122
|
+
PgEventstore.connection(@replica_config_name)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# @return [PgEventstore::TransactionQueries]
|
|
126
|
+
def replica_transaction_queries
|
|
127
|
+
TransactionQueries.new(replica_connection)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# @return [PgEventstore::ReplicaQueries]
|
|
131
|
+
def source_replica_queries
|
|
132
|
+
ReplicaQueries.new(connection, QueryStrategy::Async.new(connection))
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @return [PgEventstore::ReplicaQueries]
|
|
136
|
+
def destination_replica_queries
|
|
137
|
+
ReplicaQueries.new(replica_connection, QueryStrategy::Foreground.new(replica_connection))
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PgEventstore
|
|
4
|
+
# @!visibility private
|
|
5
|
+
# We need to differentiate between replica subscription runner and regular subscription runner. They both have the
|
|
6
|
+
# same functional though
|
|
7
|
+
class ReplicaSubscriptionRunner < SubscriptionRunner
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -102,7 +102,7 @@ module PgEventstore
|
|
|
102
102
|
|
|
103
103
|
# @return [PgEventstore::SubscriptionQueries]
|
|
104
104
|
def subscription_queries
|
|
105
|
-
SubscriptionQueries.new(connection)
|
|
105
|
+
SubscriptionQueries.new(connection, QueryStrategy::Foreground.new(connection))
|
|
106
106
|
end
|
|
107
107
|
end
|
|
108
108
|
|
|
@@ -185,6 +185,7 @@ module PgEventstore
|
|
|
185
185
|
time_between_restarts:,
|
|
186
186
|
state: RunnerState::STATES[:initial]
|
|
187
187
|
)
|
|
188
|
+
subscription_queries.create_or_replace_table_function(id, options, locked_by)
|
|
188
189
|
reload
|
|
189
190
|
end
|
|
190
191
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PgEventstore
|
|
4
|
+
module SubscriptionFeedStrategy
|
|
5
|
+
# @!visibility private
|
|
6
|
+
class Collection < Array
|
|
7
|
+
# This number is used to determine how many subscriptions we fetch per SQL query.
|
|
8
|
+
# @return [Integer]
|
|
9
|
+
DEFAULT_SUBSCRIPTIONS_NUM_PER_QUERY = 10
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
# @param runners [Array<PgEventstore::SubscriptionRunner>]
|
|
13
|
+
# @param connection [PgEventstore::Connection]
|
|
14
|
+
# @param query_strategy [PgEventstore::QueryStrategy]
|
|
15
|
+
# @param subscriptions_per_query [Integer]
|
|
16
|
+
# @return [PgEventstore::SubscriptionFeedStrategy::Collection]
|
|
17
|
+
def create(runners, connection, query_strategy, subscriptions_per_query: DEFAULT_SUBSCRIPTIONS_NUM_PER_QUERY)
|
|
18
|
+
instance = new
|
|
19
|
+
replica_runners = runners.grep(ReplicaSubscriptionRunner)
|
|
20
|
+
common_runners = runners - replica_runners
|
|
21
|
+
common_runners.each_slice(subscriptions_per_query).each do |runners_slice|
|
|
22
|
+
strategy = IndexReadStrategy.new(connection, query_strategy)
|
|
23
|
+
strategy.add(*runners_slice)
|
|
24
|
+
instance.push(strategy)
|
|
25
|
+
end
|
|
26
|
+
replica_runners.each_slice(subscriptions_per_query) do |runners_slice|
|
|
27
|
+
strategy = ReplicationStrategy.new(connection, query_strategy)
|
|
28
|
+
strategy.add(*runners_slice)
|
|
29
|
+
instance.push(strategy)
|
|
30
|
+
end
|
|
31
|
+
instance
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PgEventstore
|
|
4
|
+
module SubscriptionFeedStrategy
|
|
5
|
+
# @!visibility private
|
|
6
|
+
class IndexReadStrategy
|
|
7
|
+
include SubscriptionFeedStrategy
|
|
8
|
+
|
|
9
|
+
# Allow subscriptions to scan through up to this amount of events per a single query. This allows to make query
|
|
10
|
+
# plan more predictable. Downside: let's say subscription1 targets "Foo" event type, but between
|
|
11
|
+
# SubscriptionRunnersFeeder#feed runs more than this amount of events other than "Foo" event type are published -
|
|
12
|
+
# it will require at least one more loop to pick that event.
|
|
13
|
+
# @return [Integer]
|
|
14
|
+
INDEX_LOOK_UP_DISTANCE = 100_000
|
|
15
|
+
|
|
16
|
+
# @param connection [PgEventstore::Connection]
|
|
17
|
+
# @param query_strategy [PgEventstore::QueryStrategy]
|
|
18
|
+
def initialize(connection, query_strategy)
|
|
19
|
+
@connection = connection
|
|
20
|
+
@query_strategy = query_strategy
|
|
21
|
+
@runners = []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param runners [Array<PgEventstore::SubscriptionRunner>]
|
|
25
|
+
# @return [Array<PgEventstore::SubscriptionRunner>]
|
|
26
|
+
def add(*runners)
|
|
27
|
+
@runners.push(*runners)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @return [Integer]
|
|
31
|
+
def size
|
|
32
|
+
@runners.size
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Boolean]
|
|
36
|
+
def any?
|
|
37
|
+
@runners.any?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [void]
|
|
41
|
+
def feed
|
|
42
|
+
runners_query_options = @runners.to_h do |runner|
|
|
43
|
+
next_chunk_query_opts = runner.next_chunk_query_opts
|
|
44
|
+
next_chunk_query_opts[:to_position] =
|
|
45
|
+
[next_chunk_query_opts[:from_position] + INDEX_LOOK_UP_DISTANCE, safe_position].min
|
|
46
|
+
[runner.id, next_chunk_query_opts]
|
|
47
|
+
end
|
|
48
|
+
grouped_indexes = events_global_index_queries.fetch_indexes_for_subscriptions(runners_query_options)
|
|
49
|
+
@runners.each do |runner|
|
|
50
|
+
if grouped_indexes[runner.id]
|
|
51
|
+
chunk = Chunks::SubscriptionEventsIndexChunk.new(
|
|
52
|
+
grouped_indexes[runner.id],
|
|
53
|
+
@connection,
|
|
54
|
+
QueryStrategy::Foreground.new(@connection),
|
|
55
|
+
runners_query_options[runner.id][:resolve_link_tos]
|
|
56
|
+
)
|
|
57
|
+
runner.feed(chunk)
|
|
58
|
+
else
|
|
59
|
+
runner.feed(Chunks::SubscriptionCheckpointChunk.new(runners_query_options[runner.id][:to_position]))
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# @return [Integer]
|
|
67
|
+
def safe_position
|
|
68
|
+
event_subscription_position_queries.max_subscription_position || 0
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @return [PgEventstore::EventsGlobalIndexQueries]
|
|
72
|
+
def events_global_index_queries
|
|
73
|
+
EventsGlobalIndexQueries.new(@connection, @query_strategy)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @return [PgEventstore::EventSubscriptionPositionQueries]
|
|
77
|
+
def event_subscription_position_queries
|
|
78
|
+
EventSubscriptionPositionQueries.new(@connection)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|