Console
Sign in
Docs for Database administrator
On this page
  1. Before you start
  2. Steps
  3. 1. Wire the probes into the orchestrator
  4. 2. Give the scraper its tokens
  5. 3. Alert on the gateway
  6. 4. Alert on Access
  7. 5. Alert on Data
  8. 6. Watch the console and the control plane
  9. 7. Collect logs and follow one request ID
  10. 8. Decide what pages a person
  11. Check that it worked
  12. If something goes wrong
  13. Related

Monitor the platform

This guide sets up monitoring for a running deployment: probes wired into the orchestrator, every metrics endpoint scraped with its token, the alerts each service documents, and logs you can follow from one request ID across the gateway, Access and Data. It is for operators (sysops); secops owns the metrics tokens.

Read byPlatform operator

Before you start#

You needNotes
A deployed platformSee Deploy the platform.
A Prometheus-compatible scraperEvery service publishes Prometheus text on /metrics (platform metrics).
A metrics token per serviceGATEWAY_METRICS_TOKEN, ACCESS_METRICS_TOKEN, and a file for DATA_METRICS_TOKEN_FILE.
A log pipeline that reads standard outputAll four processes log JSON lines to stdout.
Somebody who receives alertsAccess's alerts expect a person to follow the runbook, not an automatic restart.

Steps#

1. Wire the probes into the orchestrator#

Use GET /healthz for liveness and GET /readyz for readiness and rollout gating on every process. /healthz never calls a dependency, so a failing database does not get a healthy pod killed; /readyz takes a replica out of rotation while a dependency it needs is down or while it drains (platform probes).

ProcessPort/readyz is 200 whenDuring a drain
Gateway8080A route table has loaded and the session store answers503, body draining, for up to GATEWAY_SHUTDOWN_TIMEOUT_MS (15 s)
Control plane9090The shared state answersNot documented
Access8081The database answers and every migration is applied503 {"status":"draining"}; logins get five seconds
Data8082Configuration, schema, role privileges and a recent Access discovery pass503 {"status":"draining"} for up to DATA_DRAIN_MS (30 s)
Console8083Access serves its discovery documentNot documented

A failed route-table reload does not make the gateway unready: the last valid table keeps serving and the error shows in the /readyz body as config.lastError (gateway spec §12.3). Data's 503 body carries a reason naming the failing part (Data spec §15.1).

Set each pod's termination grace period above its drain budget so the drain finishes before the kill. From an operator machine, platformctl status --watch polls the same probes every five seconds (CLI usage), and the console's Status page shows them to anyone (console user guide).

2. Give the scraper its tokens#

Each metrics endpoint is guarded by its own bearer token and exists only once you configure that token. Send the token as Authorization: Bearer <token> and keep the endpoints off the public ingress.

ServiceTokenWithout a token configuredWrong or missing bearer
GatewayGATEWAY_METRICS_TOKEN404401
AccessACCESS_METRICS_TOKEN (or ACCESS_METRICS_TOKEN_FILE)Not servedRefused; the status is not documented
DataDATA_METRICS_TOKEN_FILE404401
bash
curl -sS -H "Authorization: Bearer $GATEWAY_METRICS_TOKEN" http://gateway:8080/metrics | head
curl -sS -H "Authorization: Bearer $ACCESS_METRICS_TOKEN"  http://access:8081/metrics | head
curl -sS -H "Authorization: Bearer $(cat /run/secrets/data-metrics-token)" http://data:8082/metrics | head

The console and the control plane document no metrics endpoint; watch them through their probes and logs. Every label set is bounded on purpose (gateway routes cap at 500, Access tenants at 100, and Data carries no tenant, principal or workspace label at all), so per-tenant dashboards come from logs, not from metrics.

3. Alert on the gateway#

The gateway sits on every request, so its metrics show most incidents first. Metric names and labels are in gateway spec §12.1; the alert list is in gateway monitoring.

WatchWhat it tells youRunbook
/readyz answering 503A replica left rotation/readyz answers 503
gateway_access_unavailable_total risingCallers on workspace routes get 503 because Access cannot be asked503 access_unavailable
gateway_access_stale_total risingAn outage grace is covering for Access, so revocations lagstale decisions
gateway_shared_state_errors_total risingRedis or PostgreSQL is degraded; sessions fail closedshared state down
gateway_rate_limit_fallback_total risingLimits are being counted per processlimits look per replica
gateway_config_reloads_total{result="failed"}A route-table change was refusedroute-table change has no effect
gateway_upstream_failures_total, gateway_request_errors_totalAn upstream is failing502 bad_gateway
gateway_rate_limited_total spikingA flood is hitting the limiter429 too_many_requests
gateway_active_requests near GATEWAY_MAX_PENDING_REQUESTSAdmission is filling, usually behind a slow dependency503 gateway_overloaded

For latency, gateway_request_duration_seconds, gateway_upstream_duration_seconds and gateway_access_duration_seconds split the time between the gateway, the upstream and Access. gateway_auth_failures_total{reason} separates missing_role from access_denied.

4. Alert on Access#

Access documents eight alerts with expressions, durations and runbooks in Access monitoring. Load them as written; the expressions look like this:

