Packages
one_flight
0.1.0
Collapse concurrent duplicate Elixir calls into one supervised execution per key.
Current section
Files
Jump to
Current section
Files
one_flight
README.md
README.md
# OneFlight: singleflight for Elixir
[](https://github.com/ivan-podgurskiy/one_flight/actions/workflows/ci.yml)
[](https://hex.pm/packages/one_flight)
[](https://hexdocs.pm/one_flight)
[](LICENSE)
`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
```elixir
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:
```elixir
children = [
{OneFlight, name: MyApp.Flights}
]
```
Then coalesce duplicate in-flight work:
```elixir
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:
```elixir
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:
```elixir
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
| Event | Measurements | Metadata |
| --- | --- | --- |
| `[:one_flight, :run, :start]` | `system_time` | `key`, `role`, `scope` |
| `[:one_flight, :run, :stop]` | `duration`, `waiters_count` | `key`, `role`, `scope`, `result` |
| `[:one_flight, :run, :exception]` | `duration` | `key`, `role`, `scope`, `kind` |
| `[:one_flight, :flight, :abandoned]` | `waiters_seen` | `key`, `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](ROADMAP.md) for planned cluster scope, Req/Finch integration ideas, and practical use cases.
## License
MIT. See [LICENSE](LICENSE).