Skip to content
Leader docs1.0

Backend connectivity observability and readiness runbook

Latest stable Based on Leader release 1.0.0

This runbook describes the additive observability contract released in 1.0.0. It supplements ownership and fencing rules; it does not replace them.

This document describes how to interpret backend connectivity diagnostics. It does not replace the atomic ownership decision made by runIfLeader, and it does not turn a best-effort health signal into a fencing or force-release operation.

Every connectivity result has one status and one bounded reason. The reason is an enum, not an exception message or a provider payload.

StatusReasonMeaning
UPCONNECTEDThe existing client confirmed reachability at probe time.
DOWNDISCONNECTEDThe existing client confirmed that the backend is unavailable.
UNKNOWNCLIENT_STATE_UNCONFIRMEDA bounded read-only check could not prove connectivity.
UNKNOWNPROVIDER_UNSUPPORTEDThe provider does not expose a supported active probe.
UNKNOWNPROVIDER_EXCEPTIONAn ordinary provider exception was normalized without retaining its details.
NOT_CHECKEDNOT_CHECKEDNo active probe was requested; this is not a health signal.

UP and DOWN describe the result of an active check, not lock ownership. UNKNOWN must remain distinguishable from both. NOT_CHECKED is the expected result of passive diagnostics and must not be treated as UP.

The public result is additive:

data class LeaderBackendConnectivity(
val status: LeaderBackendConnectivityStatus,
val checkedAt: Instant?,
val latencyMillis: Long?,
val reason: LeaderBackendConnectivityReason,
)

Existing Kotlin construction and JSON fields remain compatible; consumers that strictly deserialize JSON must allow the new reason field.

Source or adapterContract
Direct/core diagnosticsdiagnostics() is passive and returns NOT_CHECKED; an active checkConnectivity returns the provider result and bounded reason.
Ktor /management/leaderElection/diagnosticsReturns HTTP 200 with the diagnostics payload when the pipeline produced a result. The payload’s connectivity.status and connectivity.reason carry backend meaning.
Spring leaderBackendDiagnosticsStatic diagnostics remain opt-in and passive. Backend health adds bounded reason detail; UP/DOWN/UNKNOWN retain their existing health mapping.
Spring readinessThe readiness indicator evaluates local lock state and lease expiry. It is a separate signal and does not automatically merge backend DOWN or UNKNOWN.
Application pipelineA custom provider exception that escapes the route is owned by Ktor StatusPages or the application’s Spring/web pipeline. The library does not rewrite that HTTP status.

Ktor returning HTTP 200 means that a diagnostics result was serialized; it does not mean that the backend is healthy. A pipeline exception is different from a payload containing status = UNKNOWN.

An instrumented elector records one leader.backend.connectivity counter event for each active checkConnectivity or diagnostics(probe = true) call. The counter is not a background poller. Passive diagnostics() calls do not create a series.

TagAllowed values and protection
backend.nameThe sanitized descriptor backend ID; never an endpoint, credential, tenant, or lock name.
statusUP, DOWN, UNKNOWN, or NOT_CHECKED.
reasonThe six LeaderBackendConnectivityReason enum names.

Registry naming converts the source meter to leader_backend_connectivity_total in Prometheus. No exception class, message, endpoint, credential, raw provider payload, or lock name is exported. Keep active probe frequency bounded in the caller’s scheduler. A counter event is an observation, not a retry instruction.

For the bundled dashboard, the following queries are intentionally warning-oriented and low-cardinality:

sum by (backend_name, status, reason) (rate(leader_backend_connectivity_total[5m]))
sum by (backend_name) (
rate(leader_backend_connectivity_total{status="DOWN",reason="DISCONNECTED"}[5m])
)
sum by (backend_name, reason) (
rate(leader_backend_connectivity_total{status="UNKNOWN"}[5m])
)

The example’s LeaderBackendConnectivityDown rule requires five minutes and is notification: no-page. The UNKNOWN and PROVIDER_EXCEPTION rules also require sustained observations and never promote UNKNOWN to DOWN automatically.

Built-in providers use the public LeaderBackendDiagnosticsProbe.check helper. It validates a positive, finite provider-native timeout, reads the clock once before the callback, maps an ordinary Exception to UNKNOWN + PROVIDER_EXCEPTION, and rethrows cancellation, restored interruption, and fatal Error values. A callback returning NOT_CHECKED is invalid for an active probe.

The helper’s unknownReason distinguishes a provider that is unsupported from one whose client state cannot be confirmed. Existing custom checkConnectivity or diagnostics overrides remain an escape hatch: they own their provider exception behavior, while Micrometer observes the result without changing it. Legacy providers that still use their manual diagnostics boundary keep the compatible default reason until a separate provider-migration issue updates them. This draft records that boundary; it does not silently expand #766’s implementation scope.

The timeout passed to LeaderBackendDiagnosticsProbe.check is a provider-native budget. It is validated and passed to the callback, but it is not a caller-thread wall-clock deadline. A client that ignores the budget or does not support cancellation can keep the calling thread waiting.

When a probe is slow or uncertain:

  1. For repeated UNKNOWN + CLIENT_STATE_UNCONFIRMED, inspect the existing client lifecycle and its native timeout settings first.
  2. For increasing UNKNOWN + PROVIDER_EXCEPTION, inspect protected structured application logs and provider-native diagnostics. Do not copy exception text into metrics or route details.
  3. If a hard request deadline is required, own an executor/future timeout or a backend cancellation API in the application. Do not add an interrupt or forced Future.cancel to the library helper.
  4. If active probing exceeds the request budget, disable the active probe and use passive diagnostics plus the existing state/readiness signal until the provider is corrected.

Disable bluetape4k.leader.observability.backend-health or backendConnectivityCheckEnabled to stop active calls. Remove or protect the Ktor/Spring management route as appropriate. A missing counter series means that no active probe ran; it is not a synthetic NOT_CHECKED health sample.

Before rolling back one train child, verify that JSON consumers ignore an additive reason field and that dashboards tolerate the counter disappearing. Revert one child at a time; do not delete a public field or meter name without a deprecation and consumer-migration window. Never force-release a lease from a connectivity result. Re-check ownership through the backend’s conditional semantics and continue using runIfLeader for execution.

  • The route is authenticated and restricted by network policy.
  • Passive diagnostics is not used as readiness proof.
  • Active probe frequency and caller wall-clock deadline are explicit.
  • backend.name, status, and reason are the only exported tags.
  • UNKNOWN is warning/no-page unless an application policy says otherwise.
  • Provider-native timeout and cancellation behavior are documented for the selected backend.
  • Built-in and custom providers are tested at the direct, Ktor, and Spring boundaries.
  • The versioned manual is promoted only after releaseRef and releaseCommit point at a commit containing this contract.

See the released source descriptions in the root diagnostics section, the Prometheus dashboard runbook, and the backend selection guide.