Skip to content

Commit 7267fe3

Browse files
authored
fix(sync-service): Fix issue where replication traffic slows down shape restoration (#2840)
Fixes #2767 by not starting the replication client streaming until the consumers are ready. This avoids the need for the consumers to buffer messages until ready, which also reduces memory consumption and uses one less feature of GenStage making it easier to replace.
1 parent 6af9f33 commit 7267fe3

6 files changed

Lines changed: 32 additions & 13 deletions

File tree

.changeset/loud-eagles-end.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@core/sync-service": patch
3+
---
4+
5+
Fix issue where replication traffic slows down shape restoration

packages/sync-service/lib/electric/connection/manager.ex

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ defmodule Electric.Connection.Manager do
6464
| {:start_replication_client, :configuring_connection}
6565
| :start_connection_pool
6666
| :start_shapes_supervisor
67+
| :waiting_for_consumers
6768
| {:start_replication_client, :start_streaming}
6869
# Steps of the :running phase:
6970
| :waiting_for_streaming_confirmation
@@ -193,6 +194,10 @@ defmodule Electric.Connection.Manager do
193194
GenServer.cast(manager, :lock_connection_started)
194195
end
195196

197+
def consumers_ready(stack_id) do
198+
GenServer.cast(name(stack_id), :consumers_ready)
199+
end
200+
196201
def exclusive_connection_lock_acquisition_failed(manager, error) do
197202
GenServer.cast(manager, {:exclusive_connection_lock_acquisition_failed, error})
198203
end
@@ -476,11 +481,11 @@ defmodule Electric.Connection.Manager do
476481
state = %State{
477482
state
478483
| shape_log_collector_pid: log_collector_pid,
479-
current_step: {:start_replication_client, :start_streaming},
484+
current_step: :waiting_for_consumers,
480485
purge_all_shapes?: false
481486
}
482487

483-
{:noreply, state, {:continue, :start_streaming}}
488+
{:noreply, state}
484489
end
485490

486491
def handle_continue(
@@ -877,6 +882,17 @@ defmodule Electric.Connection.Manager do
877882
end
878883
end
879884

885+
def handle_cast(
886+
:consumers_ready,
887+
%State{
888+
current_phase: :connection_setup,
889+
current_step: :waiting_for_consumers
890+
} = state
891+
) do
892+
state = %State{state | current_step: {:start_replication_client, :start_streaming}}
893+
{:noreply, state, {:continue, :start_streaming}}
894+
end
895+
880896
def handle_cast(
881897
:replication_client_streamed_first_message,
882898
%State{current_phase: :running, current_step: :waiting_for_streaming_confirmation} = state

packages/sync-service/lib/electric/replication/shape_log_collector.ex

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ defmodule Electric.Replication.ShapeLogCollector do
3333
Electric.ProcessRegistry.name(stack_id, __MODULE__)
3434
end
3535

36-
def start_processing(server, last_processed_lsn) do
36+
def set_last_processed_lsn(server, last_processed_lsn) do
3737
# Allow 60s for this call as it may need to wait for thousands of restored shapes
3838
# to subscribe before it returns.
39-
GenStage.call(server, {:start_processing, last_processed_lsn}, 60_000)
39+
GenStage.call(server, {:set_last_processed_lsn, last_processed_lsn}, 60_000)
4040
end
4141

4242
# use `GenStage.call/2` here to make the event processing synchronous.
@@ -87,10 +87,7 @@ defmodule Electric.Replication.ShapeLogCollector do
8787
tracked_relations: tracker_state
8888
})
8989

90-
# start in demand: :accumulate mode so that the ShapeCache is able to start
91-
# all active consumers before we start sending transactions
92-
{:producer, state,
93-
dispatcher: {Electric.Shapes.Dispatcher, inspector: state.inspector}, demand: :accumulate}
90+
{:producer, state, dispatcher: {Electric.Shapes.Dispatcher, inspector: state.inspector}}
9491
end
9592

9693
def handle_subscribe(:consumer, _opts, from, state) do
@@ -131,9 +128,8 @@ defmodule Electric.Replication.ShapeLogCollector do
131128
{:noreply, [], remove_subscription(from, state)}
132129
end
133130

134-
def handle_call({:start_processing, lsn}, _from, state) do
131+
def handle_call({:set_last_processed_lsn, lsn}, _from, state) do
135132
LsnTracker.init(lsn, state.stack_id)
136-
GenStage.demand(self(), :forward)
137133
Electric.StatusMonitor.mark_shape_log_collector_ready(state.stack_id, self())
138134
{:reply, :ok, [], Map.put(state, :last_processed_lsn, lsn)}
139135
end

packages/sync-service/lib/electric/shape_cache.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,9 @@ defmodule Electric.ShapeCache do
266266

267267
@impl GenServer
268268
def handle_continue({:consumers_ready, last_processed_lsn}, state) do
269-
ShapeLogCollector.start_processing(state.log_producer, last_processed_lsn)
269+
ShapeLogCollector.set_last_processed_lsn(state.log_producer, last_processed_lsn)
270+
Electric.Connection.Manager.consumers_ready(state.stack_id)
271+
270272
{:noreply, state}
271273
end
272274

packages/sync-service/test/electric/replication/shape_log_collector_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ defmodule Electric.Replication.ShapeLogCollectorTest do
352352
prev_lsn = Lsn.increment(start_lsn, -1)
353353
next_lsn = Lsn.increment(start_lsn, +1)
354354

355-
ShapeLogCollector.start_processing(pid, start_lsn)
355+
ShapeLogCollector.set_last_processed_lsn(pid, start_lsn)
356356

357357
assert start_lsn == LsnTracker.get_last_processed_lsn(ctx.stack_id)
358358

packages/sync-service/test/electric/shapes/consumer_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ defmodule Electric.Shapes.ConsumerTest do
118118
inspector: @base_inspector
119119
)
120120

121-
ShapeLogCollector.start_processing(producer, Lsn.from_integer(0))
121+
ShapeLogCollector.set_last_processed_lsn(producer, Lsn.from_integer(0))
122122

123123
consumers =
124124
for {shape_handle, shape} <- ctx.shapes do

0 commit comments

Comments
 (0)