Bedrock.DataPlane.CommitProxy.RoutingData (bedrock v0.7.0)

View Source

Immutable routing state for the commit proxy.

Encapsulates all information needed to route mutations to logs:

  • shards - a :gb_trees map of end_key => {tag, start_key} for key → tag ceiling search (end_key is the shard's exclusive upper bound)
  • log_map - Map of index → log_id for golden ratio routing
  • log_services - Map of log_id → pid or {otp_name, node} for contacting logs
  • materializers - Map of tag → %{worker_id => node} (strings, as committed to the materializers/ keyspace family): a shard's MEMBER SET, from which covering_entry/2 picks the client-facing ref; clients derive the callable ref from that pick
  • replication_factor - Number of logs per mutation

The value is a plain immutable term: the commit proxy server is its only writer, applying committed metadata one batch at a time in commit-version order, and every finalization task routes from the snapshot the server handed it for its batch. Concurrent batches can never observe - or race - each other's updates, which is what lets these be ordinary data structures with no versions, locks, or shared tables.

Lifecycle

  • new_empty/0 - Creates empty routing data for dynamic population
  • from_snapshot/1 - Builds routing data from a plain snapshot at unlock

Shard Updates

Log wiring (log_map, log_services, replication_factor) is epoch-constant: seeded once at unlock and never mutated — changing log topology IS a recovery (bedrock-q67.41).

Summary

Types

The client-facing covering entry for one key: the shard's bounds, its tag, and the raw materializer ref. Log wiring stays proxy-internal.

A materializer ref handed to a client: worker id and node, both strings.

One shard's committed members: worker id to node. A set, because a shard may be served by more than one materializer (bedrock-q67.21.9).

A plain-data description of routing state, safe to send between processes and nodes. from_snapshot/1 turns it into runnable routing data.

t()

Functions

Applies metadata mutations to update routing data.

The covering entry for one key: a ceiling walk over the shard tree plus the tag's committed materializer ref.

Deletes a shard entry.

Builds routing data from a plain snapshot.

Inserts or updates a shard entry.

A shard tag's committed members, or {:error, :not_found} when the keyspace names none.

Creates empty routing data.

The client-facing pick among a shard's members: real coverage beats the placeholder (which only parks), and the choice is deterministic so every proxy answers alike and a client's retry lands consistently.

Types

covering_entry()

@type covering_entry() ::
  {start_key :: Bedrock.key(), end_key :: Bedrock.key(), tag :: term(),
   materializer_ref()}

The client-facing covering entry for one key: the shard's bounds, its tag, and the raw materializer ref. Log wiring stays proxy-internal.

materializer_ref()

@type materializer_ref() :: {worker_id :: String.t(), node :: String.t()}

A materializer ref handed to a client: worker id and node, both strings.

members()

@type members() :: %{required(Bedrock.Service.Worker.id()) => String.t()}

One shard's committed members: worker id to node. A set, because a shard may be served by more than one materializer (bedrock-q67.21.9).

shard_tree()

@type shard_tree() ::
  :gb_trees.tree(
    Bedrock.key(),
    {tag :: term(), start_key :: Bedrock.key()}
  )

snapshot()

@type snapshot() :: %{
  optional(:materializers) => %{required(Bedrock.range_tag()) => members()},
  shard_layout: %{
    required(Bedrock.key()) => {tag :: term(), start_key :: Bedrock.key()}
  },
  log_map: %{required(non_neg_integer()) => Bedrock.DataPlane.Log.id()},
  log_services: %{
    required(Bedrock.DataPlane.Log.id()) => {atom(), node()} | pid()
  },
  replication_factor: pos_integer()
}

A plain-data description of routing state, safe to send between processes and nodes. from_snapshot/1 turns it into runnable routing data.

t()

@type t() :: %Bedrock.DataPlane.CommitProxy.RoutingData{
  log_map: %{required(non_neg_integer()) => Bedrock.DataPlane.Log.id()},
  log_services: %{
    required(Bedrock.DataPlane.Log.id()) => {atom(), node()} | pid()
  },
  materializers: %{required(Bedrock.range_tag()) => members()},
  replication_factor: pos_integer(),
  shards: shard_tree()
}

Functions

apply_mutations(routing_data, updates)

@spec apply_mutations(t(), [{Bedrock.version(), [term()]}]) :: t()

Applies metadata mutations to update routing data.

Handles shard_key and materializer_key mutations. Any other system key is ignored: log wiring is epoch-constant and rides the unlock seed, and an unrecognized family is forward-compatibility, not an error.

Parameters

  • routing_data - Current routing data
  • updates - List of {version, [mutations]} tuples from resolver

Returns

Updated routing data with applied mutations.

covering_entry(routing_data, key)

@spec covering_entry(t(), Bedrock.key()) ::
  {:ok, covering_entry()} | {:error, :not_found}

The covering entry for one key: a ceiling walk over the shard tree plus the tag's committed materializer ref.

Served to clients by the commit proxy (FDB's GetKeyServerLocations answered from keyInfo) — one entry per ask, O(log n), never a bulk projection of a map that can number in the thousands. Locations are unverified hints: a stale entry costs the client a retry, never a wrong answer. {:error, :not_found} covers both a key beyond every boundary and a shard whose tag names no materializer — to the client both are an unroutable key.

delete_shard(routing_data, end_key)

@spec delete_shard(t(), binary()) :: t()

Deletes a shard entry.

Called from apply_mutations/2 when processing shard_key clear mutations.

from_snapshot(snapshot)

@spec from_snapshot(snapshot()) :: t()

Builds routing data from a plain snapshot.

insert_shard(routing_data, end_key, tag, start_key)

@spec insert_shard(t(), binary(), term(), Bedrock.key()) :: t()

Inserts or updates a shard entry.

Called from apply_mutations/2 when processing shard_key mutations.

materializer_members(routing_data, tag)

@spec materializer_members(t(), non_neg_integer()) ::
  {:ok, members()} | {:error, :not_found}

A shard tag's committed members, or {:error, :not_found} when the keyspace names none.

This answers a worker's rejoin validation (FDB's storage-server rejoin through the proxy's txnStateStore: absence from the set means worker_removed) from the same routing view that serves clients — one authority, two readers asking different questions. The worker asks MEMBERSHIP, not resolution: with several members per shard, the ref a client happens to be routed to says nothing about whether any other member still belongs.

new_empty()

@spec new_empty() :: t()

Creates empty routing data.

Starts with no shards, no logs, and replication factor of 1. Shard and materializer entries populate incrementally as metadata mutations arrive; log wiring is epoch-constant and only from_snapshot/1 sets it.

pick_member(members)

@spec pick_member(members()) :: {:ok, materializer_ref()} | :error

The client-facing pick among a shard's members: real coverage beats the placeholder (which only parks), and the choice is deterministic so every proxy answers alike and a client's retry lands consistently.

THE one pick. The distributor points the placeholder at a shard's members through this same function, so the member recovery unlocks, the member clients are routed to, and the member parked reads drain into cannot disagree. Load- and locality-aware selection is bedrock-q67.46's to add here, once.