Skip to content

Commit 2a549a8

Browse files
committed
feat(client): add BatchSet, BatchGet, and BatchDelete operations
Introduce three new methods on *Client that execute their respective operations in a single HTTP round-trip against the existing POST /v1/cache/batch/{put,get,delete} wire endpoints. Key design points: - Per-item granularity: the outer error fires only on transport/auth/ HTTP-level failures; individual item failures surface via the result's Err field (*StatusError), preserving errors.Is/errors.As compatibility with the single-key sentinel set. - BatchGet treats missing keys as Found=false, not an error. - Empty input is a client-side no-op (returns empty slice, nil error) without dispatching an HTTP request. - Results mirror input order (index i of result = outcome for input i). Supporting changes: - Extract contentTypeJSON const to eliminate drift between call sites. - Add test_consts_test.go with shared JSON wire-key constants. - Eight new test cases in batch_test.go covering happy paths, per-item failures, mixed found/missing, empty-input no-ops, and HTTP-level failure wrapping ErrAllEndpointsFailed. - Extend docs/client-sdk.md with a dedicated Batch operations section and update the commands reference table with the three new methods. - Add a BatchSet demo step to the distributed-oidc-client example. - Fix markdown anchor links in docs/oncall.md (double-dash → single-dash). - Bump softprops/action-gh-release v2 → v3 in release.yml.
1 parent 52529aa commit 2a549a8

14 files changed

Lines changed: 1054 additions & 151 deletions

File tree

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ name: release
1313

1414
on:
1515
push:
16-
tags: ['v*.*.*']
16+
tags: ["v*.*.*"]
1717
workflow_dispatch:
1818
inputs:
1919
tag:
20-
description: 'Existing tag to (re)create a release for'
20+
description: "Existing tag to (re)create a release for"
2121
required: true
2222

2323
permissions:
@@ -70,7 +70,7 @@ jobs:
7070
echo "path=/tmp/release-body.md" >> "$GITHUB_OUTPUT"
7171
7272
- name: Create GitHub Release
73-
uses: softprops/action-gh-release@v2
73+
uses: softprops/action-gh-release@v3
7474
with:
7575
tag_name: ${{ steps.tag.outputs.name }}
7676
name: ${{ steps.tag.outputs.name }}

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@ All notable changes to HyperCache are recorded here. The format follows
88

99
### Added
1010

11+
- **Batch operations on the client SDK.** `BatchSet`, `BatchGet`, `BatchDelete` close the v1 SDK gap PR3's
12+
stopping conditions called out — the raw OIDC example demonstrated batch round-trips but the SDK had no
13+
equivalent. Each method takes a slice and returns per-item results so a single HTTP call can carry
14+
mixed-outcome batches (some stored, some draining) without forcing the caller to either fail-the-whole-batch
15+
or parse the wire envelope by hand. Per-item `Err` is the standard `*StatusError`, so
16+
`errors.Is(result.Err, client.ErrDraining)` works inside per-item handling the same way it does for
17+
single-key calls. Empty input short-circuits to an empty result slice without dispatching an HTTP request.
18+
Eight new test cases in [`pkg/client/batch_test.go`](pkg/client/batch_test.go) cover the happy path for each
19+
verb, per-item failures, mixed found/missing in `BatchGet`, empty-input no-op, and the HTTP-level
20+
failure-wraps-`ErrAllEndpointsFailed` regression guard. The OIDC example
21+
([`__examples/distributed-oidc-client/main.go`](__examples/distributed-oidc-client/main.go)) gains a final
22+
`BatchSet` step demonstrating the surface, and [`docs/client-sdk.md`](docs/client-sdk.md) grows a dedicated
23+
"Batch operations" section explaining the per-item granularity contract.
1124
- **Client SDK reference + example migration.** New [`docs/client-sdk.md`](docs/client-sdk.md) is the
1225
recommended starting point for Go consumers — covers every auth mode (bearer / Basic / OIDC client
1326
credentials / custom mTLS via `WithHTTPClient`), the multi-endpoint failover policy, topology refresh