text
rate(access_decisions_total{result="deny"}[5m]) / rate(access_decisions_total[5m]) > 0.2      for 10m
histogram_quantile(0.99, rate(access_decision_duration_seconds_bucket[5m])) > 0.02             for 5m
rate(access_logins_total{result!="success"}[10m]) > 0.1                                         for 10m
rate(access_store_errors_total[5m]) > 0                                                         for 5m
pg_stat_activity_count{application_name='access'} > 7                                           for 5m
rate(access_admin_requests_total{status="401"}[5m]) > 0                                         for 5m
probe_success{job="access"} == 0                                                                for 2m

The eighth, a schema older than the code, is not a metric: new replicas stay 503 not-ready and the start log names an unknown migration, so alert on readiness plus that log line. The page also lists four platform-level signals to add: replicas not ready, pod memory against 200 MB, the ingress certificate, and the age of the last database dump.

Decision reasons never reach a metric or the caller; they are in Access's decision log line (Access spec §12.3).

5. Alert on Data#

Data's metric names are in Data metrics and its alarms in Data alarms.

WatchWhat it tells youRunbook
data_outbox_oldest_age_seconds climbingA subscriber is behind; the backlog limit eventually refuses new workData alarms
data_delivery_failures_total climbingA handler fails, or a subscriber went missing in a deploydeliveries do not run
data_authorization_denials_total climbingA client exceeds its grants, or a grant was revoked403 FORBIDDEN
data_db_pool_waits_total climbingThe pool is too small or the database is slow503 DATABASE_UNAVAILABLE
data_access_requests_total{outcome="failed"}Calls to Access fail503 AUTHORIZATION_UNAVAILABLE
data_tenant_overload_totalTenants hit their share of in-flight requests429 TOO_MANY_REQUESTS
/readyz answering 503A dependency failed; the body says which503 on readiness

Worker processes become unready without a delivery store or a registered subscriber, so probe them as well as the API.

6. Watch the console and the control plane#

The console logs one JSON line per request but skips probe requests, and its readiness follows Access's discovery document (console probes and logs). A console restart signs everyone out, so count restarts.

The control plane answers 503 to every request while the shared state is unreachable, and five failed authentications in sixty seconds from one address earn 429. Every write it accepts is in GET /v1/audit, or platformctl routes audit; review it after route-table changes (gateway spec §12.7).

7. Collect logs and follow one request ID#

Every service writes JSON to stdout with secrets redacted, and every service echoes an x-request-id:

ServiceRequest ID ruleWhat its lines add
GatewayReuses the caller's x-request-id when it matches ^[A-Za-z0-9._-]{1,64}$, otherwise makes a UUID (gateway spec §2.1)Route, caller subject, workspace, and the trace ID it sends upstream in traceparent (gateway spec §7.5)
AccessReturned on every answer and written on every lineLogin outcome and refusal reason; decision subject, workspace, route and reason
DataEqual to the requestId of an error bodyError code, normalized route, dependency durations; never Access's reason
ConsoleReuses the caller's ID when it matches ^[A-Za-z0-9._-]{1,128}$Method, path, status, duration

To trace a failing request, take the x-request-id from the response (or the console's error page, or set your own with platformctl --request-id), search the gateway logs for it, then search Access's decision line and Data's line for the same ID. The gateway's running-policy lines, logged once per route-table generation, also flag permissive routes with warnings such as unauthenticated_route (gateway spec §12.2). Never rename a Data log field that carries a secret: redaction matches field names (Data logs). A full walk-through is in Troubleshoot a request.

8. Decide what pages a person#

The service pages agree on these conditions as worth an alert; route each to someone who can follow the runbook.

ConditionSignalsStart with
A replica out of rotation/readyz 503 on any serviceThat service's readiness runbook above
Access unreachable or slowgateway_access_unavailable_total, Access latency and store-error alertsAccess outages
Shared state degradedgateway_shared_state_errors_total, access_store_errors_total{store="redis"}gateway shared state
Login troubleAccess login-failure alert, gateway_auth_failures_totalAccess failed logins
Denials jumpAccess denial-rate alert, data_authorization_denials_totalAccess decisions denied
Credential guessingAccess admin 401 alert, control-plane 429administration refusals
Database pressureAccess pool alert, data_db_pool_waits_totalAccess tuning
Event backlogdata_outbox_oldest_age_secondsData failed deliveries
Backups staleAge of the last Access and Data dumpsBack up and restore

Check that it worked#

  1. Each curl in step 2 returns Prometheus text; the same call without the header returns 401 from the gateway and Data.
  2. Your scraper's target list shows every gateway, Access and Data replica as up.
  3. In staging, send SIGTERM to one replica of each service: its /readyz turns 503 with a draining body, the orchestrator stops routing to it, and your readiness alert fires if the replica does not come back.
  4. Send a request through the gateway with x-request-id: monitor-check-1, then find that ID in the gateway, Access and Data logs.
  5. Open the console's Status page with console.status and confirm versions and latencies for the three services.

If something goes wrong#

SymptomWhere to look
Gateway /metrics answers 404 or 401Gateway metrics endpoint
Data /metrics answers 404Data configuration: observability
Gateway /readyz stays 503/readyz answers 503
Access /readyz stays 503 not-readyAccess outages, start refusals
Data /readyz stays 503Data 503 on readiness
The console's status board shows a service downconsole probes and logs
A request ID is missing from one service's logsGateway start here, Data gateway answers 502