Hamilton is a lightweight Python library from DAGWorks that lets you define data transformations and ML feature pipelines as directed acyclic graphs (DAGs) by writing annotated Python functions. Each function becomes a node; Hamilton resolves dependencies automatically and executes the graph. When Hamilton pipelines run on a schedule — feeding feature stores, refreshing ML model inputs, or powering ETL workflows — silent failures are the most dangerous failure mode: a node that raises an unhandled exception, a driver process that exits without logging, or a dataflow graph that produces stale outputs without any alerting. Vigilmon gives you external monitoring for Hamilton pipeline execution health, node-level failure detection, dataflow freshness, and driver process availability so your data and ML teams catch pipeline failures before they reach downstream consumers.
What You'll Set Up
- Hamilton driver process health via cron heartbeats
- Pipeline execution success and failure detection
- Node-level error monitoring within dataflows
- Dataflow output freshness verification
- Resource utilization during pipeline execution
Prerequisites
- Hamilton 1.x+ installed (
pip install sf-hamilton) - Pipeline runs on a schedule (cron, Airflow, or similar)
- Write access to pipeline output storage (files, database, or S3)
- A free Vigilmon account
Step 1: Monitor Pipeline Driver Process Health
Hamilton pipelines execute synchronously within a Python driver process. When the driver crashes mid-execution — due to an OOM kill, an unhandled exception in the top-level script, or a transient import error — the pipeline produces no output and leaves no signal unless you have external monitoring. Start with a heartbeat that only fires on successful completion:
- In Vigilmon, click Add Monitor → Cron Heartbeat.
- Set Expected ping interval to match your pipeline schedule (e.g.,
10 minutesfor a pipeline that runs every 10 minutes). - Copy the heartbeat URL (e.g.,
https://vigilmon.online/heartbeat/abc123).
Wrap your Hamilton driver invocation:
#!/usr/bin/env python3
# run_pipeline.py
import requests
import sys
import traceback
from hamilton import driver
import my_pipeline_module # Your Hamilton module with transform functions
HEARTBEAT_URL = "https://vigilmon.online/heartbeat/abc123"
def run():
config = {
"environment": "production",
"date": "2024-01-15",
}
dr = driver.Builder().with_modules(my_pipeline_module).with_config(config).build()
# Execute the DAG and request final outputs
output_vars = ["feature_a", "feature_b", "model_input"]
inputs = {"raw_data_path": "/data/raw/latest.parquet"}
results = dr.execute(output_vars, inputs=inputs)
# Ping Vigilmon only on successful completion
requests.get(HEARTBEAT_URL, timeout=10)
print(f"Pipeline complete. Outputs: {list(results.keys())}")
return results
if __name__ == "__main__":
try:
run()
except Exception as e:
print(f"Pipeline failed: {e}", file=sys.stderr)
traceback.print_exc()
sys.exit(1)
If the heartbeat stops arriving, Vigilmon alerts you within one missed interval — far faster than discovering stale model features in production hours later.
Step 2: Detect Node-Level Failures in the Dataflow Graph
Hamilton catches exceptions in individual nodes and propagates them up through the DAG. However, some failures are silent: a node that returns None when it should return a DataFrame, a transform that produces an empty result due to an upstream data issue, or a validation step that short-circuits without raising an exception. Add node-level validation within your pipeline module:
# my_pipeline_module.py
import pandas as pd
from hamilton.function_modifiers import check_output
def raw_events(raw_data_path: str) -> pd.DataFrame:
"""Load raw event data from storage."""
df = pd.read_parquet(raw_data_path)
if df.empty:
raise ValueError(f"raw_events: empty DataFrame loaded from {raw_data_path}")
return df
def filtered_events(raw_events: pd.DataFrame) -> pd.DataFrame:
"""Filter events to the last 24 hours."""
result = raw_events[raw_events["timestamp"] > pd.Timestamp.now() - pd.Timedelta("24h")]
if len(result) < 100:
raise ValueError(
f"filtered_events: suspiciously low row count ({len(result)}). "
f"Expected >= 100 rows in the last 24 hours."
)
return result
@check_output(data_type=pd.Series, range=(0.0, 1.0))
def conversion_rate(filtered_events: pd.DataFrame) -> pd.Series:
"""Compute per-user conversion rate."""
return filtered_events.groupby("user_id")["converted"].mean()
Capture node-level failures in the driver wrapper and report them:
import requests
import json
from datetime import datetime
HEARTBEAT_URL = "https://vigilmon.online/heartbeat/abc123"
FAILURE_LOG = "/var/log/hamilton-failures.jsonl"
def run_with_node_tracking():
errors = []
try:
dr = driver.Builder().with_modules(my_pipeline_module).with_config({}).build()
results = dr.execute(["conversion_rate"], inputs={"raw_data_path": "/data/raw/latest.parquet"})
requests.get(HEARTBEAT_URL, timeout=10)
except Exception as e:
# Log structured failure for downstream alerting
failure = {
"timestamp": datetime.utcnow().isoformat(),
"pipeline": "feature_pipeline",
"error": str(e),
"node": getattr(e, "__cause__", None) and str(e.__cause__),
}
with open(FAILURE_LOG, "a") as f:
f.write(json.dumps(failure) + "\n")
raise
The structured JSONL log feeds into your log aggregator (Datadog, Loki, CloudWatch) for node-level failure analysis alongside the Vigilmon heartbeat miss for the operational alert.
Step 3: Monitor Dataflow Output Freshness
Hamilton pipelines that feed feature stores, model training jobs, or dashboards must produce fresh outputs on schedule. Even when the pipeline driver completes successfully, the outputs might not be written to storage — a silent write failure, a disk full condition, or a misconfigured output path leaves downstream consumers reading stale data. Monitor the freshness of pipeline outputs directly:
#!/bin/bash
# /usr/local/bin/hamilton-freshness-check.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/def456"
OUTPUT_PATH="/data/features/conversion_rate.parquet"
MAX_AGE_MINUTES=15 # Alert if output is older than 15 minutes
if [ ! -f "$OUTPUT_PATH" ]; then
echo "Output file missing: $OUTPUT_PATH"
exit 1
fi
FILE_AGE_MINUTES=$(( ($(date +%s) - $(stat -c %Y "$OUTPUT_PATH")) / 60 ))
if [ "$FILE_AGE_MINUTES" -le "$MAX_AGE_MINUTES" ]; then
echo "Output fresh: ${FILE_AGE_MINUTES} min old (max: ${MAX_AGE_MINUTES} min)"
curl -s "$HEARTBEAT_URL"
else
echo "Output stale: ${FILE_AGE_MINUTES} min old (max: ${MAX_AGE_MINUTES} min)"
exit 1
fi
For S3-backed outputs:
#!/bin/bash
HEARTBEAT_URL="https://vigilmon.online/heartbeat/def456"
S3_PATH="s3://your-feature-store/hamilton/conversion_rate/latest.parquet"
MAX_AGE_MINUTES=15
LAST_MODIFIED=$(aws s3 ls "$S3_PATH" 2>/dev/null | awk '{print $1 " " $2}')
if [ -z "$LAST_MODIFIED" ]; then
echo "S3 output not found: $S3_PATH"
exit 1
fi
FILE_AGE_MINUTES=$(python3 -c "
from datetime import datetime, timezone
lm = datetime.strptime('$LAST_MODIFIED', '%Y-%m-%d %H:%M:%S')
lm = lm.replace(tzinfo=timezone.utc)
age = (datetime.now(timezone.utc) - lm).total_seconds() / 60
print(int(age))
")
if [ "$FILE_AGE_MINUTES" -le "$MAX_AGE_MINUTES" ]; then
curl -s "$HEARTBEAT_URL"
else
echo "S3 output stale: ${FILE_AGE_MINUTES} min old"
exit 1
fi
Set the Vigilmon heartbeat interval to match your pipeline's expected output frequency. Freshness monitoring catches write failures independently of execution monitoring — a pipeline can appear to complete successfully but fail silently during the output write phase.
Step 4: Track Execution Time per Pipeline Run
Hamilton DAGs can grow complex as more nodes are added over time. A pipeline that previously took 2 minutes can silently degrade to 15 minutes due to a new upstream data size increase, a slow join on a growing table, or a node that materializes an unexpectedly large intermediate result. Track execution time to catch performance regressions early:
#!/usr/bin/env python3
import time
import requests
import json
from datetime import datetime
HEARTBEAT_URL = "https://vigilmon.online/heartbeat/ghi789"
MAX_EXECUTION_SECONDS = 300 # Alert if pipeline takes more than 5 minutes
METRICS_LOG = "/var/log/hamilton-metrics.jsonl"
def run_timed():
start = time.monotonic()
dr = driver.Builder().with_modules(my_pipeline_module).with_config({}).build()
results = dr.execute(["conversion_rate", "feature_a", "feature_b"],
inputs={"raw_data_path": "/data/raw/latest.parquet"})
elapsed = time.monotonic() - start
# Log execution metrics
metric = {
"timestamp": datetime.utcnow().isoformat(),
"pipeline": "feature_pipeline",
"duration_seconds": round(elapsed, 2),
"outputs": list(results.keys()),
}
with open(METRICS_LOG, "a") as f:
f.write(json.dumps(metric) + "\n")
if elapsed <= MAX_EXECUTION_SECONDS:
requests.get(HEARTBEAT_URL, timeout=10)
print(f"Pipeline OK: {elapsed:.1f}s")
else:
print(f"Pipeline too slow: {elapsed:.1f}s > {MAX_EXECUTION_SECONDS}s", flush=True)
# Don't ping — let Vigilmon alert on the missed heartbeat
raise RuntimeError(f"Execution time {elapsed:.1f}s exceeded limit {MAX_EXECUTION_SECONDS}s")
Combine with Hamilton's execution tracker for per-node timing:
from hamilton.plugins import h_tqdm
# Use h_tqdm adapter to get per-node timing feedback during development
dr = (driver.Builder()
.with_modules(my_pipeline_module)
.with_config({})
.with_adapters(h_tqdm.ProgressBar())
.build())
Set the Vigilmon heartbeat to MAX_EXECUTION_SECONDS + pipeline_interval. For a pipeline expected to run in under 5 minutes every 10 minutes, set the heartbeat expected interval to 15 minutes — enough buffer that a single slow run doesn't false-alarm, but a consistently slow or stuck pipeline still alerts.
Step 5: Monitor the Scheduler or Orchestrator Driving Hamilton
Hamilton itself has no built-in scheduler — it relies on an external trigger: a cron job, Airflow DAG, Prefect flow, or custom scheduler. If the scheduler stops triggering the Hamilton driver, the pipeline produces no output and no errors — it simply stops running silently. Monitor the orchestration layer independently:
#!/bin/bash
# /usr/local/bin/hamilton-scheduler-check.sh
# Run this from a DIFFERENT host than the pipeline host
HEARTBEAT_URL="https://vigilmon.online/heartbeat/jkl012"
PIPELINE_HOST="pipeline-worker-01.internal"
PIPELINE_LOG="/var/log/hamilton-metrics.jsonl"
MAX_SILENCE_MINUTES=20 # Alert if no pipeline run in 20 minutes
# SSH to pipeline host and check last execution timestamp
LAST_RUN=$(ssh -o ConnectTimeout=10 "$PIPELINE_HOST" \
"tail -1 $PIPELINE_LOG 2>/dev/null | python3 -c \"import sys, json; d = json.load(sys.stdin); print(d['timestamp'])\" 2>/dev/null")
if [ -z "$LAST_RUN" ]; then
echo "Cannot read pipeline log from $PIPELINE_HOST"
exit 1
fi
AGE_MINUTES=$(python3 -c "
from datetime import datetime, timezone
last = datetime.fromisoformat('$LAST_RUN').replace(tzinfo=timezone.utc)
age = (datetime.now(timezone.utc) - last).total_seconds() / 60
print(int(age))
")
if [ "$AGE_MINUTES" -le "$MAX_SILENCE_MINUTES" ]; then
echo "Scheduler OK: last run ${AGE_MINUTES} min ago"
curl -s "$HEARTBEAT_URL"
else
echo "Scheduler silent: no pipeline run in ${AGE_MINUTES} min (max: ${MAX_SILENCE_MINUTES} min)"
exit 1
fi
This check runs on a different host intentionally — if the pipeline worker itself goes down, an SSH-based check from a different machine catches both the scheduler failure and host failure at once.
Step 6: Set Up Alert Channels
Configure Vigilmon to route Hamilton pipeline alerts to the right teams:
- Go to Alert Channels in Vigilmon.
- Add a Slack webhook to
#data-platform-alerts— data engineers need pipeline failure context immediately. - Add PagerDuty for the pipeline execution and freshness monitors — stale features in production are severity-1 for ML-dependent products.
- Add email notifications for execution time monitors — performance regressions are important but not on-call worthy.
- Set Consecutive failures before alert to
2for freshness monitors — a single delayed pipeline run during deployment doesn't warrant a page.
Add maintenance windows when deploying schema changes or reprocessing historical data:
curl -X POST https://vigilmon.online/api/maintenance \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"monitor_id": "HAMILTON_FRESHNESS_MONITOR_ID",
"duration_minutes": 60,
"reason": "Historical backfill - pipeline will produce non-real-time outputs"
}'
Summary
| Monitor | Target | What It Catches | |---|---|---| | Cron heartbeat (execution) | Driver process completion | Pipeline crash, OOM kill, import error | | Structured logging | Node-level failures | Empty outputs, validation failures, bad data | | Cron heartbeat (freshness) | Output file/S3 modification time | Write failures, disk full, wrong output path | | Cron heartbeat (latency) | Pipeline execution duration | Data volume growth, slow joins, perf regression | | SSH-based check (scheduler) | Last execution timestamp | Cron/Airflow/Prefect not triggering pipeline |
Hamilton's simplicity — plain Python functions, no server, no UI — means failures are equally simple to miss. There's no dashboard showing the last successful DAG run, no built-in alerting when a pipeline stops executing, and no freshness SLA enforcement. Vigilmon's external heartbeat monitoring fills that gap: you know about Hamilton pipeline failures in minutes, not when a downstream model silently degrades and a data scientist asks why features haven't updated.
Start monitoring for free at vigilmon.online.