Apache Mahout is the distributed machine learning library that runs collaborative filtering, clustering, and classification algorithms on Apache Spark (or Hadoop MapReduce) — but when a Mahout training job silently fails because a Spark executor ran out of memory, a Zookeeper session expired mid-job, or the Spark driver lost contact with the cluster manager, your ML model refresh pipeline stalls without any visible alarm. Mahout jobs often run on long intervals (nightly retraining, weekly model updates), meaning a single failure can go undetected for days.
Vigilmon gives you external visibility into Mahout job health through Spark cluster monitoring and heartbeat monitors that confirm each model training cycle completes successfully. This tutorial covers both.
Why Mahout Needs External Monitoring
Mahout's built-in observability (Spark History Server, YARN application tracking, Spark UI on port 4040) requires active polling during job execution. External monitoring with Vigilmon adds:
- Proactive alerting when Mahout model training jobs stop completing on schedule
- Spark cluster health checks before jobs are submitted
- Driver and executor failure detection through Spark REST API probes
- Pipeline heartbeats that fire only after a training job fully succeeds and model artifacts are written
- Cross-environment visibility confirming dev, staging, and prod training jobs all complete
These layers matter because Mahout/Spark failures manifest differently: driver OOM crashes surface immediately, executor failures trigger retries that may eventually succeed (masking intermittent issues), and YARN preemption kills jobs silently with a non-obvious exit code.
Step 1: Build a Mahout Ecosystem Health Endpoint
Mahout relies on Spark (or YARN) as its execution engine, so health monitoring targets the cluster infrastructure. Build a sidecar that probes the Spark standalone cluster or YARN ResourceManager, plus optionally the model artifact store.
Python Health Sidecar (Spark REST API)
# mahout_health.py
from flask import Flask, jsonify
import requests
import os
app = Flask(__name__)
# Spark standalone master REST API (port 6066 for v1, 8080 for web UI)
SPARK_MASTER_URL = os.environ.get('SPARK_MASTER_URL', 'http://spark-master.example.com:8080')
SPARK_MASTER_API = os.environ.get('SPARK_MASTER_API', 'http://spark-master.example.com:6066')
YARN_RM_HTTP = os.environ.get('YARN_RM_HTTP', 'http://resourcemanager.example.com:8088')
MODEL_STORE_PATH = os.environ.get('MODEL_STORE_PATH') # e.g. /models or S3 path prefix
@app.route('/health/mahout')
def mahout_health():
errors = []
# Try Spark standalone master first
spark_ok = False
try:
r = requests.get(f'{SPARK_MASTER_URL}/json/', timeout=5)
if r.status_code == 200:
data = r.json()
status = data.get('status', '')
if status == 'ALIVE':
spark_ok = True
else:
errors.append(f'spark_master_status_{status}')
else:
errors.append(f'spark_master_http_{r.status_code}')
except Exception as e:
errors.append(f'spark_master_unreachable:{e}')
# Fall back to YARN if standalone not configured
if not spark_ok and YARN_RM_HTTP:
try:
r = requests.get(f'{YARN_RM_HTTP}/ws/v1/cluster/info', timeout=5)
if r.status_code == 200:
rm_state = r.json().get('clusterInfo', {}).get('state', '')
if rm_state == 'STARTED':
spark_ok = True
else:
errors.append(f'yarn_rm_state_{rm_state}')
else:
errors.append(f'yarn_rm_http_{r.status_code}')
except Exception as e:
errors.append(f'yarn_rm_unreachable:{e}')
if errors:
return jsonify({'status': 'down', 'errors': errors}), 503
return jsonify({'status': 'ok', 'backend': 'spark' if spark_ok else 'unknown'}), 200
@app.route('/health/mahout/models')
def mahout_model_health():
"""Check that the latest model artifact was written recently."""
if not MODEL_STORE_PATH:
return jsonify({'status': 'skipped', 'reason': 'MODEL_STORE_PATH not configured'}), 200
import subprocess, time
try:
result = subprocess.run(
['hdfs', 'dfs', '-stat', '%Y', MODEL_STORE_PATH],
capture_output=True, text=True, timeout=10
)
if result.returncode != 0:
return jsonify({'status': 'down', 'error': 'model_store_missing'}), 503
mtime_ms = int(result.stdout.strip())
age_hours = (time.time() * 1000 - mtime_ms) / 3_600_000
max_age_hours = float(os.environ.get('MODEL_MAX_AGE_HOURS', '26'))
if age_hours > max_age_hours:
return jsonify({'status': 'stale', 'age_hours': round(age_hours, 1)}), 503
return jsonify({'status': 'ok', 'age_hours': round(age_hours, 1)}), 200
except Exception as e:
return jsonify({'status': 'down', 'error': str(e)}), 503
if __name__ == '__main__':
app.run(port=8095)
Node.js Health Sidecar
// mahout-health.js
const express = require('express');
const axios = require('axios');
const app = express();
const SPARK_MASTER_URL = process.env.SPARK_MASTER_URL || 'http://spark-master.example.com:8080';
const YARN_RM_HTTP = process.env.YARN_RM_HTTP || 'http://resourcemanager.example.com:8088';
app.get('/health/mahout', async (req, res) => {
const errors = [];
// Check Spark standalone master
try {
const r = await axios.get(`${SPARK_MASTER_URL}/json/`, { timeout: 5000 });
const status = r.data?.status;
if (status !== 'ALIVE') errors.push(`spark_master_status_${status}`);
} catch (e) {
// Fall back to YARN
try {
const rm = await axios.get(`${YARN_RM_HTTP}/ws/v1/cluster/info`, { timeout: 5000 });
const rmState = rm.data?.clusterInfo?.state;
if (rmState !== 'STARTED') errors.push(`yarn_rm_state_${rmState}`);
} catch (e2) {
errors.push(`cluster_unreachable:${e2.message}`);
}
}
if (errors.length) return res.status(503).json({ status: 'down', errors });
return res.status(200).json({ status: 'ok' });
});
app.listen(3011, () => console.log('mahout-health listening on 3011'));
Step 2: Configure Vigilmon HTTP Monitor for Mahout
- Log in to vigilmon.online and go to Monitors → New Monitor
- Choose HTTP / HTTPS
- Set the URL to your Mahout health endpoint:
https://your-app.example.com/health/mahout - Set the check interval to 2 minutes
- Under Expected response, configure:
- Status code:
200 - Response body contains:
"status":"ok" - Response time threshold:
8000ms
- Status code:
- Under Alert channels, assign your Slack or PagerDuty channel
- Save the monitor
Add a second monitor for model freshness:
- URL:
https://your-app.example.com/health/mahout/models - Expected:
200, body contains"status":"ok" - Interval: 30 minutes
- Alert channel: ML platform team Slack channel
And a direct Spark master probe:
- URL:
http://spark-master.example.com:8080/json/ - Expected:
200 - Interval: 1 minute
- Alert channel: Spark infrastructure on-call
Step 3: Heartbeat Monitoring for Mahout Training Pipelines
Cluster health checks confirm the infrastructure is alive — but they do not tell you whether your Mahout training jobs are completing. A Spark cluster can be healthy while your collaborative filtering job is stuck in a retry loop due to a corrupt training data partition, or producing models with empty output because of a silent feature extraction failure.
Vigilmon heartbeat monitors detect these silent failures: your training pipeline pings Vigilmon after each successful model write. If pings stop, Vigilmon fires an alert.
Set Up the Heartbeat Monitor
- In Vigilmon, go to Monitors → New Monitor → Heartbeat
- Set the name:
mahout-model-retraining - Set the expected interval: 24 hours (or your retraining cadence)
- Set the grace period: 3 hours
- Save — copy the heartbeat URL, e.g.
https://vigilmon.online/heartbeat/abc123xyz
Wire It Into Your Mahout Training Script
#!/bin/bash
# mahout_train.sh — Mahout training job with Vigilmon heartbeat
set -e
VIGILMON_HB="${VIGILMON_HEARTBEAT_URL}"
MAHOUT_HOME="/opt/mahout"
HDFS_INPUT="/user/mahout/ratings"
HDFS_OUTPUT="/user/mahout/models/$(date +%Y%m%d)"
echo "[$(date)] Starting Mahout collaborative filtering training..."
# Run Mahout distributed ALS training on Spark
spark-submit \
--class org.apache.mahout.sparkbindings.als.ALS \
--master yarn \
--deploy-mode cluster \
--num-executors 8 \
--executor-memory 4g \
--driver-memory 2g \
"$MAHOUT_HOME/lib/mahout-spark-*.jar" \
--input "$HDFS_INPUT" \
--output "$HDFS_OUTPUT" \
--rank 20 \
--lambda 0.065 \
--iterations 10
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
echo "[$(date)] Training complete. Pinging Vigilmon heartbeat..."
curl -s --max-time 10 "$VIGILMON_HB" > /dev/null
echo "[$(date)] Done."
else
echo "[$(date)] Training FAILED (exit $EXIT_CODE). Heartbeat NOT sent."
exit $EXIT_CODE
fi
PySpark / Mahout Python API with Heartbeat
# mahout_train.py — Mahout-on-Spark via SparkContext + Vigilmon heartbeat
from pyspark import SparkContext, SparkConf
import requests
import os
VIGILMON_HB = os.environ['VIGILMON_HEARTBEAT_URL']
HDFS_INPUT = os.environ.get('HDFS_INPUT', 'hdfs:///user/mahout/ratings')
HDFS_OUTPUT = os.environ.get('HDFS_OUTPUT', 'hdfs:///user/mahout/models/latest')
conf = SparkConf().setAppName('mahout-als-training')
sc = SparkContext(conf=conf)
try:
ratings = sc.textFile(HDFS_INPUT)
# Example: parse and train using Mahout's distributed ALS via Spark RDD API
parsed = ratings.map(lambda line: line.split(',')) \
.map(lambda parts: (int(parts[0]), int(parts[1]), float(parts[2])))
# ... ALS training logic via org.apache.mahout integration ...
parsed.saveAsTextFile(HDFS_OUTPUT)
# Ping Vigilmon only after model artifacts are successfully written
requests.get(VIGILMON_HB, timeout=10)
print("Model written to HDFS. Vigilmon heartbeat sent.")
finally:
sc.stop()
Airflow DAG Integration
# dags/mahout_training_dag.py
from airflow import DAG
from airflow.operators.bash import BashOperator
from datetime import datetime, timedelta
with DAG(
'mahout_als_retraining',
schedule_interval='0 2 * * *',
start_date=datetime(2026, 1, 1),
catchup=False,
) as dag:
train = BashOperator(
task_id='run_mahout_training',
bash_command='/opt/pipelines/mahout_train.sh',
env={'VIGILMON_HEARTBEAT_URL': '{{ var.value.vigilmon_mahout_hb }}'},
)
Step 4: Alert Routing for Mahout Failures
Mahout failures have distinct urgency levels: Spark cluster outages block all training immediately, while stale model artifacts degrade recommendation quality gradually.
Configure alert routing in Vigilmon:
| Monitor | Alert Channel | Priority |
|---|---|---|
| Spark master web UI (port 8080) | Slack + PagerDuty | P1 |
| Mahout cluster health /health/mahout | Slack + PagerDuty | P1 |
| Model freshness check /health/mahout/models | Slack | P2 |
| Heartbeat: nightly ALS retraining | Slack + email | P2 |
| Heartbeat: weekly clustering job | Email | P3 |
Set response time thresholds for early warning:
- Alert at
6000msfor the cluster health endpoint (slow Spark REST responses signal driver memory pressure) - Alert at
15000msfor the model store check (slow HDFS stat calls signal DataNode or NameNode load)
For recommendation systems with SLA requirements, configure a model staleness budget: if a model artifact is older than 30 hours (exceeding a 24-hour retraining cycle plus a 6-hour grace window), recommendation quality degradation becomes measurable — escalate immediately rather than waiting for the next scheduled check.
Summary
Mahout failures span Spark/YARN infrastructure (immediate impact) and model pipeline execution (gradual quality degradation). External monitoring catches both:
| Monitor Type | What It Covers | |---|---| | HTTP monitor on Spark master web UI | Spark cluster availability, master liveness | | HTTP monitor on cluster health endpoint | Spark + YARN reachability, backend selection | | HTTP monitor on model freshness endpoint | Model artifact age, HDFS write confirmation | | Heartbeat monitor | Training job completion, ML pipeline liveness |
Get started free at vigilmon.online — your first Mahout training pipeline monitor is running in under two minutes.