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
71 changes: 62 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,71 @@

Thank you for your interest in contributing to the Valkey GLIDE documentation!

## Branches Overview
## Deployment Model

| Branch | Purpose |
| -------- | -------------------------------------------------------------------------------- |
| `main` | Integration branch — all PRs target here |
| `public` | Production — deploys the live site at [glide.valkey.io](https://glide.valkey.io) |
We use two branches:

In general:
| Branch | Purpose |
| -------- | ----------------------------------------------------------------------------------------------------- |
| `main` | Integration branch. Source of truth. All PRs target here. Content on `main` is awaiting release. |
| `public` | Production branch. deploys the live site at [glide.valkey.io](https://glide.valkey.io) on every push. |

- Open all PRs against `main`.
- On release day, `main` is merged onto `public` triggering a deployment.
- Only urgent fixes are accepted directly into `public`.
All documentation changes flow through `main` first, then are promoted to `public` on release.

### Contribution Flow

1. Open your PR against `main`.
2. Once approved and CI passes, merge to `main`. Merge early and often to avoid big-bang release-day merges.
3. Content sits on `main` until the next release.

### Release Process

`main` is the source of truth. On release day, a maintainer resets `public` to `main`'s HEAD and force-pushes:

@jeremyprime jeremyprime Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or just open a PR from main to public instead of force pushing. Merging should not cause any issues now that main and public are in sync, and it avoids the need for special push permissions. Force pushing can be an alternate option if main and public ever diverge.


```bash
git fetch origin
git push origin +origin/main:public
```
Comment on lines +26 to +29

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only specific maintainers are allowed force pushes to public. Force pushing are disabled in general.


Any contents on `main` that isn't ready to go live should be disabled (see [Unreleased Content](#unreleased-content)).

### Applying a Hotfix

For urgent fixes to the live site that can't wait for the next release:

1. Open a PR targeting `public`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or hotfixes always go to main and then open a PR from main to public. That avoids backporting and divergence between public and main.

2. Once merged, deployment will start automatically.
3. **Backport the same change to `main` immediately**. Because releases reset `public` to `main` HEAD, any hotfix that isn't on `main` before the next release will be erased.

### Unreleased Content

Content that has landed on `main` but isn't ready for release yet must be marked as draft so it doesn't appear on the live site when `public` is next updated.

**Draft Page** — set `draft: true` in frontmatter. The page is excluded from production builds but visible in `pnpm dev`. Always include a `draft-reason` so releases can locate what to unflip.

```
---
title: My New Feature
description: ...
draft: true
draft-reason: For release 2.5
Comment thread
jeremyprime marked this conversation as resolved.
---
```

Remove the page from sidebar entry in `astro.config.mjs` as it will fail the build if linked explicitly.

**Draft Section** (e.g., one language's example tab) — wrap the section in the `<Draft>` component. Always include a `draft-reason`.

```mdx
import Draft from "@components/Draft.astro";

<Tabs syncKey="progLangInExamples">
<TabItem label="Python">...</TabItem>
<Draft draft-reason="For release 2.5">
<TabItem label="Ruby">...</TabItem>
</Draft>
</Tabs>
```

## Getting Started

Expand Down
10 changes: 10 additions & 0 deletions src/components/Draft.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
/**
* Inline equivalent of Starlight's page-level `draft: true` frontmatter:
* hides its children in production builds; shows them in dev mode.
*
* To release the content, delete the wrapping <Draft> tags.
*/
---

{import.meta.env.DEV && <slot />}
Comment thread
jeremyprime marked this conversation as resolved.
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
163 changes: 79 additions & 84 deletions src/content/docs/how-to/connections/resilience-best-practices.mdx
Comment thread
jeremyprime marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -12,80 +12,75 @@ 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>
</Tabs>

## Single Client Instance
Expand All @@ -111,11 +106,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 +163,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