Use Client Pool
The Client Pool manages a set of reusable GLIDE client instances that can be borrowed and returned on demand. Use it when your application needs high concurrency, runs blocking commands like BLPOP, BRPOP, or XREAD BLOCK that tie up a connection, or requires connection isolation between independent workflows.
Configuration
Section titled “Configuration”The pool is configured with a PoolConfig object. All parameters are optional and have sensible defaults.
| Parameter | Type | Default | Description |
|---|---|---|---|
max_size / maxSize / MaxSize | int | 10 | Maximum number of client instances in the pool |
min_idle / minIdle / MinIdle | int | 1 | Number of clients to pre-warm at creation |
idle_timeout_ms / idleTimeout / IdleTimeout | duration | 5 min (Node.js: 30 sec) | How long an idle client stays in the pool before eviction |
request_timeout_ms / requestTimeout / RequestTimeout | duration | 5 sec | Base timeout for state reset (DISCARD, SELECT) when returning a client; the reset is abandoned and the connection discarded after 2× this value. (Node.js has no pool field; it uses the client config’s requestTimeout.) |
acquire_timeout_s / acquireTimeoutS / AcquireTimeout | duration | 5 sec | How long to wait for a client when the pool is exhausted |
abandon_timeout_ms / abandonTimeoutMs / AbandonTimeout | duration | 5 min (300000 ms) | Maximum inactivity time for a borrowed client before the pool reclaims it. The timer resets on every command sent. The abandon monitor skips clients executing blocking commands. Set to 0 to disable (in Go, set to a negative value to disable; 0 means use default). |
Create a Pool
Section titled “Create a Pool”from glide import GlideClientConfiguration, NodeAddressfrom glide import AsyncClientPool, PoolConfig
pool_config = PoolConfig( max_size=10, min_idle=2, acquire_timeout_s=5.0,)client_config = GlideClientConfiguration([NodeAddress("localhost", 6379)])pool = await AsyncClientPool.create(client_config, pool_config)import glide.api.models.pool.ClientPool;import glide.api.models.pool.ClientPoolConfig;import glide.api.models.configuration.GlideClientConfiguration;import glide.api.models.configuration.NodeAddress;
ClientPoolConfig poolConfig = ClientPoolConfig.builder() .maxSize(10) .minIdle(2) .acquireTimeout(Duration.ofSeconds(5)) .clientConfig(GlideClientConfiguration.builder() .address(NodeAddress.builder().host("localhost").port(6379).build()) .build()) .build();
ClientPool pool = ClientPool.create(poolConfig);import { ClientPool, GlideClientConfiguration } from '@valkey/valkey-glide';
const clientConfig: GlideClientConfiguration = { addresses: [{ host: 'localhost', port: 6379 }],};
const pool = await ClientPool.create(clientConfig, { maxSize: 10, minIdle: 2, acquireTimeoutS: 5,});import ( glide "github.com/valkey-io/valkey-glide/go/v2" "github.com/valkey-io/valkey-glide/go/v2/config" "time")
poolCfg := glide.PoolConfig{ MaxSize: 10, MinIdle: 2, AcquireTimeout: 5 * time.Second,}clientCfg := config.NewClientConfiguration(). WithAddress(&config.NodeAddress{Host: "localhost", Port: 6379})
pool, err := glide.NewClientPool(clientCfg, poolCfg)Borrow and Return Clients
Section titled “Borrow and Return Clients”Acquire a client from the pool before use, and always release it when done. Use context managers or try/finally blocks to guarantee the client is returned even if an error occurs.
async with pool.borrow() as client: await client.set("key", "value") result = await client.get("key")try (PooledGlideClient client = pool.acquire().get()) { client.set("key", "value").get(); String result = client.get("key").get();}// client is automatically returned to the pool on closeUsing borrow() (recommended):
const result = await pool.borrow(async (client) => { await client.set('key', 'value'); return await client.get('key');});Manual acquire/release:
const client = await pool.acquire();try { await client.set('key', 'value'); const result = await client.get('key');} finally { await pool.release(client);}clientID, err := pool.Acquire(ctx)if err != nil { log.Fatal(err)}
client, err := pool.GetClient(clientID)if err != nil { log.Fatal(err)}defer client.Close() // returns to pool
client.Set(ctx, "key", "value")result, _ := client.Get(ctx, "key")Pool Metrics
Section titled “Pool Metrics”Check the current state of the pool to monitor utilization and diagnose capacity issues.
metrics = pool.metrics()print(f"Idle: {metrics['idle']}, Active: {metrics['active']}, Total: {metrics['total']}")System.out.println("Idle: " + pool.getIdleCount() + ", Active: " + pool.getActiveCount() + ", Total: " + pool.getTotalCount());const metrics = pool.getMetrics();console.log(`Idle: ${metrics.idle}, Active: ${metrics.active}, Total: ${metrics.total}`);fmt.Printf("Idle: %d, Active: %d, Total: %d\n", pool.IdleCount(), pool.ActiveCount(), pool.TotalCount())Abandon Detection
Section titled “Abandon Detection”The pool runs a background monitor that reclaims borrowed clients when they have been inactive beyond a configurable timeout. This prevents connection leaks when application code forgets to return a client.
How it works:
- The monitor scans at half the timeout interval (e.g., every 2.5 minutes with the default 5-minute timeout).
- A borrowed client is reclaimed if no command has been sent to it within the threshold.
- The inactivity timer resets on every command sent to the borrowed client.
- Blocking commands (
BLPOP,BRPOP,XREAD BLOCK, etc.) are automatically detected and excluded from reclaim. - When a client is reclaimed, the connection is discarded and a fresh one is created on the next
acquire().
Configure a Custom Timeout
Section titled “Configure a Custom Timeout”from glide import PoolConfig
# Set abandon timeout to 1 minutepool_config = PoolConfig(abandon_timeout_ms=60000)import glide.api.models.pool.ClientPoolConfig;import java.time.Duration;
// Set abandon timeout to 1 minuteClientPoolConfig poolConfig = ClientPoolConfig.builder() .abandonTimeout(Duration.ofMinutes(1)) .build();// Set abandon timeout to 1 minuteconst pool = await ClientPool.create( { addresses: [{ host: 'localhost', port: 6379 }] }, { abandonTimeoutMs: 60000 },);// Set abandon timeout to 1 minutepoolCfg := glide.PoolConfig{ AbandonTimeout: 1 * time.Minute,}Disable Abandon Detection
Section titled “Disable Abandon Detection”# Disable abandon detectionpool_config = PoolConfig(abandon_timeout_ms=0)// Disable abandon detectionClientPoolConfig poolConfig = ClientPoolConfig.builder() .abandonTimeout(Duration.ZERO) .build();// Disable abandon detectionconst pool = await ClientPool.create( { addresses: [{ host: 'localhost', port: 6379 }] }, { abandonTimeoutMs: 0 },);// Disable abandon detection (use a negative value in Go)poolCfg := glide.PoolConfig{ AbandonTimeout: -1 * time.Second,}Close the Pool
Section titled “Close the Pool”When your application shuts down, close the pool to release all connections.
pool.close()
# Or use as an async context manager:async with await AsyncClientPool.create(config, pool_config) as pool: async with pool.borrow() as client: await client.set("key", "value")pool.close();pool.close();pool.Close()Limitations
Section titled “Limitations”The following apply to the preview release.
By design
Section titled “By design”These are intentional constraints for this release. Design your application around them.
- No Pub/Sub subscriptions: Clients with active subscriptions are rejected at pool creation, because subscription state cannot be reliably reset when the client is returned. Use a dedicated client for Pub/Sub.
- Close any
WATCHbefore releasing a client: On release the pool resets state withDISCARDandSELECT, which does not clear aWATCHthat was set without a followingMULTI. The next borrower inherits thatWATCH, so itsEXECcan abort over a key it never watched. Always end aWATCHwithEXEC,DISCARD, orUNWATCHbefore returning the client. - Limited support for custom per-client callbacks: Support for a language-side custom IAM credentials provider or custom address resolver on a pooled client config varies by binding. Java rejects both at pool creation; Go rejects a custom address resolver; Node.js supports a custom address resolver; Python does not validate either. Where a callback is unsupported, resolve addresses ahead of time and configure IAM without a custom provider.
- No background eviction: Idle clients are evicted lazily when
acquire()is called, not on a background timer. - No automatic pool sharing: Each call that creates a pool produces an independent pool. Two pools built from the same configuration are not deduplicated or shared. Hold and reuse the pool you created wherever you need it.
- Scopes do not outlive the pool: Closing the pool invalidates any isolated scope still open on a borrowed client. Commands on such a scope fail rather than continuing to run on a connection the pool no longer owns.
Known issues
Section titled “Known issues”These are being tracked and are expected to be resolved in a future release.
- Cluster pools do not reuse connections yet: In cluster mode, a borrowed client’s connection is currently discarded on release instead of being returned to the pool for reuse. Standalone pools reuse connections as expected.
- The pool can exceed its maximum size under concurrent load: On Node.js and Go, concurrent acquires can create more connections than
max_size, and the surplus stays in the pool as idle connections until idle eviction. Allow headroom if you size the pool against a hard server-side connection limit. - State reset is unconditional: When a client is returned, the pool resets connection state even if the connection was never mutated. Skipping this work for provably clean connections is planned but not yet implemented.
- Python can hang after release in cluster mode: A routed command issued on an ordinary cluster client can hang after a pool borrow is released. Avoid reusing a cluster client for routed commands immediately after returning a borrow.
- Resources can leak on cancelled blocking commands: If an unbounded blocking command (for example
BLPOPwith a timeout of0) is cancelled on a borrowed client, the underlying connection may leak. Prefer a non-zero timeout for blocking commands during the preview. - Java
close()affects shared runtime state: Closing a pool in Java can tear down JVM-wide GLIDE runtime state and affect unrelated clients. Avoid closing a pool while other GLIDE clients are in use in the same process. - TLS is not yet enforced for IAM authentication: IAM-authenticated connections are not required to use TLS. Configure TLS explicitly when using IAM auth.