tutorial

Monitoring GridDB with Vigilmon: IoT Time-Series Availability, Container Health, and External Uptime Checks

Practical guide to uptime and health monitoring for GridDB — HTTP health endpoints that probe real container connectivity, time-series ingestion health, and independent external monitoring with Vigilmon.

GridDB is a purpose-built IoT and time-series database developed by Toshiba, designed for sensor data collection at massive scale — millions of devices streaming telemetry, energy usage, industrial equipment metrics, and environmental readings. Its container-based data model pairs time-series containers with standard collection containers under a transactional consistency model that balances write throughput against query flexibility. When a GridDB node falls behind replication, a container fails to open, or the write buffer reaches its watermark, ingestion silently drops without surfacing to upstream collectors. Vigilmon adds an independent external monitoring layer — HTTP health checks that verify real GridDB connectivity from your application's perspective and alert before data gaps reach your dashboards.

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

What You'll Build

  • A health endpoint that probes real GridDB container connectivity and checks node status
  • Time-series write and read probe for ingestion pipeline health
  • A Vigilmon HTTP monitor with appropriate timeout and assertion settings
  • A heartbeat monitor for GridDB-backed IoT data pipelines
  • An alerting strategy tuned for time-series ingestion failure modes

Prerequisites

  • A running GridDB cluster (single-node or multi-node)
  • An application connected to GridDB via its Java API, C API, or REST API
  • A free account at vigilmon.online

Step 1: Add a Health Endpoint to Your GridDB Service

GridDB exposes a WebAPI on port 8080 for REST access. Use it alongside the native Java client to build a /health route that gives Vigilmon something meaningful to probe.

Node.js (GridDB WebAPI)

// routes/health.js
import fetch from 'node-fetch';

const GRIDDB_WEBAPI = process.env.GRIDDB_WEBAPI_URL || 'http://localhost:8080';
const GRIDDB_CLUSTER = process.env.GRIDDB_CLUSTER_NAME || 'myCluster';
const GRIDDB_USER = process.env.GRIDDB_USER || 'admin';
const GRIDDB_PASS = process.env.GRIDDB_PASS || 'admin';

const authHeader = 'Basic ' + Buffer.from(`${GRIDDB_USER}:${GRIDDB_PASS}`).toString('base64');

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

  // Check 1: Cluster connectivity via container list
  try {
    const resp = await fetch(
      `${GRIDDB_WEBAPI}/griddb/v2/${GRIDDB_CLUSTER}/dbs/public/containers`,
      {
        headers: { Authorization: authHeader },
        signal: AbortSignal.timeout(5000),
      }
    );
    checks.pingLatencyMs = Date.now() - start;

    if (!resp.ok) {
      checks.connectivity = `error: HTTP ${resp.status}`;
      ok = false;
    } else {
      checks.connectivity = 'ok';
    }
  } catch (err) {
    checks.connectivity = `error: ${err.message}`;
    ok = false;
  }

  // Check 2: Time-series write probe
  try {
    const containerName = 'vigilmon_probe';
    const writeStart = Date.now();
    const timestamp = new Date().toISOString();

    // Ensure probe container exists (idempotent)
    await fetch(
      `${GRIDDB_WEBAPI}/griddb/v2/${GRIDDB_CLUSTER}/dbs/public/containers`,
      {
        method: 'POST',
        headers: { Authorization: authHeader, 'Content-Type': 'application/json' },
        body: JSON.stringify({
          container_name: containerName,
          container_type: 'TIME_SERIES',
          rowkey: true,
          columns: [
            { name: 'timestamp', type: 'TIMESTAMP' },
            { name: 'value', type: 'DOUBLE' },
          ],
        }),
        signal: AbortSignal.timeout(5000),
      }
    ).catch(() => {}); // ignore if already exists

    // Write a probe row
    const writeResp = await fetch(
      `${GRIDDB_WEBAPI}/griddb/v2/${GRIDDB_CLUSTER}/dbs/public/containers/${containerName}/rows`,
      {
        method: 'PUT',
        headers: { Authorization: authHeader, 'Content-Type': 'application/json' },
        body: JSON.stringify([[timestamp, 1.0]]),
        signal: AbortSignal.timeout(5000),
      }
    );
    checks.writeLatencyMs = Date.now() - writeStart;

    if (!writeResp.ok) {
      checks.writeProbe = `error: HTTP ${writeResp.status}`;
      ok = false;
    } else {
      checks.writeProbe = 'ok';
    }
  } catch (err) {
    checks.writeProbe = `error: ${err.message}`;
    ok = false;
  }

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

