tutorial

Monitoring ArangoDB with Vigilmon: Multi-Model Database Availability, Cluster Health, and External Uptime Checks

Practical guide to uptime and health monitoring for ArangoDB — HTTP health endpoints that probe real cluster connectivity, coordinator and DB-server availability, and independent external monitoring with Vigilmon.

ArangoDB is a multi-model database that handles documents, graphs, and key-value data in a single engine, deployed as a single server or a distributed cluster with coordinators and DB-servers. Its flexibility is a production strength, but its distributed mode introduces multiple independent failure surfaces: a coordinator being removed from the agency pool, a DB-server falling out of sync with its leader shard, and collection-level replication factor violations can all cause partial write failures that internal health dashboards may not surface clearly. Vigilmon adds an independent external layer — HTTP health checks on your ArangoDB-connected services that verify real coordinator connectivity, surface shard availability issues, and alert on application-level failures before your users notice.

This tutorial shows you how to build meaningful monitoring for ArangoDB-backed applications using Vigilmon.

What You'll Build

  • A health endpoint that probes real ArangoDB coordinator connectivity and database availability
  • Shard health and replication factor detection
  • A Vigilmon HTTP monitor with appropriate timeout and assertion settings
  • A heartbeat monitor for ArangoDB-backed background workers
  • A cluster-aware alerting strategy for coordinator and DB-server tiers

Prerequisites

  • A running ArangoDB instance (single-server or cluster mode)
  • An application connected to ArangoDB (any runtime)
  • A free account at vigilmon.online

Step 1: Add a Health Endpoint to Your ArangoDB Service

Add a /health endpoint that performs a lightweight ArangoDB operation to verify real connectivity and checks cluster state via the /_api/cluster/health endpoint (cluster mode) or the built-in /_admin/server/availability endpoint (any mode).

Node.js (arangojs)

// routes/health.js
import { Database } from 'arangojs';
import fetch from 'node-fetch';

const db = new Database({
  url: process.env.ARANGODB_URL || 'http://localhost:8529',
  databaseName: process.env.ARANGODB_DATABASE || '_system',
  auth: {
    username: process.env.ARANGODB_USER || 'root',
    password: process.env.ARANGODB_PASSWORD || '',
  },
});

const ARANGO_URL = process.env.ARANGODB_URL || 'http://localhost:8529';

export async function healthHandler(req, res) {
  const checks = {};
  let ok = true;
  const start = Date.now();

  // Check 1: ArangoDB availability endpoint
  try {
    const availRes = await fetch(`${ARANGO_URL}/_admin/server/availability`, {
      signal: AbortSignal.timeout(5000),
    });
    checks.serverAvailability = availRes.ok ? 'ok' : `http ${availRes.status}`;
    checks.availabilityLatencyMs = Date.now() - start;
    if (!availRes.ok) ok = false;
  } catch (err) {
    checks.serverAvailability = `error: ${err.message}`;
    ok = false;
  }

  // Check 2: AQL probe query
  try {
    const queryStart = Date.now();
    const cursor = await db.query('RETURN DATE_NOW()');
    const result = await cursor.next();
    checks.queryStatus = result ? 'ok' : 'no result';
    checks.queryLatencyMs = Date.now() - queryStart;
    if (!result) ok = false;
  } catch (err) {
    checks.queryStatus = `error: ${err.message}`;
    ok = false;
  }

  // Check 3: Cluster health (cluster mode only)
  if (process.env.ARANGODB_CLUSTER === 'true') {
    try {
      const clusterRes = await fetch(
        `${ARANGO_URL}/_admin/cluster/health`,
        {
          headers: {
            Authorization: `Basic ${Buffer.from(
              `${process.env.ARANGODB_USER || 'root'}:${process.env.ARANGODB_PASSWORD || ''}`
            ).toString('base64')}`,
          },
          signal: AbortSignal.timeout(5000),
        }
      );
      if (clusterRes.ok) {
        const body = await clusterRes.json();
        const health = body.Health || {};
        const nodes = Object.values(health);
        const unhealthy = nodes.filter(n => n.Status !== 'GOOD');
        checks.clusterNodeCount = nodes.length;
        checks.unhealthyNodes = unhealthy.length;
        checks.clusterStatus = unhealthy.length > 0 ? `${unhealthy.length} nodes not GOOD` : 'ok';
        if (unhealthy.length > 0) ok = false;
      } else {
        checks.clusterStatus = `http ${clusterRes.status}`;
        ok = false;
      }
    } catch (err) {
      checks.clusterStatus = `error: ${err.message}`;
      ok = false;
    }
  }

  res.status(ok ? 200 : 503).json({
    status: ok ? 'ok' : 'degraded',
    service: 'arangodb-service',
    checks,
  });
}