__examples/distributed-oidc-client/main.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,27 @@ func demo(ctx context.Context, c *client.Client) error {
135135

136136
fmt.Fprintln(os.Stdout, "deleted")
137137

138+
// Batch shape: write three keys in a single round-trip. The
139+
// per-item Stored / Err fields surface per-key results; the
140+
// outer error fires only on transport / auth / HTTP-level
141+
// failures (4xx / 5xx).
142+
batchResults, err := c.BatchSet(ctx, []client.BatchSetItem{
143+
{Key: "batch-1", Value: []byte("one"), TTL: time.Minute},
144+
{Key: "batch-2", Value: []byte("two"), TTL: time.Minute},
145+
{Key: "batch-3", Value: []byte("three"), TTL: time.Minute},
146+
})
147+
if err != nil {
148+
return fmt.Errorf("batch set: %w", err)
149+
}
150+
151+
for _, r := range batchResults {
152+
if r.Stored {
153+
fmt.Fprintf(os.Stdout, "batch stored %s (%d bytes)\n", r.Key, r.Bytes)
154+
} else {
155+
fmt.Fprintf(os.Stdout, "batch %s FAILED: %v\n", r.Key, r.Err)
156+
}
157+
}
158+
138159
return nil
139160
}
140161

cspell.config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ words:
6969
- cmap
7070
- Cmder
7171
- codacy
72+
- codebook
7273
- codegen
7374
- codemod
7475
- containedctx
@@ -147,6 +148,7 @@ words:
147148
- HTTPTLS
148149
- hypercache
149150
- Hyperd
151+
- idempotently
150152
- idxs
151153
- Iface
152154
- ineff

docs/client-sdk.md

Lines changed: 167 additions & 133 deletions
Large diffs are not rendered by default.

docs/oncall.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ periodic ticks `rebalance.batches` increments visible at `/dist/metrics`.
7272
if scan duration exceeds the interval, you have a sustained backlog.
7373

7474
**What to do.** Usually wait. If wait is unbounded, see
75-
[Rebalance under load](operations.md#failure-mode--rebalance-under-load).
75+
[Rebalance under load](operations.md#failure-mode-rebalance-under-load).
7676

7777
## Heartbeat flapping
7878

@@ -109,7 +109,7 @@ emit the second line, with no preceding `pruned (dead)`).
109109

110110
**What to do.** If `refuted` is climbing in step with `failure`, the system is self-correcting — extend
111111
`WithDistHeartbeat`'s `suspectAfter` / `deadAfter` if the flap is noisy. If `indirect_probe.failure` is also
112-
climbing, the peer is genuinely unreachable — see [replica loss](operations.md#failure-mode--replica-loss).
112+
climbing, the peer is genuinely unreachable — see [replica loss](operations.md#failure-mode-replica-loss).
113113

114114
## Hint queue building
115115

@@ -134,7 +134,7 @@ mismatch, schema drift, or a truly bad value.
134134
- `dist.hinted.global_dropped` (counter) — caps exceeded; hints are being silently dropped. Hard limit hit.
135135
- `dist.hinted.expired` (counter) — hints aged past `WithDistHintTTL`.
136136

137-
**What to do.** See [Hint queue overflow](operations.md#failure-mode--hint-queue-overflow) for the full
137+
**What to do.** See [Hint queue overflow](operations.md#failure-mode-hint-queue-overflow) for the full
138138
playbook. Short version: restore the peer, or remove it from membership and let hints expire.
139139

140140
## Auth failures
@@ -224,7 +224,7 @@ log-spam under load).
224224
err := dm.SyncWith(ctx, "peer-node-id")
225225
```
226226

227-
The full discussion is in [Split-brain](operations.md#failure-mode--split-brain).
227+
The full discussion is in [Split-brain](operations.md#failure-mode-split-brain).
228228

229229
## Drain not draining
230230

docs/rfcs/0003-client-sdk-and-redis-style-affordances.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ Closing these gaps is the SDK work.
6767

6868
### Where the existing roadmap lands
6969

70-
[ROADMAP.md](../../ROADMAP.md) Phase 5 already scopes "Go client: seed discovery, ring bootstrap, direct owner
70+
[ROADMAP.md](https://github.com/hyp3rd/hypercache/blob/main/ROADMAP.md) Phase 5 already scopes "Go client: seed discovery, ring bootstrap, direct owner
7171
hashing, parallel fan-out for QUORUM/ALL". This RFC refines that scope and folds in the auth and
7272
operational-error work surfaced by the example.
7373

0 commit comments

Comments
 (0)