Python (GridDB WebAPI)

# routers/health.py
import os, time, base64
from datetime import datetime, timezone
import httpx
from fastapi import APIRouter
from fastapi.responses import JSONResponse

router = APIRouter()

GRIDDB_WEBAPI = os.environ.get('GRIDDB_WEBAPI_URL', 'http://localhost:8080')
GRIDDB_CLUSTER = os.environ.get('GRIDDB_CLUSTER_NAME', 'myCluster')
GRIDDB_USER = os.environ.get('GRIDDB_USER', 'admin')
GRIDDB_PASS = os.environ.get('GRIDDB_PASS', 'admin')

_credentials = base64.b64encode(f'{GRIDDB_USER}:{GRIDDB_PASS}'.encode()).decode()
AUTH_HEADER = {'Authorization': f'Basic {_credentials}'}

BASE = f'{GRIDDB_WEBAPI}/griddb/v2/{GRIDDB_CLUSTER}/dbs/public'

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

    async with httpx.AsyncClient(timeout=5.0, headers=AUTH_HEADER) as client:
        # Check 1: Cluster connectivity
        try:
            resp = await client.get(f'{BASE}/containers')
            checks['pingLatencyMs'] = int((time.monotonic() - start) * 1000)

            if resp.status_code != 200:
                checks['connectivity'] = f'error: HTTP {resp.status_code}'
                ok = False
            else:
                checks['connectivity'] = 'ok'
        except Exception as e:
            checks['connectivity'] = f'error: {e}'
            ok = False

        # Check 2: Time-series write probe
        try:
            write_start = time.monotonic()
            container = 'vigilmon_probe'
            ts = datetime.now(timezone.utc).isoformat()

            # Create container if needed (ignore errors — may already exist)
            await client.post(f'{BASE}/containers', json={
                'container_name': container,
                'container_type': 'TIME_SERIES',
                'rowkey': True,
                'columns': [
                    {'name': 'timestamp', 'type': 'TIMESTAMP'},
                    {'name': 'value', 'type': 'DOUBLE'},
                ],
            })

            write_resp = await client.put(
                f'{BASE}/containers/{container}/rows',
                json=[[ts, 1.0]]
            )
            checks['writeLatencyMs'] = int((time.monotonic() - write_start) * 1000)

            if write_resp.status_code not in (200, 201):
                checks['writeProbe'] = f'error: HTTP {write_resp.status_code}'
                ok = False
            else:
                checks['writeProbe'] = 'ok'
        except Exception as e:
            checks['writeProbe'] = f'error: {e}'
            ok = False

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

Java (GridDB native client)

// HealthController.java
import com.toshiba.mwcloud.gs.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.*;
import java.util.*;

@RestController
public class HealthController {

    private final GridStore gridStore;

    public HealthController(GridStore gridStore) {
        this.gridStore = gridStore;
    }