Python (python-arango)

# routers/health.py
import os, time, base64
import requests
from arango import ArangoClient
from fastapi import APIRouter
from fastapi.responses import JSONResponse

router = APIRouter()

ARANGO_URL = os.environ.get('ARANGODB_URL', 'http://localhost:8529')
ARANGO_USER = os.environ.get('ARANGODB_USER', 'root')
ARANGO_PASSWORD = os.environ.get('ARANGODB_PASSWORD', '')
ARANGO_DB = os.environ.get('ARANGODB_DATABASE', '_system')

client = ArangoClient(hosts=ARANGO_URL)
db = client.db(ARANGO_DB, username=ARANGO_USER, password=ARANGO_PASSWORD)

auth_header = {
    'Authorization': 'Basic ' + base64.b64encode(
        f'{ARANGO_USER}:{ARANGO_PASSWORD}'.encode()
    ).decode()
}

@router.get("/health")
async def health():
    checks = {}
    ok = True
    start = time.monotonic()

    # Check 1: Server availability
    try:
        r = requests.get(f'{ARANGO_URL}/_admin/server/availability', timeout=5)
        checks['availabilityLatencyMs'] = int((time.monotonic() - start) * 1000)
        checks['serverAvailability'] = 'ok' if r.ok else f'http {r.status_code}'
        if not r.ok:
            ok = False
    except Exception as e:
        checks['serverAvailability'] = f'error: {e}'
        ok = False

    # Check 2: AQL probe query
    try:
        q_start = time.monotonic()
        cursor = db.aql.execute('RETURN DATE_NOW()')
        result = list(cursor)
        checks['queryStatus'] = 'ok' if result else 'no result'
        checks['queryLatencyMs'] = int((time.monotonic() - q_start) * 1000)
        if not result:
            ok = False
    except Exception as e:
        checks['queryStatus'] = f'error: {e}'
        ok = False

    # Check 3: Cluster health
    if os.environ.get('ARANGODB_CLUSTER') == 'true':
        try:
            r = requests.get(
                f'{ARANGO_URL}/_admin/cluster/health',
                headers=auth_header,
                timeout=5,
            )
            if r.ok:
                health_data = r.json().get('Health', {})
                nodes = list(health_data.values())
                unhealthy = [n for n in nodes if n.get('Status') != 'GOOD']
                checks['clusterNodeCount'] = len(nodes)
                checks['unhealthyNodes'] = len(unhealthy)
                checks['clusterStatus'] = f'{len(unhealthy)} nodes not GOOD' if unhealthy else 'ok'
                if unhealthy:
                    ok = False
            else:
                checks['clusterStatus'] = f'http {r.status_code}'
                ok = False
        except Exception as e:
            checks['clusterStatus'] = f'error: {e}'
            ok = False

    return JSONResponse(
        status_code=200 if ok else 503,
        content={'status': 'ok' if ok else 'degraded', 'service': 'arangodb-service', 'checks': checks},
    )

Go (arangodb-go-driver)

// internal/health/handler.go
package health

import (
    "context"
    "encoding/json"
    "net/http"
    "os"
    "time"

    driver "github.com/arangodb/go-driver"
    "github.com/arangodb/go-driver/http"
)

