Ferricstore.FetchOrCompute (ferricstore v0.3.6)

Copy Markdown View Source

GenServer managing compute locks for cache-aside with stampede protection.

When a cache miss occurs, only one client across the cluster is allowed to compute the value (the "computer"). All other clients requesting the same key block as "waiters" until the computer delivers the result or an error.

Cluster-wide stampede protection

Stampede protection uses a Raft-replicated distributed lock keyed on "__foc_lock__:" <> key. Because Router.lock/4 is forced through the quorum path (see Router.always_quorum?/1), exactly one caller across the whole cluster wins the lock and receives {:compute, hint}. All other callers — local or remote — fall into a poll-wait loop on Router.get/2 until the computer publishes the value (also Raft-replicated) or the compute_timeout expires.

Local fast-path waiter coordination

Within a single node we still maintain a local ETS table of waiters. When a caller on the same node sees an existing local lock, it parks as a waiter and is woken directly via GenServer.reply/2 when the computer calls fetch_or_compute_result/3 on this node. This avoids the polling overhead for same-node waiters; cross-node waiters poll.

Timeout handling

If the computer does not publish a result within compute_timeout_ms (default 30 seconds), waiters' polling loops give up with {:error, :timeout}. The Raft lock has its own TTL (also compute_timeout_ms) so it auto-expires and the next caller can retry.

Summary

Functions

Returns a specification to start this module under a supervisor.

Attempts to fetch a key, or initiates a cluster-wide compute lock.

Reports a compute error. Releases the lock, wakes local waiters with the error.

Delivers the computed result for a key.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

fetch_or_compute(key, ttl_ms, hint)

@spec fetch_or_compute(binary(), pos_integer(), binary()) ::
  {:hit, binary()} | {:compute, binary()} | {:ok, binary()} | {:error, term()}

Attempts to fetch a key, or initiates a cluster-wide compute lock.

Returns:

  • {:hit, value} — cache hit
  • {:compute, hint} — caller won the lock and must compute the value, then call fetch_or_compute_result/3
  • {:ok, value} — caller waited and received the result from another node
  • {:error, reason} — compute failed or timed out

fetch_or_compute_error(key, error_msg)

@spec fetch_or_compute_error(binary(), binary()) :: :ok

Reports a compute error. Releases the lock, wakes local waiters with the error.

fetch_or_compute_result(key, value, ttl_ms)

@spec fetch_or_compute_result(binary(), binary(), pos_integer()) :: :ok

Delivers the computed result for a key.

Stores the value via Router (Raft-replicated to all nodes), releases the distributed lock, and wakes all local waiters.

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()