Replicant.CheckpointStore (Replicant v0.1.0)

Copy Markdown View Source

The lib-owned checkpoint store (spec §7) for non-transactional sinks. A GenServer over a normal Postgrex connection that owns one replicant_checkpoints row per slot and reads/writes commit_lsn behind the value-free boundary (Critical Rule 1 — the store carries slot_name + LSN only; a Postgrex fault is scrubbed to a structural %Replicant.Error{}, never echoing a parameter).

The connection uses a non-sync connect so a transient store blip at boot does not fail the :temporary pipeline (Postgrex reconnects in the background); a genuine outage surfaces as a query fault that halts fail-closed. The table is created + shape-probed lazily on the first read/write (once), so init/1 never blocks on the DB.

Summary

Functions

Returns a specification to start this module under a supervisor.

Default max_retries when :checkpoint_store omits it (spec §6).

Default retry_backoff_ms when :checkpoint_store omits it (spec §6).

Emit the value-free [:replicant, :checkpoint_store, :retrying] event (slot_name + ints only).

True when a store fault reason is PERMANENT (retrying cannot fix it): a wrong pre-existing column type (:checkpoint_store_schema_mismatch) or an invalid table identifier (:config_invalid). Every other reason — :checkpoint_store_failed, into which a %Postgrex.Error{} / %DBConnection.ConnectionError{} is scrubbed — is transient AT THE VALUE-FREE BOUNDARY (a momentary blip and a wrong-host misconfig are indistinguishable here), so it is retried, then halted (spec §7).

Read the durable checkpoint for this slot (nil = never written) or a value-free error.

The shared retry decision: :retry while attempt < max_retries, else :halt. attempt is the count of retries ALREADY made (0 on the first fault). max_retries: 0 ⇒ always :halt (halt-now opt-out). Used identically by the connect-read (timer) and mid-stream write (sleep) sites so their counter semantics cannot drift (spec §4).

The Registry via-name a pipeline's CheckpointStore registers under.

Write the checkpoint (INSERT ... ON CONFLICT) or return a value-free error. Never advance the ack on {:error, _}.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

default_max_retries()

@spec default_max_retries() :: non_neg_integer()

Default max_retries when :checkpoint_store omits it (spec §6).

default_retry_backoff_ms()

@spec default_retry_backoff_ms() :: pos_integer()

Default retry_backoff_ms when :checkpoint_store omits it (spec §6).

emit_retrying(slot_name, attempt, max_retries)

@spec emit_retrying(String.t(), pos_integer(), non_neg_integer()) :: :ok

Emit the value-free [:replicant, :checkpoint_store, :retrying] event (slot_name + ints only).

permanent_reason?(arg1)

@spec permanent_reason?(atom()) :: boolean()

True when a store fault reason is PERMANENT (retrying cannot fix it): a wrong pre-existing column type (:checkpoint_store_schema_mismatch) or an invalid table identifier (:config_invalid). Every other reason — :checkpoint_store_failed, into which a %Postgrex.Error{} / %DBConnection.ConnectionError{} is scrubbed — is transient AT THE VALUE-FREE BOUNDARY (a momentary blip and a wrong-host misconfig are indistinguishable here), so it is retried, then halted (spec §7).

read(server)

@spec read(GenServer.server()) ::
  {:ok, Replicant.lsn() | nil} | {:error, Replicant.Error.t()}

Read the durable checkpoint for this slot (nil = never written) or a value-free error.

retry_decision(attempt, max_retries)

@spec retry_decision(non_neg_integer(), non_neg_integer()) :: :retry | :halt

The shared retry decision: :retry while attempt < max_retries, else :halt. attempt is the count of retries ALREADY made (0 on the first fault). max_retries: 0 ⇒ always :halt (halt-now opt-out). Used identically by the connect-read (timer) and mid-stream write (sleep) sites so their counter semantics cannot drift (spec §4).

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

via(slot_name)

@spec via(String.t()) :: {:via, module(), term()}

The Registry via-name a pipeline's CheckpointStore registers under.

write(server, lsn)

@spec write(GenServer.server(), Replicant.lsn()) ::
  :ok | {:error, Replicant.Error.t()}

Write the checkpoint (INSERT ... ON CONFLICT) or return a value-free error. Never advance the ack on {:error, _}.