Apache Toree is a Jupyter kernel that enables data scientists and engineers to write and execute Scala, Python, R, and SQL code in Jupyter notebooks against remote Apache Spark clusters. Unlike local kernels, Toree maintains a persistent Spark application context inside the remote cluster, allowing notebooks to share the same SparkSession across cells and across users on multi-tenant platforms. When Toree's kernel process crashes, when its ZMQ transport becomes unreachable, or when the underlying Spark cluster loses the application context, every notebook connected to that kernel silently fails — cells stop executing, job submissions hang indefinitely, and users receive cryptic connection errors with no clear indication of what failed.
Vigilmon gives you external visibility into Toree kernel availability and Spark cluster connectivity through HTTP probe monitoring and heartbeat monitoring. This tutorial walks through both.
Why Apache Toree Monitoring Matters
Toree's multi-layered architecture introduces failure modes that standard notebook platform monitoring misses:
- Kernel process crash kills the Spark application context — all connected notebooks lose their SparkSession and any in-memory DataFrames; users must reconnect and re-run all initialization cells
- ZMQ transport failure causes Jupyter to show the kernel as "busy" indefinitely while the kernel is actually dead — users cannot interrupt or restart the kernel through the UI
- Spark cluster disconnection leaves the Toree kernel process alive but unable to submit jobs — notebooks appear to run but cells executing Spark code never return
- YARN ResourceManager unavailability prevents Toree from allocating executors — the kernel starts but all Spark operations fail immediately with resource allocation errors
- Toree Gateway (Enterprise Gateway) unavailability on JupyterHub deployments blocks all new kernel starts — existing kernels may continue but no new notebook sessions can begin
External monitoring through Vigilmon verifies whether Toree and its Spark backend are reachable and operational — independently of Jupyter's kernel status display.
Step 1: Build a Toree Health Endpoint
Apache Toree integrates with JupyterHub and Jupyter Enterprise Gateway. The Jupyter REST API exposes kernel status. Test kernel availability through the Jupyter API:
# List running kernels via Jupyter REST API
curl -i http://your-jupyter-host:8888/api/kernels \
-H "Authorization: token YOUR_JUPYTER_TOKEN"
# HTTP/1.1 200 OK (returns list of running kernels)
For a deeper check that verifies Toree's Spark connectivity, build a sidecar that starts a Toree kernel, executes a probe, and checks the result:
Node.js Sidecar Health Endpoint
// toree-health.js — verifies Toree kernel availability via Jupyter API
const express = require('express');
const axios = require('axios');
const app = express();
const JUPYTER_URL = process.env.JUPYTER_URL || 'http://localhost:8888';
const JUPYTER_TOKEN = process.env.JUPYTER_TOKEN || '';
const headers = { Authorization: `token ${JUPYTER_TOKEN}` };
app.get('/health/toree', async (req, res) => {
try {
// Check Jupyter API is reachable
await axios.get(`${JUPYTER_URL}/api`, { headers, timeout: 5000 });
// List kernelspecs — verify the Toree kernel spec is registered
const specsRes = await axios.get(`${JUPYTER_URL}/api/kernelspecs`, { headers, timeout: 10000 });
const specs = specsRes.data.kernelspecs || {};
const toreeSpecs = Object.keys(specs).filter(k => k.toLowerCase().includes('toree') || k.toLowerCase().includes('scala'));
if (toreeSpecs.length === 0) {
return res.status(503).json({
status: 'degraded',
reason: 'no_toree_kernelspec',
available: Object.keys(specs),
});
}
// Check running kernels — any Toree kernels that are currently alive?
const kernelsRes = await axios.get(`${JUPYTER_URL}/api/kernels`, { headers, timeout: 10000 });
const kernels = kernelsRes.data || [];
const toreeKernels = kernels.filter(k => toreeSpecs.some(s => k.name === s));
return res.status(200).json({
status: 'ok',
toree_kernelspecs: toreeSpecs,
running_toree_kernels: toreeKernels.length,
});
} catch (err) {
return res.status(503).json({ status: 'down', error: err.message });
}
});
app.listen(3001);
Python Sidecar Health Endpoint
# toree_health.py — FastAPI sidecar for Apache Toree health
import os
import httpx
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
JUPYTER_URL = os.environ.get("JUPYTER_URL", "http://localhost:8888")
JUPYTER_TOKEN = os.environ.get("JUPYTER_TOKEN", "")
HEADERS = {"Authorization": f"token {JUPYTER_TOKEN}"}
@app.get("/health/toree")
async def toree_health():
async with httpx.AsyncClient(timeout=10.0) as client:
# Check Jupyter API reachability
try:
r = await client.get(f"{JUPYTER_URL}/api", headers=HEADERS)
if r.status_code >= 500:
return JSONResponse(status_code=503,
content={"status": "down", "check": "jupyter_api",
"code": r.status_code})
except Exception as e:
return JSONResponse(status_code=503,
content={"status": "down", "check": "jupyter_api", "error": str(e)})
# Verify Toree kernelspec is registered
try:
r = await client.get(f"{JUPYTER_URL}/api/kernelspecs", headers=HEADERS)
r.raise_for_status()
specs = r.json().get("kernelspecs", {})
except Exception as e:
return JSONResponse(status_code=503,
content={"status": "down", "check": "kernelspecs", "error": str(e)})
toree_specs = [k for k in specs if "toree" in k.lower() or "scala" in k.lower()]
if not toree_specs:
return JSONResponse(status_code=503, content={
"status": "degraded",
"reason": "no_toree_kernelspec",
"available_specs": list(specs.keys()),
})
# Check running kernels
try:
r = await client.get(f"{JUPYTER_URL}/api/kernels", headers=HEADERS)
r.raise_for_status()
kernels = r.json()
except Exception as e:
return JSONResponse(status_code=503,
content={"status": "degraded", "check": "kernels", "error": str(e)})
running_toree = [k for k in kernels if k.get("name") in toree_specs]
return {
"status": "ok",
"toree_kernelspecs": toree_specs,
"running_toree_kernels": len(running_toree),
}
Deploy and verify:
curl -i https://your-app.example.com/health/toree
# HTTP/1.1 200 OK
# {"status":"ok","toree_kernelspecs":["toree_scala","toree_pyspark"],"running_toree_kernels":3}
Step 2: Configure a Vigilmon HTTP Monitor for Toree
- Log in to vigilmon.online and go to Monitors → New Monitor
- Choose HTTP / HTTPS
- Set the URL to your health endpoint:
https://your-app.example.com/health/toree - Set the check interval to 1 minute
- Under Expected response, configure:
- Status code:
200 - Response body contains:
"status":"ok" - Response time threshold:
15000ms
- Status code:
- Under Alert channels, assign your Slack or email channel
- Save the monitor
For a lighter-weight check against the Jupyter API directly:
- Create a second monitor pointing to
http://your-jupyter-host:8888/api - Set the expected status code to
200 - Add a response body check:
version - Set the response time threshold to
3000ms
Failure Coverage
| Failure Mode | Jupyter UI Monitor | Vigilmon | |---|---|---| | Jupyter server crash | ✓ | ✓ | | Toree kernelspec removed/corrupted | ✗ | ✓ | | All Toree kernels dead (no running instances) | ✗ | ✓ | | Spark cluster disconnected (kernel alive, jobs hang) | ✗ | ✓ (via heartbeat) | | Enterprise Gateway unavailable | ✗ | ✓ |
Step 3: Heartbeat Monitoring for Toree Spark Job Execution
The most critical failure mode for Toree is silent Spark disconnection — the kernel process is alive, the Jupyter API reports it as running, but all Spark operations hang because the cluster is unreachable or the YARN application has been killed. Vigilmon's heartbeat monitor detects this hang by requiring periodic proof of successful job execution.
Set Up the Heartbeat Monitor
- In Vigilmon, go to Monitors → New Monitor → Heartbeat
- Set the name:
toree-spark-execution - Set the expected interval: 10 minutes (match your notebook execution cadence)
- Set the grace period: 20 minutes
- Save — copy the heartbeat URL, e.g.
https://vigilmon.online/heartbeat/abc123xyz
Wire It Into a Jupyter Notebook or Scheduled Script
Use the Jupyter kernel client Python library to execute a probe against a Toree kernel and ping Vigilmon on success:
# toree_heartbeat_probe.py — executes a Spark probe via Toree and pings Vigilmon
import os
import time
import requests
from jupyter_client import KernelManager
HEARTBEAT_URL = os.environ["VIGILMON_HEARTBEAT_URL"]
JUPYTER_URL = os.environ.get("JUPYTER_URL", "http://localhost:8888")
JUPYTER_TOKEN = os.environ.get("JUPYTER_TOKEN", "")
# A minimal Spark probe that verifies Spark context is alive
SPARK_PROBE_CODE = """
val rdd = spark.sparkContext.parallelize(Seq(1, 2, 3))
val count = rdd.count()
println(s"probe:count=$count")
"""
def probe_toree_kernel():
km = KernelManager(kernel_name="toree_scala")
km.start_kernel()
kc = km.client()
kc.start_channels()
try:
kc.wait_for_ready(timeout=60)
msg_id = kc.execute(SPARK_PROBE_CODE)
# Wait for execution to complete (up to 5 minutes)
deadline = time.time() + 300
while time.time() < deadline:
msg = kc.get_iopub_msg(timeout=10)
if msg["msg_type"] == "execute_reply":
status = msg["content"]["status"]
if status == "ok":
requests.get(HEARTBEAT_URL, timeout=5)
return True
else:
return False
finally:
kc.stop_channels()
km.shutdown_kernel(now=True)
return False
if __name__ == "__main__":
success = probe_toree_kernel()
print(f"Probe {'succeeded' if success else 'FAILED'}")
Simpler Shell-Based Heartbeat via nbconvert
For lighter-weight probing without kernel client code, use jupyter nbconvert to execute a probe notebook:
#!/bin/bash
# toree_probe.sh — executes a probe notebook and pings Vigilmon on success
HEARTBEAT_URL="${VIGILMON_HEARTBEAT_URL}"
jupyter nbconvert \
--to notebook \
--execute \
--ExecutePreprocessor.timeout=300 \
--ExecutePreprocessor.kernel_name=toree_scala \
/probes/spark_probe.ipynb \
--output /tmp/spark_probe_output.ipynb 2>&1
if [ $? -eq 0 ]; then
curl -fsS "${HEARTBEAT_URL}" > /dev/null
echo "Probe succeeded. Vigilmon pinged."
else
echo "Probe FAILED. Vigilmon NOT pinged." >&2
exit 1
fi
Schedule this probe via cron or your workflow scheduler at a frequency shorter than the heartbeat interval.
Step 4: Monitor Multiple Toree and JupyterHub Components
A production Toree/JupyterHub deployment spans multiple services. Monitor each independently:
[jupyterhub] :8000/hub/api
[jupyter-single-user] :8888/api
[toree-health] /health/toree (sidecar)
[enterprise-gateway] :8888/api/kernelspecs (Enterprise Gateway)
[spark-master] :8080 (Spark master UI)
[yarn-resourcemanager] :8088/ws/v1/cluster/info
[toree-execution] (heartbeat monitor)
Name monitors with the bracket convention so alert notifications immediately identify which layer failed.
Group all Toree monitors into a Vigilmon Status Page for your data platform team and share the URL with notebook platform users so they can self-diagnose before filing support tickets.
Step 5: Alert Routing for Toree Outages
Toree outages block data scientists from running any Spark workloads from notebooks — feature engineering pipelines stall, ad-hoc query results stop, and ML training jobs submitted from notebooks fail to start. On shared JupyterHub deployments, a single Toree kernel crash can affect dozens of concurrent users.
Configure alert channels in Vigilmon:
- JupyterHub API monitor → Slack + PagerDuty (P1 — platform login and session creation blocked)
- Toree health sidecar → Slack + PagerDuty (P1 — no Toree kernels available)
- Spark execution heartbeat → Slack + PagerDuty (P1 — Spark jobs not completing)
- Spark master / YARN ResourceManager monitors → Slack (P2 — compute cluster degraded)
Set response time thresholds carefully:
- Alert at
10000mson the Toree health sidecar — slow kernelspec enumeration indicates the Jupyter server is under load or the Enterprise Gateway is overloaded - Alert at
3000mson the JupyterHub API — the hub API should respond instantly; slowness here signals proxy or database issues - Alert at
2000mson the Jupyter single-user server root — a slow response on/apiindicates notebook server load or memory pressure
Summary
Apache Toree failures are silent and impactful — kernel crashes leave users staring at spinning cells, Spark disconnections cause jobs to hang indefinitely, and Jupyter shows the kernel as "running" throughout. Vigilmon monitors the full path from JupyterHub availability through active Spark job execution.
| Monitor Type | What It Covers |
|---|---|
| HTTP monitor on JupyterHub /hub/api | Hub availability, session creation |
| HTTP monitor on /health/toree | Toree kernelspec availability, running kernels |
| Heartbeat monitor | Spark job execution liveness through Toree |
| Infrastructure monitors (Spark, YARN) | Compute cluster availability |
Get started free at vigilmon.online — your first Toree monitor takes under two minutes to configure.