Apache Spark GraphX is the graph computation API built into Apache Spark — enabling distributed graph analytics, PageRank, connected components, and triangle counting at petabyte scale. But when a GraphX job hangs on a superstep barrier, a Spark driver OOMs processing a large graph RDD, or a graph analytics pipeline stalls mid-iteration, your downstream systems receive no graph updates. Recommendation engines, fraud detection graphs, and knowledge graph pipelines degrade silently while Spark's UI may still show an "active" application that never completes.
Vigilmon gives you external visibility into Spark GraphX job health through HTTP probe monitoring (via Spark's REST API or a custom job health sidecar) and heartbeat monitoring for your graph analytics pipeline applications. This tutorial covers both.
Why Spark GraphX Needs External Monitoring
Spark's built-in tooling (Web UI on port 4040, History Server, Spark REST API on port 4040) provides rich internal diagnostics — but only if someone is watching. External monitoring with Vigilmon adds:
- Proactive alerting when the Spark driver becomes unreachable (immediately visible to downstream graph consumers as missing or stale graph outputs)
- Job completion detection by verifying graph computation jobs finish within expected durations rather than looping on retries or hanging on barrier synchronization
- Heartbeat monitoring so you know immediately when a GraphX pipeline application stops producing graph snapshots or checkpoints
- Multi-region availability checking from outside your Spark cluster network
These layers work together: the Spark driver can be reachable while a GraphX Pregel iteration is deadlocked, and Spark metrics can look healthy while your graph pipeline has produced no output for hours.
Step 1: Build a GraphX Health Endpoint
Spark exposes a REST API (default port 4040 on the driver) for application, job, and stage status. Wrap it in a health sidecar for precise GraphX pipeline status.
Using the Spark REST API Directly
# List active Spark applications
curl -i http://spark-driver.example.com:4040/api/v1/applications
# Check active jobs for an application
curl -i "http://spark-driver.example.com:4040/api/v1/applications/app-20240101000000-0001/jobs"
# Check stages (useful for detecting GraphX superstep stalls)
curl -i "http://spark-driver.example.com:4040/api/v1/applications/app-20240101000000-0001/stages"
Point a Vigilmon monitor at the applications endpoint for basic driver reachability. For deeper GraphX pipeline status, use a custom sidecar.
Custom Python Health Sidecar
For GraphX-specific health checks — job completion rate, stalled stage detection, and pipeline freshness:
# graphx_health.py
from flask import Flask, jsonify
import requests
import os
import time
app = Flask(__name__)
SPARK_UI_URL = os.environ.get('SPARK_UI_URL', 'http://localhost:4040')
MAX_STALE_SECONDS = int(os.environ.get('MAX_STALE_SECONDS', '3600'))
LAST_COMPLETED_FILE = os.environ.get('LAST_COMPLETED_FILE', '/tmp/graphx_last_completed')
@app.route('/health/graphx')
def graphx_health():
issues = []
# Check Spark driver reachability
try:
apps_resp = requests.get(f'{SPARK_UI_URL}/api/v1/applications', timeout=5)
apps_resp.raise_for_status()
apps = apps_resp.json()
except requests.RequestException as e:
return jsonify({
'status': 'down',
'reason': 'spark_driver_unreachable',
'error': str(e),
}), 503
if not apps:
return jsonify({
'status': 'down',
'reason': 'no_active_spark_applications',
}), 503
app_id = apps[0]['id']
active_jobs = []
failed_jobs = []
try:
jobs_resp = requests.get(
f'{SPARK_UI_URL}/api/v1/applications/{app_id}/jobs',
timeout=5
)
jobs_resp.raise_for_status()
jobs = jobs_resp.json()
active_jobs = [j for j in jobs if j.get('status') == 'RUNNING']
failed_jobs = [j for j in jobs if j.get('status') == 'FAILED']
except requests.RequestException as e:
issues.append(f'jobs_api_unreachable: {str(e)}')
# Check for stalled stages (GraphX superstep hangs)
stalled_stages = []
try:
stages_resp = requests.get(
f'{SPARK_UI_URL}/api/v1/applications/{app_id}/stages',
timeout=5
)
stages_resp.raise_for_status()
stages = stages_resp.json()
now_ms = int(time.time() * 1000)
for stage in stages:
if stage.get('status') == 'ACTIVE':
submission_time = stage.get('submissionTime', now_ms)
if isinstance(submission_time, str):
# Parse ISO format if needed
pass
duration_s = (now_ms - submission_time) / 1000 if isinstance(submission_time, int) else 0
if duration_s > MAX_STALE_SECONDS:
stalled_stages.append({
'stage_id': stage.get('stageId'),
'duration_s': int(duration_s),
})
except requests.RequestException as e:
issues.append(f'stages_api_unreachable: {str(e)}')
if stalled_stages:
issues.append(f'stalled_stages_detected: {stalled_stages}')
if failed_jobs:
issues.append(f'failed_jobs: {[j.get("jobId") for j in failed_jobs]}')
if issues:
return jsonify({
'status': 'degraded',
'issues': issues,
'active_jobs': len(active_jobs),
'failed_jobs': len(failed_jobs),
}), 503
return jsonify({
'status': 'ok',
'app_id': app_id,
'active_jobs': len(active_jobs),
'stalled_stages': len(stalled_stages),
}), 200
if __name__ == '__main__':
app.run(port=8095)
Scala/JVM Health Sidecar
For teams running GraphX from Scala applications, expose a health endpoint directly from the Spark driver process:
// GraphXHealthServer.scala
import org.http4s._
import org.http4s.dsl.io._
import org.http4s.circe._
import io.circe.syntax._
import cats.effect.IO
import org.apache.spark.SparkContext
import org.apache.spark.graphx._
object GraphXHealthServer {
def healthRoutes(sc: SparkContext, lastCheckpointTime: () => Long): HttpRoutes[IO] =
HttpRoutes.of[IO] {
case GET -> Root / "health" / "graphx" =>
val staleMs = System.currentTimeMillis() - lastCheckpointTime()
val staleSeconds = staleMs / 1000
if (sc.isStopped) {
val body = Map(
"status" -> "down",
"reason" -> "spark_context_stopped"
).asJson
ServiceUnavailable(body)
} else if (staleSeconds > 3600) {
val body = Map(
"status" -> "degraded",
"reason" -> "graph_checkpoint_stale",
"stale_seconds" -> staleSeconds.toString
).asJson
ServiceUnavailable(body)
} else {
val body = Map(
"status" -> "ok",
"spark_app_id" -> sc.applicationId,
"last_checkpoint_seconds_ago" -> staleSeconds.toString
).asJson
Ok(body)
}
}
}
Step 2: Configure Vigilmon HTTP Monitor for GraphX
- Log in to vigilmon.online and go to Monitors → New Monitor
- Choose HTTP / HTTPS
- Set the URL to your GraphX health endpoint:
https://your-app.example.com/health/graphx - Set the check interval to 2 minutes
- Under Expected response, configure:
- Status code:
200 - Response body contains:
"status":"ok" - Response time threshold:
15000ms(graph computation health checks involve RDD metadata queries that can be slow on large graphs)
- Status code:
- Under Alert channels, assign your Slack or PagerDuty channel
- Save the monitor
Add a second monitor for the Spark REST API directly:
- URL:
http://spark-driver.example.com:4040/api/v1/applications - Expected:
200 - Interval:
5 minutes - Alert channel: data-engineering channel
Vigilmon's multi-region probe consensus prevents false positives from transient network blips between probe nodes and your Spark cluster.
Step 3: Heartbeat Monitoring for GraphX Analytics Pipelines
Driver reachability checks are necessary — but not sufficient. A GraphX Pregel algorithm can run indefinitely on a malformed superstep without the driver appearing down. A graph analytics pipeline can stall between computation phases without any visible error. Vigilmon heartbeat monitors detect these silent stalls: your GraphX pipeline pings Vigilmon after each graph computation batch or checkpoint write. If pings stop, Vigilmon fires an alert.
Set Up the Heartbeat Monitor
- In Vigilmon, go to Monitors → New Monitor → Heartbeat
- Set the name:
graphx-analytics-pipeline - Set the expected interval: 60 minutes (adjust to your graph computation batch duration)
- Set the grace period: 30 minutes
- Save — copy the heartbeat URL, e.g.
https://vigilmon.online/heartbeat/abc123xyz
Wire It Into Your GraphX Pipeline (Python / PySpark)
# graphx_pipeline.py
from pyspark.sql import SparkSession
import requests
import os
HEARTBEAT_URL = os.environ['VIGILMON_HEARTBEAT_URL']
def ping_vigilmon():
try:
requests.get(HEARTBEAT_URL, timeout=5)
except Exception:
pass # Non-fatal: don't interrupt the graph computation
spark = SparkSession.builder.appName('GraphXPipeline').getOrCreate()
sc = spark.sparkContext
def run_graph_batch(edges_path: str, output_path: str):
# Load edges RDD
edges = sc.textFile(edges_path).map(
lambda line: line.split(',')
).map(
lambda parts: (int(parts[0]), int(parts[1]))
)
# Build GraphX graph via GraphFrames (PySpark bridge)
from graphframes import GraphFrame
vertices = edges.flatMap(lambda e: [e[0], e[1]]).distinct() \
.map(lambda v: (v,)).toDF(['id'])
edges_df = edges.toDF(['src', 'dst'])
g = GraphFrame(vertices, edges_df)
# Run PageRank
results = g.pageRank(resetProbability=0.15, maxIter=10)
results.vertices.write.mode('overwrite').parquet(output_path)
# Ping Vigilmon after successful computation
ping_vigilmon()
print(f'Graph batch complete, results written to {output_path}')
Wire It Into Your GraphX Pipeline (Scala)
// GraphXPipeline.scala
import org.apache.spark.graphx._
import org.apache.spark.rdd.RDD
import scala.util.Try
import java.net.URL
object GraphXPipeline {
val heartbeatUrl = sys.env("VIGILMON_HEARTBEAT_URL")
def pingVigilmon(): Unit = Try {
new URL(heartbeatUrl).openStream().close()
} // Non-fatal: swallow errors so graph computation isn't interrupted
def runPageRankBatch(graph: Graph[Long, Double]): Unit = {
val ranks = graph.pageRank(0.0001).vertices
ranks.saveAsTextFile(s"hdfs:///output/pagerank-${System.currentTimeMillis()}")
// Ping after successful graph write
pingVigilmon()
println("PageRank batch complete")
}
def runConnectedComponents(graph: Graph[Long, Double]): Unit = {
val cc = graph.connectedComponents().vertices
cc.saveAsTextFile(s"hdfs:///output/cc-${System.currentTimeMillis()}")
pingVigilmon()
println("Connected components batch complete")
}
}
Step 4: Alert Routing for GraphX Failures
GraphX failures cascade: a stalled Pregel iteration blocks all downstream graph consumers; a driver OOM terminates all active graph computations; a checkpoint failure causes graph state loss on the next incremental computation. Alert routing should reflect this cascade.
Configure alert routing in Vigilmon:
| Monitor | Alert Channel | Priority |
|---|---|---|
| GraphX health /health/graphx | Slack + PagerDuty | P1 |
| Spark REST API /api/v1/applications | Slack | P2 |
| Heartbeat: graph analytics pipeline | Slack + email | P2 |
| Heartbeat: graph export/ETL job | Email | P3 |
Set response time thresholds for early warning:
- Alert at
10000msfor the health endpoint (slow metadata queries signal memory pressure or GC pauses on the driver) - Alert at
60000msfor long-running graph computation batches
For bursty graph workloads, enable two consecutive failures before alerting — Spark drivers can briefly become unresponsive during large graph RDD partition shuffles without representing a full outage.
Summary
GraphX failures are silent at the graph computation layer. External monitoring catches driver crashes, Pregel superstep stalls, and analytics pipeline stalls before they degrade downstream graph consumers or leave recommendation and fraud detection systems running on stale graph data:
| Monitor Type | What It Covers |
|---|---|
| HTTP monitor on /health/graphx | Driver liveness, stalled stage detection, failed job alerts |
| HTTP monitor on Spark REST API | Driver-level availability for direct API callers |
| Heartbeat monitor | Graph computation batch progress, checkpoint write liveness |
Get started free at vigilmon.online — your first GraphX monitor is running in under two minutes.