Reliability Design Summary

Reliability Design Summary

Quick Reference: This is a condensed overview of the full Reliability Design.

Critical Gaps Identified

1. No Task State Persistence (CRITICAL)

Current: All task state lives in memory (`RwLock<HashMap>`). Server crash = all tasks lost.

Impact: Running tasks become orphaned VMs, no way to recover state, manual cleanup required.

Fix: Implement checkpoint system that persists task state to disk on every state transition.


2. No Timeout Enforcement

Current: No orchestrator-level timeouts. Relies on script/SSH timeouts which may be infinite.

Impact: Tasks can hang forever in any state, consuming resources indefinitely.

Fix: Implement `TimeoutEnforcer` with per-operation and per-stage timeouts:

  • Git clone: 10m
  • VM provision: 5m
  • Task stages: 15m (staging), 10m (provisioning), 24h (running)

3. No Retry Logic

Current: All failures are terminal. Transient network issues cause task failures.

Impact: ~30% of failures are retryable (network timeouts, git rate limits, VM races).

Fix: Implement `RetryPolicy` with exponential backoff for:

  • Git clone (3 attempts)
  • VM provision (2 attempts)
  • SSH connect (5 attempts)
  • Artifact SCP (3 attempts)

4. No Hang Detection

Current: Tasks can run forever with no output. No automated detection or intervention.

Impact: Wasted resources, degraded user experience, manual intervention required.

Fix: Implement `HangDetector` that monitors:

  • No stdout/stderr for 30m
  • No state change for 1h
  • Auto-cancel after critical threshold

5. No Observability

Current: Basic logs only. No metrics, no alerts, no distributed tracing.

Impact: Cannot measure reliability, detect issues proactively, or debug complex failures.

Fix: Implement comprehensive observability:

  • Prometheus metrics (task lifecycle, resources, errors)
  • Structured logging with trace IDs
  • Alert rules (failure rate, stuck tasks, storage)
  • OpenTelemetry distributed tracing

Priority Implementation Order

Phase 1: Foundation (Weeks 1-2) - CRITICAL

Goals: Survive crashes, handle timeouts, retry transients

Must-Have: 1. Checkpoint/restore system for state persistence 2. Timeout enforcement at all levels 3. Retry logic for transient failures 4. Basic health checks

Success Criteria:

  • Tasks survive server restart
  • Transient git failures auto-retry
  • Tasks timeout properly

Phase 2: Observability (Weeks 3-4) - HIGH PRIORITY

Goals: See what's happening, detect issues early

Must-Have: 1. Prometheus metrics instrumentation 2. Grafana dashboards 3. Alert rules for critical scenarios 4. Structured logging with trace IDs

Success Criteria:

  • Real-time metrics dashboard
  • Alerts fire on simulated failures
  • Logs searchable by task ID

Phase 3: Advanced Recovery (Weeks 5-6) - MEDIUM PRIORITY

Goals: Handle edge cases, degrade gracefully

Must-Have: 1. Hang detection and auto-cancellation 2. Graceful degradation (storage thresholds) 3. State reconciliation from VMs 4. Resource monitoring

Success Criteria:

  • Hung tasks cancelled within 30m
  • Server rejects tasks when storage >90%
  • No task state loss on crash

Phase 4: SLO/SLI & Chaos (Weeks 7-8) - MEDIUM PRIORITY

Goals: Define reliability targets, validate under stress

Must-Have: 1. SLO definitions and tracking 2. Chaos experiments (kill server, fill disk, network partition) 3. Validated runbooks 4. Error budget tracking

Success Criteria:

  • SLOs meet targets during chaos
  • MTTR <5m for crash recovery
  • All runbooks tested

Phase 5: Production Hardening (Weeks 9-10) - NICE TO HAVE

Goals: Scale, optimize, secure

Nice-to-Have: 1. Circuit breakers for external APIs 2. Distributed tracing (Jaeger) 3. Artifact streaming for large files 4. VM pool management 5. Security audit

Success Criteria:

  • Large artifacts (>10GB) collected
  • Resource quotas enforced
  • Security audit passes

Quick Start: Implementing Checkpoints

The single most critical improvement is adding state persistence. Here's a minimal implementation:

// src/orchestrator/checkpoint.rs

use std::path::PathBuf;
use tokio::fs;
use serde::{Serialize, Deserialize};
use chrono::{DateTime, Utc};

#[derive(Serialize, Deserialize)]
struct Checkpoint {
    task: Task,
    checkpointed_at: DateTime<Utc>,
    version: u32,
}

pub struct CheckpointStore {
    root: PathBuf,
}

