feat(cluster): add metrics for endpoint snapshot publication#962
feat(cluster): add metrics for endpoint snapshot publication#962mochengqian wants to merge 3 commits into
Conversation
Add observability for endpoint snapshot publication so operators can diagnose registry churn, unexpected cluster size, or excessive health update publication. Emit three OpenTelemetry instruments, labeled only by cluster name, on each successful snapshot CompareAndSwap: - pixiu_cluster_snapshot_publish_total (counter) - pixiu_cluster_snapshot_endpoint_count (gauge) - pixiu_cluster_snapshot_healthy_endpoint_count (gauge) Instruments bind lazily to the global MeterProvider, so they stay no-op when metrics are disabled and do not alter snapshot publication behavior. Closes apache#944
Move registerOtelMetricMeter before cluster construction so static clusters' initial snapshot publish lands on the real meter provider rather than the no-op default. Without this fix, static clusters with no health transitions show empty counter/gauges in steady state — defeating the PR's goal of diagnosing unexpected cluster sizes. Changes: - Move registerOtelMetricMeter call from (*Server).Start() to Start(bs), positioned before server.initialize(bs) which constructs clusters - Add TestStaticClusterSnapshotMetricsRecordedAtStartup in pkg/server to guard against ordering regression (models production: provider → clusters) - Expand installSnapshotMetricsReader doc to note all cluster construction triggers global instrument init (t.Parallel constraint) Issue: apache#944
There was a problem hiding this comment.
Pull request overview
Adds OpenTelemetry metrics to improve observability of cluster endpoint snapshot publication, including a startup-ordering fix so initial static-cluster snapshot emissions are not dropped before a real meter provider is installed.
Changes:
- Introduces three
pixiu_cluster_snapshot_*instruments (publish counter + endpoint/healthy endpoint gauges) and records them on successful snapshotCompareAndSwap. - Adds focused metric tests in
pkg/clusterplus apkg/serverregression test to ensure startup ordering records static cluster initial publish metrics. - Moves
registerOtelMetricMeterearlier in startup (Start(bs)) so cluster construction happens after the meter provider is installed.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pkg/server/pixiu_start.go | Registers the OTel meter provider before cluster initialization to avoid losing initial snapshot metric emissions. |
| pkg/server/cluster_manager_test.go | Adds a regression test ensuring static cluster initial snapshot publish metrics are recorded at startup. |
| pkg/cluster/metrics.go | Adds lazy-initialized OTel instruments and a recordSnapshotPublish helper invoked on successful snapshot CAS. |
| pkg/cluster/metrics_test.go | Adds ManualReader-based tests validating counter/gauges and confirming no-op health updates don’t increment publish count. |
| pkg/cluster/cluster.go | Hooks recordSnapshotPublish into the successful CAS branches of the three snapshot publication paths. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #962 +/- ##
===========================================
+ Coverage 26.14% 26.21% +0.06%
===========================================
Files 275 276 +1
Lines 21981 22007 +26
===========================================
+ Hits 5748 5770 +22
Misses 15623 15623
- Partials 610 614 +4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Update the helper's documentation to accurately reflect its behavior: it installs a ManualReader provider and restores the previous provider on cleanup, but does not reset pkg/cluster's instrument variables (which are in a different package and not accessible from pkg/server tests). The comment previously claimed it "resets the global instrument state" and mentioned "pkg/cluster instrument variables," but the implementation only swaps otel.MeterProvider without touching snapshotPublishTotal, snapshotEndpointCount, or snapshotHealthyCount. Addresses review feedback on PR apache#962.
|
|
|
||
| if snapshotPublishTotal != nil { | ||
| snapshotPublishTotal.Add(ctx, 1, attrs) | ||
| } |
There was a problem hiding this comment.
[P1] initSnapshotMetrics 第一次调用会在 healthMu 持有期间执行:UpdateEndpointHealth/UpdateEndpointAddressHealth 都在 healthMu.Lock() 之后直接走到 recordSnapshotPublish → initSnapshotMetrics → meter.Int64Counter/Int64Gauge × 3,注册失败时还要走 logger.Errorf,整个路径的分配和潜在 IO 全部发生在锁内。这与本函数注释“health-update callers invoke this while holding the cluster s healthMu, so the body must stay allocation-light and must not block”直接冲突,会让首个健康事件、或者每次 registerOtelMeterProvider 之后第一次 publish 抢占 healthMu,阻塞所有并发健康事件。建议在 cluster 包初始化(或 registerOtelMetricMeter 完成后)显式调用一次 initSnapshotMetrics 把仪表预绑定好,让 recordSnapshotPublish 只剩 Add/Record。



What
Adds observability for endpoint snapshot publication (follow-up to #932). Operators currently have no direct signal for snapshot publish frequency or snapshot endpoint size, which makes registry churn, unexpected cluster size, or excessive health-update publication hard to diagnose.
Metrics
Three OpenTelemetry instruments, emitted on each successful snapshot
CompareAndSwap, labeled only bycluster:pixiu_cluster_snapshot_publish_totalpixiu_cluster_snapshot_endpoint_countpixiu_cluster_snapshot_healthy_endpoint_countInstruments follow the existing
pixiu_/snake_case convention and bind lazily tootel.GetMeterProvider()(same pattern aspkg/filter/metricand the LLM tokenizer), so they wire into the existing Prometheus exporter automatically.Where it emits
recordSnapshotPublishis called inside the successful-CAS branch of the three publish paths inpkg/cluster/cluster.go:RefreshEndpointsFrom— membership/address changesUpdateEndpointHealth— endpoint-keyed health flipsUpdateEndpointAddressHealth— address-keyed health flipsThe counter increments only on a real swap: no-op health updates return before CAS and are not counted, which is what makes "excessive health update publication" diagnosable.
Design notes / trade-offs
ObservableGauge. A callback gauge would require a global live-cluster registry to enumerate clusters at scrape time; this PR avoids introducing new global mutable state and emits at the publish site instead.WithAttributes). Publish is a low-frequency path (not the pick hot path), so this is not cached; the read/pick path is untouched.Out of scope (per #944)
No new metrics backend, no endpoint-level (high-cardinality) labels, and no change to snapshot publication behavior.
Tests
pkg/cluster/metrics_test.gouses aManualReaderto assert:pkg/server/cluster_manager_test.goincludesTestStaticClusterSnapshotMetricsRecordedAtStartupto guard against startup ordering regression (see fix below).go test ./pkg/cluster/... ./pkg/server/... go vet ./pkg/cluster/All pass;
gofmtclean.🔧 Fix: Startup ordering for static clusters
Problem found during review: Static clusters' initial snapshot publish was landing on the no-op delegating provider, causing counter/gauges to remain empty in steady state — exactly the "unexpected cluster size" scenario this PR is meant to diagnose.
Root cause:
Start(bs)calledserver.initialize(bs)(which constructs clusters) beforeregisterOtelMetricMeter(). While otel-go's delegation back-fills instruments whenSetMeterProvideris called later, the data from those dropped emissions is lost forever.Fix applied (commit
7ac917f):registerOtelMetricMeter(bs.Metric)from(*Server).Start()toStart(bs), positioned beforeserver.initialize(bs)TestStaticClusterSnapshotMetricsRecordedAtStartupinpkg/serverto guard against ordering regressionVerification: All tests pass including the new ordering guard; local CI clean (gofmt, imports, golangci-lint, race detector).
Closes #944