Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ export default defineConfig({
"how-to/connection-management",
"how-to/publish-and-subscribe-messages",
"how-to/send-batch-commands",
"how-to/client-pool",
"how-to/isolated-scope",
"how-to/synchronous-connection",
"how-to/execute-custom-scripts",
"how-to/execute-custom-commands",
Expand Down
230 changes: 230 additions & 0 deletions src/content/docs/how-to/client-pool.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
---
title: Use Client Pool
description: Manage a pool of reusable GLIDE client connections for high-concurrency workloads, blocking commands, and connection isolation.
---

import { Tabs, TabItem } from '@astrojs/starlight/components';

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

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 | How long an idle client stays in the pool before eviction |
| `request_timeout_ms` / `requestTimeout` / `RequestTimeout` | duration | 5 sec | Timeout for state reset (DISCARD, UNWATCH) when returning a client |
| `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 | Max inactivity time before the pool discards an unreturned client. Set to 0 to disable (Go: use a negative value to disable, 0 means default). |

## Create a Pool

<Tabs syncKey="progLangInExamples">
<TabItem label="Python">
```python
from glide import GlideClientConfiguration, NodeAddress
from 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)
```
</TabItem>

<TabItem label="Java">
```java
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);
```
</TabItem>

<TabItem label="Node">
```typescript
import { ClientPool, GlideClientConfiguration } from '@valkey/valkey-glide';

const pool = await ClientPool.create(
{ addresses: [{ host: 'localhost', port: 6379 }] },
{ maxSize: 10, minIdle: 2, acquireTimeoutS: 5 }
);
```
</TabItem>

<TabItem label="Go">
```go
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)
```
</TabItem>
</Tabs>

## 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.

<Tabs syncKey="progLangInExamples">
<TabItem label="Python">
```python
async with pool.borrow() as client:
await client.set("key", "value")
result = await client.get("key")
```
</TabItem>

<TabItem label="Java">
```java
try (PooledGlideClient client = pool.acquire().get()) {
client.set("key", "value").get();
String val = client.get("key").get();
} // automatically returned to pool
```
</TabItem>

<TabItem label="Node">
```typescript
const client = await pool.acquire();
try {
await client.set('key', 'value');
const result = await client.get('key');
} finally {
await pool.release(client);
}
```
</TabItem>

<TabItem label="Go">
```go
clientID, err := pool.Acquire(ctx)
if err != nil {
log.Fatal(err)
}
defer pool.Release(clientID)

client, err := pool.GetClient(clientID)
if err != nil {
log.Fatal(err)
}
client.Set(ctx, "key", "value")
result, _ := client.Get(ctx, "key")
```
</TabItem>
</Tabs>

## Pool Metrics

Check the current state of the pool to monitor utilization and diagnose capacity issues.

<Tabs syncKey="progLangInExamples">
<TabItem label="Python">
```python
metrics = pool.metrics()
print(f"Idle: {metrics['idle']}, Active: {metrics['active']}, Total: {metrics['total']}")
```
</TabItem>

<TabItem label="Java">
```java
System.out.println("Idle: " + pool.getIdleCount()
+ ", Active: " + pool.getActiveCount()
+ ", Total: " + pool.getTotalCount());
```
</TabItem>

<TabItem label="Node">
```typescript
const metrics = pool.getMetrics();
console.log(`Idle: ${metrics.idle}, Active: ${metrics.active}, Total: ${metrics.total}`);
```
</TabItem>

<TabItem label="Go">
```go
fmt.Printf("Idle: %d, Active: %d, Total: %d\n", pool.IdleCount(), pool.ActiveCount(), pool.TotalCount())
```
</TabItem>
</Tabs>

## Close the Pool

When your application shuts down, close the pool to release all connections.

<Tabs syncKey="progLangInExamples">
<TabItem label="Python">
```python
pool.close()

# Or use as an async context manager:
async with AsyncClientPool.create(config, pool_config) as pool:
async with pool.borrow() as client:
await client.set("key", "value")
```
</TabItem>

<TabItem label="Java">
```java
pool.close();
```
</TabItem>

<TabItem label="Node">
```typescript
pool.close();
```
</TabItem>

<TabItem label="Go">
```go
pool.Close()
```
</TabItem>
</Tabs>

## Abandon Detection

The pool runs a background monitor that detects borrowed clients left idle (no commands sent) for longer than `abandon_timeout`. When triggered:

1. The idle client's connection is discarded (closed).
2. The pool slot is freed for a new connection on the next `acquire()`.
3. A warning is logged identifying the abandoned client.

The inactivity timer resets on every command sent through the borrowed client. Blocking commands (`BLPOP`, `BRPOP`, `XREAD BLOCK`, etc.) are automatically exempt — the monitor skips clients currently executing a blocking command.

Set `abandon_timeout` to 0 (or negative in Go) to disable the monitor entirely.

## Limitations

- **No Pub/Sub subscriptions:** Clients with active subscriptions are rejected at pool creation because `UNSUBSCRIBE` cannot be reliably sent during state reset.
- **No background eviction:** Idle clients are evicted lazily when `acquire()` is called, not on a background timer.
- **State reset always sends DISCARD:** When a client is returned, the pool sends `DISCARD` and `UNWATCH` even if no `MULTI` transaction was active. This adds a small round-trip on each release.
Loading
Loading