Saltar al contenido

Automatizar el toil: eliminar el trabajo repetitivo de ingeniería

Aplica los principios SRE de reducción de toil: identifica tareas manuales repetitivas, mide su coste real, automatiza y cuéntalo como trabajo real.

4 min de lectura
Un gráfico que muestra cómo el toil manual disminuye con el tiempo a medida que se introducen scripts de automatización en los cuellos de botella clave del flujo de trabajo

Qué califica como toil

El toil no es todo trabajo manual. Es trabajo manual repetitivo, automatizable, táctico, sin valor duradero, y que escala linealmente con el crecimiento del servicio. Desplegar un servicio haciendo clics en una UI es toil. Diseñar la arquitectura de despliegue no lo es. La distinción importa porque el toil se disfraza de trabajo productivo.

Medir el toil antes de automatizar

Antes de escribir automatización, cuantifica el coste. Muchos equipos automatizan lo incorrecto: tareas que resultan molestas pero consumen poco tiempo real, mientras ignoran sumideros de tiempo invisibles y de alta frecuencia.

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
  },
];

El marco de decisión para automatizar

No toda tarea de toil debería automatizarse de inmediato. Equilibra el coste de la automatización frente al coste continuo del toil.

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),
  };
}

Patrones prácticos de automatización

Empieza por las automatizaciones de mayor valor y menor esfuerzo. Estos patrones eliminan el toil de ingeniería más común.

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 };
}

Construir un presupuesto de toil

Define un objetivo a nivel de equipo: no más de un cierto porcentaje del tiempo de ingeniería debería destinarse al toil. Hazle seguimiento igual que a los error budgets.

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

Medir el éxito de la automatización

Haz seguimiento del impacto de la automatización con el tiempo. ¿Realmente redujo el toil, o creó nuevo toil de mantenimiento?

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),
  };
}

Conclusiones clave

El toil es trabajo repetitivo y automatizable que escala linealmente y no aporta valor duradero. Mídelo antes de automatizar: cuantifica horas por semana, tasas de error y número de personas involucradas. Prioriza con datos: la tarea que consume 5 horas por semana con una tasa de error del 15% es mejor candidata para automatizar que la que simplemente resulta molesta.

Usa el cálculo de punto de equilibrio para decidir qué automatizar ahora y qué después. Define un presupuesto de toil para tu equipo: cuando el toil supera el presupuesto, la automatización deja de ser opcional y se vuelve obligatoria. Haz seguimiento de las métricas de automatización después del despliegue para verificar que redujo el toil en lugar de desplazarlo. El objetivo no es eliminar todo el toil, sino mantenerlo en un nivel donde los ingenieros dediquen la mayor parte de su tiempo a trabajo creativo y duradero, en lugar de a tareas operativas repetitivas.

Wilfredo Rujel

Wilfredo Rujel

Ingeniero de Software Full Stack

Compartir esta publicaciónX