Skip to content

Connection pool design advice #25

Closed
@PopFlamingo

Description

@PopFlamingo

I’m seeking for design advice on how to build a connection pool, I would like to spend some time implementing it and hopefully contribute them if I get good results.
I don't know exactly where to start so I thought it might be good to expose what I understand on how the connection pool should work, and show design ideas I found by exploring already existing implementations.

First, the general concept of the connection pool: on each new request, the client asks its connection pool for a connection to the server. When it receives a new connection request, the pool has essentially three choices:

  • Use an already opened, unused connection
  • Open a new connection if no other connection is available
  • If there are already too many concurrent connections to the server, wait until an already opened connection becomes available
    In practice the connection is a NIO Channel that is either directly a TCP connection in the case of HTTP/1.1, or a stream for an HTTP/2 multiplexed connection.

After some research, I stumbled upon the Apache Foundation HttpClient. It uses an HttpClientConnectionManager interface that lets the user provide the manager they wish. A PoolingHttpClientConnectionManager class implements this interface.
I wonder wether the principle of a user provided connection manager makes sense for the Swift NIO HTTP client? In the case of the Apache, I see the other class that implements HttpClientConnectionManager is BasicHttpClientConnectionManager, a connection manager that maintains only a single active connection at a time, but if I understand it correctly, this manager purpose is to enable to have one client per thread, which doesn’t seem appliable/useful with NIO? We can also imagine concurrent yet unpooled connection management, which is how the client currently works, but I don’t see the advantage of this over a pooled connection management?

Speaking more specifically about the connection pool, I have a few questions:

  • How to give back a connection to the pool? Should we pass it as an argument to a pool method (such as this) , or could we have a design where the pool passes an object (maybe a promise?) to the client on which it calls the appropriate method when it has finished leasing the connection?
  • How should we implement the thread safety of the pool? The already existing APIs based on Future/Promises should make this easy for many parts, but the data structure storing the connections must still be protected. Regarding the threading model of NIO, is this a good practice to hold a reference to a specific event loop and to use .execute to ensure what we want is executed on the right thread?
  • Speaking of the data structure storing the connections, could this be as simple as a dictionary that keys the connections per host/scheme?
  • I saw that some pools have a global cleanup delay where the manager will loop over all opened connections to see which ones have been idle for too long in order to evict them. How does this « global cleanup interval » strategy compare to scheduling timeouts per connection using NIO tasks? From what I see, for instance with the IdleStateHandler, NIO doesn’t hesitate to schedule tasks frequently, so I guess the performance impact isn’t too high?
  • Some pool designs enable specifying a delay after which a connection request is failed if the pool didn’t provide one in time, is this something we would also want for a pool?

Thank you!

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/supportAdopter support requests.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions