Skip to content

Deploy

Observability

Metrics listeners, role labels, supplied alerting rules, health probes, and scrape configuration.


Every ysearch process serves its own Prometheus exposition. Metric families use bounded cardinality: labels are closed enumerations, and index names, query text, document identities, object keys, and target addresses never become labels. A registry that would violate that is refused at construction — the process uses a pedantic registry and validates every gathered family before it serves.

That is a design constraint with an operational consequence. You will not find a per-index query rate in Prometheus. A host serving several indexes reports one series per role and sums its gauges; per-index detail lives in the admin console and in durable receipts.

The metrics listener

Setting observability.metrics_listen
Flag --metrics-listen
Environment YSEARCH_OBSERVABILITY_METRICS_LISTEN
Default :9550 — on, all interfaces
Path /metrics
Disable Set it to the empty string

It is served by ysearch serve, ysearch node, ysearchd, and ysearch-local. Unlike debug.pprof_listen, it is on by default and binds every interface. Profiles expose heap contents and goroutine stacks; an exposition of counters and histograms carries no document text, no query, and no key material. A scrape target that has to be switched on is one nobody switches on.

A bind failure is reported on stderr and survived. Telemetry is not a precondition for serving queries, so an occupied scrape port must not turn an observability problem into an availability one.

What the role label means

Every series carries role as a constant label, and its value is the role mix the process serves, not a single role. node --roles builder,publisher reports role="builder-publisher".

This is why the shipped rules match {role=~".*publisher.*"} rather than role="publisher". Under the Kubernetes operator each Deployment runs exactly one role, so the values are the plain role names there.

instance and pod are not emitted by the process and must come from the scrape target. The leader-lost alert aggregates with max without(instance, pod) (...), so a scrape config that does not supply them will not evaluate correctly.

Scrape configuration

Under the operator, set spec.monitoring on the YSearchCluster and let it render the ServiceMonitor and the NetworkPolicy peer together:

yaml
monitoring:
  serviceMonitor: Auto
  interval: 30s
  scrapeNamespaceSelector:
    matchLabels: {kubernetes.io/metadata.name: monitoring}
  labels: {release: kube-prometheus-stack}

scrapeNamespaceSelector is the same value in both places: it names the namespaces allowed through the rendered NetworkPolicies to the metrics port. If your Prometheus lives somewhere other than a namespace called monitoring, set it, or the scrape is denied at the network before it reaches a pod.

labels is attached to the rendered ServiceMonitor, for a Prometheus whose serviceMonitorSelector requires a release label.

Outside Kubernetes, scrape port 9550 on every ysearch process and make sure your job supplies instance. A static configuration is enough:

yaml
scrape_configs:
  - job_name: ysearch
    static_configs:
      - targets:
          - coordinator-1:9550
          - worker-1:9550
          - worker-2:9550
          - publisher-1:9550

The admin console's own metrics

The console's /metrics covers the console process only — six label-free gauges about its own bridge:

Metric Meaning
ysearch_admin_build_info Always 1; process information
ysearch_admin_configured_targets Allowlisted upstream targets
ysearch_admin_fleet_collectors Shared upstream fleet streams currently active
ysearch_admin_fleet_subscribers Browser fleet-stream subscribers currently active
ysearch_admin_search_in_flight Query Lab searches occupying a bounded slot
ysearch_admin_search_capacity Maximum concurrent Query Lab searches

The engine's families — ysearch_query_*, ysearch_ingest_*, ysearch_publisher_*, ysearch_cache_*, and the rest — come from the data-plane processes. Scraping the console does not scrape the engine.

The shipped alerting rules

deploy/prometheus/ysearch.rules.yml is a single group, ysearch-m4, evaluated every 30 seconds. It is the M4 ingest and maintenance surface, not a complete alert set — nothing there covers query latency or cache behavior yet.

Alert Severity Fires when
YsearchPublicationLagHigh page p99 publication lag over 60s for 5m
YsearchRouterSaturated warning Router admission queue over 85% for 5m
YsearchNoEligibleBuilder page A router has zero eligible builders for 2m
YsearchPublisherLeaseLost page No publisher holds the catalog lease for 2m
YsearchPublisherLeaseFlapping warning More than 4 leadership changes in 15m
YsearchCompactionRefusedOrFailed warning Any refused or failed compaction in 15m
YsearchGCSweepFailed page Any failed destructive GC sweep in 15m
YsearchLineageCapacityHigh warning Catalog lineage over 80% of a safety bound for 10m
YsearchEmbeddingProviderUnavailable page Over 20% of embedding batches failing for 5m

