Skip to content

Commit 21ab9d2

Browse files
authored
Update metrics and error handling docs (#812)
* Add a doc explaining available metrics Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com> * docs: drop per-Resp helpers from user docs, recommend for/switch User-facing docs (v5preview/opensearchapi/README.md and guides/error_handling.md) no longer document the per-Resp helper methods (BulkItemFailures, SearchShardFailures, WriteShardFailures, MultiSearchItemFailures, PartialFailures(mask)). The Recommended pattern section presents two paths: - Treat any server or API failure as a hard error -- the idiomatic `if err != nil { return err }` for operations where any failure is reason to stop. - Inspect categories with a `for`/`switch` over opensearchapi.Errors(err) when partial error handling lets the application recover from known tolerated failure modes. The antipattern discussion now covers errors.As, `Has`-style helpers, and per-Resp helpers together: all three answer the narrow question "did this category happen?" and silently miss categories added in a future release. The type switch is the only category-aware pattern recommended. opensearchapi/partial_failure_methods.go file header reframes the helpers as engine machinery for the dispatch and points at guides/error_handling.md for the recommended call-site pattern. The methods stay available without deprecation markers. DEVELOPER_GUIDE.md and cmd/osgen/README.md describe the helpers as engine machinery the dispatch consumes and redirect call-site authors to opensearchapi.Errors(err) + for/switch. Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com> * docs: round out v5-prep doc fixes - guides/routing.md: add COORDINATOR and NULL rows to the policy override env-var table so it matches the 10 policy types in policy_override_env.go (USER_GUIDE.md already says "10 variables"). - guides/search.md: replace three Transport: &opensearchtransport.Client{Router: ...} blocks with Router: ... directly on opensearch.Config. The old form did not compile -- opensearch.Config.Transport is http.RoundTripper and *opensearchtransport.Client does not implement it; the canonical field for the router has been Config.Router since the routing system was introduced. Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com> * docs: address PR #812 review feedback - guides/metrics.md: replace nonexistent opensearchtransport.WithObserver() with the Observer field on opensearch.Config - guides/metrics.md: hoist safeFloat above the for loop so the polling example is parseable Go - guides/metrics.md: align aggregate tables (Request Counters, Pool State, Lifecycle, Health Checks) to Field (Go name) + JSON (tag) layout - guides/metrics.md: soften mechanism claims on IsOverloaded, RTTBucket, EstLoad to describe field meaning rather than how they get set - guides/error_handling.md, v5preview/opensearchapi/README.md: drop *PartialBulkError and *ShardFailureError from MSearch switch examples; fix the duplicate pre-existing example in the v5preview README - guides/error_handling.md: qualify "folded into err" with the v4-vs- v5preview default-mask caveat - guides/error_handling.md: nil-check ErrorCause.Reason deref in v5preview bulk example; switch the version-agnostic MSearch helper to log item.Status (portable across v4 and v5preview) - v5preview/opensearchapi/README.md: fix NewMuxRouter() to single-value assignment (returns Router, not (Router, error)) - v5preview/opensearchapi/MIGRATING.md: drop the forward-compatible `replace` section -- the cited path has no go.mod and the directive cannot resolve - UPGRADING.md: reword the partial-failure intro so the recommended type-switch pattern is the only advertised entry point (drops the in-tension `errors.As` mention) - CHANGELOG.md: add (#812) backlink to the metrics-guide bullet Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com> --------- Signed-off-by: Sean Chittenden <sean.chittenden@crowdstrike.com>
1 parent 9d17c7f commit 21ab9d2

11 files changed

Lines changed: 364 additions & 94 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1616
- Add `OPENSEARCH_GO_SHARD_COST` environment variable and `WithShardCosts()` router option with `r:base`/`r:amplify`/`r:exponent` curve keys and static cost overrides
1717
- Add `ShardCostConfig` field to `Config` struct for programmatic shard cost override passthrough
1818
- Add `OPENSEARCH_GO_ROUTER` environment variable to enable the DefaultRouter without code changes; set to `true` to opt in (off by default in v4, on by default in v5, removed in v6) ([#815](https://github.com/opensearch-project/opensearch-go/pull/815))
19+
- Add client-side metrics guide covering Metrics API, ConnectionMetric, PolicySnapshot, and RouterSnapshot ([#812](https://github.com/opensearch-project/opensearch-go/pull/812))
1920
- Add `InsecureSkipVerify` config option to disable TLS certificate verification without constructing a custom `http.Transport`, preserving `DefaultTransport` connection pooling, HTTP/2, and timeout defaults ([#786](https://github.com/opensearch-project/opensearch-go/issues/786))
2021
- Add `DisableResponseBuffering` config option to skip eager `io.ReadAll` buffering of response bodies in `Perform()`, reducing per-request allocations and TTFB for proxy and streaming use cases ([#786](https://github.com/opensearch-project/opensearch-go/issues/786))
2122
- Add per-attempt `RequestTimeout` to bound individual HTTP round-trips, preventing indefinite hangs on stalled connections ([#786](https://github.com/opensearch-project/opensearch-go/issues/786))
@@ -98,10 +99,10 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
9899
- `MultiSearchItemError` returned from `MSearch`/`MSearchTemplate` for per-sub-response Error envelopes
99100
- `MSearchErrors` / `MSearchTemplateErrors` per-op containers (Go 1.20+ multi-error contract via `Unwrap() []error`) when 2+ wrapper categories fire on the same response
100101
- `PartialFailureError` marker interface with `IsPartial() bool` for type-switching across all partial-failure types
101-
- Per-Resp helper methods (`BulkItemFailures`, `SearchShardFailures`, `WriteShardFailures`, `MultiSearchItemFailures`) plus `PartialFailures(mask)` aggregator for focused inspection at the call site
102-
- `opensearchapi.Errors(err) []error` package-level helper that flattens single- and multi-wrapper errors into a uniform slice for `switch` dispatch
102+
- `opensearchapi.Errors(err) []error` package-level helper that flattens single- and multi-wrapper errors into a uniform slice; recommended call-site pattern is a `for`/`switch` over the result (not `errors.As` against a specific type)
103103
- Helper functions: `IsPartialFailure`, `ToleratePartialFailures`, `RequireSuccessRate` for threshold-based error tolerance
104104
- Operation constants: `OperationIndex`, `OperationCreate`, `OperationUpdate`, `OperationDelete`
105+
- Per-Resp helper methods (`BulkItemFailures`, `SearchShardFailures`, `WriteShardFailures`, `MultiSearchItemFailures`, `PartialFailures(mask)`) exist on the response types as engine machinery for the dispatch; new code should prefer a `for`/`switch` over `opensearchapi.Errors(err)` rather than the per-Resp helpers, for forward compatibility
105106
- `Config.Errors *errmask.ErrorMask` replaces a single boolean: each bit suppresses one wrapper category. v4 defaults to `errmask.All` (mask everything, preserves pre-bitfield behavior); v5+ defaults to `errmask.Empty` (report everything)
106107
- `OPENSEARCH_GO_ERROR_MASK` environment variable overrides `Config.Errors` at runtime via comma-separated `+`/`-` tokens (lowercase snake_case wrapper names; unknown tokens silently dropped, debug-logged)
107108
- Both `(resp, error)` are non-nil on partial failure -- response is fully populated

DEVELOPER_GUIDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ The `x-error-responses` extension on a spec operation declares the categories of
399399

400400
- A typed Go error (e.g. `*PartialBulkError`, `*PartialSearchError`, `*ShardFailureError`, `*MultiSearchItemError`) decoded from the response body when that category fires.
401401
- A bit on `errmask.ErrorMask` (PascalCase, e.g. `errmask.BulkItems`) plus the corresponding env-var token (`bulk_items`) so callers can suppress or surface it via `Config.Errors` or `OPENSEARCH_GO_ERROR_MASK`.
402-
- A per-Resp helper method on the operation's typed response (e.g. `BulkResp.BulkItemFailures()`, `SearchResp.SearchShardFailures()`).
402+
- A per-Resp helper method on the operation's typed response (e.g. `BulkResp.BulkItemFailures()`, `SearchResp.SearchShardFailures()`). These exist as engine machinery for the dispatch and are not the recommended call-site pattern; user docs point callers at a `for`/`switch` over `opensearchapi.Errors(err)` instead.
403403
- A `PartialFailures(mask)` aggregator on the same Resp.
404404

405405
Operations that declare two or more categories also get a per-op error container (e.g. `*MSearchErrors`) implementing `Unwrap() []error`, used when more than one category fires on a single response.

UPGRADING.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
### Partial Failure Errors (Config.Errors)
3636

37-
Version 5.0.0 introduces typed partial-failure errors and a per-category bitmask that controls which categories surface as Go errors. OpenSearch returns HTTP 200 for many operations that partially succeed (bulk item failures, shard failures on search, replica failures on writes). The new model turns those partial failures into typed errors callers can match on with `errors.As`.
37+
Version 5.0.0 introduces typed partial-failure errors and a per-category bitmask that controls which categories surface as Go errors. OpenSearch returns HTTP 200 for many operations that partially succeed (bulk item failures, shard failures on search, replica failures on writes). The new model turns those partial failures into typed errors that callers can dispatch on; idiomatic partial error handling is shown below.
3838

3939
**Default behavior change:**
4040

@@ -52,9 +52,10 @@ A v4 caller upgrading to v5 who never set `Config.Errors` will start seeing part
5252
- Typed errors: `*PartialBulkError`, `*PartialSearchError`, `*ShardFailureError`, `*MultiSearchItemError`, `*MSearchErrors`, `*MSearchTemplateErrors`.
5353
- `opensearchapi.Errors(err) []error` to flatten single- and multi-wrapper error shapes into a uniform slice.
5454
- Helper functions: `IsPartialFailure(err)`, `ToleratePartialFailures(err)`, `RequireSuccessRate(err, threshold)`.
55-
- Per-Resp helper methods: `BulkItemFailures()`, `SearchShardFailures()`, `WriteShardFailures()`, `MultiSearchItemFailures()`, `PartialFailures(mask)`.
5655
- Operation constants: `OperationIndex`, `OperationCreate`, `OperationUpdate`, `OperationDelete`.
5756

57+
The recommended call-site pattern is a `for`/`switch` over `opensearchapi.Errors(err)`, not `errors.As` against a specific type. Per-Resp helper methods (`BulkItemFailures()`, `SearchShardFailures()`, `WriteShardFailures()`, `MultiSearchItemFailures()`, `PartialFailures(mask)`) exist on the response types as engine machinery for the dispatch and remain available for focused inspection of a known category, but new code should use the type switch -- see [`guides/error_handling.md`](guides/error_handling.md#why-a-type-switch-not-errorsas-has-or-per-resp-helpers) for why.
58+
5859
**Where to read more:**
5960

6061
- [`v5preview/opensearchapi/README.md`](v5preview/opensearchapi/README.md) - full v5preview usage guide for these errors, including the type-switch pattern and the rationale for preferring it over `errors.As`/`Has`.

cmd/osgen/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ go run . api \
157157
3. Routes each operation to either the core `opensearchapi` package or a plugin package based on the operation group prefix.
158158
4. Renders Req structs (with path builder embedding, optional body, and header support), Params structs (with typed encode methods), and Resp stubs.
159159
5. Annotates generated code with availability (`x-version-added`), deprecation (`x-version-deprecated`, `x-deprecation-message`), and distribution exclusion metadata.
160-
6. Reads each operation's `x-error-responses` extension to emit typed partial-failure errors (`*PartialBulkError`, `*PartialSearchError`, `*ShardFailureError`, `*MultiSearchItemError`, ...), the corresponding `errmask` bits and env-var tokens, per-Resp helper methods (`BulkItemFailures()`, `SearchShardFailures()`, `WriteShardFailures()`, `MultiSearchItemFailures()`, `PartialFailures(mask)`), and -- for operations declaring two or more categories -- a per-op multi-error container implementing `Unwrap() []error`. See [`DEVELOPER_GUIDE.md` Partial-failure error generation](../../DEVELOPER_GUIDE.md#partial-failure-error-generation) for the full surface this produces, and [`v5preview/opensearchapi/README.md` Partial Failure Errors](../../v5preview/opensearchapi/README.md#partial-failure-errors) for the user-facing usage guide.
160+
6. Reads each operation's `x-error-responses` extension to emit typed partial-failure errors (`*PartialBulkError`, `*PartialSearchError`, `*ShardFailureError`, `*MultiSearchItemError`, ...), the corresponding `errmask` bits and env-var tokens, per-Resp helper methods (`BulkItemFailures()`, `SearchShardFailures()`, `WriteShardFailures()`, `MultiSearchItemFailures()`, `PartialFailures(mask)`) used internally by the dispatch, and -- for operations declaring two or more categories -- a per-op multi-error container implementing `Unwrap() []error`. The recommended call-site pattern in user code is a `for`/`switch` over `opensearchapi.Errors(err)`, not the per-Resp helpers; see [`DEVELOPER_GUIDE.md` Partial-failure error generation](../../DEVELOPER_GUIDE.md#partial-failure-error-generation) for the generated surface and [`v5preview/opensearchapi/README.md` Partial Failure Errors](../../v5preview/opensearchapi/README.md#partial-failure-errors) for the user-facing usage guide.
161161

162162
## Separate Module
163163

guides/error_handling.md

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,18 @@ for _, sub := range opensearchapi.Errors(err) {
8686
len(e.FailedItems),
8787
e.SucceededCount+len(e.FailedItems))
8888
for _, item := range e.FailedItems {
89-
// BulkRespItem.ID and BulkRespItem.Error are pointers in v5preview.
89+
// BulkRespItem.ID, BulkRespItem.Error, and ErrorCause.Reason are pointers in v5preview.
9090
id := ""
9191
if item.ID != nil {
9292
id = *item.ID
9393
}
9494
if item.Error != nil {
95+
reason := ""
96+
if item.Error.Reason != nil {
97+
reason = *item.Error.Reason
98+
}
9599
log.Printf(" %s %s/%s: %s",
96-
item.Error.Type, item.Index, id, item.Error.Reason)
100+
item.Error.Type, item.Index, id, reason)
97101
}
98102
}
99103
default:
@@ -208,32 +212,41 @@ if err != nil {
208212
}
209213
```
210214

211-
### Per-Resp helper methods
215+
### Recommended pattern
216+
217+
Two patterns cover every partial-failure use case. Pick the one that matches your operation's tolerance:
212218

213-
Every operation that can return a partial failure exposes per-category helper methods on its typed response, plus a `PartialFailures(mask)` aggregator. Use these when you want focused inspection at the call site without going through the dispatch error. The helpers exist on both v4 `opensearchapi/` and v5preview `v5preview/opensearchapi/` Resp types and are nil-safe on a nil receiver.
219+
**Treat any server or API failure as a hard error** -- the simplest and most idiomatic Go path. Use this when the operation has no meaningful "partial success" -- any error is a reason to stop:
214220

215221
```go
216-
resp, _ := client.Bulk(ctx, req)
217-
if e := resp.BulkItemFailures(); e != nil {
218-
log.Printf("%d items failed", len(e.FailedItems))
222+
resp, err := client.Bulk(ctx, req)
223+
if err != nil {
224+
return err
219225
}
220-
221-
resp2, _ := client.MSearch(ctx, req)
222-
if e := resp2.SearchShardFailures(); e != nil { /* ... */ }
223-
if e := resp2.MultiSearchItemFailures(); e != nil { /* ... */ }
224-
225-
resp3, _ := client.Index(ctx, req)
226-
if e := resp3.WriteShardFailures(); e != nil { /* ... */ }
226+
// resp is fully populated; partial failures (if any) are folded into err
227+
// when the wrapper bits are unmasked (the v5preview default, or v4 with
228+
// Config.Errors: errmask.New()).
227229
```
228230

229-
`r.PartialFailures(mask errmask.ErrorMask) []error` returns every wrapper category that fired and was not suppressed by `mask`. Useful for recreating the dispatch's mask-gated behavior at the call site -- pass `errmask.Empty` to see every category, or any narrower mask to suppress specific ones:
231+
**Inspect categories with a `for`/`switch`** -- when partial error handling is appropriate. Partial error handling lets the client and its application recover from known failure modes they can tolerate (e.g. continue serving a search with a few failed shards, or retry only the bulk items the server rejected) instead of failing the whole operation. The `default` arm catches transport / HTTP / decode errors and any partial-failure category added in a future release:
230232

231233
```go
232-
for _, sub := range resp.PartialFailures(errmask.Empty) {
233-
// same shape as opensearchapi.Errors(err)
234+
resp, err := client.MSearch(ctx, req)
235+
for _, sub := range opensearchapi.Errors(err) {
236+
switch e := sub.(type) {
237+
case *opensearchapi.PartialSearchError:
238+
log.Printf("%d/%d shards failed", e.FailedShards, e.TotalShards)
239+
case *opensearchapi.MultiSearchItemError:
240+
log.Printf("%d sub-queries failed", len(e.Items))
241+
default:
242+
return err
243+
}
234244
}
245+
// resp is fully populated; use it regardless of partial failure.
235246
```
236247

248+
`opensearchapi.Errors(err)` flattens every error shape into a uniform slice -- single sub-error, multi-wrapper container, transport error, or `nil` (returns `nil`). The switch is the only pattern this guide recommends for category-aware handling: it stays correct when the API adds new categories, and a missing `case` is reviewable / lint-able.
249+
237250
### Inspecting Multi-Wrapper Errors with `opensearchapi.Errors`
238251

239252
Operations that can return more than one category of partial failure on the same response (today: `MSearch`, `MSearchTemplate`) sometimes do. The dispatch handler applies a runtime-collapse rule:
@@ -278,7 +291,7 @@ func handleMSearchError(err error) {
278291
metrics.ShardFailures.Add(int64(e.FailedShards))
279292
case *opensearchapi.MultiSearchItemError:
280293
for _, item := range e.Items {
281-
log.Printf("sub-query %d failed: %s", item.Index, item.Error.Reason)
294+
log.Printf("sub-query %d failed (status=%d)", item.Index, item.Status)
282295
}
283296
default:
284297
log.Printf("non-partial msearch error: %v", e)
@@ -289,11 +302,13 @@ func handleMSearchError(err error) {
289302

290303
`opensearchapi.Errors(nil)` returns `nil`. A non-partial `err` (transport, HTTP, decode) returns a single-element slice containing `err`. Adding a new wrapper category later is purely additive: a new `case` in the switch picks it up; the `default` keeps catching everything else.
291304

292-
### Why a type switch, not `errors.As` or `Has`-style helpers
305+
### Why a type switch, not `errors.As`, `Has`, or per-Resp helpers
306+
307+
The set of partial-failure categories grows as the OpenSearch API evolves -- a future server or client release can add a category today's call sites have never seen. A type switch over `opensearchapi.Errors(err)` makes that growth visible: static analysis and code review can grep for the switch and flag missing cases, and the `default` arm keeps existing call sites safe in the meantime. `errors.As(err, &target)` and `Has`-style helpers (e.g. `multierror.Contains`, `errors.Has`) only answer "did _this_ category happen?" -- they cannot tell a call site that a _new_ category appeared and is being silently dropped, because the categories of interest are arguments rather than cases.
293308

294-
The set of partial-failure categories grows as the OpenSearch API evolves -- a future server or client release can add a category today's call sites have never seen. A type switch over `opensearchapi.Errors(err)` makes that growth visible: static analysis and code review can grep for the switch and flag missing cases, and the `default` arm keeps existing call sites safe in the meantime. `errors.As(err, &target)` and HashiCorp-style helpers (`multierror.Contains`, `errors.Has`) only answer "did _this_ category happen?" -- they cannot tell a call site that a _new_ category appeared and is being silently dropped, because the categories of interest are arguments rather than cases.
309+
The per-Resp helper methods (`resp.BulkItemFailures()`, `resp.SearchShardFailures()`, `resp.WriteShardFailures()`, `resp.MultiSearchItemFailures()`) and the per-Resp `PartialFailures(mask)` aggregator suffer the same forward-compatibility problem: a call site only sees the categories whose helpers it explicitly invokes. They exist on the response types as engine machinery for the dispatch and remain available for focused inspection of a known category. New code should use the `for`/`switch` pattern shown above.
295310

296-
Treat `As`/`Has` against the partial-failure error types as an antipattern: every call site that uses them becomes an audit liability the next time a category is added, because the omission is invisible to lint-time checks. The same reasoning applies to operations that today produce a single category -- preferring the type switch from day one means a future addition is purely additive rather than a silent behavior change.
311+
Treat `As`/`Has` and the per-Resp helpers against the partial-failure error types as an antipattern: every call site that uses them becomes an audit liability the next time a category is added, because the omission is invisible to lint-time checks. The same reasoning applies to operations that today produce a single category -- preferring the type switch from day one means a future addition is purely additive rather than a silent behavior change.
297312

298313
### Error Type Reference
299314

0 commit comments

Comments
 (0)