brazier/store/replicated

Replicated mode for brazier/store: every node of the cluster keeps a full replica of the store and the replicas converge.

Open a replicated store with store.open(name, store.Replicated(config)) (or store.open_list). This module holds its configuration, its error types and the calls into the replica process (brazier_store_replica); applications use the brazier/store API, which is the same in both modes.

How it works (see docs/CLUSTERING.md):

Types

Replicated store settings. Build one from default_config() with a record update, e.g. Config(..replicated.default_config(), max_entries: 50_000).

pub type Config {
  Config(
    sync_interval_ms: Int,
    max_entries: Int,
    max_value_bytes: Int,
    max_delta_bytes: Int,
    max_sync_bytes: Int,
  )
}

Constructors

  • Config(
      sync_interval_ms: Int,
      max_entries: Int,
      max_value_bytes: Int,
      max_delta_bytes: Int,
      max_sync_bytes: Int,
    )

    Arguments

    sync_interval_ms

    How often each replica broadcasts its digest (the anti-entropy period): the longest a lost delta stays lost once nodes can talk.

    max_entries

    The most entries a replica holds: keys of a key-value store, values of a list store (counting concurrent values of one key until the next write replaces them).

    max_value_bytes

    The largest value (with its key), as erlang:external_size/1.

    max_delta_bytes

    The largest delta a write may produce or a replica may merge.

    max_sync_bytes

    The largest full state a replica sends or merges.

The kind of store a replica holds.

pub type Kind {
  KeyValue
  List
}

Constructors

  • KeyValue
  • List

The registered replica process of one store on this node.

pub type Server

Why a replicated store could not be opened.

pub type StartError {
  InvalidName(name: String, reason: String)
  InvalidConfig(reason: String)
  StartFailed(reason: String)
}

Constructors

  • InvalidName(name: String, reason: String)

    The name is not 1-200 bytes of [A-Za-z0-9_-].

  • InvalidConfig(reason: String)

    The configuration is out of range, or the store is already open in local mode or with a different configuration.

  • StartFailed(reason: String)

    The replica process failed to start.

Why a write to a store failed. A node-local store never fails a write except for an invalid per-key topic.

pub type WriteError {
  TooManyEntries(limit: Int)
  ValueTooLarge(bytes: Int, limit: Int)
  DeltaTooLarge(bytes: Int, limit: Int)
  Unavailable(reason: String)
  InvalidTopic(topic: String)
}

Constructors

  • TooManyEntries(limit: Int)

    The write would grow the replica past max_entries.

  • ValueTooLarge(bytes: Int, limit: Int)

    A value is larger than max_value_bytes.

  • DeltaTooLarge(bytes: Int, limit: Int)

    The write’s delta is larger than max_delta_bytes.

  • Unavailable(reason: String)

    The local replica did not answer (not running, restarting or stuck).

  • InvalidTopic(topic: String)

    The per-key topic prefix <> key is not a valid PubSub topic.

Values

pub fn append(
  server: Server,
  key: String,
  values: List(value),
  topics: List(String),
) -> Result(Nil, WriteError)

Add values to key (list stores); topics are notified on the nodes that merge them, whenever they merge them.

pub fn context_size(server: Server) -> Result(Int, WriteError)

The number of elements of the replica’s causal context (clock entries, out-of-order dots and floors). A replica restart adds one clock entry for its new incarnation; once it is in sync the node’s floor drops the earlier incarnations’ entries, so this stays bounded however often the replicas restart (see brazier/store/crdt, “Floors”).

pub fn count(table: @internal EtsTable) -> Result(Int, String)

The number of keys in a key-value store’s mirror.

pub fn default_config() -> Config

Defaults: a digest every second, 100_000 entries, 64 KiB values, 1 MiB deltas and 64 MiB full syncs.

pub fn describe_start_error(err: StartError) -> String

A human-readable description of a start error.

pub fn describe_write_error(err: WriteError) -> String

A human-readable description of a write error.

pub fn digest(server: Server) -> Result(BitArray, WriteError)

A hash of the replica’s whole state: two replicas have converged when their digests are equal.

pub fn get(
  table: @internal EtsTable,
  key: String,
) -> Result(option.Option(value), String)

The value of key in a key-value store’s mirror.

pub fn get_all(
  table: @internal EtsTable,
  key: String,
) -> Result(List(value), String)

Every value of key in a list store’s mirror, in timestamp order.

pub const max_name_bytes: Int

The longest replicated store name, in bytes (its PubSub topic brazier_store:list:<name> must stay within 255 bytes).

pub fn put(
  server: Server,
  key: String,
  value: value,
) -> Result(Nil, WriteError)

Set key to value (key-value stores).

pub fn remove(
  server: Server,
  key: String,
  topics: List(String),
) -> Result(Nil, WriteError)

Remove every value of key this replica has seen; topics are notified on the nodes that merge the removal.

pub fn size(server: Server) -> Result(Int, WriteError)

The number of entries the replica holds.

pub fn start(
  kind: Kind,
  name: String,
  config: Config,
) -> Result(#(Server, @internal EtsTable), StartError)

Start this node’s replica of a store (or return the running one, when it was started with the same configuration), with the ETS table its values are mirrored into.

pub fn stop(server: Server) -> Result(Nil, String)

Stop this node’s replica of a store and its supervisor. Its ETS mirror goes with it.

pub fn synced(server: Server) -> Result(Bool, WriteError)

Whether the replica is in sync: every peer replica (the replicas of this store on verified cluster members) last sent a digest equal to this replica’s digest. Ok(True) with no peer. A local write, a merge or a new peer makes it Ok(False) until the next digests (one sync_interval_ms apart) agree; a peer that has not yet sent a digest since it joined counts as not in sync. Every value written anywhere before the last digests were sent is then on this node.

pub fn validate_config(
  config: Config,
) -> Result(Config, StartError)

Check a configuration’s ranges.

✨ Search Document