Ferricstore.Merge.Semaphore (ferricstore v0.3.1)

Copy Markdown View Source

Node-level semaphore that limits concurrent merge operations.

Only one shard merge may run at a time across the entire node. This prevents merge I/O from multiple shards competing for the same NVMe bandwidth and PCIe bus, which would cause both merges to run slower than a single sequential merge.

The semaphore uses a simple acquire/release model backed by a GenServer. The caller must release the semaphore after the merge completes (or fails). To guard against callers that crash without releasing, the semaphore monitors the acquiring process and auto-releases on DOWN.

Usage

case Ferricstore.Merge.Semaphore.acquire(shard_index) do
  :ok ->
    try do
      do_merge(...)
    after
      Ferricstore.Merge.Semaphore.release(shard_index)
    end
  {:busy, holder_shard} ->
    # Another shard is merging, try later.
    :ok
end

Summary

Functions

Attempts to acquire the merge semaphore for the given shard.

Returns a specification to start this module under a supervisor.

Releases the merge semaphore. Must be called by the same shard that acquired it.

Starts the merge semaphore GenServer.

Returns the current state of the semaphore for observability.

Types

state()

@type state() :: %{
  holder: {non_neg_integer(), pid()} | nil,
  monitor_ref: reference() | nil
}

Functions

acquire(shard_index, server \\ __MODULE__)

@spec acquire(non_neg_integer(), GenServer.server()) ::
  :ok | {:busy, non_neg_integer()}

Attempts to acquire the merge semaphore for the given shard.

Returns :ok if the semaphore was acquired, or {:busy, holder_shard_index} if another shard currently holds it.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

release(shard_index, server \\ __MODULE__)

@spec release(non_neg_integer(), GenServer.server()) :: :ok | {:error, :not_holder}

Releases the merge semaphore. Must be called by the same shard that acquired it.

Returns :ok on success, or {:error, :not_holder} if the caller is not the current holder.

start_link(opts \\ [])

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

Starts the merge semaphore GenServer.

status(server \\ __MODULE__)

@spec status(GenServer.server()) :: :free | {:held, non_neg_integer()}

Returns the current state of the semaphore for observability.

Returns {:held, shard_index} if a merge is in progress, or :free.