    @GetMapping("/health")
    public ResponseEntity<Map<String, Object>> health() {
        Map<String, Object> checks = new LinkedHashMap<>();
        boolean ok = true;
        long start = System.currentTimeMillis();

        // Check 1: Connectivity — open probe time-series container
        TimeSeries<Row> ts = null;
        try {
            ts = gridStore.getTimeSeries("vigilmon_probe", Row.class);
            if (ts == null) {
                // Create if not exists
                TimeSeriesProperties props = new TimeSeriesProperties();
                ts = gridStore.putTimeSeries("vigilmon_probe", Row.class, props, false);
            }
            checks.put("pingLatencyMs", System.currentTimeMillis() - start);
            checks.put("connectivity", "ok");
        } catch (GSException e) {
            checks.put("connectivity", "error: " + e.getMessage());
            ok = false;
        }

        // Check 2: Write probe
        if (ts != null) {
            try {
                long writeStart = System.currentTimeMillis();
                Row row = ts.createRow();
                row.setValue(0, new java.sql.Timestamp(System.currentTimeMillis()));
                row.setValue(1, 1.0);
                ts.put(row);
                checks.put("writeLatencyMs", System.currentTimeMillis() - writeStart);
                checks.put("writeProbe", "ok");
            } catch (GSException e) {
                checks.put("writeProbe", "error: " + e.getMessage());
                ok = false;
            }
        }

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("status", ok ? "ok" : "degraded");
        body.put("service", "griddb-service");
        body.put("checks", checks);
        return ResponseEntity.status(ok ? 200 : 503).body(body);
    }
}

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":"griddb-service","checks":{"pingLatencyMs":4,"connectivity":"ok","writeLatencyMs":6,"writeProbe":"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://iot-ingest.example.com/health).
  3. Check interval: 60 seconds.
  4. Response timeout: 15 seconds (GridDB WebAPI operations can take up to 5 seconds on heavily loaded nodes).
  5. Expected status: 200.
  6. JSON assertion: path status, expected value ok.
  7. Save the monitor.

Vigilmon probes from multiple geographic regions simultaneously, so a single-region blip will not page you. This matters for GridDB IoT deployments because sensor data bursts can briefly saturate write buffers — you want durable alerts on sustained failures, not noise from momentary backpressure.

Tip: Add a second JSON assertion on checks.writeProbe equals ok. This fires specifically when GridDB is reachable but time-series writes are failing — a failure mode that a plain HTTP status check won't catch.


Step 3: Heartbeat Monitoring for GridDB-Backed IoT Pipelines

GridDB deployments typically run continuous ingestion pipelines — MQTT consumers, sensor polling agents, or stream processors writing telemetry into time-series containers. These processes can silently stall when GridDB write buffer pressure builds or when a node partition becomes unavailable. Vigilmon's heartbeat monitors detect this: your pipeline pings Vigilmon after each successful batch commit, and missed pings trigger an alert.

Set Up the Heartbeat Monitor

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

Wire It Into Your Pipeline

Node.js:

import fetch from 'node-fetch';

const GRIDDB_WEBAPI = process.env.GRIDDB_WEBAPI_URL;
const GRIDDB_CLUSTER = process.env.GRIDDB_CLUSTER_NAME;
const HEARTBEAT_URL = process.env.VIGILMON_HEARTBEAT_URL;

async function ingestBatch(readings) {
  const authHeader = 'Basic ' + Buffer.from(
    `${process.env.GRIDDB_USER}:${process.env.GRIDDB_PASS}`
  ).toString('base64');

  const base = `${GRIDDB_WEBAPI}/griddb/v2/${GRIDDB_CLUSTER}/dbs/public`;

  // Write batch to time-series container
  const resp = await fetch(`${base}/containers/sensor_readings/rows`, {
    method: 'PUT',
    headers: { Authorization: authHeader, 'Content-Type': 'application/json' },
    body: JSON.stringify(readings.map(r => [r.timestamp, r.value, r.sensorId])),
    signal: AbortSignal.timeout(10000),
  });

  if (!resp.ok) {
    throw new Error(`Write failed: HTTP ${resp.status}`);
  }

  // Only ping Vigilmon after successful commit
  await fetch(HEARTBEAT_URL).catch(() => {});
}

Python:

import os, requests

