OneFlight: singleflight for Elixir

Copy Markdown View Source

CI Hex.pm HexDocs License: MIT

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.1.0"}
  ]
end

Usage

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)

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

EventMeasurementsMetadata
[:one_flight, :run, :start]system_timekey, role, scope
[:one_flight, :run, :stop]duration, waiters_countkey, role, scope, result
[:one_flight, :run, :exception]durationkey, role, scope, kind
[:one_flight, :flight, :abandoned]waiters_seenkey, scope

The waiters_count measurement is the core value metric: how many callers were served by one execution.

Guarantees

v0.1 guarantees at most one execution per key per named instance on one node. Distributed scope: :cluster is intentionally not implemented in v0.1.

Roadmap

See ROADMAP.md for planned cluster scope, Req/Finch integration ideas, and practical use cases.

License

MIT. See LICENSE.