Skip to content

gun_pool: O(n) checkout cost for finding available connections #362

Description

@studzien

Context

We're using gun_pool with HTTP/1.1 pools of 300+ connections for high-throughput services. We've noticed that checkout latency increases linearly with pool size, and traced it to find_available_connection/1 in gun_pool.erl.

Current behavior

On every checkout, the function:

  1. Extracts all connection PIDs from the map — maps:keys(Conns) — O(n)
  2. Pairs each with a random value and sorts the list — O(n log n)
  3. Linearly scans for a connection with available capacity — O(n) worst case
find_available_connection(#state{table=Tid, conns=Conns}) ->
    I = lists:sort([{rand:uniform(), K} || K <- maps:keys(Conns)]),
    find_available_connection(I, Conns, Tid).

For HTTP/1.1 (max_streams = 1), under load most connections are busy, so the scan often walks through a large portion of the list before finding a free one. Since checkout is a synchronous gen_server:call, all checkouts are serialized through the pool process, and the O(n log n) work per call becomes a bottleneck.

For HTTP/2 with multiplexing this is less of a problem because pools tend to be smaller and connections almost always have capacity, but it still means requests are not balanced across connections.

Question

Is the random shuffle approach intentional for a specific reason (e.g. fairness, simplicity), or would you be open to an alternative?

Possible approach

One option could be a priority queue (or similar structure) keyed by current stream count, which would give O(log n) checkout and naturally balance load across connections for multiplexed protocols. The queue would be updated on stream open/close via the existing event handler.

This would also make checkout cost independent of pool size, which matters when pools are large.

Happy to discuss further or contribute a PR if you're interested.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions