Skip to content
Open
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
122 changes: 113 additions & 9 deletions src/content/docs/how-to/synchronous-connection.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ Starting with version 2.1, Glide introduces a new synchronous Python client, alo

The following features were added to the sync client in subsequent releases:

| Feature | Version |
| ----------------------- | ----------------------------------------------------------------------- |
| Cluster Scan | [v2.2.0](https://github.com/valkey-io/valkey-glide/releases/tag/v2.2.0) |
| In-flight Request Limit | [v2.3.0](https://github.com/valkey-io/valkey-glide/releases/tag/v2.3.0) |
| OpenTelemetry | [v2.3.0](https://github.com/valkey-io/valkey-glide/releases/tag/v2.3.0) |
| PubSub | [v2.3.0](https://github.com/valkey-io/valkey-glide/releases/tag/v2.3.0) |
| Feature | Version |
| ------------------------ | ----------------------------------------------------------------------- |
| Cluster Scan | [v2.2.0](https://github.com/valkey-io/valkey-glide/releases/tag/v2.2.0) |
| In-flight Request Limit | [v2.3.0](https://github.com/valkey-io/valkey-glide/releases/tag/v2.3.0) |
| OpenTelemetry | [v2.3.0](https://github.com/valkey-io/valkey-glide/releases/tag/v2.3.0) |
| PubSub | [v2.3.0](https://github.com/valkey-io/valkey-glide/releases/tag/v2.3.0) |

## Installing

Expand Down Expand Up @@ -78,9 +78,9 @@ The following example compares the synchronous vs asynchronous clients usage for
)

addresses = [
NodeAddress(host="primary.example.com", port=6379),
NodeAddress(host="replica1.example.com", port=6379),
NodeAddress(host="replica2.example.com", port=6379)
NodeAddress(host="primary.example.com", port=6379),
NodeAddress(host="replica1.example.com", port=6379),
NodeAddress(host="replica2.example.com", port=6379)
]
client_config = GlideClientConfiguration(addresses)

Expand All @@ -97,3 +97,107 @@ The following example compares the synchronous vs asynchronous clients usage for
```
</TabItem>
</Tabs>


## Zero-Copy Buffers

When reading large values (KV-cache blobs, embeddings, serialized tensors), the default `get` and `mget` commands allocate a new Python `bytes` object for each value returned. For high-throughput workloads this allocation overhead adds up. Zero-copy buffers let you pass pre-allocated memory so values are written directly into your buffers without intermediate allocations.

:::caution[Sync Client Only]
Zero-copy buffer parameters are available only on the synchronous client (`glide_sync`). The async client does not support this feature.
:::

### Single key: `get` with buffer

Pass a writable `memoryview` to `get` to receive the value directly in your buffer:

```python
buf = bytearray(4096)
view = memoryview(buf)

result = client.get("my_key", buffer=view)
if result is not None:
bytes_written = int(result)
value = bytes(buf[:bytes_written])
```

When a `buffer` is provided, `get` returns the number of bytes written as a byte string (e.g. `b'4096'`), or `None` if the key does not exist.

### Multiple keys: `mget` with buffers

The `buffers` parameter extends the same pattern to multi-key reads:

```python
keys = ["embedding:1", "embedding:2", "embedding:3"]
BUFFER_SIZE = 8192

# Pre-allocate one buffer per key
buffers = [memoryview(bytearray(BUFFER_SIZE)) for _ in keys]

results = client.mget(keys, buffers=buffers)

for i, result in enumerate(results):
if result is None:
print(f"{keys[i]}: missing")
else:
bytes_written = int(result)
# Access the value directly from the buffer — no extra copy
value = bytes(buffers[i][:bytes_written])
print(f"{keys[i]}: {bytes_written} bytes")
```

:::tip[Buffer Sizing]
Size buffers appropriately for your expected values. If a value exceeds its buffer capacity, a `RequestError` is raised. For variable-sized data, consider using a conservative upper bound or implementing retry logic with larger buffers.
:::

Each element in the returned list is either:
- A byte string containing the number of bytes written (e.g. `b'8192'`) — read the value from `buffers[i][:int(result)]`
- `None` — the key does not exist

### Requirements

- **Writable and C-contiguous**: Each buffer must be a writable, C-contiguous `memoryview`. The simplest way to create one is `memoryview(bytearray(size))`.
- **Same length as keys**: The `buffers` list must have exactly the same number of elements as `keys`.

### Error Handling

- **Buffer too small**: If a value exceeds its buffer capacity, a `RequestError` with "exceeds buffer capacity" is raised.
- **Length mismatch**: If the `buffers` list length differs from `keys`, a `ValueError` is raised.
- **Invalid buffer type**: Non-writable or non-C-contiguous buffers raise a `TypeError`.

```python
try:
small_buf = memoryview(bytearray(10)) # Too small for large values
result = client.get("large_key", buffer=small_buf)
except RequestError as e:
if "exceeds buffer capacity" in str(e):
# Retry with a larger buffer
larger_buf = memoryview(bytearray(8192))
result = client.get("large_key", buffer=larger_buf)
```

### Complete example

```python
from glide_sync import GlideClient, GlideClientConfiguration, NodeAddress

config = GlideClientConfiguration([NodeAddress("localhost", 6379)])
client = GlideClient.create(config)

# Store some test data
client.mset({"vec:1": b"A" * 4096, "vec:2": b"B" * 2048})

# Read with zero-copy buffers
keys = ["vec:1", "vec:2", "vec:missing"]
bufs = [memoryview(bytearray(8192)) for _ in keys]

results = client.mget(keys, buffers=bufs)
# results == [b'4096', b'2048', None]

for i, r in enumerate(results):
if r is None:
print(f"{keys[i]} does not exist")
else:
n = int(r)
print(f"{keys[i]}: {n} bytes -> {bytes(bufs[i][:n])[:20]}...")
```
Loading