impl CheckpointStore {
    pub async fn save(&self, task: &Task) -> Result<(), std::io::Error> {
        let path = self.root.join(&task.id).join("checkpoint.json");
        fs::create_dir_all(path.parent().unwrap()).await?;

        let checkpoint = Checkpoint {
            task: task.clone(),
            checkpointed_at: Utc::now(),
            version: 1,
        };

        // Atomic write: temp file + rename
        let temp = path.with_extension("tmp");
        fs::write(&temp, serde_json::to_vec_pretty(&checkpoint)?).await?;
        fs::rename(&temp, &path).await?;

        Ok(())
    }

    pub async fn load(&self, task_id: &str) -> Result<Option<Task>, std::io::Error> {
        let path = self.root.join(task_id).join("checkpoint.json");
        if !path.exists() {
            return Ok(None);
        }

        let data = fs::read(&path).await?;
        let checkpoint: Checkpoint = serde_json::from_slice(&data)?;
        Ok(Some(checkpoint.task))
    }
}

Integration:

// In Task::transition_to()
pub fn transition_to(&mut self, next: TaskState) -> Result<(), OrchestratorError> {
    // ... existing validation ...

    self.state = next;
    self.state_changed_at = Utc::now();

    // NEW: Checkpoint after state change
    if let Some(checkpoint_store) = &self.checkpoint_store {
        checkpoint_store.save(self).await?;
    }

    Ok(())
}

// In Orchestrator::new()
pub fn new(...) -> Self {
    let checkpoint_store = Arc::new(CheckpointStore::new(tasks_root.clone()));

    // NEW: Recover tasks on startup
    tokio::spawn({
        let store = checkpoint_store.clone();
        let orchestrator = Arc::new(self);
        async move {
            orchestrator.recover_from_checkpoints(store).await;
        }
    });

    // ...
}

SLOTargetWhy
Task Success Rate95% over 7 daysIndustry standard for batch jobs
Task Submission Latencyp99 < 5sFast feedback to users
VM Provisioning Success97% over 1 daySlightly lower due to libvirt variance
Storage Availability99.9% over 30 daysCritical dependency
Server Uptime99.5% over 30 days~3.6h downtime/month for maintenance

Error Budget Example:

  • SLO: 95% task success rate
  • Window: 7 days
  • Tasks: 700 (100/day)
  • Error Budget: 35 failures
  • Alert: >20 failures in 1h (fast burn)

Common Failure Scenarios

Scenario: Git Clone Timeout

Symptoms: Tasks stuck in Staging for >10m

Runbook: 1. Check network: `curl -I https://github.com` 2. Check repo size: Large repos may need shallow clone 3. Cancel stuck task: `POST /api/v1/tasks/{id}/cancel` 4. Fix: Add `--depth 1` to git clone, increase timeout to 15m


Scenario: VM Won't Provision

Symptoms: Tasks failing in Provisioning state

Runbook: 1. Check libvirt: `systemctl status libvirtd` 2. Check storage: `df -h /var/lib/libvirt/images` 3. Check network: `virsh net-list` 4. Manual test: `sudo provision-vm.sh --start test-vm` 5. Fix: Restart libvirt, cleanup old VMs, expand storage


Scenario: Server Crashed

Symptoms: Server not responding, tasks orphaned

Runbook: 1. Restart: `systemctl start management-server` 2. Verify recovery: `curl /healthz` 3. Check tasks: `curl /api/v1/tasks | jq '.tasks | length'` 4. Compare to VMs: `virsh list | grep task- | wc -l` 5. Fix orphaned VMs: Run reconciliation loop


Metrics to Watch

Health Dashboard:

  • `tasks_active` - Should be relatively stable
  • `tasks_failed_total` rate - Should be <5% of submissions
  • `storage_usage_percent` - Alert if >80%
  • `vm_pool_available` - Alert if 0

Latency Dashboard:

  • `task_duration_seconds` p50/p95/p99
  • `task_stage_duration_seconds{stage="provisioning"}` p95
  • `git_clone_duration_seconds` p95

Error Dashboard:

  • `errors_total` by component, operation, error_type
  • `retries_total` by operation
  • `hangs_detected_total`

Testing Checklist

Before deploying reliability improvements, validate:

  • [ ] Task survives server restart (checkpoint test)
  • [ ] Task times out correctly (set timeout=1m, run long task)
  • [ ] Git clone retries on network failure (simulate with iptables)
  • [ ] Hung task detected and cancelled (run infinite loop prompt)
  • [ ] Storage full handled gracefully (fill disk, submit task)
  • [ ] VM provision failure retried (kill libvirt during provision)
  • [ ] Metrics exported correctly (check /metrics endpoint)
  • [ ] Alerts fire on simulated failures (Prometheus alert test)
  • [ ] Runbooks accurate (follow each runbook scenario)

Next Steps

1. Read full design: reliability-design.md 2. Review current code: Understand gaps in executor/orchestrator 3. Prioritize features: Discuss with team which phases to implement 4. Create tickets: Break down Phase 1 into implementable stories 5. Set up monitoring: Install Prometheus/Grafana before implementing metrics


Questions? See full design document for detailed architecture, code examples, and comprehensive runbooks.