func Handler(arangoClient driver.Client) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
        defer cancel()

        checks := map[string]any{}
        ok := true

        // Check 1: Database version (lightweight connectivity probe)
        start := time.Now()
        info, err := arangoClient.Version(ctx)
        if err != nil {
            checks["serverAvailability"] = "error: " + err.Error()
            ok = false
        } else {
            checks["serverAvailability"] = "ok"
            checks["availabilityLatencyMs"] = time.Since(start).Milliseconds()
            checks["arangoVersion"] = info.Version
        }

        // Check 2: AQL probe query
        db, err := arangoClient.Database(ctx, os.Getenv("ARANGODB_DATABASE"))
        if err == nil {
            qStart := time.Now()
            cursor, err := db.Query(ctx, "RETURN DATE_NOW()", nil)
            if err != nil {
                checks["queryStatus"] = "error: " + err.Error()
                ok = false
            } else {
                defer cursor.Close()
                checks["queryStatus"] = "ok"
                checks["queryLatencyMs"] = time.Since(qStart).Milliseconds()
            }
        } else {
            checks["queryStatus"] = "error: database open: " + err.Error()
            ok = false
        }

        // Check 3: Cluster health (cluster mode only)
        if os.Getenv("ARANGODB_CLUSTER") == "true" {
            cluster, err := arangoClient.Cluster(ctx)
            if err == nil {
                health, err := cluster.Health(ctx)
                if err == nil {
                    unhealthy := 0
                    for _, node := range health.Health {
                        if node.Status != driver.ServerStatusGood {
                            unhealthy++
                        }
                    }
                    checks["clusterNodeCount"] = len(health.Health)
                    checks["unhealthyNodes"] = unhealthy
                    checks["clusterStatus"] = map[bool]string{true: "ok", false: "degraded"}[unhealthy == 0]
                    if unhealthy > 0 {
                        ok = false
                    }
                } else {
                    checks["clusterStatus"] = "error: " + err.Error()
                    ok = false
                }
            }
        }

        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": "arangodb-service",
            "checks":  checks,
        })
    }
}

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":"arangodb-service","checks":{"serverAvailability":"ok","queryStatus":"ok","queryLatencyMs":8,"clusterStatus":"ok"}}

Step 2: Configure the Vigilmon HTTP Monitor

  1. Log in to VigilmonAdd Monitor → HTTP.
  2. URL: your service's health endpoint (e.g. https://api.example.com/health).
  3. Check interval: 60 seconds.
  4. Response timeout: 15 seconds.
  5. Expected status: 200.
  6. JSON assertion: path status, expected value ok.
  7. 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 valuable for ArangoDB cluster deployments where a coordinator failure may affect some client connections but not others depending on load balancer behavior.

Tip: Create a second monitor with a JSON assertion on checks.unhealthyNodes equals 0. This surfaces partial cluster degradation — a DB-server losing shard leadership — before write errors reach your application.


Step 3: Heartbeat Monitoring for ArangoDB-Backed Workers

ArangoDB is commonly used for graph analytics pipelines, recommendation engines backed by traversal queries, and document-processing workflows. Workers that depend on ArangoDB will stall silently when coordinator connectivity is lost or when shard rebalancing causes temporary query failures. Vigilmon's heartbeat monitors detect silent worker death: your worker pings Vigilmon after each successful processing cycle, and if pings stop arriving, Vigilmon fires an alert.

Set Up the Heartbeat Monitor

  1. In Vigilmon, go to Monitors → New Monitor → Heartbeat.
  2. Set the name: arangodb-worker.
  3. Set the expected interval: 5 minutes (adjust to your pipeline frequency).
  4. Set the grace period: 10 minutes.
  5. Save — copy the unique heartbeat URL.

Wire It Into Your Worker

Node.js:

import { Database } from 'arangojs';
import fetch from 'node-fetch';

const db = new Database({
  url: process.env.ARANGODB_URL,
  auth: { username: process.env.ARANGODB_USER, password: process.env.ARANGODB_PASSWORD },
});
const collection = db.collection('jobs');

