Skip to content

Connection Pooling

Mark Paluch edited this page Apr 4, 2017 · 9 revisions

Lettuce connections are designed to be thread-safe so one connection can be shared amongst multiple threads and lettuce connections auto-reconnection by default. While connection pooling is not necessary in most cases it can be helpful in certain use cases. lettuce provides generic connection pooling support.

Is connection pooling necessary?

Lettuce is thread-safe by design which is sufficient for most cases. All Redis user operations are executed single-threaded. Using multiple connections does not impact the performance of an application in a positive way. The use of blocking operations usually goes hand in hand with worker threads that get their dedicated connection. The use of Redis Transactions is the typical use case for dynamic connection pooling as the number of threads requiring a dedicated connection tends to be dynamic. That said, the requirement for dynamic connection pooling is limited. Connection pooling always comes with a cost of complexity and maintenance.

Prerequisites

Lettuce requires Apache’s common-pool2 dependency (at least 2.2) to provide connection pooling. Make sure to include that dependency on your classpath. Otherwise you won’t be able using connection pooling.

If using Maven, add the following dependency to your pom.xml:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.4.2</version>
</dependency>

Connection pool support

lettuce provides generic connection pool support. It requires a connection Supplier that is used to create connections of any supported type (Redis Standalone, Pub/Sub, Sentinel, Master/Slave, Redis Cluster). ConnectionPoolSupport will create a GenericObjectPool or SoftReferenceObjectPool, depending on your needs. The pool can allocate either wrapped or direct connections.

  • Wrapped instances will return the connection back to the pool when called StatefulConnection.close().

  • Regular connections need to be returned to the pool with GenericObjectPool.returnObject(…).

Basic usage

RedisClient client = RedisClient.create(RedisURI.create(host, port));
GenericObjectPool<StatefulRedisConnection<String, String>> pool = ConnectionPoolSupport
               .createGenericObjectPool(() -> client.connect(), new GenericObjectPoolConfig());

// executing work
try (StatefulRedisConnection<String, String> connection = pool.borrowObject()) {
    connection.multi();
    connection.set("key", "value");
    connection.set("key2", "value2");
    connection.exec();
}

// terminating
pool.close();
client.shutdown();

Cluster usage

RedisClusterClient clusterClient = RedisClusterClient.create(RedisURI.create(host, port));
GenericObjectPool<StatefulRedisClusterConnection<String, String>> pool = ConnectionPoolSupport
               .createGenericObjectPool(() -> clusterClient.connect(), new GenericObjectPoolConfig());

// executing work
try (StatefulRedisClusterConnection<String, String> connection = pool.borrowObject()) {
    connection.sync().set("key", "value");
    connection.sync().blpop(10, "list");
}

// terminating
pool.close();
clusterClient.shutdown();

Reactive usage

Reactive execution consists of two part: Construction and subscription. Connection pooling affects how you release the borrowed connection back to the pool. Like in a synchronous programming model, you obtain a connection upon construction time to get a handle for Publisher creation. I

n a synchronous programming model, you would release the connection immediately once you’re done with the call. That’s different for reactive programming: You need to retain the connection until the actual execution is terminated and then release the connection back to the pool. Eager release keeps reference to the connection in the reactive sequence and release the connection back to the pool. Any other thread then can borrow the same connection and run into false sharing issues.

Reactive usage example

RedisClient client = RedisClient.create(RedisURI.create(host, port));
GenericObjectPool<StatefulRedisConnection<String, String>> pool = ConnectionPoolSupport
               .createGenericObjectPool(() -> client.connect(), new GenericObjectPoolConfig());

// executing work
StatefulRedisConnection<String, String> connection = pool.borrowObject();

Mono<String> result = connection.reactive().set("key", "value")
                     .then(connection.reactive().get("key"))
                     .doOnTerminate((s, throwable) -> connection.close());

// execute & synchronize result

// terminating
pool.close();
client.shutdown();

Pooling for Redis Standalone and Redis Sentinel-managed connections (deprecated)

lettuce provides connection pooling for Redis Standalone and Redis Sentinel-managed connections. The connection pool allows to create connections and use them as you require. Used connections can be returned to the pool and can be reused. Connection pooling is available for sync and async APIs.

Basic usage

RedisConnectionPool<RedisConnection<String, String>> pool = client.pool();
try (RedisConnection<String, String> connection = pool.allocateConnection()) {
    connection.multi();
    connection.set("key", "value");
    connection.set("key2", "value2");
    connection.exec();
}

// when terminating
pool.close();

Async usage

RedisConnectionPool<RedisAsyncConnection<String, String>> pool = client.asyncPool();
try (RedisConnection<String, String> connection = pool.allocateConnection()) {
    connection.multi();
    connection.set("key", "value");
    connection.set("key2", "value2");
    connection.exec().get();
}

// when terminating
pool.close();

Pooled connections implement AutoCloseable to support try-with-resources notion. Calling close() on a pooled instance will redirect the call to release the connection back to the pool. Pooled connections are only useful if:

  • You use blocking calls such as BLPOP, BRPOP, …​ to not block the whole connection. Once a connection is blocked by a blocking command, it will stay in this state until Redis responds with the result

  • You use transactions (MULTI/EXEC). Transactions will switch your connection in a transactional state. Other threads which share the connection would fall unintentionally into the transaction.

The connection pool can be configured with maxIdle and maxActive parameters which default to 5 and 20.

Clone this wiki locally