Malachi. BrokerServer
(malachi v0.8.13)
View Source
A GenServer that owns a Malachi.Broker (the control-plane router) and a local
Malachi.Cluster.ReplicationServer (segment storage/replication), serializing concurrent
access. It wires the broker's injected effect functions to the replication server:
produce replicates through it and read/stream_history read segments from it.
Writes are durable on return: by default each batch is fsynced on a quorum by the replication
server before it commits, so there is no buffering. With group commit enabled (:group_commit,
single-node rf=1), a produce instead buffers its batch and the client reply is deferred until the
next time-based flush (~:group_commit_interval_ms), so many concurrent producers coalesce into
one fsync; the reply is still returned only once the batch is durable. Under group commit the flush
is also triggered early once :group_commit_flush_max_records are parked (so each fsync, and so each
reply, stays bounded and a produce never waits long enough to time out), and beyond
:group_commit_max_inflight parked records new produces are shed with {:error, :overloaded} rather
than dropped. Either way sync/1 is a no-op kept for API compatibility.
On the replicated (non-group-commit) path the produce is NON-BLOCKING for this server's loop (the NorthGuard end-to-end pipelined shape): the loop plans the produce (routing, segment opening, optimistic offset commit), fires the replication dispatches as casts, parks the caller, and replies from the replication results, waking consumers only after every dispatch is quorum-durable. So the node accepts the next produce while earlier ones replicate, instead of one produce per replication round trip.
The Broker (and the layers it composes) are pure immutable values; routing all mutations
through this single process is what makes concurrent producers/consumers safe.
Supersedes Malachi.TopicServer.
Summary
Functions
The ids of a topic's active ranges.
Rebuilds a sharded broker's metadata routing for a new ring topology (a vnode split adopted via
gossip): the local read cache takes the new ring: existing vnodes keep their cached Metadata, a
newly-added vnode starts empty until the next refresh from ra, and the write path is re-routed
over the topology's %{vnode_id => nodes} placements (server id = {vnode_id, a_member}). Pure: the
metadata catch-up for a new/changed vnode is the separate refresh side effect. Returns the new broker.
Applies :set_segment_replicas healing commands to the control plane.
Returns a specification to start this module under a supervisor.
Durably commits a consumer group's position for a topic; returns the control-plane reply.
A consumer group's committed offsets for a topic (empty if it never committed).
Consumes a topic's current ranges from positions, returning {records, next_positions}. When
wait_ms > 0 and nothing is available yet, the call blocks (long-poll) until a produce to the
topic delivers data or wait_ms elapses (then records is []). With wait_ms == 0 it returns
immediately. positions/next_positions map each range id to its Broker.consume_cursor.
Creates a topic (and its root range); returns {:ok, root_range_id} or an error.
Removes a sealed segment from the control plane (retention); returns the control-plane reply.
Merges two buddy ranges; returns {:ok, child_id} or an error.
The current control-plane metadata (e.g. for a healing coordinator to inspect).
Whether this broker has read every metadata vnode at least once since boot. Until it has, a topic
whose vnode is still silent is represented by an empty placeholder in the local cache, and every read
of it answers a page with no records: durable data reported as a drained topic. A node in that state
can accept connections and pass /health while being unable to answer a single read honestly.
Routes, replicates and commits records; returns {:ok, placements} or an error.
Reads up to max_records committed records from a range, starting at offset.
Reads one cross-epoch consume page of a range, tailing the active range (see Malachi.Broker.read_consume/5).
The Malachi.Cluster.ReplicationServer this broker owns, or nil when it was given an external
broker set (the clustered shape, where the replication server is supervised and named separately).
Splits a range; returns {:ok, left_id, right_id} or an error.
Starts a server whose segment storage is rooted at directory.
Stops the server (and its replication storage).
Acks count streamed records of topic/group at positions (a decoded cursor): returns that much
window credit (unblocking further pushes) and durably commits the group's position. Returns :ok.
Streams one bounded page of a range's cross-epoch history (see Malachi.Broker.stream_history/5).
Subscribes the calling process as a streaming consumer of topic for consumer group: records
are pushed to it as {:log_records, topic, records, next_positions} as they are produced, resuming
from the group's committed position and bounded by a credit window (at most window records in
flight, at most max per push). Ack with stream_ack/5 to return credit and commit progress. :ok.
No-op: writes are already durable on return. Kept for API compatibility.
The per-topic overview (Malachi.Metadata.overview/1) annotated with each topic's failure-domain
violation count (Malachi.Broker.domain_violations/2), computed from a single merged-metadata view.
Removes the calling process's streaming subscription to topic. Returns :ok.
Functions
@spec active_range_ids(GenServer.server(), Malachi.Metadata.topic_name()) :: [ Malachi.Metadata.range_id() ]
The ids of a topic's active ranges.
@spec adopt_topology(Malachi.Broker.t(), Malachi.Cluster.RingTopology.t()) :: Malachi.Broker.t()
Rebuilds a sharded broker's metadata routing for a new ring topology (a vnode split adopted via
gossip): the local read cache takes the new ring: existing vnodes keep their cached Metadata, a
newly-added vnode starts empty until the next refresh from ra, and the write path is re-routed
over the topology's %{vnode_id => nodes} placements (server id = {vnode_id, a_member}). Pure: the
metadata catch-up for a new/changed vnode is the separate refresh side effect. Returns the new broker.
@spec apply_heal(GenServer.server(), [Malachi.Metadata.command()]) :: :ok
Applies :set_segment_replicas healing commands to the control plane.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec commit_offset( GenServer.server(), Malachi.Metadata.group(), Malachi.Metadata.topic_name(), Malachi.Metadata.offsets() ) :: term()
Durably commits a consumer group's position for a topic; returns the control-plane reply.
@spec committed_offsets( GenServer.server(), Malachi.Metadata.group(), Malachi.Metadata.topic_name() ) :: Malachi.Metadata.offsets()
A consumer group's committed offsets for a topic (empty if it never committed).
@spec consume( GenServer.server(), Malachi.Metadata.topic_name(), map(), pos_integer(), non_neg_integer(), [term()] | nil ) :: {[Malachi.Log.Record.t()], map()} | {:error, :metadata_unavailable}
Consumes a topic's current ranges from positions, returning {records, next_positions}. When
wait_ms > 0 and nothing is available yet, the call blocks (long-poll) until a produce to the
topic delivers data or wait_ms elapses (then records is []). With wait_ms == 0 it returns
immediately. positions/next_positions map each range id to its Broker.consume_cursor.
@spec create_topic(GenServer.server(), Malachi.Metadata.topic_name(), pos_integer()) :: term()
Creates a topic (and its root range); returns {:ok, root_range_id} or an error.
@spec delete_segment(GenServer.server(), Malachi.Metadata.segment_id()) :: term()
Removes a sealed segment from the control plane (retention); returns the control-plane reply.
@spec merge_ranges( GenServer.server(), Malachi.Metadata.range_id(), Malachi.Metadata.range_id() ) :: {:ok, Malachi.Metadata.range_id()} | {:error, term()}
Merges two buddy ranges; returns {:ok, child_id} or an error.
@spec metadata(GenServer.server()) :: Malachi.Metadata.t()
The current control-plane metadata (e.g. for a healing coordinator to inspect).
@spec metadata_ready?(GenServer.server(), timeout()) :: boolean()
Whether this broker has read every metadata vnode at least once since boot. Until it has, a topic
whose vnode is still silent is represented by an empty placeholder in the local cache, and every read
of it answers a page with no records: durable data reported as a drained topic. A node in that state
can accept connections and pass /health while being unable to answer a single read honestly.
@spec produce(GenServer.server(), Malachi.Metadata.topic_name(), [ Malachi.Log.Record.t() ]) :: {:ok, %{ required(Malachi.Metadata.range_id()) => {non_neg_integer(), non_neg_integer()} }} | {:error, term()}
Routes, replicates and commits records; returns {:ok, placements} or an error.
@spec read( GenServer.server(), Malachi.Metadata.range_id(), non_neg_integer(), pos_integer() ) :: {:ok, [Malachi.Log.Record.t()]} | :eof | {:error, term()}
Reads up to max_records committed records from a range, starting at offset.
@spec read_consume( GenServer.server(), Malachi.Metadata.range_id(), Malachi.Broker.consume_cursor(), pos_integer() ) :: {:ok, [Malachi.Log.Record.t()], Malachi.Broker.consume_cursor()} | {:error, term()}
Reads one cross-epoch consume page of a range, tailing the active range (see Malachi.Broker.read_consume/5).
@spec replication_ref(GenServer.server()) :: pid() | nil
The Malachi.Cluster.ReplicationServer this broker owns, or nil when it was given an external
broker set (the clustered shape, where the replication server is supervised and named separately).
A single-node broker starts its own, unnamed, so this is the only way to name it as a replica: the integrity scrub needs it to tell which stored segments are its own. Ask per use rather than caching it, since a broker restart replaces the process.
@spec split_range(GenServer.server(), Malachi.Metadata.range_id()) :: {:ok, Malachi.Metadata.range_id(), Malachi.Metadata.range_id()} | {:error, term()}
Splits a range; returns {:ok, left_id, right_id} or an error.
@spec start_link( Path.t(), keyword() ) :: GenServer.on_start()
Starts a server whose segment storage is rooted at directory.
Options
:brokers- references of theMalachi.Cluster.ReplicationServers that segments are placed on. When given, this server uses them and does not start its own; when omitted, it starts a single local replication server rooted atdirectory(single-node default).:live_brokers- a(-> [broker])(e.g. from membership); when given, the placement broker set is refreshed from it every:brokers_refresh_intervalms, so new segments land on currently-alive brokers. An empty result is ignored (the last non-empty set is kept).:brokers_refresh_interval- refresh period in ms (default 1000).:metadata_cluster- a Raft cluster name (atom). When given, the metadata is made authoritative via thatracluster (mutations go through the log; reads come from a local cache);ramust already be running. When omitted, metadata is in-memory (single node).:metadata_nodes- the nodes the metadata Raft cluster spans (default[node()]); several nodes make the control plane HA (the metadata survives losing a member).:replication_factor- replicas per segment (default 1; clamped to the broker count).:group_commit- when true, produce buffers its batch and defers the client reply to the next flush so concurrent producers coalesce into one fsync (default from app env; only active at rf=1). See the moduledoc.:group_commit_interval_ms- group-commit flush period in ms (default 5, or app env).:group_commit_flush_max_records- flush eagerly once this many records are parked, bounding the per-flush fsync (default 8000, or app env).:group_commit_max_inflight- past this many parked records, shed produces with:overloadedinstead of dropping the connection (default 200000, or app env).:segment_max_bytes- byte threshold at which the active segment seals and rolls.- remaining options are forwarded to a started
Malachi.Cluster.ReplicationServer(segment log options such as:max_bytes,:flush_bytes,:index_interval); ignored with:brokers. - standard
GenServeroptions (:name, etc.) are honored.
@spec stop(GenServer.server()) :: :ok
Stops the server (and its replication storage).
@spec stream_ack( GenServer.server(), Malachi.Metadata.topic_name(), Malachi.Metadata.group(), Malachi.Metadata.offsets(), non_neg_integer(), [term()] | nil, GenServer.server() | nil ) :: :ok
Acks count streamed records of topic/group at positions (a decoded cursor): returns that much
window credit (unblocking further pushes) and durably commits the group's position. Returns :ok.
@spec stream_history( GenServer.server(), Malachi.Metadata.range_id(), Malachi.Broker.history_cursor(), pos_integer() ) :: {:ok, [Malachi.Log.Record.t()], Malachi.Broker.history_cursor()} | {:error, term()}
Streams one bounded page of a range's cross-epoch history (see Malachi.Broker.stream_history/5).
@spec subscribe( GenServer.server(), Malachi.Metadata.topic_name(), Malachi.Metadata.group(), pos_integer(), pos_integer(), keyword() ) :: :ok
Subscribes the calling process as a streaming consumer of topic for consumer group: records
are pushed to it as {:log_records, topic, records, next_positions} as they are produced, resuming
from the group's committed position and bounded by a credit window (at most window records in
flight, at most max per push). Ack with stream_ack/5 to return credit and commit progress. :ok.
@spec sync(GenServer.server()) :: :ok
No-op: writes are already durable on return. Kept for API compatibility.
The per-topic overview (Malachi.Metadata.overview/1) annotated with each topic's failure-domain
violation count (Malachi.Broker.domain_violations/2), computed from a single merged-metadata view.
@spec unsubscribe(GenServer.server(), Malachi.Metadata.topic_name()) :: :ok
Removes the calling process's streaming subscription to topic. Returns :ok.