async function runWorkerCycle() {
  const cursor = await db.query({
    query: 'FOR job IN jobs FILTER job.status == "pending" LIMIT 50 RETURN job',
    bindVars: {},
  });

  const jobs = await cursor.all();
  for (const job of jobs) {
    await collection.update(job._key, { status: 'processed', processedAt: Date.now() });
  }

  // Only ping Vigilmon after successful processing
  await fetch(process.env.VIGILMON_HEARTBEAT_URL).catch(() => {});
}

setInterval(runWorkerCycle, 5 * 60 * 1000);

Python:

import os, time, requests
from arango import ArangoClient

client = ArangoClient(hosts=os.environ['ARANGODB_URL'])
db = client.db(
    os.environ['ARANGODB_DATABASE'],
    username=os.environ['ARANGODB_USER'],
    password=os.environ['ARANGODB_PASSWORD'],
)

def run_worker_cycle():
    cursor = db.aql.execute(
        "FOR job IN jobs FILTER job.status == 'pending' LIMIT 50 RETURN job"
    )
    jobs = list(cursor)
    jobs_col = db.collection('jobs')
    for job in jobs:
        jobs_col.update({'_key': job['_key'], 'status': 'processed'})

    # Only ping Vigilmon after successful batch
    requests.get(os.environ['VIGILMON_HEARTBEAT_URL'], timeout=5)

while True:
    try:
        run_worker_cycle()
    except Exception as e:
        print(f'Worker cycle failed — heartbeat NOT sent: {e}')
    time.sleep(300)

Step 4: ArangoDB Built-In Health Endpoints

ArangoDB exposes several built-in administrative endpoints you can monitor directly with Vigilmon:

| Endpoint | Purpose | Expected response | |---|---|---| | /_admin/server/availability | Single-server readiness | HTTP 200 when ready | | /_admin/cluster/health | Cluster node health | JSON Health map, all Status: GOOD | | /_api/version | ArangoDB version (connectivity probe) | HTTP 200 with version JSON |

For direct monitoring of the ArangoDB coordinator without an application-level proxy, point a Vigilmon monitor at /_admin/server/availability:

URL: https://arangodb.internal:8529/_admin/server/availability
Auth: Basic (root / your-password)
Expected status: 200
Interval: 60s

This is the most direct signal that ArangoDB is serving requests. Use it alongside your application-level health endpoint for defense in depth.


Step 5: Alerting Strategy

| Alert channel | When to use | |---|---| | PagerDuty | Server availability check fails (coordinator unreachable) | | PagerDuty | AQL probe query fails (database not accepting queries) | | Slack webhook | Unhealthy cluster nodes > 0 (partial degradation) | | Slack + Email | Query latency > 1000ms sustained (cluster under load) | | PagerDuty | Heartbeat missed (worker pipeline stopped) | | Status page | Any user-facing service backed by ArangoDB |

Set response time thresholds as an early warning:

  • Alert at 50ms for AQL probe query latency (simple queries should complete in < 20ms)
  • Alert at 1000ms for health endpoint response time

What Vigilmon Catches That Internal Monitoring Misses

| Scenario | ArangoDB internal metrics | Vigilmon | |---|---|---| | Coordinator reachable internally but not from app servers | No | HTTP probe from app's perspective | | DB-server shard leader change causes transient write failures | Cluster log only | AQL probe fails — 503 fires | | Agency quorum lost (cluster operations blocked) | Internal cluster event | Cluster health check returns degraded | | Worker pipeline silently stopped | Process appears running | Heartbeat grace period expires → alert | | AQL query timeout on overloaded cluster | Latency metric only | Query probe fails with timeout | | Monitoring pipeline (Prometheus/Grafana) fails | Alerts silently lost | Vigilmon is external — unaffected |


ArangoDB's multi-model flexibility makes it a powerful choice for complex data workloads — but distributed cluster failures require external monitoring that goes beyond process-level checks. External health checks with Vigilmon give you the independent signal you need — before query failures reach your users.

Start monitoring your ArangoDB applications in under 5 minutes — register free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →