Zum Inhalt springen

Toil automatisieren: Wiederkehrende Engineering-Arbeit beseitigen

Wende SRE-Prinzipien zur Toil-Reduktion an: repetitive Aufgaben erkennen, ihre Kosten messen, gezielt automatisieren – und das als Leistung zählen.

4 Min. Lesezeit
Ein Diagramm, das zeigt, wie manueller Toil im Laufe der Zeit abnimmt, wenn Automatisierungsskripte an zentralen Engpässen des Workflows eingeführt werden

Was als Toil zählt

Toil ist nicht jede manuelle Arbeit. Es ist manuelle Arbeit, die repetitiv, automatisierbar und taktisch ist, keinen bleibenden Wert hat und linear mit dem Wachstum des Services skaliert. Ein Service per Klicks durch eine UI zu deployen ist Toil. Die Deployment-Architektur zu entwerfen ist es nicht. Die Unterscheidung ist wichtig, weil Toil sich als produktive Arbeit tarnt.

Toil messen, bevor du automatisierst

Bevor du Automatisierung schreibst, beziffer die Kosten. Viele Teams automatisieren das Falsche – Aufgaben, die sich lästig anfühlen, aber wenig tatsächliche Zeit kosten – und übersehen dabei häufige, unsichtbare Zeitfresser.

tstypescript
interface ToilEntry {
  task: string;
  category: "deployment" | "incident" | "provisioning" | "data" | "communication";
  frequencyPerWeek: number;
  minutesPerOccurrence: number;
  peopleInvolved: number;
  errorRate: number; // Percentage of times the manual process fails
  canBeAutomated: boolean;
}
 
function calculateToilCost(entries: ToilEntry[]): {
  weeklyHours: number;
  monthlyHours: number;
  yearlyHours: number;
  topCandidates: ToilEntry[];
} {
  const withCost = entries.map((entry) => ({
    ...entry,
    weeklyMinutes:
      entry.frequencyPerWeek *
      entry.minutesPerOccurrence *
      entry.peopleInvolved,
  }));
 
  const totalWeeklyMinutes = withCost.reduce(
    (sum, e) => sum + e.weeklyMinutes,
    0
  );
 
  // Rank by weekly time cost, filtered to automatable tasks
  const topCandidates = withCost
    .filter((e) => e.canBeAutomated)
    .sort((a, b) => b.weeklyMinutes - a.weeklyMinutes)
    .slice(0, 5)
    .map(({ weeklyMinutes, ...entry }) => entry);
 
  return {
    weeklyHours: totalWeeklyMinutes / 60,
    monthlyHours: (totalWeeklyMinutes / 60) * 4.33,
    yearlyHours: (totalWeeklyMinutes / 60) * 52,
    topCandidates,
  };
}
tstypescript
// ❌ Automating based on gut feeling
// "I hate updating Jira tickets, let me automate that"
// (Takes 2 minutes/week — automation saves almost nothing)
 
// ✅ Automating based on data
const teamToil: ToilEntry[] = [
  {
    task: "Manually creating staging environments for PR review",
    category: "provisioning",
    frequencyPerWeek: 15,
    minutesPerOccurrence: 20,
    peopleInvolved: 1,
    errorRate: 0.1,
    canBeAutomated: true,
    // 300 min/week = 5 hours/week = 260 hours/year
  },
  {
    task: "Rotating database credentials quarterly",
    category: "provisioning",
    frequencyPerWeek: 0.08,
    minutesPerOccurrence: 120,
    peopleInvolved: 2,
    errorRate: 0.25,
    canBeAutomated: true,
    // Low frequency but high error rate — automate for reliability
  },
  {
    task: "Copying production data to staging (sanitized)",
    category: "data",
    frequencyPerWeek: 3,
    minutesPerOccurrence: 45,
    peopleInvolved: 1,
    errorRate: 0.15,
    canBeAutomated: true,
    // 135 min/week + 15% failure rate = high value target
  },
];

Das Entscheidungsframework für Automatisierung

Nicht jede Toil-Aufgabe sollte sofort automatisiert werden. Wäge die Kosten der Automatisierung gegen die laufenden Kosten des Toils ab.

tstypescript
interface AutomationDecision {
  task: string;
  toilCostPerYear: number;        // Hours spent on toil yearly
  automationEstimate: number;      // Hours to build automation
  maintenanceCostPerYear: number;  // Hours to maintain automation yearly
  errorReduction: number;          // Percentage reduction in errors
  breakEvenMonths: number;
}
 
function evaluateAutomation(
  entry: ToilEntry,
  buildHours: number,
  maintenanceHoursPerYear: number
): AutomationDecision {
  const yearlyToilHours =
    ((entry.frequencyPerWeek *
      entry.minutesPerOccurrence *
      entry.peopleInvolved) /
      60) *
    52;
 
  const yearlyNetSavings = yearlyToilHours - maintenanceHoursPerYear;
  const breakEvenMonths =
    yearlyNetSavings > 0
      ? (buildHours / yearlyNetSavings) * 12
      : Infinity;
 
  return {
    task: entry.task,
    toilCostPerYear: yearlyToilHours,
    automationEstimate: buildHours,
    maintenanceCostPerYear: maintenanceHoursPerYear,
    errorReduction: entry.errorRate * 0.95, // Automation eliminates ~95% of errors
    breakEvenMonths: Math.round(breakEvenMonths),
  };
}

Praktische Automatisierungsmuster

Beginne mit den Automatisierungen mit dem höchsten Nutzen und dem geringsten Aufwand. Diese Muster beseitigen den häufigsten Engineering-Toil.

tstypescript
// Pattern 1: PR-triggered ephemeral environments
// Before: Manually create staging env for each PR (20 min × 15/week)
// After: GitHub Action creates environment automatically
 
// Pattern 2: Automated data sanitization pipeline
async function sanitizeProductionData(
  sourceDb: string,
  targetDb: string
): Promise<void> {
  const tables = await getTableList(sourceDb);
 
  for (const table of tables) {
    const sanitizers = getSanitizers(table);
    await copyTableWithSanitization(sourceDb, targetDb, table, sanitizers);
  }
}
 
type Sanitizer = (value: unknown, column: string) => unknown;
 
function getSanitizers(table: string): Map<string, Sanitizer> {
  const rules: Map<string, Sanitizer> = new Map();
 
  // PII columns get deterministic fakes
  if (table === "users") {
    rules.set("email", (_, col) => `user-${hashForDev(_)}@example.com`);
    rules.set("phone", () => "+1-555-000-0000");
    rules.set("name", (val) => `User ${hashForDev(val).slice(0, 6)}`);
  }
 
  return rules;
}
 
// Pattern 3: Credential rotation script
async function rotateCredentials(
  service: string
): Promise<{ previousKey: string; newKey: string }> {
  // Generate new credentials
  const newKey = crypto.randomBytes(32).toString("base64");
 
  // Update in secrets manager
  await secretsManager.putSecretValue(service, newKey);
 
  // Update service configuration (no restart needed with hot reload)
  await updateServiceConfig(service, { apiKey: newKey });
 
  // Verify new credentials work
  const healthy = await healthCheck(service);
  if (!healthy) {
    // Rollback
    await secretsManager.putSecretValue(service, previousKey);
    throw new Error(`Health check failed after credential rotation for ${service}`);
  }
 
  return { previousKey, newKey };
}

Ein Toil-Budget aufsetzen

Lege ein Ziel auf Teamebene fest: nicht mehr als ein bestimmter Prozentsatz der Engineering-Zeit sollte für Toil draufgehen. Tracke es so wie du Error Budgets trackst.

tstypescript
interface ToilBudget {
  teamSize: number;
  maxToilPercentage: number;
  currentToilHoursPerWeek: number;
  budgetHoursPerWeek: number;
  remainingBudget: number;
  overBudget: boolean;
}
 
function calculateToilBudget(
  teamSize: number,
  maxToilPercentage: number,
  currentToilHoursPerWeek: number
): ToilBudget {
  const totalTeamHours = teamSize * 40;
  const budgetHours = totalTeamHours * (maxToilPercentage / 100);
 
  return {
    teamSize,
    maxToilPercentage,
    currentToilHoursPerWeek: currentToilHoursPerWeek,
    budgetHoursPerWeek: budgetHours,
    remainingBudget: budgetHours - currentToilHoursPerWeek,
    overBudget: currentToilHoursPerWeek > budgetHours,
  };
}
 
// Example: team of 8, max 20% toil
// Budget: 8 × 40 × 0.2 = 64 hours/week
// Current: 45 hours/week — within budget
// If new toil pushes past 64, automation becomes mandatory

Den Erfolg der Automatisierung messen

Verfolge die Wirkung der Automatisierung über die Zeit. Hat sie den Toil tatsächlich reduziert – oder neuen Wartungs-Toil geschaffen?

tstypescript
interface AutomationMetrics {
  taskName: string;
  beforeAutomation: {
    manualOccurrencesPerWeek: number;
    minutesPerOccurrence: number;
    errorRate: number;
  };
  afterAutomation: {
    manualInterventionsPerWeek: number;
    minutesPerIntervention: number;
    errorRate: number;
    maintenanceHoursPerMonth: number;
  };
  netSavingsPerWeek: number;
  reliabilityImprovement: number;
}
 
function calculateAutomationROI(
  metrics: AutomationMetrics,
  buildHours: number
): { monthsToROI: number; yearlyHoursSaved: number } {
  const beforeWeekly =
    metrics.beforeAutomation.manualOccurrencesPerWeek *
    (metrics.beforeAutomation.minutesPerOccurrence / 60);
 
  const afterWeekly =
    metrics.afterAutomation.manualInterventionsPerWeek *
    (metrics.afterAutomation.minutesPerIntervention / 60) +
    metrics.afterAutomation.maintenanceHoursPerMonth / 4.33;
 
  const weeklySavings = beforeWeekly - afterWeekly;
  const monthsToROI = buildHours / (weeklySavings * 4.33);
 
  return {
    monthsToROI: Math.round(monthsToROI * 10) / 10,
    yearlyHoursSaved: Math.round(weeklySavings * 52),
  };
}

Die wichtigsten Erkenntnisse

Toil ist repetitive, automatisierbare Arbeit, die linear skaliert und keinen bleibenden Wert liefert. Miss ihn, bevor du automatisierst – beziffere Stunden pro Woche, Fehlerraten und die Zahl der beteiligten Personen. Priorisiere nach Daten: Die Aufgabe, die 5 Stunden pro Woche bei 15% Fehlerrate kostet, ist ein besserer Automatisierungskandidat als die, die sich nur lästig anfühlt.

Nutze die Break-even-Rechnung, um zu entscheiden, was du jetzt und was du später automatisierst. Lege ein Toil-Budget für dein Team fest – wenn der Toil das Budget überschreitet, wird Automatisierung zur Pflicht statt zur Option. Tracke die Automatisierungsmetriken nach dem Deployment, um zu prüfen, ob der Toil wirklich reduziert und nicht nur verlagert wurde. Das Ziel ist nicht null Toil – es ist, Toil auf einem Niveau zu halten, bei dem Engineers den Großteil ihrer Zeit für kreative, nachhaltige Arbeit statt für repetitive operative Aufgaben aufwenden.

Wilfredo Rujel

Wilfredo Rujel

Full-Stack-Softwareentwickler

Diesen Beitrag teilenX