GRIDDB_WEBAPI = os.environ.get('GRIDDB_WEBAPI_URL')
GRIDDB_CLUSTER = os.environ.get('GRIDDB_CLUSTER_NAME')
HEARTBEAT_URL = os.environ['VIGILMON_HEARTBEAT_URL']

import base64
_creds = base64.b64encode(
    f"{os.environ['GRIDDB_USER']}:{os.environ['GRIDDB_PASS']}".encode()
).decode()
AUTH = {'Authorization': f'Basic {_creds}'}
BASE = f'{GRIDDB_WEBAPI}/griddb/v2/{GRIDDB_CLUSTER}/dbs/public'

def ingest_batch(readings):
    rows = [[r['timestamp'], r['value'], r['sensor_id']] for r in readings]
    resp = requests.put(
        f'{BASE}/containers/sensor_readings/rows',
        headers={**AUTH, 'Content-Type': 'application/json'},
        json=rows,
        timeout=10
    )
    if not resp.ok:
        raise RuntimeError(f'Write failed: HTTP {resp.status_code}')

    # Only ping after successful commit
    requests.get(HEARTBEAT_URL, timeout=5)

Step 4: GridDB CLI and WebAPI for Direct Health Checks

For infrastructure-level monitoring alongside Vigilmon's application-layer checks:

# Check cluster status via GridDB WebAPI
curl -u admin:admin 'http://localhost:8080/griddb/v2/myCluster/dbs/public/containers' | python3 -m json.tool

# List time-series containers
curl -u admin:admin 'http://localhost:8080/griddb/v2/myCluster/dbs/public/containers?type=TIME_SERIES'

# Use gs_stat for node-level statistics
gs_stat -u admin/admin

# Key fields from gs_stat:
# checkpoint.status — IDLE (healthy) vs RUNNING (checkpoint in progress)
# cluster.nodeStatus — ACTIVE vs degraded states
# cluster.nodeList — count should match your configured cluster size
# memory.workMemory.limit/used — watch for approaching limits

Wrap gs_stat in a cron job or monitoring probe that pushes metrics to your observability stack — and use Vigilmon for the independent external HTTP check that confirms your application's data ingestion path is working end-to-end.


Step 5: Alerting Strategy

| Alert channel | When to use | |---|---| | PagerDuty | Connectivity probe fails (cluster unreachable) | | PagerDuty | writeProbe not ok (time-series writes failing) | | Slack webhook | Write latency > 50ms (write buffer pressure building) | | Slack + Email | Ingestion pipeline heartbeat missed once | | PagerDuty | Ingestion pipeline heartbeat missed twice consecutively | | Status page | Any IoT dashboard backed by GridDB |

Set response time thresholds as an early warning:

  • Alert at 20ms for write probe latency (GridDB writes should be near-instantaneous under normal load)
  • Alert at 5000ms for health endpoint response time (anything beyond that signals node distress or checkpoint I/O saturation)

What Vigilmon Catches That Internal Monitoring Misses

| Scenario | gs_stat / internal metrics | Vigilmon | |---|---|---| | Cluster reachable from ops host but not from app servers | No | HTTP probe from app's perspective | | Time-series write buffer full (ingestion silently dropping) | Internal metric only | Write probe fails — 503 fires | | Node partition unavailable (some containers inaccessible) | gs_stat node list | Container-level write probe fails | | IoT pipeline silently stopped | Process appears running | Heartbeat grace period expires → alert | | Write latency spike (checkpoint I/O saturation) | gs_stat metric only | Write latency threshold fires | | Monitoring pipeline (Prometheus/Grafana) fails | Alerts silently lost | Vigilmon is external — unaffected |


GridDB's IoT strengths — time-series container isolation, high-frequency ingestion, and ACID consistency — are only as reliable as the node serving your application's write path. Write buffer pressure, node checkpoint pauses, and network partition events can all cause data gaps in your sensor streams without any visible error. External health checks with Vigilmon give you the independent signal that data is actually making it into the database before gaps reach your dashboards.

Start monitoring your GridDB 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 →