The serial process shell over the pure Replicant.Assembler (spec §4). Receives
decoded pgoutput messages from Replicant.Connection (which decodes behind
the value-free boundary and never applies the sink), assembles transactions, and
applies the sink synchronously — blocking THIS process, off the Connection's
keepalive path. On a durable sink commit (or a watermark skip) it messages the
Connection so the ack advances asynchronously; on a fail-closed condition
(destructive schema change, sink WRITE fault, an unidentifiable-relation row) it
halts the whole pipeline permanently (spec §6/§9).
It is a single serial GenServer (not a Task per transaction) so transactions
apply strictly in commit order — the correctness baseline of synchronous
per-transaction delivery.
Summary
Functions
Returns a specification to start this module under a supervisor.
Hand a read chunk to the applier. The reply is DEFERRED while max_pending_chunks are buffered.
{:error, :window_reset} on a reconnect; {:error, :table_discarded} on a contention discard
(drop-cap breach / a PK-less table's concurrent write) — the reader re-reads from durable progress.
A PK-less table's whole-read BARRIER (spec §6.4): after the reader streams all of a keyless
table's provisional batches, it calls this to block until those batches have APPLIED (converged
past any late contention) — replying :ok — or the table was contention-discarded — replying
{:error, :table_discarded} so the reader redoes the whole table. Without this barrier a
single-batch keyless read can complete before a late-arriving concurrent write discards its
still-pending batch, silently losing the rows.
Open the drop-set tracking window for a table BEFORE the reader captures LW (spec §2 R1). The reply is DEFERRED while the assembler-local in-flight estimate (frontier − last applied LSN) exceeds max_inflight_lag ÷ 2 — the pacing gate (spec §4 R2): stream drain gets priority, chunks fill idle capacity.
The Registry via-name a pipeline's AssemblerServer registers under.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec deliver_snapshot_chunk(GenServer.server(), Replicant.SnapshotWindow.chunk()) :: :ok | {:error, :window_reset} | {:error, :table_discarded}
Hand a read chunk to the applier. The reply is DEFERRED while max_pending_chunks are buffered.
{:error, :window_reset} on a reconnect; {:error, :table_discarded} on a contention discard
(drop-cap breach / a PK-less table's concurrent write) — the reader re-reads from durable progress.
@spec finish_snapshot_table(GenServer.server(), String.t(), non_neg_integer()) :: :ok | {:error, :window_reset} | {:error, :table_discarded}
A PK-less table's whole-read BARRIER (spec §6.4): after the reader streams all of a keyless
table's provisional batches, it calls this to block until those batches have APPLIED (converged
past any late contention) — replying :ok — or the table was contention-discarded — replying
{:error, :table_discarded} so the reader redoes the whole table. Without this barrier a
single-batch keyless read can complete before a late-arriving concurrent write discards its
still-pending batch, silently losing the rows.
epoch is the window GENERATION the reader captured at open_snapshot_window/2. If a reconnect
reset re-seated the window (bumped the epoch) since the reader opened this table, its provisional
batches were WIPED (not applied): the barrier MUST reply {:error, :window_reset} — never a
spurious :ok (which would mark a never-delivered table done → data loss). The stale-generation
check comes FIRST because a reset also clears discarded/pending, so a wiped table otherwise
reads as clean. {:error, :window_reset} also fires when a reconnect releases a deferred barrier.
@spec open_snapshot_window(GenServer.server(), String.t()) :: {:ok, non_neg_integer()} | {:error, :window_reset | :table_discarded}
Open the drop-set tracking window for a table BEFORE the reader captures LW (spec §2 R1). The reply is DEFERRED while the assembler-local in-flight estimate (frontier − last applied LSN) exceeds max_inflight_lag ÷ 2 — the pacing gate (spec §4 R2): stream drain gets priority, chunks fill idle capacity.
Replies {:ok, epoch} — the window GENERATION the reader now operates under. The
keyless reader threads this back to finish_snapshot_table/3 so the barrier can
reject a stale generation (a reconnect reset bumped the epoch since the reader
opened → its batches were WIPED, not applied — spec §4/§6.4, the 85672f1
sender-tags-epoch discipline). Replies {:error, :table_discarded} if the table was
contention-discarded (reader re-reads); {:error, :window_reset} if a reconnect
released the deferred open.
@spec start_link(keyword()) :: GenServer.on_start()
The Registry via-name a pipeline's AssemblerServer registers under.