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
- Log in to Vigilmon → Add Monitor → HTTP.
- URL: your service's health endpoint (e.g.
https://iot-ingest.example.com/health). - Check interval: 60 seconds.
- Response timeout: 15 seconds (GridDB WebAPI operations can take up to 5 seconds on heavily loaded nodes).
- Expected status:
200. - JSON assertion: path
status, expected valueok. - 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.writeProbeequalsok. 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
- In Vigilmon, go to Monitors → New Monitor → Heartbeat.
- Set the name:
griddb-iot-pipeline. - Set the expected interval: 2 minutes (adjust to your ingest frequency).
- Set the grace period: 5 minutes.
- 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
20msfor write probe latency (GridDB writes should be near-instantaneous under normal load) - Alert at
5000msfor 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.