fix: keep proxy metric status label stable, split cause into its own label (#51495)

issue: #51493

### What

#50221 split the `status` label values of `milvus_proxy_req_count` /
`milvus_proxy_grpc_latency` in place. Released in **v2.6.19**, that
silently zeroes every existing dashboard panel and alert rule matching
`status="fail"` / `status="rejected"` — an alert that stops firing
rather than erroring.

This keeps the classification but moves it onto its own dimension,
restoring the status domain to what <= v2.6.18 emitted:

```
status: success | fail | rejected | retry | total | abandon    (as before v2.6.19)
cause:  user | system | cancel | na                            (new)
```

| v2.6.19 / v2.6.20 | this PR |
|---|---|
| `status="fail_input"` | `status="fail", cause="user"` |
| `status="fail_system"` | `status="fail", cause="system"` |
| `status="rejected_user"` | `status="rejected", cause="user"` |
| `status="rejected_system"` | `status="rejected", cause="system"` |
| `status="cancel"` | `status="fail"` or `"rejected"`, `cause="cancel"`
|
| `success` / `retry` / `total` / `abandon` | unchanged, `cause="na"` |

### Why a label instead of new status values

- **Old queries work unchanged and count each request once.**
`status="fail"` is again every hard failure; Prometheus aggregates over
`cause` for free. That rollup is what a label dimension *is* — the split
forces every consumer who just wants "how many failures" to hand-write
`status=~"fail_.*"`.
- **Cardinality is unchanged.** `cause` is functionally dependent on the
outcome, so the realized `(status, cause)` pairs are exactly the series
the split already produces. Additive, not multiplicative.
- **New consumers lose nothing**: alert on `status="fail",
cause="system"`; route `cause="user"` to the owning application team.

The alternative — emitting old and new `status` values side by side
during a transition — was rejected: it defers the break rather than
removing it (the old values must still be dropped eventually, forcing
the same migration later), and meanwhile double-counts every request in
unfiltered aggregations.

`cancel` is folded into `cause` for the same reason: before v2.6.19
client cancellations were counted as `fail` (cancel in the response
status) or `rejected` (cancel at the interceptor), so promoting it to a
`status` value quietly makes `status="fail"` under-count versus older
releases. As a cause it stays excludable via `cause!="cancel"` without
redefining `status`.

### Also in this PR

- The 7 `snapshot_impl` sites that emitted a bare `fail` with no
classification now report their real cause via `failMetricLabel(err)`
(e.g. `snapshot_metadata_uri is required` is `cause="user"`, not a
system fault). Their `status` is unchanged.
- `deployments/monitor/grafana/milvus-dashboard.json`: the "Faild
Request Rate" panel goes back to `status="fail"` — a verbatim revert of
what #50221 changed, which is the compatibility claim demonstrated.
- Dev docs and stale comments that named the old label values.

### Verification

- `TestParseMetricLabelStatusDomainIsStable` pins the status domain, so
re-splitting it fails CI rather than shipping.
- Adding a label makes every `WithLabelValues` site arity-sensitive **at
runtime, not compile time** — a missed site panics in production. Unit
tests do not reach all of them (coverage shows
`GetRestoreSnapshotState`, `ListRestoreSnapshotJobs`, `PinSnapshotData`,
`UnpinSnapshotData` at 0%), so all **58 emit sites were verified
statically via AST**, not only the ones tests happen to hit.
- Passing: `pkg/metrics`, `pkg/util/requestutil`, `pkg/util/merr`,
`internal/distributed/proxy` (incl. `httpserver`, which covers the REST
emit path), and the `internal/proxy` snapshot / metric-label tests.
`golangci-lint` clean on every touched package.
- Pre-existing and unrelated: `service_test.go` bind failures (`listen
tcp :19529: address already in use`) reproduce identically on unmodified
master — a local process holds the port.

### Rollout

Should be cherry-picked to 2.6 so the window in which the fine-grained
`status` values exist stays confined to v2.6.19–v2.6.20. Users who
already adopted the new values on those two releases need a one-time
query change (`status="fail_system"` -> `status="fail",
cause="system"`); that is a known, bounded set, and preferable to
leaving every pre-2.6.19 dashboard silently broken.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: zhenshan.cao <zhenshan.cao@zilliz.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
zhenshan.cao
2026-07-17 17:18:40 +08:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 6fed75383b
commit 0d2637fd63
21 changed files with 262 additions and 179 deletions
@@ -4794,7 +4794,7 @@
"uid": "${datasource}"
},
"exemplar": true,
"expr": "sum(increase(milvus_proxy_req_count{app_kubernetes_io_instance=~\"$instance\", app_kubernetes_io_name=\"$app_name\", namespace=\"$namespace\", status=~\"fail_.*\"}[2m])/120) by(function_name, pod, node_id)",
"expr": "sum(increase(milvus_proxy_req_count{app_kubernetes_io_instance=~\"$instance\", app_kubernetes_io_name=\"$app_name\", namespace=\"$namespace\", status=\"fail\"}[2m])/120) by(function_name, pod, node_id)",
"interval": "",
"legendFormat": "{{function_name}}-{{pod}}-{{node_id}}",
"queryType": "randomWalk",
+1 -1
View File
@@ -163,7 +163,7 @@ Two mechanisms, used in different situations:
| Surface | InputError behavior |
|---|---|
| `commonpb.Status` | `ExtraInfo["is_input_error"]="true"`, `Retriable` forced `false` |
| Prometheus | request counted as `fail_input` / `rejected_user` (vs `fail_system` / `rejected_system`) |
| Prometheus | request counted with `cause="user"` (vs `cause="system"`) alongside the coarse `status="fail"` / `status="rejected"` |
| Access log / failure log | `error_type` field set accordingly |
| proxy lb_policy | **no cross-replica failover** — retrying a bad request elsewhere can't help |
| `retry.Do` | aborts immediately instead of retrying |
+2 -2
View File
@@ -105,8 +105,8 @@ component-to-component) must be `*merr.milvusError` defined in
If an error needs to be visible to the wire, it lives here. No exceptions.
Every sentinel also carries an Input-vs-System classification (who is to
blame) that drives `Status.Retriable`, the `fail_input`/`fail_system` metric
labels, lb_policy failover and `retry.Do`; see "Input vs System: who is to
blame) that drives `Status.Retriable`, the `cause` metric label, lb_policy
failover and `retry.Do`; see "Input vs System: who is to
blame?" in [error_handling_guide.md](error_handling_guide.md).
#### Code-range partition
@@ -108,7 +108,7 @@ func (s *ImportCallbacksSuite) TestValidateImportRequest_MaxJobsExceededReturnsE
s.Error(err)
// Job-count backpressure is a server-side condition -> ErrImportSysFailed
// (must not be bucketed as fail_input).
// (must not be bucketed as a user-caused failure).
s.True(errors.Is(err, merr.ErrImportSysFailed))
s.Contains(err.Error(), "The number of jobs has reached the limit")
}
+1 -1
View File
@@ -486,7 +486,7 @@ func (m *indexMeta) canCreateIndex(req *indexpb.CreateIndexRequest, isJSON bool)
// Client-caused conflict, same family as the "one distinct index per
// field" case above: return an input-class error rather than
// ServiceInternal (code 5, "never return out of Milvus") so the
// caller is not blamed with a system error / counted as fail_system.
// caller is not blamed with a system error / counted as a system-caused failure.
return 0, merr.WrapErrParameterInvalidMsg("%s", errMsg)
}
}
@@ -410,24 +410,26 @@ func wrapperPost(newReq newReqFunc, v2 handlerFuncV2) gin.HandlerFunc {
strconv.FormatInt(paramtable.GetNodeID(), 10),
methodTag,
metrics.TotalLabel,
metrics.CauseNA,
dbName,
collectionName,
).Inc()
label := requestutil.ParseMetricLabel(resp, err)
label, cause := requestutil.ParseMetricLabel(resp, err)
// set metrics for state code
metrics.ProxyFunctionCall.WithLabelValues(
strconv.FormatInt(paramtable.GetNodeID(), 10),
methodTag,
label,
cause,
dbName,
collectionName,
).Inc()
// Mirror the fail_input/fail_system metric split into the logs so a
// failed REST request can be filtered by error_type. System failures are
// logged at Warn (actionable); input failures at Info (expected user
// mistakes — keeping them at Warn would spam the logs).
if label == metrics.FailSystemLabel || label == metrics.FailInputLabel {
// Mirror the metric's cause into the logs so a failed REST request can be
// filtered by error_type. System failures are logged at Warn (actionable);
// input failures at Info (expected user mistakes — keeping them at Warn
// would spam the logs).
if label == metrics.FailLabel && (cause == metrics.CauseSystem || cause == metrics.CauseUser) {
var status *commonpb.Status
switch r := resp.(type) {
case interface{ GetStatus() *commonpb.Status }:
@@ -436,7 +438,7 @@ func wrapperPost(newReq newReqFunc, v2 handlerFuncV2) gin.HandlerFunc {
status = r
}
errType := merr.SystemError
if label == metrics.FailInputLabel {
if cause == metrics.CauseUser {
errType = merr.InputError
}
logger := mlog.With(
@@ -60,28 +60,30 @@ func UnaryRequestStatsInterceptor(ctx context.Context, req any, rpcInfo *grpc.Un
strconv.FormatInt(paramtable.GetNodeID(), 10),
methodTag,
metrics.TotalLabel,
metrics.CauseNA,
dbName,
collectionName,
).Inc()
start := time.Now()
resp, err := handler(ctx, req)
label := requestutil.ParseMetricLabel(resp, err)
label, cause := requestutil.ParseMetricLabel(resp, err)
// set metrics for state code
metrics.ProxyFunctionCall.WithLabelValues(
strconv.FormatInt(paramtable.GetNodeID(), 10),
methodTag,
label,
cause,
dbName,
collectionName,
).Inc()
// Mirror the fail_input/fail_system metric split into the logs so a failed
// request can be filtered by error_type the same way the metric is. System
// failures are logged at Warn (actionable for SRE); input failures at Info
// (expected user mistakes — keeping them at Warn would spam the logs).
if label == metrics.FailSystemLabel || label == metrics.FailInputLabel {
// Mirror the metric's cause into the logs so a failed request can be
// filtered by error_type the same way the metric is. System failures are
// logged at Warn (actionable for SRE); input failures at Info (expected user
// mistakes — keeping them at Warn would spam the logs).
if label == metrics.FailLabel && (cause == metrics.CauseSystem || cause == metrics.CauseUser) {
var status *commonpb.Status
switch r := resp.(type) {
case interface{ GetStatus() *commonpb.Status }:
@@ -90,7 +92,7 @@ func UnaryRequestStatsInterceptor(ctx context.Context, req any, rpcInfo *grpc.Un
status = r
}
errType := merr.SystemError
if label == metrics.FailInputLabel {
if cause == metrics.CauseUser {
errType = merr.InputError
}
logger := mlog.With(
@@ -111,6 +113,7 @@ func UnaryRequestStatsInterceptor(ctx context.Context, req any, rpcInfo *grpc.Un
strconv.FormatInt(paramtable.GetNodeID(), 10),
methodTag,
label,
cause,
).Observe(float64(time.Since(start).Milliseconds()))
return resp, err
@@ -66,8 +66,8 @@ func (suite *StatsInterceptorSuite) TestUnaryRequestStatsInterceptor() {
return merr.Success(), nil
},
expectLabels: [][]string{
{paramtable.GetStringNodeID(), "CreateCollection", metrics.TotalLabel, dbName, collection},
{paramtable.GetStringNodeID(), "CreateCollection", metrics.SuccessLabel, dbName, collection},
{paramtable.GetStringNodeID(), "CreateCollection", metrics.TotalLabel, metrics.CauseNA, dbName, collection},
{paramtable.GetStringNodeID(), "CreateCollection", metrics.SuccessLabel, metrics.CauseNA, dbName, collection},
},
},
{
@@ -83,8 +83,8 @@ func (suite *StatsInterceptorSuite) TestUnaryRequestStatsInterceptor() {
return merr.Status(merr.WrapErrServiceInternal("unexpcted")), nil
},
expectLabels: [][]string{
{paramtable.GetStringNodeID(), "CreateCollection", metrics.TotalLabel, dbName, collection},
{paramtable.GetStringNodeID(), "CreateCollection", metrics.FailSystemLabel, dbName, collection},
{paramtable.GetStringNodeID(), "CreateCollection", metrics.TotalLabel, metrics.CauseNA, dbName, collection},
{paramtable.GetStringNodeID(), "CreateCollection", metrics.FailLabel, metrics.CauseSystem, dbName, collection},
},
},
{
@@ -102,8 +102,8 @@ func (suite *StatsInterceptorSuite) TestUnaryRequestStatsInterceptor() {
}, nil
},
expectLabels: [][]string{
{paramtable.GetStringNodeID(), "Insert", metrics.TotalLabel, dbName, collection},
{paramtable.GetStringNodeID(), "Insert", metrics.RetryLabel, dbName, collection},
{paramtable.GetStringNodeID(), "Insert", metrics.TotalLabel, metrics.CauseNA, dbName, collection},
{paramtable.GetStringNodeID(), "Insert", metrics.RetryLabel, metrics.CauseNA, dbName, collection},
},
},
{
@@ -119,9 +119,9 @@ func (suite *StatsInterceptorSuite) TestUnaryRequestStatsInterceptor() {
return nil, status.Error(codes.Unauthenticated, "auth check failure, please check api key is correct")
},
expectLabels: [][]string{
{paramtable.GetStringNodeID(), "CreateCollection", metrics.TotalLabel, dbName, collection},
// Unauthenticated is a caller-side rejection -> rejected_user (review §8).
{paramtable.GetStringNodeID(), "CreateCollection", metrics.RejectedUserLabel, dbName, collection},
{paramtable.GetStringNodeID(), "CreateCollection", metrics.TotalLabel, metrics.CauseNA, dbName, collection},
// Unauthenticated is the caller's fault -> rejected, cause=user.
{paramtable.GetStringNodeID(), "CreateCollection", metrics.RejectedLabel, metrics.CauseUser, dbName, collection},
},
},
}
+3 -2
View File
@@ -70,6 +70,7 @@ func updateProxyFunctionCallMetric(fullMethod string, err error) {
if method == "" {
return
}
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, "", "").Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), "", "").Inc()
status, cause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, metrics.CauseNA, "", "").Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, status, cause, "", "").Inc()
}
+5 -4
View File
@@ -78,7 +78,7 @@ func (i *InterceptorImpl[Req, Resp]) Call(ctx context.Context, request Req,
defer sp.End()
tr := timerecord.NewTimeRecorder(i.method)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), i.method,
metrics.TotalLabel, request.GetDbName(), request.GetCollectionName()).Inc()
metrics.TotalLabel, metrics.CauseNA, request.GetDbName(), request.GetCollectionName()).Inc()
resp, err := i.onCall(ctx, request)
if err != nil {
@@ -86,7 +86,7 @@ func (i *InterceptorImpl[Req, Resp]) Call(ctx context.Context, request Req,
}
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), i.method,
metrics.SuccessLabel, request.GetDbName(), request.GetCollectionName()).Inc()
metrics.SuccessLabel, metrics.CauseNA, request.GetDbName(), request.GetCollectionName()).Inc()
metrics.ProxyReqLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), i.method).Observe(float64(tr.ElapseSpan().Milliseconds()))
return resp, err
@@ -281,7 +281,7 @@ func (node *RemoteProxyServiceProvider) DescribeCollection(ctx context.Context,
mlog.Err(err))
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method,
metrics.AbandonLabel, request.GetDbName(), request.GetCollectionName()).Inc()
metrics.AbandonLabel, metrics.CauseNA, request.GetDbName(), request.GetCollectionName()).Inc()
return nil, err
}
@@ -295,8 +295,9 @@ func (node *RemoteProxyServiceProvider) DescribeCollection(ctx context.Context,
mlog.Uint64("BeginTS", dct.BeginTs()),
mlog.Uint64("EndTS", dct.EndTs()))
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method,
failMetricLabel(err), request.GetDbName(), request.GetCollectionName()).Inc()
failStatus, failCause, request.GetDbName(), request.GetCollectionName()).Inc()
return nil, err
}
+63 -47
View File
@@ -47,7 +47,7 @@ func (node *Proxy) CreateSnapshot(ctx context.Context, req *milvuspb.CreateSnaps
tr := timerecord.NewTimeRecorder(method)
log.Info(ctx, rpcReceived(method))
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
t := &createSnapshotTask{
req: req,
ctx: ctx,
@@ -57,20 +57,21 @@ func (node *Proxy) CreateSnapshot(ctx context.Context, req *milvuspb.CreateSnaps
err := node.sched.ddQueue.Enqueue(t)
if err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "CreateSnapshot failed to Enqueue",
mlog.Err(err))
return merr.Status(err), nil
}
if err := t.WaitToFinish(); err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), req.GetDbName(), req.GetCollectionName()).Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "CreateSnapshot failed to WaitToFinish",
mlog.Err(err))
return merr.Status(err), nil
}
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyReqLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method).Observe(float64(tr.ElapseSpan().Milliseconds()))
return t.result, nil
}
@@ -87,7 +88,7 @@ func (node *Proxy) DropSnapshot(ctx context.Context, req *milvuspb.DropSnapshotR
tr := timerecord.NewTimeRecorder(method)
log.Info(ctx, rpcReceived(method))
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
t := &dropSnapshotTask{
req: req,
ctx: ctx,
@@ -97,20 +98,21 @@ func (node *Proxy) DropSnapshot(ctx context.Context, req *milvuspb.DropSnapshotR
err := node.sched.ddQueue.Enqueue(t)
if err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "DropSnapshot failed to Enqueue",
mlog.Err(err))
return merr.Status(err), nil
}
if err := t.WaitToFinish(); err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), req.GetDbName(), req.GetCollectionName()).Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "DropSnapshot failed to WaitToFinish",
mlog.Err(err))
return merr.Status(err), nil
}
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyReqLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method).Observe(float64(tr.ElapseSpan().Milliseconds()))
return t.result, nil
}
@@ -127,7 +129,7 @@ func (node *Proxy) DescribeSnapshot(ctx context.Context, req *milvuspb.DescribeS
tr := timerecord.NewTimeRecorder(method)
log.Info(ctx, rpcReceived(method))
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
t := &describeSnapshotTask{
req: req,
ctx: ctx,
@@ -137,7 +139,7 @@ func (node *Proxy) DescribeSnapshot(ctx context.Context, req *milvuspb.DescribeS
err := node.sched.ddQueue.Enqueue(t)
if err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "DescribeSnapshot failed to Enqueue",
mlog.Err(err))
return &milvuspb.DescribeSnapshotResponse{
@@ -146,7 +148,8 @@ func (node *Proxy) DescribeSnapshot(ctx context.Context, req *milvuspb.DescribeS
}
if err := t.WaitToFinish(); err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), req.GetDbName(), req.GetCollectionName()).Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "DescribeSnapshot failed to WaitToFinish",
mlog.Err(err))
return &milvuspb.DescribeSnapshotResponse{
@@ -154,7 +157,7 @@ func (node *Proxy) DescribeSnapshot(ctx context.Context, req *milvuspb.DescribeS
}, nil
}
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyReqLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method).Observe(float64(tr.ElapseSpan().Milliseconds()))
return t.result, nil
}
@@ -170,7 +173,7 @@ func (node *Proxy) ListSnapshots(ctx context.Context, req *milvuspb.ListSnapshot
tr := timerecord.NewTimeRecorder(method)
log.Info(ctx, rpcReceived(method))
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
t := &listSnapshotsTask{
req: req,
ctx: ctx,
@@ -180,7 +183,7 @@ func (node *Proxy) ListSnapshots(ctx context.Context, req *milvuspb.ListSnapshot
err := node.sched.ddQueue.Enqueue(t)
if err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "ListSnapshots failed to Enqueue",
mlog.Err(err))
return &milvuspb.ListSnapshotsResponse{
@@ -189,7 +192,8 @@ func (node *Proxy) ListSnapshots(ctx context.Context, req *milvuspb.ListSnapshot
}
if err := t.WaitToFinish(); err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), req.GetDbName(), req.GetCollectionName()).Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "ListSnapshots failed to WaitToFinish",
mlog.Err(err))
return &milvuspb.ListSnapshotsResponse{
@@ -197,7 +201,7 @@ func (node *Proxy) ListSnapshots(ctx context.Context, req *milvuspb.ListSnapshot
}, nil
}
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyReqLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method).Observe(float64(tr.ElapseSpan().Milliseconds()))
return t.result, nil
}
@@ -221,10 +225,11 @@ func (node *Proxy) RestoreExternalSnapshot(ctx context.Context, req *milvuspb.Re
)
log.Info(ctx, rpcReceived(method))
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, req.GetDbName(), req.GetTargetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, metrics.CauseNA, req.GetDbName(), req.GetTargetCollectionName()).Inc()
if req.GetSnapshotMetadataUri() == "" {
err := merr.WrapErrParameterInvalidMsg("snapshot_metadata_uri is required for restore external snapshot")
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, req.GetDbName(), req.GetTargetCollectionName()).Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, req.GetDbName(), req.GetTargetCollectionName()).Inc()
return &milvuspb.RestoreExternalSnapshotResponse{Status: merr.Status(err)}, nil
}
resp, err := node.mixCoord.RestoreSnapshot(ctx, &datapb.RestoreSnapshotRequest{
@@ -238,12 +243,13 @@ func (node *Proxy) RestoreExternalSnapshot(ctx context.Context, req *milvuspb.Re
ExternalSpec: req.GetExternalSpec(),
})
if err = merr.CheckRPCCall(resp, err); err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, req.GetDbName(), req.GetTargetCollectionName()).Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, req.GetDbName(), req.GetTargetCollectionName()).Inc()
log.Warn(ctx, "RestoreExternalSnapshot failed", mlog.Err(err))
return &milvuspb.RestoreExternalSnapshotResponse{Status: merr.Status(err)}, nil
}
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, req.GetDbName(), req.GetTargetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, metrics.CauseNA, req.GetDbName(), req.GetTargetCollectionName()).Inc()
metrics.ProxyReqLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method).Observe(float64(tr.ElapseSpan().Milliseconds()))
return &milvuspb.RestoreExternalSnapshotResponse{
Status: resp.GetStatus(),
@@ -271,25 +277,29 @@ func (node *Proxy) ExportSnapshot(ctx context.Context, req *milvuspb.ExportSnaps
)
log.Info(ctx, rpcReceived(method))
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
if err := ValidateSnapshotName(req.GetName()); err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, req.GetDbName(), req.GetCollectionName()).Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, req.GetDbName(), req.GetCollectionName()).Inc()
return &milvuspb.ExportSnapshotResponse{Status: merr.Status(err)}, nil
}
if req.GetCollectionName() == "" {
err := merr.WrapErrParameterInvalidMsg("collection_name is required for export snapshot")
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, req.GetDbName(), req.GetCollectionName()).Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, req.GetDbName(), req.GetCollectionName()).Inc()
return &milvuspb.ExportSnapshotResponse{Status: merr.Status(err)}, nil
}
if req.GetTargetS3Path() == "" {
err := merr.WrapErrParameterInvalidMsg("target_s3_path is required for export snapshot")
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, req.GetDbName(), req.GetCollectionName()).Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, req.GetDbName(), req.GetCollectionName()).Inc()
return &milvuspb.ExportSnapshotResponse{Status: merr.Status(err)}, nil
}
collectionID, err := globalMetaCache.GetCollectionID(ctx, req.GetDbName(), req.GetCollectionName())
if err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, req.GetDbName(), req.GetCollectionName()).Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "ExportSnapshot failed to resolve collection", mlog.Err(err))
return &milvuspb.ExportSnapshotResponse{Status: merr.Status(err)}, nil
}
@@ -303,12 +313,13 @@ func (node *Proxy) ExportSnapshot(ctx context.Context, req *milvuspb.ExportSnaps
ExternalSpec: req.GetExternalSpec(),
})
if err = merr.CheckRPCCall(resp, err); err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, req.GetDbName(), req.GetCollectionName()).Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "ExportSnapshot failed", mlog.Err(err))
return &milvuspb.ExportSnapshotResponse{Status: merr.Status(err)}, nil
}
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyReqLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method).Observe(float64(tr.ElapseSpan().Milliseconds()))
return &milvuspb.ExportSnapshotResponse{
Status: resp.GetStatus(),
@@ -328,7 +339,7 @@ func (node *Proxy) RestoreSnapshot(ctx context.Context, req *milvuspb.RestoreSna
tr := timerecord.NewTimeRecorder(method)
log.Info(ctx, rpcReceived(method))
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
t := &restoreSnapshotTask{
req: req,
ctx: ctx,
@@ -338,20 +349,21 @@ func (node *Proxy) RestoreSnapshot(ctx context.Context, req *milvuspb.RestoreSna
err := node.sched.ddQueue.Enqueue(t)
if err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "RestoreSnapshot failed to Enqueue",
mlog.Err(err))
return &milvuspb.RestoreSnapshotResponse{Status: merr.Status(err)}, nil
}
if err := t.WaitToFinish(); err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), req.GetDbName(), req.GetCollectionName()).Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "RestoreSnapshot failed to WaitToFinish",
mlog.Err(err))
return &milvuspb.RestoreSnapshotResponse{Status: merr.Status(err)}, nil
}
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyReqLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method).Observe(float64(tr.ElapseSpan().Milliseconds()))
return t.result, nil
}
@@ -368,7 +380,7 @@ func (node *Proxy) GetRestoreSnapshotState(ctx context.Context, req *milvuspb.Ge
tr := timerecord.NewTimeRecorder(method)
log.Info(ctx, rpcReceived(method))
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, "", "").Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, metrics.CauseNA, "", "").Inc()
t := &getRestoreSnapshotStateTask{
req: req,
ctx: ctx,
@@ -378,7 +390,7 @@ func (node *Proxy) GetRestoreSnapshotState(ctx context.Context, req *milvuspb.Ge
err := node.sched.ddQueue.Enqueue(t)
if err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, "", "").Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, metrics.CauseNA, "", "").Inc()
log.Warn(ctx, "GetRestoreSnapshotState failed to Enqueue",
mlog.Err(err))
return &milvuspb.GetRestoreSnapshotStateResponse{
@@ -387,7 +399,8 @@ func (node *Proxy) GetRestoreSnapshotState(ctx context.Context, req *milvuspb.Ge
}
if err := t.WaitToFinish(); err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), "", "").Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, "", "").Inc()
log.Warn(ctx, "GetRestoreSnapshotState failed to WaitToFinish",
mlog.Err(err))
return &milvuspb.GetRestoreSnapshotStateResponse{
@@ -395,7 +408,7 @@ func (node *Proxy) GetRestoreSnapshotState(ctx context.Context, req *milvuspb.Ge
}, nil
}
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, "", "").Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, metrics.CauseNA, "", "").Inc()
metrics.ProxyReqLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method).Observe(float64(tr.ElapseSpan().Milliseconds()))
return t.result, nil
}
@@ -412,7 +425,7 @@ func (node *Proxy) ListRestoreSnapshotJobs(ctx context.Context, req *milvuspb.Li
tr := timerecord.NewTimeRecorder(method)
log.Info(ctx, rpcReceived(method))
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
t := &listRestoreSnapshotJobsTask{
req: req,
ctx: ctx,
@@ -422,7 +435,7 @@ func (node *Proxy) ListRestoreSnapshotJobs(ctx context.Context, req *milvuspb.Li
err := node.sched.ddQueue.Enqueue(t)
if err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "ListRestoreSnapshotJobs failed to Enqueue",
mlog.Err(err))
return &milvuspb.ListRestoreSnapshotJobsResponse{
@@ -431,7 +444,8 @@ func (node *Proxy) ListRestoreSnapshotJobs(ctx context.Context, req *milvuspb.Li
}
if err := t.WaitToFinish(); err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), req.GetDbName(), req.GetCollectionName()).Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "ListRestoreSnapshotJobs failed to WaitToFinish",
mlog.Err(err))
return &milvuspb.ListRestoreSnapshotJobsResponse{
@@ -439,7 +453,7 @@ func (node *Proxy) ListRestoreSnapshotJobs(ctx context.Context, req *milvuspb.Li
}, nil
}
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyReqLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method).Observe(float64(tr.ElapseSpan().Milliseconds()))
return t.result, nil
}
@@ -458,7 +472,7 @@ func (node *Proxy) PinSnapshotData(ctx context.Context, req *milvuspb.PinSnapsho
tr := timerecord.NewTimeRecorder(method)
log.Info(ctx, rpcReceived(method))
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
t := &pinSnapshotDataTask{
req: req,
ctx: ctx,
@@ -467,7 +481,7 @@ func (node *Proxy) PinSnapshotData(ctx context.Context, req *milvuspb.PinSnapsho
}
if err := node.sched.ddQueue.Enqueue(t); err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "PinSnapshotData failed to Enqueue",
mlog.Err(err))
return &milvuspb.PinSnapshotDataResponse{
@@ -476,7 +490,8 @@ func (node *Proxy) PinSnapshotData(ctx context.Context, req *milvuspb.PinSnapsho
}
if err := t.WaitToFinish(); err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), req.GetDbName(), req.GetCollectionName()).Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, req.GetDbName(), req.GetCollectionName()).Inc()
log.Warn(ctx, "PinSnapshotData failed to WaitToFinish",
mlog.Err(err))
return &milvuspb.PinSnapshotDataResponse{
@@ -484,7 +499,7 @@ func (node *Proxy) PinSnapshotData(ctx context.Context, req *milvuspb.PinSnapsho
}, nil
}
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, metrics.CauseNA, req.GetDbName(), req.GetCollectionName()).Inc()
metrics.ProxyReqLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method).Observe(float64(tr.ElapseSpan().Milliseconds()))
return t.result, nil
}
@@ -501,7 +516,7 @@ func (node *Proxy) UnpinSnapshotData(ctx context.Context, req *milvuspb.UnpinSna
tr := timerecord.NewTimeRecorder(method)
log.Info(ctx, rpcReceived(method))
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, "", "").Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, metrics.CauseNA, "", "").Inc()
t := &unpinSnapshotDataTask{
req: req,
ctx: ctx,
@@ -510,20 +525,21 @@ func (node *Proxy) UnpinSnapshotData(ctx context.Context, req *milvuspb.UnpinSna
}
if err := node.sched.ddQueue.Enqueue(t); err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, "", "").Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.AbandonLabel, metrics.CauseNA, "", "").Inc()
log.Warn(ctx, "UnpinSnapshotData failed to Enqueue",
mlog.Err(err))
return merr.Status(err), nil
}
if err := t.WaitToFinish(); err != nil {
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), "", "").Inc()
failStatus, failCause := failMetricLabel(err)
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failStatus, failCause, "", "").Inc()
log.Warn(ctx, "UnpinSnapshotData failed to WaitToFinish",
mlog.Err(err))
return merr.Status(err), nil
}
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, "", "").Inc()
metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.SuccessLabel, metrics.CauseNA, "", "").Inc()
metrics.ProxyReqLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method).Observe(float64(tr.ElapseSpan().Milliseconds()))
return t.result, nil
}
+10 -9
View File
@@ -3509,17 +3509,18 @@ func getBM25FunctionOfAnnsField(fieldID int64, functions []*schemapb.FunctionSch
}
// failMetricLabel classifies a request failure for the ProxyFunctionCall
// metric, matching the fail_input/fail_system split that
// requestutil.ParseMetricLabel emits at the gRPC interceptor; the legacy
// bare "fail" value must not reappear in this metric's value domain.
func failMetricLabel(err error) string {
// Client cancellation is neither party's failure; keep it out of the
// fail_system bucket (parity with ParseMetricLabel).
// metric, returning the same (status, cause) pair that
// requestutil.ParseMetricLabel emits at the gRPC interceptor. The status stays
// the coarse "fail" so that a query written against it counts every hard
// failure regardless of who caused it.
func failMetricLabel(err error) (status string, cause string) {
// Client cancellation is neither party's failure; cause is what lets a
// consumer exclude it (parity with ParseMetricLabel).
if errors.Is(err, context.Canceled) {
return metrics.CancelLabel
return metrics.FailLabel, metrics.CauseCancel
}
if merr.GetErrorType(err) == merr.InputError {
return metrics.FailInputLabel
return metrics.FailLabel, metrics.CauseUser
}
return metrics.FailSystemLabel
return metrics.FailLabel, metrics.CauseSystem
}
+16 -7
View File
@@ -6869,14 +6869,23 @@ func TestInjectVirtualPKForExternalCollection(t *testing.T) {
}
func TestFailMetricLabel(t *testing.T) {
assertLabels := func(wantCause string, err error) {
t.Helper()
status, cause := failMetricLabel(err)
// Every failure keeps the coarse status a pre-2.6.19 dashboard queries;
// only the cause tells the parties apart.
assert.Equal(t, metrics.FailLabel, status)
assert.Equal(t, wantCause, cause)
}
// untyped / nil errors must take the conservative system bucket
assert.Equal(t, metrics.FailSystemLabel, failMetricLabel(nil))
assert.Equal(t, metrics.FailSystemLabel, failMetricLabel(errors.New("plain error")))
assert.Equal(t, metrics.FailSystemLabel, failMetricLabel(merr.WrapErrServiceInternalMsg("internal failure")))
assertLabels(metrics.CauseSystem, nil)
assertLabels(metrics.CauseSystem, errors.New("plain error"))
assertLabels(metrics.CauseSystem, merr.WrapErrServiceInternalMsg("internal failure"))
// input-classified merr errors go to the user bucket, also through wrapping
assert.Equal(t, metrics.FailInputLabel, failMetricLabel(merr.WrapErrParameterInvalidMsg("bad parameter")))
assert.Equal(t, metrics.FailInputLabel, failMetricLabel(merr.Wrap(merr.WrapErrParameterInvalidMsg("bad parameter"), "context")))
assertLabels(metrics.CauseUser, merr.WrapErrParameterInvalidMsg("bad parameter"))
assertLabels(metrics.CauseUser, merr.Wrap(merr.WrapErrParameterInvalidMsg("bad parameter"), "context"))
// client cancellation is neither party's failure
assert.Equal(t, metrics.CancelLabel, failMetricLabel(context.Canceled))
assert.Equal(t, metrics.CancelLabel, failMetricLabel(errors.Wrap(context.Canceled, "rpc aborted")))
assertLabels(metrics.CauseCancel, context.Canceled)
assertLabels(metrics.CauseCancel, errors.Wrap(context.Canceled, "rpc aborted"))
}
+1 -1
View File
@@ -62,7 +62,7 @@ func NewInsertDataWithCap(schema *schemapb.CollectionSchema, cap int, withFuncti
// A nil schema here is an internal invariant violation (callers within
// Milvus always pass a schema), not a client-supplied missing parameter.
// ErrParameterMissing defaults to InputError, so mark this one site as a
// system error to keep it out of the client-fault (fail_input) bucket.
// system error to keep it out of the client-fault (cause="user") bucket.
return nil, merr.WrapErrAsSysError(merr.WrapErrParameterMissing("collection schema"))
}
+1 -1
View File
@@ -17,7 +17,7 @@
// The merge/slice helpers in this file consume RetrieveResults produced by
// segcore (and merged across query nodes), never raw user input, so every
// data-shape assertion below classifies as ServiceInternal: a violation means
// a segcore/Milvus bug, and must not be attributed to the user (fail_input)
// a segcore/Milvus bug, and must not be attributed to the user (cause="user")
// or suppress cross-replica failover the way an InputError would.
package queryutil
+14 -15
View File
@@ -34,21 +34,19 @@ const (
RetryLabel = "retry"
RejectedLabel = "rejected"
// Fine-grained hard-failure labels (composite status values, not a new
// dimension label, to keep Prometheus cardinality additive rather than
// multiplicative). They split the coarse "fail"/"rejected" into the
// responsible party so monitoring/alerting can tell a user-input error
// (the caller must fix the request) apart from an internal system error
// (operators must intervene). MIGRATION: old queries on status="fail" must
// move to status=~"fail_.*" (fail_input/fail_system), and old queries on
// status="rejected" must move to status=~"rejected_.*" (rejected_user for
// caller-side auth/privilege/bad-arg rejections, rejected_system otherwise).
// Dashboards still matching the bare "fail"/"rejected" values return nothing
// after this split.
FailInputLabel = "fail_input"
FailSystemLabel = "fail_system"
RejectedSystemLabel = "rejected_system"
RejectedUserLabel = "rejected_user"
// Values of the "cause" label, a dimension orthogonal to "status" that names
// the responsible party for a failed request, so monitoring can tell a
// user-input error (the caller must fix the request) apart from an internal
// system error (operators must intervene). It is a separate label rather
// than extra "status" values on purpose: the coarse status stays a valid
// query on its own ("fail" is still every hard failure, aggregated over
// cause by Prometheus), so dashboards written against it keep working.
// Cause is functionally dependent on the outcome, so the realized
// (status, cause) pairs are additive, not the product of both domains.
CauseUser = "user" // the request itself is at fault: bad arguments, missing auth, no such collection
CauseSystem = "system" // Milvus is at fault: component failure, IO error, internal bug
CauseCancel = "cancel" // neither party: the client gave up before the request completed
CauseNA = "na" // no cause applies: the request did not hard-fail
HybridSearchLabel = "hybrid_search"
@@ -123,6 +121,7 @@ const (
nodeIDLabelName = "node_id"
nodeHostLabelName = "node_host"
statusLabelName = "status"
causeLabelName = "cause"
indexTaskStatusLabelName = "index_task_status"
msgTypeLabelName = "msg_type"
collectionIDLabelName = "collection_id"
+2 -2
View File
@@ -233,7 +233,7 @@ var (
Subsystem: typeutil.ProxyRole,
Name: "req_count",
Help: "count of operation executed",
}, []string{nodeIDLabelName, functionLabelName, statusLabelName, databaseLabelName, collectionName})
}, []string{nodeIDLabelName, functionLabelName, statusLabelName, causeLabelName, databaseLabelName, collectionName})
// ProxyReqLatency records the latency for each grpc request.
ProxyGRPCLatency = prometheus.NewHistogramVec(
@@ -243,7 +243,7 @@ var (
Name: "grpc_latency",
Help: "latency of each grpc request",
Buckets: buckets, // unit: ms
}, []string{nodeIDLabelName, functionLabelName, statusLabelName})
}, []string{nodeIDLabelName, functionLabelName, statusLabelName, causeLabelName})
// ProxyReqLatency records the latency that for all requests, like "CreateCollection".
ProxyReqLatency = prometheus.NewHistogramVec(
+1 -1
View File
@@ -100,7 +100,7 @@ func TestErrorTypeMarker(t *testing.T) {
}
// TestSentinelErrorTypeClassification guards the input/system split that the
// proxy fail_input/fail_system alerting relies on. A sentinel silently losing
// proxy cause="user"/cause="system" alerting relies on. A sentinel silently losing
// (or gaining) WithErrorType(InputError) flips which party an alert blames.
func TestSentinelErrorTypeClassification(t *testing.T) {
// The request's own fault -> InputError (and therefore non-retriable).
+1 -1
View File
@@ -262,7 +262,7 @@ var (
// import job orchestration (job not found / no vchannels / restore /
// job-count backpressure) and object-storage IO failures in the readers.
// These are the operator's concern, not the caller's, so they stay
// SystemError and must not be bucketed as fail_input.
// SystemError and must not be bucketed as a user-caused failure.
ErrImportSysFailed = newMilvusError("importing data failed on server side", 2101, false)
// Search/Query related
+27 -28
View File
@@ -23,7 +23,7 @@ import (
"github.com/cockroachdb/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
grpcstatus "google.golang.org/grpc/status"
"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
"github.com/milvus-io/milvus-proto/go-api/v3/schemapb"
@@ -230,19 +230,18 @@ var retryableCode typeutil.Set[int32] = typeutil.NewSet(
// )
// }
// ParseMetricLabel determines the final Prometheus status label based on the
// response and error. It implements the composite-label scheme: the coarse
// "fail"/"rejected" values are split into fine-grained labels (fail_input /
// fail_system / rejected_system) so monitoring can tell the responsible party
// apart. The split is encoded into the existing status label's value domain
// (additive cardinality) rather than a new dimension label (which would be
// multiplicative). Retryability takes priority over classification.
func ParseMetricLabel(resp any, err error) string {
// ParseMetricLabel determines the Prometheus status and cause labels of a
// finished request. The status domain is the coarse outcome and is deliberately
// the same one pre-2.6.19 emitted, so a query written against it keeps its
// meaning; cause is an orthogonal dimension naming the responsible party, which
// Prometheus aggregates away for consumers that only ask for the status.
// Retryability takes priority over classification.
func ParseMetricLabel(resp any, err error) (status string, cause string) {
// A response carrying a non-OK status means the request was PROCESSED and
// failed (fail_*), and takes priority over a non-nil err: the REST v2
// wrappers reconstruct err = merr.Error(status) from that same response,
// which previously routed every processed REST failure into the rejected_*
// buckets and left the fail_* series blind to the entire REST surface.
// failed, and takes priority over a non-nil err: the REST v2 wrappers
// reconstruct err = merr.Error(status) from that same response, which
// otherwise routes every processed REST failure into the rejected buckets
// and leaves the fail series blind to the entire REST surface.
var st *commonpb.Status
switch resp := resp.(type) {
case interface{ GetStatus() *commonpb.Status }:
@@ -251,48 +250,48 @@ func ParseMetricLabel(resp any, err error) string {
st = resp
}
if st != nil && !merr.Ok(st) {
// Client cancellation is neither party's failure.
// Client cancellation is neither party's failure, but it stays a "fail"
// like it was before the cause dimension existed; cause is what lets a
// consumer exclude it.
if st.GetCode() == merr.CanceledCode {
return metrics.CancelLabel
return metrics.FailLabel, metrics.CauseCancel
}
// Retryability takes priority over input/system classification.
// Retryability takes priority over classification.
if retryableCode.Contain(st.GetCode()) {
return metrics.RetryLabel
return metrics.RetryLabel, metrics.CauseNA
}
// Hard failure: classify by responsible party. merr.Status already
// stamps the InputError flag into ExtraInfo, so read it directly instead
// of reconstructing the whole milvusError (this is the proxy hot path).
if st.GetExtraInfo()[merr.InputErrorFlagKey] == "true" {
return metrics.FailInputLabel
return metrics.FailLabel, metrics.CauseUser
}
return metrics.FailSystemLabel
return metrics.FailLabel, metrics.CauseSystem
}
// No usable response status: err is the interceptor-level outcome (context
// cancellation, flow control, transport issues, auth/privilege rejection)
// — the request was rejected around processing. Classify merr first: a
// merr error has no GRPCStatus(), so status.Code(err) degrades to
// merr error has no GRPCStatus(), so grpcstatus.Code(err) degrades to
// codes.Unknown and would misbucket user input errors as system
// rejections. The auth/privilege interceptors deliberately return raw gRPC
// codes (not merr, to keep SDK retry behavior correct); those are the
// caller's fault, so bucket them as a user-side rejection. Everything else
// is a system-side rejection.
if err != nil {
// Client cancellation is neither party's failure; don't count it as a
// system rejection.
if errors.Is(err, context.Canceled) {
return metrics.CancelLabel
return metrics.RejectedLabel, metrics.CauseCancel
}
if merr.GetErrorType(err) == merr.InputError {
return metrics.RejectedUserLabel
return metrics.RejectedLabel, metrics.CauseUser
}
switch status.Code(err) {
switch grpcstatus.Code(err) {
case codes.Unauthenticated, codes.PermissionDenied, codes.InvalidArgument:
return metrics.RejectedUserLabel
return metrics.RejectedLabel, metrics.CauseUser
default:
return metrics.RejectedSystemLabel
return metrics.RejectedLabel, metrics.CauseSystem
}
}
return metrics.SuccessLabel
return metrics.SuccessLabel, metrics.CauseNA
}
+84 -32
View File
@@ -30,6 +30,7 @@ import (
"github.com/milvus-io/milvus-proto/go-api/v3/milvuspb"
"github.com/milvus-io/milvus/pkg/v3/metrics"
"github.com/milvus-io/milvus/pkg/v3/util/merr"
"github.com/milvus-io/milvus/pkg/v3/util/typeutil"
)
func TestGetCollectionNameFromRequest(t *testing.T) {
@@ -516,55 +517,106 @@ func TestGetStatusFromResponse(t *testing.T) {
}
func TestParseMetricLabel(t *testing.T) {
// transport / interceptor error -> rejected_system (highest priority)
assert.Equal(t, metrics.RejectedSystemLabel,
ParseMetricLabel(&commonpb.Status{}, errors.New("transport failed")))
assertLabels := func(wantStatus, wantCause string, resp any, err error) {
t.Helper()
status, cause := ParseMetricLabel(resp, err)
assert.Equal(t, wantStatus, status)
assert.Equal(t, wantCause, cause)
}
// transport / interceptor error -> rejected by the system
assertLabels(metrics.RejectedLabel, metrics.CauseSystem,
&commonpb.Status{}, errors.New("transport failed"))
// success
assert.Equal(t, metrics.SuccessLabel,
ParseMetricLabel(&commonpb.Status{}, nil))
assertLabels(metrics.SuccessLabel, metrics.CauseNA, &commonpb.Status{}, nil)
// retryable hard failure -> retry (retryability beats classification)
assert.Equal(t, metrics.RetryLabel,
ParseMetricLabel(merr.Status(merr.ErrServiceRateLimit), nil))
assertLabels(metrics.RetryLabel, metrics.CauseNA,
merr.Status(merr.ErrServiceRateLimit), nil)
// hard failure, system error -> fail_system
assert.Equal(t, metrics.FailSystemLabel,
ParseMetricLabel(merr.Status(merr.ErrSegcore), nil))
// hard failure caused by Milvus itself
assertLabels(metrics.FailLabel, metrics.CauseSystem,
merr.Status(merr.ErrSegcore), nil)
// hard failure, input error -> fail_input
// hard failure caused by the request
inputErr := merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("bad param"))
assert.Equal(t, metrics.FailInputLabel,
ParseMetricLabel(merr.Status(inputErr), nil))
assertLabels(metrics.FailLabel, metrics.CauseUser, merr.Status(inputErr), nil)
// response that itself implements GetStatus
assert.Equal(t, metrics.FailInputLabel,
ParseMetricLabel(&milvuspb.BoolResponse{Status: merr.Status(inputErr)}, nil))
assertLabels(metrics.FailLabel, metrics.CauseUser,
&milvuspb.BoolResponse{Status: merr.Status(inputErr)}, nil)
// merr input error through the err path (REST v2 handlers abort with merr
// directly; no GRPCStatus, so the gRPC switch alone would misbucket it as
// rejected_system) -> rejected_user
assert.Equal(t, metrics.RejectedUserLabel,
ParseMetricLabel(&commonpb.Status{}, merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("bad param"))))
// directly; no GRPCStatus, so the gRPC switch alone would misbucket it as a
// system rejection)
assertLabels(metrics.RejectedLabel, metrics.CauseUser, &commonpb.Status{},
merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("bad param")))
// merr system error through the err path -> rejected_system
assert.Equal(t, metrics.RejectedSystemLabel,
ParseMetricLabel(&commonpb.Status{}, merr.WrapErrServiceInternalMsg("boom")))
// merr system error through the err path
assertLabels(metrics.RejectedLabel, metrics.CauseSystem,
&commonpb.Status{}, merr.WrapErrServiceInternalMsg("boom"))
// client cancellation through the err path -> cancel, not a system rejection
assert.Equal(t, metrics.CancelLabel,
ParseMetricLabel(&commonpb.Status{}, context.Canceled))
assert.Equal(t, metrics.CancelLabel,
ParseMetricLabel(&commonpb.Status{}, errors.Wrap(context.Canceled, "rpc aborted")))
// client cancellation is neither party's fault, but it stays a rejection so
// that a pre-existing status="rejected" query keeps counting it
assertLabels(metrics.RejectedLabel, metrics.CauseCancel, &commonpb.Status{}, context.Canceled)
assertLabels(metrics.RejectedLabel, metrics.CauseCancel,
&commonpb.Status{}, errors.Wrap(context.Canceled, "rpc aborted"))
// REST v2 wrapper shape: the response carries the failed status AND the
// wrapper reconstructs err from it. Processed failures must classify by
// the status (fail_*), not be misrouted into the rejected_* buckets.
// the status ("fail"), not be misrouted into the "rejected" bucket.
restSys := merr.Status(merr.ErrSegcore)
assert.Equal(t, metrics.FailSystemLabel, ParseMetricLabel(restSys, merr.Error(restSys)))
assertLabels(metrics.FailLabel, metrics.CauseSystem, restSys, merr.Error(restSys))
restInput := merr.Status(merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("bad param")))
assert.Equal(t, metrics.FailInputLabel, ParseMetricLabel(restInput, merr.Error(restInput)))
// Cancellation surfaced through the response status stays out of fail_system.
assertLabels(metrics.FailLabel, metrics.CauseUser, restInput, merr.Error(restInput))
// Cancellation surfaced through the response status is still a "fail", as it
// was before the cause dimension existed; cause is what excludes it.
restCancel := merr.Status(context.Canceled)
assert.Equal(t, metrics.CancelLabel, ParseMetricLabel(restCancel, merr.Error(restCancel)))
assertLabels(metrics.FailLabel, metrics.CauseCancel, restCancel, merr.Error(restCancel))
}
// The status label is a public monitoring contract: dashboards and alert rules
// written against status="fail" / status="rejected" predate the cause dimension
// and must keep working. Splitting the responsible party into new status values
// (fail_input, rejected_system, ...) silently zeroes those queries, so pin the
// domain here: anything finer belongs on the orthogonal cause label.
func TestParseMetricLabelStatusDomainIsStable(t *testing.T) {
legacyStatus := typeutil.NewSet(
metrics.SuccessLabel,
metrics.FailLabel,
metrics.RejectedLabel,
metrics.RetryLabel,
)
knownCause := typeutil.NewSet(
metrics.CauseUser,
metrics.CauseSystem,
metrics.CauseCancel,
metrics.CauseNA,
)
inputErr := merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("bad param"))
cases := []struct {
name string
resp any
err error
}{
{"success", &commonpb.Status{}, nil},
{"transport error", &commonpb.Status{}, errors.New("transport failed")},
{"rate limited", merr.Status(merr.ErrServiceRateLimit), nil},
{"system failure", merr.Status(merr.ErrSegcore), nil},
{"input failure", merr.Status(inputErr), nil},
{"input rejection", &commonpb.Status{}, inputErr},
{"canceled at interceptor", &commonpb.Status{}, context.Canceled},
{"canceled in response", merr.Status(context.Canceled), nil},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
status, cause := ParseMetricLabel(tc.resp, tc.err)
assert.Truef(t, legacyStatus.Contain(status),
"status %q is outside the pre-2.6.19 domain %v; put the distinction on the cause label instead",
status, legacyStatus.Collect())
assert.Truef(t, knownCause.Contain(cause), "unknown cause %q", cause)
})
}
}