Packages
vela_cache
0.1.0
A distributed cache library for Elixir with pluggable backends, topologies, and near-cache support.
Current section
Files
Jump to
Current section
Files
lib/vela/topology/replicated.ex
defmodule Vela.Topology.Replicated do
@moduledoc """
Replicated topology: every node holds a complete copy of all data.
Writes are applied locally first (fast), then broadcast to all other
nodes asynchronously via :erpc.cast (eventual consistency).
Reads always serve from the local node — no network hop needed.
"""
@behaviour Vela.Topology
alias Vela.Cache.Entry
defstruct [:backend, :backend_state, :cache_name]
@impl true
def init(config) do
{:ok, backend_state} = config.backend.init(config)
state = %__MODULE__{
backend: config.backend,
backend_state: backend_state,
cache_name: config.name
}
{:ok, state}
end
@impl true
def get(%__MODULE__{backend: b, backend_state: bs}, key) do
b.get(bs, key)
end
@impl true
def put(%__MODULE__{backend: b, backend_state: bs} = state, %Entry{} = entry) do
# Write locally first
{:ok, new_bs} = b.put(bs, entry)
# Broadcast to other nodes asynchronously
broadcast_to_cluster(state, {:replicate_put, entry})
{:ok, %{state | backend_state: new_bs}}
end
@impl true
def delete(%__MODULE__{backend: b, backend_state: bs} = state, key) do
{:ok, new_bs} = b.delete(bs, key)
broadcast_to_cluster(state, {:replicate_delete, key})
{:ok, %{state | backend_state: new_bs}}
end
@impl true
def get_many(%__MODULE__{backend: b, backend_state: bs}, keys) do
b.get_many(bs, keys)
end
@impl true
def put_many(%__MODULE__{} = state, entries) do
Enum.reduce(entries, {:ok, state}, fn entry, {:ok, acc} ->
put(acc, entry)
end)
end
@impl true
def flush(%__MODULE__{backend: b, backend_state: bs} = state) do
{:ok, new_bs} = b.flush(bs)
broadcast_to_cluster(state, :replicate_flush)
{:ok, %{state | backend_state: new_bs}}
end
@impl true
def size(%__MODULE__{backend: b, backend_state: bs}) do
b.size(bs)
end
@impl true
def invalidate_tag(%__MODULE__{backend: b, backend_state: bs} = state, tag) do
{:ok, count, new_bs} = b.delete_by_tag(bs, tag)
broadcast_to_cluster(state, {:replicate_invalidate_tag, tag})
{:ok, count, %{state | backend_state: new_bs}}
end
# ---- Replication Internals ----
@doc """
Handles replication messages arriving from other nodes.
Reads topology state from persistent_term — no GenServer needed.
Called via :erpc.cast on the remote node.
"""
def handle_replication_message(message, cache_name) do
ts = :persistent_term.get({:vela_topology_state, cache_name})
case message do
{:replicate_put, entry} ->
ts.backend.put(ts.backend_state, entry)
{:replicate_delete, key} ->
ts.backend.delete(ts.backend_state, key)
:replicate_flush ->
ts.backend.flush(ts.backend_state)
{:replicate_invalidate_tag, tag} ->
ts.backend.delete_by_tag(ts.backend_state, tag)
end
end
defp broadcast_to_cluster(state, message) do
other_nodes = Node.list()
Enum.each(other_nodes, fn target_node ->
:erpc.cast(target_node, __MODULE__, :handle_replication_message, [
message,
state.cache_name
])
end)
end
end