Monitoring
PuppyGraph makes it easy to keep an eye on your system's health and performance. With built-in health check endpoints and detailed metrics collection, you can quickly monitor how your PuppyGraph instances are running and catch any issues early.
Health Check
PuppyGraph has two kinds of health checks. A liveness check tells you that a container is running. A readiness check tells you that PuppyGraph can serve Gremlin and openCypher queries. Use the readiness check before sending queries, and to decide when a deployment or upgrade is done.
Version requirement
/readyz and the image HEALTHCHECK are available in PuppyGraph 1.15.0 and later.
Endpoints
None of these endpoints requires a login, even with SSO, RBAC or authentication for Gremlin and openCypher clients turned on.
| Endpoint | Served by | What it reports |
|---|---|---|
:8081/readyz |
The PuppyGraph container, or the controlplane in a cluster | Whether the whole deployment can serve queries now. Returns 200 when ready, 503 when not. |
:8081/healthz |
The PuppyGraph container, or the controlplane in a cluster | Liveness only. Returns 200 as soon as the Web UI and REST API are up, long before queries work. |
:8083/healthz |
Each leader and compute container, and the single PuppyGraph container | Whether this container is ready to take part in serving queries. Returns 200 with YES, or 500 with NO. |
If you set BASE_PATH, add it before the path, for example :8081/puppygraph/readyz.
Readiness response
/readyz returns a JSON body:
readyistruewhen at least one leader and at least one compute container are ready, or, in a single-container deployment, when the container can serve queries. The HTTP status is200. Otherwisereadyisfalseand the status is503.degradedistruewhen PuppyGraph is ready but some leader or compute containers are not, so the cluster runs with less capacity or redundancy than configured.reasonsexplains a503, or adegradedanswer. It holds stable codes only, never container names. Signed-in users can see which container each reason belongs to inGET /status, which has the sameready,degradedandready_reasonsfields plusnot_ready_reasonsfor each container.
A fresh instance with no graph schema is ready, so it can accept its first schema. While a schema loads, for example for a few seconds after the first upload or after a restart, /readyz returns 503 with SCHEMA_SYNC_PENDING or SCHEMA_NOT_LOADED. Probes with a few retries ride this out.
Wait for the current schema
Add ?schema=current to wait until a graph schema is uploaded and every leader that serves queries has applied its latest version. Use it after you upload a schema, and after an upgrade or restart. A single container, or a cluster whose only leader cannot load the stored schema, stays at 503.
In a cluster with several leaders, /readyz?schema=current returns 200 once every leader that serves queries has applied the current schema. A ready leader on an older schema version keeps it at 503 with SCHEMA_NOT_CURRENT. A leader that is not ready, for example because it is still restarting or cannot load the schema, does not block the answer but makes it degraded. To confirm that every leader and compute container has recovered, for example at the end of an upgrade, also wait for "degraded":false:
until curl -sf "http://localhost:8081/readyz?schema=current" | grep -q '"degraded":false'; do
sleep 3
done
On an instance with no schema, /readyz?schema=current stays at 503 with NO_SCHEMA, so use plain /readyz before the first schema upload. Any other value for schema returns 400.
Which endpoint to use
| Deployment | Readiness | Liveness |
|---|---|---|
| Single container (Docker, Docker Compose) | :8081/readyz |
:8081/healthz |
| Cluster controlplane | :8081/healthz |
:8081/healthz |
| Cluster leader or compute container | :8083/healthz |
|
Helm chart, single-node mode |
:8083/healthz |
:8081/healthz |
| Scripts and clients waiting for a cluster | :8081/readyz on the controlplane |
Don't use /readyz as the controlplane's own readiness or health check in a cluster. Leader and compute containers register through the controlplane, so a controlplane that waits for the cluster to be ready never becomes ready. The Helm chart already sets these probes.
Upgrading from 1.14.0 or earlier
Existing health checks and wait scripts keep working. :8081/healthz still reports liveness only, and GET /status keeps all its fields and only gains new ones. Two things change without any action on your part:
:8083/healthzon leader and compute containers is stricter. A compute container answersYESonly once it has finished starting and can execute its share of query work. A leader answersYESonly once it can serve queries and, if a graph schema has been uploaded, has a graph schema loaded. Pods probed on:8083, as in the Helm chart, become Ready later at startup. They also leave their Service while the container cannot serve queries: briefly while a schema loads, and for as long as a leader cannot load its stored schema. In the chart'ssingle-nodemode the Web UI is behind the same Service, so to investigate such a pod, forward the port of the pod itself:kubectl -n $NAMESPACE port-forward pod/$RELEASE_NAME-single-node-0 8081:8081.- The image has a built-in
HEALTHCHECK, described below. Ahealthcheckin your Compose file replaces it. Tools that restart unhealthy containers, such as Docker Swarm, now restart a single container that stays not ready, for example one that cannot load its stored schema.
No action is needed unless one of these applies:
- Your own scripts or monitors treat
NOfrom:8083/healthzas down. ExpectNOfor longer at startup and briefly while a schema loads, and allow for it in their retries. - You use
:8081/healthzto decide that PuppyGraph can serve queries. Switch to/readyz, because:8081/healthzreports liveness only. - You don't want the built-in
HEALTHCHECK, for example because your orchestrator runs its own check. Sethealthcheck: disable: truein Compose or pass--no-healthchecktodocker run.
Usage
Wait until PuppyGraph is ready
Poll /readyz before you send queries or upload a schema from a script:
until curl -sf -o /dev/null http://localhost:8081/readyz; do
sleep 3
done
echo "PuppyGraph is ready."
Docker container health check
The PuppyGraph image has a built-in HEALTHCHECK. On a single container it checks :8081/readyz. On cluster containers it checks :8081/healthz for the controlplane and :8083/healthz for leader and compute containers. docker ps shows the container as healthy once the check passes.
In Docker Compose, you can make another service wait for PuppyGraph without adding a healthcheck of your own:
services:
puppygraph:
image: puppygraph/puppygraph:latest
# ...
my-app:
image: my-app:latest
depends_on:
puppygraph:
condition: service_healthy
docker compose up -d --wait also returns only after PuppyGraph is healthy.
Kubernetes readiness probe (cluster deployments)
In a cluster, probe each leader and compute pod on :8083/healthz, and the controlplane pod on :8081/healthz. A pod's readiness should describe that pod, so that Kubernetes stops routing queries to a leader that cannot serve them. /readyz describes the whole cluster: it stays 200 while any leader is ready, so a broken leader would stay in the Service. The Helm chart configures these probes for you. For your own manifests, use this on leader and compute pods:
readinessProbe:
httpGet:
path: /healthz
port: 8083
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 5
For more information about cluster deployment, see Cluster Deployment.
Metrics
PuppyGraph exposes metrics in Prometheus / OpenMetrics format on two endpoints:
| Endpoint | Served by | Metrics | Credentials |
|---|---|---|---|
:8083/metrics |
The node agent. In a single-container deployment (Docker, or the Helm chart's single-node mode), the PuppyGraph container. In a cluster, each leader and compute container. |
Node metrics: memory, JVM, component health, and optionally the Gremlin server | The admin credentials, PUPPYGRAPH_USERNAME and PUPPYGRAPH_PASSWORD, unless METRICS_AUTH_ENABLED=false |
:8081/metrics |
The controlplane. In a single-container deployment (Docker, or the Helm chart's single-node mode), the PuppyGraph container. In a cluster, the controlplane container. |
Controlplane metrics: local-table loads and Periodic Load | A user or service account with CLUSTER:read |
Both endpoints use HTTP basic authentication. If you set BASE_PATH, add it before the path, for example :8083/puppygraph/metrics.
Configuration
Set these on every PuppyGraph container: the single container, or the controlplane, leaders, and compute nodes of a cluster.
| Environment Variable | Default Value | Description |
|---|---|---|
METRICS_ENABLED |
false |
Enables both metrics endpoints. While it is false, :8083/metrics returns 403, and :8081/metrics returns 404 to a signed-in caller. |
METRICS_AUTH_ENABLED |
true |
Requires the admin credentials on :8083/metrics. Set it to false to serve node metrics without credentials. It has no effect on :8081/metrics, which always requires credentials. |
GREMLINSERVER_METRICS_ENABLED |
false |
Adds the puppy_gremlin_* metrics of the Gremlin server to :8083/metrics. |
BOLTSERVER_METRICS_ENABLED |
false |
Adds the puppy_bolt_* metrics of the Bolt (openCypher) server to :8083/metrics. |
Node metrics
Scrape :8083/metrics on each leader and compute container. In a single-container deployment, one endpoint covers both roles. The Gremlin and Bolt server metrics (puppy_gremlin_*, puppy_bolt_*) come only from leader containers, or the single container, so sum them across leaders for cluster totals.
With METRICS_AUTH_ENABLED=true, the endpoint accepts only the admin username and password set with PUPPYGRAPH_USERNAME and PUPPYGRAPH_PASSWORD (default puppygraph and puppygraph123). Other users and service accounts get 401.
The table lists common node metrics. Others appear depending on the features you enable and the data sources you use, for example puppy_compute_jdbc_pool_* for each JDBC data source, and puppy_leader_iceberg_metadata_cache_max_age_seconds and puppy_leader_iceberg_metadata_last_success_age_seconds for each Iceberg catalog.
| Metric Name | Description |
|---|---|
puppygraph_node_alive |
Whether each node of the cluster is alive. |
puppygraph_component_running |
Whether each process of a node (dataaccess, bolt, gremlinserver, nodeagent) is running. |
puppygraph_component_health |
Whether each process of a node is healthy. |
puppygraph_client_status |
Whether the gotty, bolt, and notebook clients are alive. |
puppygraph_gremlin_server_status |
Whether the Gremlin server is alive. |
puppy_compute_memory_usage_bytes |
Current memory consumption of the compute node. |
puppy_compute_memory_limit_bytes |
Configured memory limit of the compute node. |
puppy_compute_query_memory_usage_bytes |
Memory used by queries on the compute node. |
puppy_compute_data_cache_memory_usage_bytes |
Memory used by the compute node's data cache. |
puppy_compute_query_cache_memory_usage_bytes |
Memory used by the compute node's query cache. |
puppy_compute_local_load_memory_usage_bytes |
Memory used by local-table loads on the compute node. |
puppy_compute_jvm_heap_size_bytes, puppy_compute_jvm_non_heap_size_bytes |
Compute JVM heap and non-heap size, by type. |
puppy_leader_jvm_heap_size_bytes, puppy_leader_jvm_non_heap_size_bytes |
Leader JVM heap and non-heap size, by type. |
puppy_leader_jvm_young_gc, puppy_leader_jvm_old_gc |
Leader JVM garbage collection statistics, by type. |
puppy_leader_iceberg_metadata_refresh_total |
Completed Iceberg table metadata refreshes, by outcome. |
puppy_gremlin_* |
Gremlin server metrics, such as puppy_gremlin_op_eval_count (Gremlin queries; Cypher queries are not counted), puppy_gremlin_op_eval_mean and puppy_gremlin_errors_count. Values reset when the Gremlin server restarts. Only with GREMLINSERVER_METRICS_ENABLED=true. |
puppy_bolt_* |
Bolt server metrics, such as puppy_bolt_queries_total (queries received over the Bolt protocol, by result and language) and puppy_bolt_connections_active. Queries from the Web UI or the REST API are not counted. Only with BOLTSERVER_METRICS_ENABLED=true. |
Controlplane metrics
Version requirement
Controlplane local-table load metrics are available in PuppyGraph 1.16.0 and later.
The controlplane reports on local-table loads and the Periodic Load scheduler at :8081/metrics. The endpoint uses the same sign-in as the REST API, not METRICS_AUTH_ENABLED:
| Request | Response |
|---|---|
| No credentials, or wrong credentials | 401 |
A user or service account without CLUSTER:read |
403, permission denied: requires CLUSTER:read |
A user or service account with CLUSTER:read, and METRICS_ENABLED=false |
404, metrics are not enabled; set METRICS_ENABLED=true |
A user or service account with CLUSTER:read, and METRICS_ENABLED=true |
200 with the metrics |
Of the built-in roles, Admin, UserAdmin, and GraphAdmin have CLUSTER:read. None of them is limited to reading metrics, so treat a scraper's credentials like those of an administrator. For a scraper, create a service account with one of these roles and use its name and secret as the basic authentication username and password:
Every series carries a hostName label with the controlplane's host name. No series carries a per-table label.
| Metric Name | Type | Labels | Description |
|---|---|---|---|
puppy_controlplane_local_table_load_tasks_total |
counter | hostName, trigger, result |
Load task attempts, by what started the load and how the attempt ended. |
puppy_controlplane_local_table_load_task_duration_seconds |
histogram | hostName, trigger |
Duration of successful load tasks. |
puppy_controlplane_local_table_load_active_task_max_age_seconds |
gauge | hostName |
Seconds since the oldest unfinished (pending, running, or retrying) load task was submitted. 0 when there is none. |
puppy_controlplane_local_table_load_periodic_submissions_total |
counter | hostName, result |
Periodic Load submissions, by result. |
puppy_controlplane_local_table_load_periodic_poll_last_timestamp_seconds |
gauge | hostName |
Unix time of the Periodic Load scheduler's last successful check. 0 before the first. |
trigger is what started the load:
| Value | Load started by |
|---|---|
on_demand |
An explicit request to load now: POST /ui-api/loadLocalTable or loadLocalTables, the Web UI's Import from Source, PuppyGraph AI, or a Gremlin load action. |
periodic |
The Periodic Load scheduler, which reloads a local table on a fixed interval. |
schema_upload |
A schema upload with postUploadBehavior=load or switch. |
table_create |
Creating a local table for a node or edge, for example with Enable Local Replication. |
unknown |
A load recorded without a trigger, such as one submitted before an upgrade to a version with these metrics. |
result of puppy_controlplane_local_table_load_tasks_total:
| Value | Meaning |
|---|---|
success |
The task succeeded. |
failure |
The task failed, and no retry follows. |
retried |
The task failed, and a retry was submitted. The retry is counted again when it finishes. |
cancelled |
The task was cancelled. |
result of puppy_controlplane_local_table_load_periodic_submissions_total:
| Value | Meaning |
|---|---|
accepted |
The load was submitted. Its tasks are counted in puppy_controlplane_local_table_load_tasks_total with trigger="periodic". |
rejected |
The load could not be submitted, for example because no leader was available. The scheduler tries again later. |
superseded |
The schema changed before the load was submitted, for example because Periodic Load was turned off, so it was not submitted. |
How to read them:
puppy_controlplane_local_table_load_tasks_totalcounts load tasks, not requests. A load of a partitioned table or of a time range runs one task per partition, and each retry is another task. A retry keeps the trigger of the load it retries, so a load that succeeds on its second attempt countsretriedonce andsuccessonce under the same trigger.failurecounts only failures that are final. The load statusFAILEDshown in the Web UI also covers attempts that were retried.puppy_controlplane_local_table_load_task_duration_secondsincludes successful tasks only, including any time a task waited in a queue after it was submitted.puppy_controlplane_local_table_load_active_task_max_age_secondskeeps growing while a task is stuck. Such a task also stops Periodic Load from loading its table again. Find the table withgetLocalTableLoadStatus.puppy_controlplane_local_table_load_periodic_poll_last_timestamp_secondsshows that the scheduler is running, not that any table was due.- The counters belong to the controlplane process. They start at zero when a controlplane starts, or takes over from another one, and
rate()andincrease()treat that as a counter reset.
Example queries:
# Final load failures per second, by trigger
sum by (trigger) (rate(puppy_controlplane_local_table_load_tasks_total{result="failure"}[5m]))
# Successful periodic load tasks per second
rate(puppy_controlplane_local_table_load_tasks_total{trigger="periodic", result="success"}[5m])
# Rejected periodic submissions in the last hour
increase(puppy_controlplane_local_table_load_periodic_submissions_total{result="rejected"}[1h])
# A load task that has not finished for more than 13 hours (the default task timeout is 12 hours)
puppy_controlplane_local_table_load_active_task_max_age_seconds > 13 * 3600
# Seconds since the scheduler last checked; alert when it stays above a few minutes
time() - puppy_controlplane_local_table_load_periodic_poll_last_timestamp_seconds
Prometheus Integration
Use the following Prometheus scrape configuration as a template. It has one job for node metrics and one for controlplane metrics. Replace the placeholder values to match your environment.
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: puppygraph-node
metrics_path: /metrics
static_configs:
- targets: ["<PUPPYGRAPH_HOST>:8083"]
basic_auth:
username: <PUPPYGRAPH_USERNAME>
password: <PUPPYGRAPH_PASSWORD>
- job_name: puppygraph-controlplane
metrics_path: /metrics
static_configs:
- targets: ["<PUPPYGRAPH_HOST>:8081"]
basic_auth:
username: <SERVICE_ACCOUNT_NAME>
password: <SERVICE_ACCOUNT_SECRET>
- For
puppygraph-node, list:8083of every leader and compute container, or of the single PuppyGraph container. Use the admin credentials set withPUPPYGRAPH_USERNAMEandPUPPYGRAPH_PASSWORD, or removebasic_authifMETRICS_AUTH_ENABLED=false. - For
puppygraph-controlplane, use the controlplane, or the single PuppyGraph container, and a service account withCLUSTER:read. Leave this job out before PuppyGraph 1.16.0. - Adjust the scrape intervals to match your monitoring requirements.
For a complete walkthrough, see Monitoring PuppyGraph with Prometheus.
Datadog Integration
Here's an example of how to integrate PuppyGraph's metrics endpoints with Datadog for monitoring.
Run Datadog agent
Follow Start the Datadog Agent with Docker to start the Datadog agent.
Pass the credentials to the Datadog agent container as environment variables: the admin credentials for node metrics, and a service account with CLUSTER:read for controlplane metrics:
docker run -d --name dd-agent \
-e PUPPYGRAPH_USERNAME="<YOUR_PUPPYGRAPH_USERNAME>" \
-e PUPPYGRAPH_PASSWORD="<YOUR_PUPPYGRAPH_PASSWORD>" \
-e PUPPYGRAPH_SA_NAME="<SERVICE_ACCOUNT_NAME>" \
-e PUPPYGRAPH_SA_SECRET="<SERVICE_ACCOUNT_SECRET>" \
-e DD_API_KEY="<DATADOG_API_KEY>" \
-e DD_SITE="<YOUR_DATADOG_SITE>" \
-e DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v /proc/:/host/proc/:ro \
-v /sys/fs/cgroup/:/host/sys/fs/cgroup:ro \
-v /var/lib/docker/containers:/var/lib/docker/containers:ro \
gcr.io/datadoghq/agent:latest
Run PuppyGraph with Datadog labels
Add labels to a PuppyGraph container so that Datadog discovers and scrapes its metrics. For example, if you are running PuppyGraph with Docker, add the following arguments to the docker run command. They configure the openmetrics check twice: the first instance collects node metrics from :8083, and the second collects controlplane metrics from :8081:
-l com.datadoghq.ad.check_names='["openmetrics","openmetrics"]' \
-l com.datadoghq.ad.init_configs='[{},{}]' \
-l com.datadoghq.ad.instances='[{"openmetrics_endpoint":"http://%%host%%:8083/metrics","username":"%%env_PUPPYGRAPH_USERNAME%%","password":"%%env_PUPPYGRAPH_PASSWORD%%","namespace":"puppy","metrics":[".*"]},{"openmetrics_endpoint":"http://%%host%%:8081/metrics","username":"%%env_PUPPYGRAPH_SA_NAME%%","password":"%%env_PUPPYGRAPH_SA_SECRET%%","namespace":"puppy","metrics":[".*"]}]' \
- If
METRICS_AUTH_ENABLED=false, removeusernameandpasswordfrom the:8083instance. Keep them on the:8081instance, which always requires credentials. - Before PuppyGraph 1.16.0, configure the check once:
check_names'["openmetrics"]',init_configs'[{}]', and only the:8083instance.