Grove.Register.MVRegister (Grove v0.1.1)

View Source

A Multi-Value Register (MV-Register) CRDT.

Unlike LWW-Register which picks a winner, MV-Register preserves all concurrent values. The application is responsible for resolving conflicts (e.g., showing all values to the user).

Each value is tagged with a vector clock. On merge, values with causally dominated clocks are discarded, and concurrent values are all preserved.

Delta-State Support

This CRDT supports delta-state replication. The delta contains only the locally added value entry with its causal context (vector clock).

  • delta/1 - Returns accumulated changes since last reset
  • reset_delta/1 - Clears the delta buffer after synchronization

Example

iex> reg = Grove.Register.MVRegister.new(:node_a)
iex> reg = Grove.Register.MVRegister.set(reg, "hello")
iex> Grove.Register.MVRegister.value(reg)
["hello"]

# After merging concurrent updates:
iex> Grove.Register.MVRegister.value(merged_reg)
["hello", "world"]  # Both values preserved

Summary

Functions

Returns the current vector clock.

Returns true if there are conflicting concurrent values.

Returns the accumulated delta since the last reset.

Merges two MV-Registers.

Creates a new MV-Register for the given actor.

Resets the delta buffer after synchronization.

Resolves conflicts by picking a single value.

Sets the value of the register.

Returns the current value(s) of the register.

Types

actor()

@type actor() :: term()

t()

@type t() :: %Grove.Register.MVRegister{
  actor: actor(),
  clock: Grove.VectorClock.t(),
  delta_buffer: t() | nil,
  values: %{required({term(), Grove.VectorClock.t()}) => true}
}

Functions

clock(mv_register)

@spec clock(t()) :: Grove.VectorClock.t()

Returns the current vector clock.

conflict?(register)

@spec conflict?(t()) :: boolean()

Returns true if there are conflicting concurrent values.

delta(mv_register)

@spec delta(t()) :: t()

Returns the accumulated delta since the last reset.

The delta contains only the locally added value entry with its causal context (vector clock).

merge(reg1, reg2)

@spec merge(t(), t()) :: t()

Merges two MV-Registers.

Values with causally dominated clocks are discarded. Concurrent values are all preserved.

new(actor)

@spec new(actor()) :: t()

Creates a new MV-Register for the given actor.

reset_delta(register)

@spec reset_delta(t()) :: t()

Resets the delta buffer after synchronization.

Call this after sending the delta to other replicas.

resolve(register, resolver)

@spec resolve(t(), (list() -> term())) :: t()

Resolves conflicts by picking a single value.

Uses the provided resolver function which receives the list of concurrent values and should return the chosen value.

set(register, value)

@spec set(t(), term()) :: t()

Sets the value of the register.

This replaces any previous value set by this actor, but concurrent values from other actors are preserved on merge.

value(mv_register)

@spec value(t()) :: [term()]

Returns the current value(s) of the register.

Returns a list of all concurrent values. If there's only one value (no conflict), returns a single-element list.