In order to get the best performance and efficiency from a directory-enabled application, you will likely want to use some form of connection pooling. Most LDAP APIs provide support for some level of connection pooling, but I’m not aware of any other API that provides anywhere near the power and convenience of the connection pooling capabilities of the UnboundID LDAP SDK for Java. This post will examine many of the connection pooling features that it provides.
Connection Pool Implementations
The UnboundID LDAP SDK for Java actually provides two connection pool implementations. The com.unboundid.ldap.sdk.LDAPConnectionPool
class provides a single set of connections intended to be used for all types of operations. The com.unboundid.ldap.sdk.LDAPReadWriteConnectionPool
class maintains two sets of connections, one set intended for use in processing write (add, delete, modify, and modify DN) operations, and the other for use in processing read (bind, compare, and search) operations. The former is the best choice for environments in which all of the directory servers have the same capabilities, while the latter is better for environments containing some a mix of read-only and read-write servers. The read-write connection pool implementation may also be useful in environments in which it is recommended to try to send all writes to the same server, even if all of the servers technically support read and write operations.
Failover and Load Balancing
Connection pools can be created so that all connections in the pool are established to the same server, or they can be spread across multiple servers. This can be accomplished through the com.unboundid.ldap.sdk.ServerSet
API, which is used to determine which server to use at the time that a connection is created. There are three server set implementations provided in the LDAP SDK, and you can create your own with whatever logic you choose. The included implementations are:
com.unboundid.ldap.sdk.SingleServerSet
— This is a simple implementation in which connections will always be established to the same server.com.unboundid.ldap.sdk.RoundRobinServerSet
— This is an implementation that allows you to specify a list of servers. The first attempt to create a connection will use the first server, the second will choose the second server, and so on, and once it hits the end of the list it will wrap back around to the beginning. If the selected server is unavailable, it will skip ahead to the next server in the list, and will only fail if none of the servers are available.com.unboundid.ldap.sdk.FailoverServerSet
— This is an implementation that allows you to specify an ordered list of servers, and when attempting to establish a new connection it will always try the first server, but if it isn’t available then it will try the second, then the third, and so on. This implementation also provides the ability to fail over between server sets rather than individual servers, which can provide a lot of flexibility (e.g., try round-robin across all servers in the local data center, but if none of them are available then try round-robin across all the servers in a remote data center).
When a connection pool is created with a server set, then that server set will be used to establish all connections in that pool. For example, if you create a pool with ten connections and you use the round-robin server set with a list of two servers, then five of the connections in that pool will be established to the first server and five to the second. This provides a simple mechanism for client-side load balancing, since operations performed using pooled connections will be spread across the two servers. It also provides better availability, since if either server becomes unavailable then connections established to it will fail over to the other (and in many cases it will be transparent to the application).
If all of the servers defined for use in a connection pool are unavailable at the same time, then obviously any attempt to use that connection pool during that time will fail. However, as soon as any of those servers is back online, then the pool will resume normal operation and operations will again be successful without the need to restart the application or re-create the pool.
Checking out and Releasing Connections
Traditionally, connection pools simply maintain a set of connections that the application can check out, use as needed, and return. Although the implementations provided by the UnboundID LDAP SDK for Java go well beyond this, it still possible to check out and release connections as needed.
In the LDAPConnectionPool
implementation, a connection can be obtained using the getConnection()
method. The application can then do the appropriate processing, and when complete it can release the connection back to the pool with the releaseDefunctConnection(LDAPConnection)
method. If an error occurs during processing that indicates the connection is no longer viable, then the releaseDefunctConnection(LDAPConnection)
method should be used instead, which will cause the provided connection to be closed and discarded a new connection created in its place.
The LDAPReadWriteConnectionPool
implementation provides similar methods for checking out and releasing connections, but there are separate methods for interacting with the read and write pools. The getReadConnection()
method may be used to check out a connection from the read pool, and the getWriteConnection()
method will check out a connection from the write pool. When returning connections, the releaseReadConnection(LDAPConnection)
and releaseWriteConnection(LDAPConnection)
methods may be used to return valid connections, and the releaseDefunctReadConnection(LDAPConnection)
and releaseDefunctWriteConnection(LDAPConnection)
methods may be used to return connections that are no longer valid.
Performing Operations in the Pool
One nice feature about the connection pools provided by the UnboundID LDAP SDK for Java is that they include convenience methods for performing operations using connections from that pool. As a result, it is not necessary to check out and release connections in order to process an operation because the pool will do it on behalf of the requester. It will also perform any necessary error handling, and evaluate the result code to indicate whether the connection should be destroyed and a new connection created in its place.
The provided connection pool implementations implement the com.unboundid.ldap.sdk.LDAPInterface
interface, which is the same interface implemented by the LDAPConnection
class. You can process a search using a pooled connection by invoking one of the LDAPConnectionPool.search
or LDAPReadWriteConnectionPool.search
methods just like you can call LDAPConnection.search
. Because they all implement the same interface, it’s relatively simple to write an application that can be used to work with either single connections or connection pools.
The LDAPConnectionPool
class also provides a processRequests(List<LDAPRequest>,boolean)
method that can be used to process multiple requests in succession on a single connection from the pool. In this case, the results of those operations will be returned in a list, and in the event that an any of those requests does not complete successfully then it may either continue attempting to process subsequent requests in the lists or it may stop at that point.
Connection Availability
It is important to understand the behavior that a connection pool exhibits whenever a connection is needed but none are immediately available. The UnboundID LDAP SDK for Java provides two settings that can be used to control the connection pool’s behavior in this case. The first setting is whether the connection pool should be allowed to create a new connection if one is needed but none are available, and the second is how long to wait for a connection to become available before either creating a new connection or returning an error. These settings are controlled by the setCreateIfNecessary(boolean)
and setMaxWaitTimeMillis(long)
methods, respectively.
With the combination of these settings, any of the following behaviors may be selected if a connection is needed but none are available:
- The connection pool should immediately throw an exception
- The connection pool should immediately create a new connection
- The connection pool should immediately create a new connection
- The connection pool should wait for up to a specified length of time for a connection to become available, and if that time passes without a connection becoming available then it will throw an exception
- The connection pool should wait for up to a specified length of time for a connection to become available, and if that time passes without a connection becoming available then it will throw an exception
- The connection pool should wait as long as it takes for a connection to become available.
If the pool is configured to create new connections if none are available, then there may be periods of time in which there are more total connections associated with that connection pool than the maximum number of connections to maintain in the pool. In this case, the behavior that the pool will exhibit when a connection is released will depend on the number of connections available in the pool at that time. As long as the number of available connections in the pool is less than the maximum, then the connection will be released back to the pool and will be made available for use by subsequent requests. If an attempt is made to release a connection when the pool already has the maximum number of available connections, then the released connection will be closed. This provides a scalable and efficient way for the connection pool to grow in size as needed under heavy load while ensuring that it doesn’t hold onto too many connections during slower periods.