Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -535,11 +535,10 @@ Multiple clients can share the same cache instance by passing the same `ClientSi
## Server-Assisted Invalidation

Server-assisted invalidation can be enabled in the client side configuration by setting server assisted to true.
You do not need to call [`CLIENT TRACKING`](https://valkey.io/commands/client-tracking/) yourself; GLIDE issues `CLIENT TRACKING ON BCAST` on your behalf during connection setup.
You do not need to call [`CLIENT TRACKING`](https://valkey.io/commands/client-tracking/) yourself; GLIDE issues `CLIENT TRACKING ON BCAST` on your behalf during connection setup.

Once enabled, the client receives push invalidation messages from the server whenever a cached key is modified, eliminating the staleness window inherent in TTL-only mode — the server notifies the client immediately when data changes, and the client removes the stale entry from its local cache.


### Enabling server-assisted mode

<Tabs syncKey="progLangInExamples">
Expand Down
32 changes: 32 additions & 0 deletions src/content/docs/how-to/client-initialization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ The `NodeAddress` type represents the host and port of a cluster node. The host
await using var client = await GlideClusterClient.CreateClient(config);
```
</TabItem>

<TabItem label="Ruby">
```ruby
require "valkey"

client = Valkey.new(
nodes: [{ host: "address.example.com", port: 6379 }],
cluster_mode: true
)
```
</TabItem>
</Tabs>

