one_flight is a small Elixir library for request coalescing, also known as the
singleflight pattern: when many processes ask for the same expensive value at the
same time, only one function runs and the rest wait for its result.
Use it for hot cache misses, token refresh storms, duplicate outbound API calls, remote configuration loads, report generation, or any other work where repeating the same in-flight operation would waste resources or overload an upstream system.
The result is shared only with current waiters and then discarded. one_flight
is deliberately not a cache; it is the primitive you can put in front of a cache,
HTTP client, database query, or any custom expensive operation.
Installation
def deps do
[
{:one_flight, "~> 0.2.0"}
]
endUsage
The library starts a default instance automatically. For application code, a named instance in your supervision tree is usually clearer:
children = [
{OneFlight, name: MyApp.Flights}
]Then coalesce duplicate in-flight work:
OneFlight.run("user:#{id}", fn ->
MyApp.Repo.get!(User, id)
end, name: MyApp.Flights)If 500 callers ask for the same key at the same time, one function execution runs and the other callers receive the same in-flight result. After completion, the result is discarded; the next caller starts a new flight.
Use run!/3 when you want errors raised:
user =
OneFlight.run!("user:#{id}", fn ->
MyApp.Repo.get!(User, id)
end, name: MyApp.Flights)Use async/3 and await/2 when the caller wants a handle:
flight = OneFlight.async(:refresh_token, fn -> refresh_token() end, name: MyApp.Flights)
OneFlight.await(flight, 5_000)Use scope: :cluster when the same named instance runs on connected Erlang
nodes and duplicate work should be collapsed across the cluster:
OneFlight.run({:config, tenant_id}, fn ->
MyApp.ConfigClient.fetch!(tenant_id)
end, name: MyApp.Flights, scope: :cluster)Cluster scope uses local coalescing first, then :global registration between
local leaders. The function still runs only on the caller's node; closures and
arguments are not shipped to another node. If cluster coordination cannot find
or follow the owner, fallback: :local (the default) runs the function locally,
while fallback: :error returns a OneFlight.Error with
reason: :cluster_unreachable.
Why?
Cache stampedes happen when many processes miss the same expensive value at once. Without coalescing, every process repeats the same DB query, API call, token refresh, report generation, or inference request.
one_flight gives that request-coalescing primitive directly, without forcing you to adopt a cache.
Why this is not a cache
one_flight only shares work that is already in progress. It does not store results, apply TTLs, evict entries, persist values, or serve stale data. Use it beside Cachex, ETS, Redis, or any other cache when you need to collapse duplicate misses.
Why not Cachex?
Cachex is a cache. Its fetch path can deduplicate work as part of cache population. one_flight is for code that needs coalescing without result storage: token refresh, upstream API calls, expensive computations, or library internals.
Telemetry events
| Event | Measurements | Metadata |
|---|---|---|
[:one_flight, :run, :start] | system_time | key, role, node_role, scope |
[:one_flight, :run, :stop] | duration, waiters_count | key, role, node_role, scope, result |
[:one_flight, :run, :exception] | duration | key, role, node_role, scope, kind |
[:one_flight, :flight, :abandoned] | waiters_seen | key, node_role, scope |
The waiters_count measurement is the core value metric: how many callers were served by one execution.
Guarantees
With scope: :node, one_flight guarantees at most one execution per key per
named instance on one node.
With scope: :cluster, one_flight coalesces local callers first, then uses
:global to choose one owner for the same {named_instance, key} across the
currently connected Erlang cluster. The practical guarantee is at most one
execution per connected partition in steady state. During node joins, node
leaves, or netsplits, duplicate execution windows are possible; use idempotent
functions for work where this matters.
Roadmap
See ROADMAP.md for planned cluster scope, Req/Finch integration ideas, and practical use cases.
License
MIT. See LICENSE.