DGraph is a native graph database built for high-performance graph traversals, distributed across a cluster of Alpha nodes coordinated by Zero nodes. Its distributed architecture means there are multiple independent failure surfaces: a Zero node losing quorum, an Alpha node falling behind replication, a schema mutation leaving the cluster in a partially applied state, and client-side gRPC connection pool exhaustion can all cause silent degradation while DGraph's internal processes appear healthy. Vigilmon adds an independent external layer — HTTP health checks on your DGraph-connected services that verify real Alpha connectivity, surface cluster health signals, and alert on application-level failures before your users notice.
This tutorial shows you how to build meaningful monitoring for DGraph-backed applications using Vigilmon.
What You'll Build
- A health endpoint that probes real DGraph Alpha and Zero connectivity
- Cluster state and schema health detection
- A Vigilmon HTTP monitor with appropriate timeout and assertion settings
- A heartbeat monitor for DGraph-backed background workers
- A cluster-aware alerting strategy for multi-Alpha deployments
Prerequisites
- A running DGraph cluster (single-node or multi-Alpha production)
- An application connected to DGraph (any runtime)
- A free account at vigilmon.online
Step 1: Add a Health Endpoint to Your DGraph Service
Add a /health endpoint that performs a lightweight DGraph query to verify real Alpha connectivity and checks cluster state via the admin API.
Node.js (dgraph-js-http)
// routes/health.js
import fetch from 'node-fetch';
const DGRAPH_ALPHA = process.env.DGRAPH_ALPHA_URL || 'http://localhost:8080';
const DGRAPH_ZERO = process.env.DGRAPH_ZERO_URL || 'http://localhost:6080';
export async function healthHandler(req, res) {
const checks = {};
let ok = true;
const start = Date.now();
// Check 1: Alpha health via built-in /health endpoint
try {
const alphaRes = await fetch(`${DGRAPH_ALPHA}/health`, {
signal: AbortSignal.timeout(5000),
});
const alphaBody = await alphaRes.json();
checks.alphaStatus = alphaRes.ok ? 'ok' : 'degraded';
checks.alphaLatencyMs = Date.now() - start;
// DGraph /health returns an array of node health objects
if (Array.isArray(alphaBody)) {
const unhealthy = alphaBody.filter(n => n.status !== 'healthy');
checks.alphaNodeCount = alphaBody.length;
checks.unhealthyAlphaNodes = unhealthy.length;
if (unhealthy.length > 0) {
checks.alphaStatus = `${unhealthy.length} unhealthy nodes`;
ok = false;
}
} else if (!alphaRes.ok) {
ok = false;
}
} catch (err) {
checks.alphaStatus = `error: ${err.message}`;
ok = false;
}
// Check 2: Zero state — cluster membership and leader election
try {
const zeroRes = await fetch(`${DGRAPH_ZERO}/state`, {
signal: AbortSignal.timeout(5000),
});
if (zeroRes.ok) {
const state = await zeroRes.json();
const groups = state.groups ? Object.keys(state.groups).length : 0;
checks.groupCount = groups;
checks.zeroLeader = state.leader ?? 'unknown';
checks.zeroStatus = 'ok';
} else {
checks.zeroStatus = `http ${zeroRes.status}`;
ok = false;
}
} catch (err) {
checks.zeroStatus = `error: ${err.message}`;
ok = false;
}
// Check 3: Probe with a minimal DQL query
try {
const queryStart = Date.now();
const queryRes = await fetch(`${DGRAPH_ALPHA}/query`, {
method: 'POST',
headers: { 'Content-Type': 'application/dql' },
body: '{ vigilmon(func: has(dgraph.type), first: 1) { uid } }',
signal: AbortSignal.timeout(5000),
});
if (queryRes.ok) {
checks.queryStatus = 'ok';
checks.queryLatencyMs = Date.now() - queryStart;
} else {
const body = await queryRes.text();
checks.queryStatus = `error: ${body.slice(0, 100)}`;
ok = false;
}
} catch (err) {
checks.queryStatus = `error: ${err.message}`;
ok = false;
}
res.status(ok ? 200 : 503).json({
status: ok ? 'ok' : 'degraded',
service: 'dgraph-service',
checks,
});
}
Python (pydgraph)
# routers/health.py
import os, time
import requests
from fastapi import APIRouter
from fastapi.responses import JSONResponse
router = APIRouter()
DGRAPH_ALPHA = os.environ.get('DGRAPH_ALPHA_URL', 'http://localhost:8080')
DGRAPH_ZERO = os.environ.get('DGRAPH_ZERO_URL', 'http://localhost:6080')
@router.get("/health")
async def health():
checks = {}
ok = True
start = time.monotonic()
# Check 1: Alpha health
try:
r = requests.get(f'{DGRAPH_ALPHA}/health', timeout=5)
checks['alphaLatencyMs'] = int((time.monotonic() - start) * 1000)
if r.ok:
body = r.json()
if isinstance(body, list):
unhealthy = [n for n in body if n.get('status') != 'healthy']
checks['alphaNodeCount'] = len(body)
checks['unhealthyAlphaNodes'] = len(unhealthy)
checks['alphaStatus'] = f'{len(unhealthy)} unhealthy' if unhealthy else 'ok'
if unhealthy:
ok = False
else:
checks['alphaStatus'] = 'ok'
else:
checks['alphaStatus'] = f'http {r.status_code}'
ok = False
except Exception as e:
checks['alphaStatus'] = f'error: {e}'
ok = False
# Check 2: Zero state
try:
r = requests.get(f'{DGRAPH_ZERO}/state', timeout=5)
if r.ok:
state = r.json()
checks['groupCount'] = len(state.get('groups', {}))
checks['zeroLeader'] = state.get('leader', 'unknown')
checks['zeroStatus'] = 'ok'
else:
checks['zeroStatus'] = f'http {r.status_code}'
ok = False
except Exception as e:
checks['zeroStatus'] = f'error: {e}'
ok = False
# Check 3: Minimal DQL query
try:
q_start = time.monotonic()
r = requests.post(
f'{DGRAPH_ALPHA}/query',
data='{ vigilmon(func: has(dgraph.type), first: 1) { uid } }',
headers={'Content-Type': 'application/dql'},
timeout=5,
)
if r.ok:
checks['queryStatus'] = 'ok'
checks['queryLatencyMs'] = int((time.monotonic() - q_start) * 1000)
else:
checks['queryStatus'] = f'error: {r.text[:100]}'
ok = False
except Exception as e:
checks['queryStatus'] = f'error: {e}'
ok = False
return JSONResponse(
status_code=200 if ok else 503,
content={'status': 'ok' if ok else 'degraded', 'service': 'dgraph-service', 'checks': checks},
)
Go (dgo)
// internal/health/handler.go
package health
import (
"context"
"encoding/json"
"net/http"
"os"
"time"
)
func Handler() http.HandlerFunc {
alphaURL := os.Getenv("DGRAPH_ALPHA_URL")
if alphaURL == "" {
alphaURL = "http://localhost:8080"
}
zeroURL := os.Getenv("DGRAPH_ZERO_URL")
if zeroURL == "" {
zeroURL = "http://localhost:6080"
}
httpClient := &http.Client{Timeout: 6 * time.Second}
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
_ = ctx
checks := map[string]any{}
ok := true
// Check 1: Alpha health endpoint
start := time.Now()
alphaResp, err := httpClient.Get(alphaURL + "/health")
if err != nil {
checks["alphaStatus"] = "error: " + err.Error()
ok = false
} else {
checks["alphaLatencyMs"] = time.Since(start).Milliseconds()
if alphaResp.StatusCode == 200 {
checks["alphaStatus"] = "ok"
} else {
checks["alphaStatus"] = "degraded"
ok = false
}
alphaResp.Body.Close()
}
// Check 2: Zero state
zeroResp, err := httpClient.Get(zeroURL + "/state")
if err != nil {
checks["zeroStatus"] = "error: " + err.Error()
ok = false
} else {
if zeroResp.StatusCode == 200 {
checks["zeroStatus"] = "ok"
} else {
checks["zeroStatus"] = "degraded"
ok = false
}
zeroResp.Body.Close()
}
// Check 3: Minimal DQL query
qStart := time.Now()
body := []byte(`{ vigilmon(func: has(dgraph.type), first: 1) { uid } }`)
qReq, _ := http.NewRequest("POST", alphaURL+"/query", bytesReader(body))
qReq.Header.Set("Content-Type", "application/dql")
qResp, err := httpClient.Do(qReq)
if err != nil {
checks["queryStatus"] = "error: " + err.Error()
ok = false
} else {
checks["queryLatencyMs"] = time.Since(qStart).Milliseconds()
checks["queryStatus"] = map[bool]string{true: "ok", false: "error"}[qResp.StatusCode == 200]
if qResp.StatusCode != 200 {
ok = false
}
qResp.Body.Close()
}
w.Header().Set("Content-Type", "application/json")
if !ok {
w.WriteHeader(http.StatusServiceUnavailable)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"status": map[bool]string{true: "ok", false: "degraded"}[ok],
"service": "dgraph-service",
"checks": checks,
})
}
}
func bytesReader(b []byte) *bytesReaderWrapper {
return &bytesReaderWrapper{data: b, pos: 0}
}
type bytesReaderWrapper struct {
data []byte
pos int
}
func (br *bytesReaderWrapper) Read(p []byte) (n int, err error) {
if br.pos >= len(br.data) {
return 0, io.EOF
}
n = copy(p, br.data[br.pos:])
br.pos += n
return
}
Deploy and verify manually before wiring it into Vigilmon:
curl -i https://your-app.example.com/health
# HTTP/1.1 200 OK
# {"status":"ok","service":"dgraph-service","checks":{"alphaStatus":"ok","zeroStatus":"ok","queryStatus":"ok","queryLatencyMs":12}}
Step 2: Configure the Vigilmon HTTP Monitor
- Log in to Vigilmon → Add Monitor → HTTP.
- URL: your service's health endpoint (e.g.
https://api.example.com/health). - Check interval: 60 seconds.
- Response timeout: 15 seconds (DQL queries can take slightly longer than key-value lookups).
- Expected status:
200. - JSON assertion: path
status, expected valueok. - Save the monitor.
Vigilmon probes from multiple geographic regions simultaneously. A single-region blip will not page you; multi-region consensus is required before an incident opens. This is especially valuable for DGraph because Alpha node failures may leave the cluster degraded but not fully down, causing partial failures that single-region checks miss.
Tip: Create a second monitor that asserts on
checks.unhealthyAlphaNodesequals0. This alerts on partial cluster degradation before your primary connectivity check fails.
Step 3: Heartbeat Monitoring for DGraph-Backed Workers
DGraph is commonly used for graph traversal pipelines, recommendation engines, and fraud detection jobs that run on a schedule or event trigger. These workers will stall silently when the Alpha cluster becomes unavailable or when a schema mutation leaves the database in an inconsistent state. Vigilmon's heartbeat monitors detect silent worker death: your worker pings Vigilmon after each successful cycle, and if pings stop arriving, Vigilmon fires an alert.
Set Up the Heartbeat Monitor
- In Vigilmon, go to Monitors → New Monitor → Heartbeat.
- Set the name:
dgraph-worker. - Set the expected interval: 5 minutes (adjust to your pipeline frequency).
- Set the grace period: 10 minutes.
- Save — copy the unique heartbeat URL.
Wire It Into Your Worker
Node.js:
import fetch from 'node-fetch';
import { DgraphClient, DgraphClientStub, Mutation } from 'dgraph-js-http';
const stub = new DgraphClientStub(process.env.DGRAPH_ALPHA_URL);
const client = new DgraphClient(stub);
async function runGraphPipeline() {
const txn = client.newTxn();
try {
const query = `{ nodes(func: eq(status, "pending"), first: 50) { uid status } }`;
const result = await txn.query(query);
const nodes = result.data.nodes;
// Process each node
for (const node of nodes) {
const mu = new Mutation();
mu.setSetJson({ uid: node.uid, status: 'processed' });
await txn.mutate(mu);
}
await txn.commit();
// Only ping Vigilmon after successful commit
await fetch(process.env.VIGILMON_HEARTBEAT_URL).catch(() => {});
} finally {
await txn.discard();
}
}
setInterval(runGraphPipeline, 5 * 60 * 1000);
Python:
import os, time, requests
import pydgraph
stub = pydgraph.DgraphClientStub(os.environ['DGRAPH_ALPHA_GRPC'])
client = pydgraph.DgraphClient(stub)
def run_graph_pipeline():
txn = client.txn()
try:
query = '{ nodes(func: eq(status, "pending"), first: 50) { uid status } }'
result = txn.query(query)
nodes = result.json.get('nodes', [])
for node in nodes:
mutation = {'uid': node['uid'], 'status': 'processed'}
txn.mutate(set_obj=mutation)
txn.commit()
# Only ping Vigilmon after successful commit
requests.get(os.environ['VIGILMON_HEARTBEAT_URL'], timeout=5)
except Exception as e:
print(f"Pipeline failed — heartbeat NOT sent: {e}")
finally:
txn.discard()
while True:
run_graph_pipeline()
time.sleep(300)
Step 4: Multi-Alpha Cluster Monitoring
In production DGraph deployments, each Alpha group serves a shard of the data. Monitor each group independently:
- Create one Vigilmon monitor per Alpha group if you expose per-group health endpoints
- Name monitors clearly:
[dgraph-alpha-1] graph-api /health,[dgraph-alpha-2] graph-api /health - Monitor Zero separately: Zero handles cluster membership and schema operations; a Zero outage blocks all schema mutations even if Alpha nodes remain queryable
DGraph exposes a built-in /health endpoint on each Alpha node at port 8080. Wire Vigilmon directly to this endpoint for the most direct signal:
# Monitor DGraph Alpha directly
# URL: http://alpha1.internal:8080/health
# Expected: status 200, body contains "healthy"
Group all DGraph monitors into a single Status Page so your team has a single pane of glass for the graph tier.
Step 5: Alerting Strategy
| Alert channel | When to use | |---|---| | PagerDuty | Alpha connectivity fails (cluster unreachable) | | PagerDuty | Zero unreachable (schema mutations blocked) | | Slack webhook | Unhealthy Alpha node count > 0 (partial degradation) | | Slack + Email | Query latency > 500ms sustained (cluster under load) | | PagerDuty | Heartbeat missed (graph pipeline stopped) | | Status page | Any user-facing service backed by DGraph |
Set response time thresholds as an early warning:
- Alert at
100msfor DQL probe query latency (simple traversals should complete in < 50ms) - Alert at
2000msfor health endpoint response time
What Vigilmon Catches That Internal Monitoring Misses
| Scenario | DGraph internal metrics | Vigilmon | |---|---|---| | Alpha node reachable internally but not from app servers | No | HTTP probe from app's perspective | | Zero loses quorum (schema mutations silently blocked) | Cluster log only | Zero /state check returns degraded | | DQL query timeout (cluster overloaded) | Latency metric only | Query check fails — 503 fires | | Worker pipeline silently stopped | Process appears running | Heartbeat grace period expires → alert | | Schema mutation left database inconsistent | No | Query probe returns error | | Monitoring pipeline (Prometheus/Grafana) fails | Alerts silently lost | Vigilmon is external — unaffected |
DGraph's distributed architecture is its strength for graph workloads and its challenge for operations — multiple failure surfaces that process-level checks miss. External health checks with Vigilmon give you the independent signal you need — before graph traversal latency reaches your users.
Start monitoring your DGraph applications in under 5 minutes — register free at vigilmon.online.