### Request Routing
Expand Down Expand Up @@ -143,6 +154,10 @@ For more details on the routing of specific commands:
<TabItem label="C#">
Please refer to [the documentation within the code](https://github.com/valkey-io/valkey-glide-csharp/blob/main/sources/Valkey.Glide/Route.cs) for routing configuration.
</TabItem>

<TabItem label="Ruby">
Please refer to [the documentation within the code](https://github.com/valkey-io/valkey-glide-ruby/blob/main/lib/valkey/route.rb) for routing configuration.
</TabItem>
</Tabs>

### Response Aggregation
Expand Down Expand Up @@ -175,6 +190,10 @@ To learn more about response aggregation for specific commands:
<TabItem label="C#">
Please refer to [the documentation within the code](https://github.com/valkey-io/valkey-glide-csharp/blob/main/sources/Valkey.Glide/ClusterValue.cs) for cluster response types.
</TabItem>

<TabItem label="Ruby">
Please refer to the documentation within the code.
</TabItem>
</Tabs>

### Topology Updates
Expand Down Expand Up @@ -306,6 +325,19 @@ Valkey GLIDE also supports Standalone deployments, where the database is hosted
await using var client = await GlideClient.CreateClient(config);
```
</TabItem>

<TabItem label="Ruby">
```ruby
require "valkey"

# The Ruby client connects to a single standalone address.
# Use the read_from option to route reads to replicas.
client = Valkey.new(host: "primary.example.com", port: 6379)

# Or connect with a URL
client = Valkey.new(url: "redis://primary.example.com:6379/0")
```
</TabItem>
</Tabs>

## Configuration Options
Expand Down
28 changes: 28 additions & 0 deletions src/content/docs/how-to/connections/configure-lazy-connection.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,34 @@ Lazy connection can be configured through the client configuration object.
$clusterClient->close();
```
</TabItem>

<TabItem label="Ruby">
```ruby
require "valkey"

# Standalone client with lazy connect
standalone_client = Valkey.new(
host: "localhost",
port: 6379,
lazy_connect: true,
timeout: 5.0
)

# Cluster client with lazy connect
cluster_client = Valkey.new(
nodes: [
{ host: "localhost", port: 7000 },
{ host: "localhost", port: 7001 }
],
cluster_mode: true,
lazy_connect: true,
timeout: 5.0
)

# No connection established yet - this will trigger the connection
result = standalone_client.ping
```
</TabItem>
</Tabs>

## How It Works
Expand Down
11 changes: 11 additions & 0 deletions src/content/docs/how-to/connections/limit-inflight-requests.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ Inflight requests limit can be configured through the general client configurati
)
```
</TabItem>

<TabItem label="Ruby">
```ruby
# Limit to 1000 inflight requests per connection
client = Valkey.new(
nodes: [{ host: "localhost", port: 7000 }],
cluster_mode: true,
inflight_requests_limit: 1000
)
```
</TabItem>
</Tabs>

## Why Limit Inflight Requests
Expand Down
50 changes: 50 additions & 0 deletions src/content/docs/how-to/connections/read-strategy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,22 @@ Valkey GLIDE provides support for the following read strategies, allowing you to
$client->get('key1');
```
</TabItem>

<TabItem label="Ruby">
```ruby
require "valkey"

client = Valkey.new(
nodes: [{ host: "address.example.com", port: 6379 }],
cluster_mode: true,
read_from: Valkey::ReadFrom::PREFER_REPLICA
)

client.set("key1", "val1")
# get will read from one of the replicas
client.get("key1")
```
</TabItem>
</Tabs>

### AZ Affinity
Expand Down Expand Up @@ -272,6 +288,23 @@ When using the AZ Affinity read strategy, the `clientAz` setting is required to
$client->get('key1');
```
</TabItem>

<TabItem label="Ruby">
```ruby
require "valkey"

client = Valkey.new(
nodes: [{ host: "address.example.com", port: 6379 }],
cluster_mode: true,
read_from: Valkey::ReadFrom::AZ_AFFINITY,
client_az: "us-east-1a"
)

client.set("key1", "val1")
# get will read from one of the replicas in the same client's availability zone if they exist
client.get("key1")
```
</TabItem>
</Tabs>

### AZ Affinity Replicas and Primary Read Strategy
Expand Down Expand Up @@ -403,4 +436,21 @@ When using the AZ Affinity Replicas and Primary read strategy, the `clientAz` se
$client->get('key1');
```
</TabItem>

<TabItem label="Ruby">
```ruby
require "valkey"

client = Valkey.new(
nodes: [{ host: "address.example.com", port: 6379 }],
cluster_mode: true,
read_from: Valkey::ReadFrom::AZ_AFFINITY_REPLICAS_AND_PRIMARY,
client_az: "us-east-1a"
)

client.set("key1", "val1")
# get will read from one of the replicas or the primary in the same client's availability zone if they exist
client.get("key1")
```
</TabItem>
</Tabs>
180 changes: 96 additions & 84 deletions src/content/docs/how-to/connections/resilience-best-practices.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,80 +12,92 @@ This guide covers configuration and operational practices that help Valkey GLIDE
A resilient client combines request timeout, inflight limiting, and the circuit breaker:

<Tabs syncKey="progLangInExamples">
<TabItem label="Python">

```python
from glide import (
GlideClusterClient,
GlideClusterClientConfiguration,
ClientCircuitBreakerConfiguration,
NodeAddress,
)

config = GlideClusterClientConfiguration(
addresses=[NodeAddress(host="localhost", port=6379)],
request_timeout=500,
inflight_requests_limit=1000,
client_circuit_breaker=ClientCircuitBreakerConfiguration(),
)
client = await GlideClusterClient.create(config)
```

</TabItem>
<TabItem label="Java">

```java
import glide.api.GlideClusterClient;
import glide.api.models.configuration.GlideClusterClientConfiguration;
import glide.api.models.configuration.ClientCircuitBreakerConfiguration;
import glide.api.models.configuration.NodeAddress;

GlideClusterClient client = GlideClusterClient.createClient(
GlideClusterClientConfiguration.builder()
.address(NodeAddress.builder().host("localhost").port(6379).build())
.requestTimeout(500)
.inflightRequestsLimit(1000)
.clientCircuitBreakerConfiguration(
ClientCircuitBreakerConfiguration.builder().build())
.build()
).get();
```

</TabItem>
<TabItem label="Node">

```typescript
import { GlideClusterClient } from "@valkey/valkey-glide";

const client = await GlideClusterClient.createClient({
addresses: [{ host: "localhost", port: 6379 }],
requestTimeout: 500,
inflightRequestsLimit: 1000,
clientCircuitBreaker: {},
});
```

</TabItem>
<TabItem label="Go">

```go
import (
"time"

glide "github.com/valkey-io/valkey-glide/go/v2"
"github.com/valkey-io/valkey-glide/go/v2/config"
)

clientConfig := config.NewClusterClientConfiguration().
WithAddress(&config.NodeAddress{Host: "localhost", Port: 6379}).
WithRequestTimeout(500 * time.Millisecond).
WithInflightRequestsLimit(1000).
WithClientCircuitBreaker(&config.ClientCircuitBreakerConfiguration{})

client, err := glide.NewClusterClient(clientConfig)
```

</TabItem>
<TabItem label="Python">
```python
from glide import (
GlideClusterClient,
GlideClusterClientConfiguration,
ClientCircuitBreakerConfiguration,
NodeAddress,
)

config = GlideClusterClientConfiguration(
addresses=[NodeAddress(host="localhost", port=6379)],
request_timeout=500,
inflight_requests_limit=1000,
client_circuit_breaker=ClientCircuitBreakerConfiguration(),
)
client = await GlideClusterClient.create(config)
```
</TabItem>

<TabItem label="Java">
```java
import glide.api.GlideClusterClient;
import glide.api.models.configuration.GlideClusterClientConfiguration;
import glide.api.models.configuration.ClientCircuitBreakerConfiguration;
import glide.api.models.configuration.NodeAddress;

GlideClusterClient client = GlideClusterClient.createClient(
GlideClusterClientConfiguration.builder()
.address(NodeAddress.builder().host("localhost").port(6379).build())
.requestTimeout(500)
.inflightRequestsLimit(1000)
.clientCircuitBreakerConfiguration(
ClientCircuitBreakerConfiguration.builder().build())
.build()
).get();
```
</TabItem>

<TabItem label="Node">
```typescript
import { GlideClusterClient } from "@valkey/valkey-glide";

const client = await GlideClusterClient.createClient({
addresses: [{ host: "localhost", port: 6379 }],
requestTimeout: 500,
inflightRequestsLimit: 1000,
clientCircuitBreaker: {},
});
```
</TabItem>

<TabItem label="Go">
```go
import (
"time"

glide "github.com/valkey-io/valkey-glide/go/v2"
"github.com/valkey-io/valkey-glide/go/v2/config"
)

clientConfig := config.NewClusterClientConfiguration().
WithAddress(&config.NodeAddress{Host: "localhost", Port: 6379}).
WithRequestTimeout(500 * time.Millisecond).
WithInflightRequestsLimit(1000).
WithClientCircuitBreaker(&config.ClientCircuitBreakerConfiguration{})

client, err := glide.NewClusterClient(clientConfig)
```
</TabItem>

<TabItem label="Ruby">
```ruby
require "valkey"

client = Valkey.new(
nodes: [{ host: "localhost", port: 6379 }],
cluster_mode: true,
timeout: 0.5, # request timeout in seconds (500 ms)
inflight_requests_limit: 1000
)
```

:::note
The client circuit breaker is not yet available in GLIDE Ruby.
:::
</TabItem>
</Tabs>

## Single Client Instance
Expand All @@ -111,11 +123,11 @@ The `inflightRequestsLimit` controls how many concurrent requests a single clien

**Key points:**

- The inflight limit should accommodate your expected concurrency: at throughput `T` ops/s with average latency `L` seconds, you need roughly `T × L` inflight slots (e.g., 50k ops/s at 1ms = ~50 slots, 50k ops/s at 10ms = ~500 slots).
- When the limit is reached, new requests block until a slot frees up. The circuit breaker (if enabled) rejects before this blocking occurs.
- If using Java with `ForkJoinPool.managedBlock()` (common in Akka, Play, or reactive frameworks), set the limit to 2-3x your thread pool size. This prevents thread explosion where `managedBlock()` spawns compensating threads faster than requests complete.
- If you see "inflight requests limit reached" warnings in logs, either increase the limit or reduce concurrency at the application level.
- Setting the limit too high risks memory pressure under sustained backpressure. The default of 1000 is appropriate for most workloads.
* The inflight limit should accommodate your expected concurrency: at throughput `T` ops/s with average latency `L` seconds, you need roughly `T × L` inflight slots (e.g., 50k ops/s at 1ms = ~50 slots, 50k ops/s at 10ms = ~500 slots).
* When the limit is reached, new requests block until a slot frees up. The circuit breaker (if enabled) rejects before this blocking occurs.
* If using Java with `ForkJoinPool.managedBlock()` (common in Akka, Play, or reactive frameworks), set the limit to 2-3x your thread pool size. This prevents thread explosion where `managedBlock()` spawns compensating threads faster than requests complete.
* If you see "inflight requests limit reached" warnings in logs, either increase the limit or reduce concurrency at the application level.
* Setting the limit too high risks memory pressure under sustained backpressure. The default of 1000 is appropriate for most workloads.

## Thread Pool Sizing (Java)

Expand Down Expand Up @@ -168,11 +180,11 @@ See the dedicated [Circuit Breaker guide](/how-to/connections/circuit-breaker) f

**Key points:**

- Enable CB for workloads where thread exhaustion is a concern during outages.
- The CB rejects before the request reaches the network, so rejections are near-zero-cost.
- Enable `countTimeouts` to protect against dead or unreachable nodes. In cluster mode, dead nodes surface primarily as timeouts, so this flag is required for the CB to trip on sustained node outages.
- Recovery is optimistic: in HalfOpen, all traffic is allowed through. After consecutive successes the breaker closes. If a failure occurs, it returns to Open.
- When the CB rejects, handle the `CircuitBreakerException`/`CircuitBreakerError` in your application by returning a cached or degraded response, or signaling the caller to retry after a delay.
* Enable CB for workloads where thread exhaustion is a concern during outages.
* The CB rejects before the request reaches the network, so rejections are near-zero-cost.
* Enable `countTimeouts` to protect against dead or unreachable nodes. In cluster mode, dead nodes surface primarily as timeouts, so this flag is required for the CB to trip on sustained node outages.
* Recovery is optimistic: in HalfOpen, all traffic is allowed through. After consecutive successes the breaker closes. If a failure occurs, it returns to Open.
* When the CB rejects, handle the `CircuitBreakerException`/`CircuitBreakerError` in your application by returning a cached or degraded response, or signaling the caller to retry after a delay.

## Standalone vs Cluster

Expand Down
Loading
Loading