Forma 1: Reparación de estado. Forma 2: Informe derrochador.
Un corazón medido late según un reloj. No según la necesidad. No según el cambio. Según un temporizador.
Dos formas, una causa raíz: un trabajo programado que sustituye al diseño correcto.
Forma 1: Reparación de estado
Una transición de estado no se completa de forma atómica. En lugar de corregir la transición, un trabajo en segundo plano se ejecuta tras un retraso y reconcilia. Los usuarios ven estado roto durante la ventana de reconciliación.
Ejemplo de GitHub (2026-04-08): Un pull request de un repositorio upstream se volvió privado. GitHub intentó una transición de estado: cerrar el PR, actualizar el estado de la rama, limpiar el estado de fusión. La transición no se completó de forma atómica. El estado del PR mostró simultáneamente 'branch-forced-closed' y 'Merge status cannot be loaded'. Un job en segundo plano de Sidekiq se ejecutó minutos después y completó la reconciliación. Los observadores vieron un estado inconsistente durante toda la ventana.
The Metered Heart: el job de Sidekiq se ejecutó según un horario. No se ejecutó porque GitHub detectó un estado inconsistente; se ejecutó porque el temporizador se activó. Un usuario que observaba el PR en tiempo real vio un PR que se contradecía a sí mismo hasta la siguiente ejecución del job.
Form 2: Wasteful Report
Un reporte o agregación se recalcula desde cero en un intervalo fijo. Sin verificación de caché. Sin protección de idempotencia. Sin actualización incremental. Cada ejecución: un escaneo completo.
Ejemplos: un cron job nocturno que recalcula el monto total de compras de cada usuario escaneando todos los pedidos desde el principio de los tiempos. Un job diario de analítica que regenera un dashboard a partir de logs de eventos crudos. Un correo resumen semanal que consulta cada fila de la tabla de actividad.
Cada uno se ejecuta independientemente de si los datos cambiaron desde la última ejecución. Cada uno escanea el historial completo incluso cuando solo las últimas 24 horas contienen datos nuevos. Cada uno sustituye la repetición programada por un diseño incremental.
The Shared Root
A Metered Heart no puede decir la verdad sobre su propio estado. Solo conoce el reloj. Form 1: el job de reparación de estado se ejecuta a T+5 minutos independientemente de si el estado está roto en T+0. Form 2: el job de reporte se ejecuta a las 2 AM independientemente de si algún dato cambió desde ayer.
El reloj no lleva información sobre lo que hay que hacer. Un evento lleva esa información: 'una transición de estado acaba de fallar', 'han llegado nuevos pedidos'. Un Metered Heart descarta esa información y la sustituye por un horario.
Drenaje de capital
Un Metered Heart drena capital vivo: ingenieros de guardia para incidentes de estado roto. Erosiona la confianza social: los usuarios ven datos inconsistentes e informan defectos que se resuelven solos. Amplifica otros MOAD: un trabajo de reparación de estado que escanea todos los registros para encontrar estado roto suele contener MOAD-0001 (escaneo O(N²)). Un trabajo de informes que recalcula datos fríos puede desencadenar MOAD-0005 (estampida de caché). MOAD-0009 agrava otros defectos.
La raíz compartida
La Forma 1 y la Forma 2 parecen diferentes en la superficie: una repara estado, la otra recalcula datos. La causa raíz las conecta.
Fire on Change, Not on Clock
El diseño orientado a eventos se activa cuando algo cambia. El cambio de estado es el evento. El evento es el disparador.
Forma 1: la transición atómica reemplaza al trabajo de reparación.
Si una transición de estado puede dejar el sistema en un estado intermedio roto, el defecto reside en la transición, no en la ausencia de un trabajo de reparación. Corrige la transición para que se complete de forma atómica (o transaccional). Cuando la transición se completa atómicamente, nunca existe un estado roto. El trabajo de reparación no tiene nada que reparar.
# DEFECTO: la transición no atómica deja un estado roto
def close_pr_on_repo_private(pr_id):
pr = PR.get(pr_id)
pr.status = 'branch-forced-closed' # paso 1: estado parcial
pr.save() # visible para los usuarios AHORA
# ... otros pasos pueden fallar ...
pr.merge_status = 'not_applicable'
pr.save() # paso 2: ahora consistente
# Job de Sidekiq reconcilia si falla el paso 2
# FIX: transición atómica; sin estado intermedio visible
def close_pr_on_repo_private(pr_id):
with db.transaction():
pr = PR.get(pr_id)
pr.status = 'branch-forced-closed'
pr.merge_status = 'not_applicable'
pr.save() # ambos campos se guardan de forma atómica; nunca quedan a medias
Forma 2: la actualización incremental reemplaza el recálculo completo.
Un informe que se recalcula desde cero se ejecuta porque datos antiguos + datos nuevos = nuevo resultado. Pero resultado antiguo + delta = mismo nuevo resultado, calculado de forma incremental. El evento: llegaron datos nuevos. El disparador: actualizar el agregado solo con los datos nuevos.
# DEFECTO: recálculo completo programado
def nightly_totals_job():
for user in all_users():
total = sum(o.amount for o in user.orders) # escanea todo el historial
user.total_purchases = total
user.save()
# SOLUCIÓN: actualización incremental basada en eventos
def on_order_placed(order):
order.user.total_purchases += order.amount # solo el delta
order.user.save()
La actualización incremental se dispara cuando llega un pedido, no a las 2 AM. Actualiza solo al usuario afectado. Lee solo el nuevo pedido, no todos los pedidos de todos los tiempos. El trabajo nocturno desaparece.
Por qué el Formulario 1 revela una transición rota
Un Formulario 1 Metered Heart revela que una transición de estado quedó incompleta. El trabajo de reparación existe porque un ingeniero detectó un estado roto y añadió un mecanismo de reconciliación en lugar de corregir la transición. El trabajo de reparación: un parche sobre una decisión arquitectónica rota.
MOAD-0009 como amplificador
MOAD-0009 amplifica otros MOADs. Un trabajo de reparación de estado que escanea todos los registros para encontrar estado roto: MOAD-0001 (escaneo O(N) u O(N²) por ejecución del trabajo). Un trabajo de informe que recalcula todo en frío: MOAD-0005 (estampida de caché cuando el trabajo inicia e impacta un upstream caliente). MOAD-0009 no solo causa daño por sí mismo; entrega otros MOADs según un horario.
Diagnosticar y rediseñar
Un equipo ejecuta un cron job nocturno a las 2 AM. El trabajo escanea todos los pedidos de todos los usuarios y recalcula desde cero el monto total de compras de cada usuario. El trabajo tarda 4 horas. A las 6 AM, el dashboard muestra totales actualizados. Entre las 2 AM y las 6 AM, el dashboard muestra los totales de ayer.