The two that matter most on a first deployment:

YsearchPublisherLeaseLost is the one that means documents stop becoming visible. Exactly one publisher holds a lease at a time; when none does, sealed segments accumulate without being announced.

yaml
expr: |
  max without(instance, pod) (ysearch_publisher_leader{role=~".*publisher.*"}) == 0
  or
  absent(ysearch_publisher_leader{role=~".*publisher.*"})
for: 2m

The absent() arm is what catches a publisher that vanished entirely rather than one that lost its lease. Both are the same outage.

YsearchPublicationLagHigh is the freshness alert. The histogram measures commit-marker durability to catalog-generation publication:

yaml
expr: |
  histogram_quantile(
    0.99,
    sum by (le, role) (
      rate(ysearch_publisher_publication_lag_seconds_bucket{role=~".*publisher.*"}[10m])
    )
  ) > 60
for: 5m

Each rule carries a runbook annotation pointing into the repository's milestone evidence, for example docs/milestones/m4-evidence.md#publisher-leadership.

Installing them

Outside the Prometheus Operator, load the file directly:

yaml
rule_files:
  - /etc/prometheus/rules/ysearch.rules.yml

The repository keeps deploy/prometheus/rules_test.go alongside it, so the expressions are unit-tested rather than only linted.

Under the Prometheus Operator, wrap the same groups in a PrometheusRule. deploy/operator/monitoring.yaml shows the shape and adds one rule for the operator process itself:

yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: ysearch-operator
  namespace: ysearch-system
spec:
  groups:
  - name: ysearch-operator
    rules:
    - alert: YSearchOperatorReconcileErrors
      expr: increase(controller_runtime_reconcile_errors_total{controller=~"ysearch.*"}[10m]) > 0
      for: 10m
      labels: {severity: warning}
      annotations:
        summary: YSearch operator reconciliation is failing

Probes

Data-plane and control-plane processes use different health mechanisms.

Process Mechanism Endpoint
ysearch serve, ysearch node gRPC grpc.health.v1.Health on the data port, 9500
ysearch-admin HTTP /livez, /readyz, /metrics
ysearch-operator HTTP /healthz, /readyz on port 8081

A data-plane process has no HTTP health endpoint. Use a gRPC probe:

yaml
startupProbe:
  grpc: {port: 9500}
  failureThreshold: 60
  periodSeconds: 2
  timeoutSeconds: 2
readinessProbe:
  grpc: {port: 9500}
  failureThreshold: 3
  periodSeconds: 5
  timeoutSeconds: 2
livenessProbe:
  grpc: {port: 9500}
  failureThreshold: 3
  periodSeconds: 10
  timeoutSeconds: 2

These are the operator's probe defaults. Catalog-bound roles resolve their generation before opening the listener, so startup probes must allow time for catalog reads and cache validation.

The admin console's /readyz becomes unavailable before graceful drain, which is what removes it from Service endpoints ahead of shutdown.

Admin-console rules and dashboard

A second, smaller set of artifacts covers the console process rather than the engine: observability/alerts/admin.yaml, observability/recording-rules/admin.yaml, and a starter Grafana dashboard at observability/dashboards/admin.json.

They assume a scrape job named ysearch-admin and define one recorded ratio plus three alerts:

Alert Severity Fires when
YSearchAdminUnavailable page No console replica has been scrapeable for 5m
YSearchAdminReplicaDegraded warning Fewer than two replicas scrapeable for 15m
YSearchAdminQueryLabSaturated warning Every bounded Query Lab slot occupied for 10m

There is no equivalent shipped dashboard for the data plane yet.

What to watch first

If you are picking a handful of series to graph on day one:

Question Series
Are queries being served? ysearch_query_completed_total{outcome}
How slow? ysearch_query_duration_seconds by lane and exactness
Are documents becoming visible? ysearch_publisher_publication_lag_seconds
Is ingest being admitted? ysearch_ingest_batches_total{ack_status}
Is the cache working? ysearch_cache_operations_total{cache_tier,outcome}
Which storage mode is winning? ysearch_storage_decisions_total{requested_mode,policy_reason}
How much object traffic? ysearch_storage_process_object_read_bytes

Every one of these, with its labels and meaning, is in the metrics reference.

Next