Forma 1: Riparazione dello Stato. Forma 2: Report Inefficiente.
Un Cuore a Scatti batte su un orologio. Non sul bisogno. Non sul cambiamento. Su un timer.
Due forme, una causa comune: un job schedulato che sostituisce una progettazione corretta.
Forma 1: Riparazione dello Stato
Una transizione di stato non si completa in modo atomico. Invece di correggere la transizione, un job in background viene eseguito dopo un ritardo e riconcilia. Gli utenti vedono lo stato corrotto durante la finestra di riconciliazione.
Esempio GitHub (2026-04-08): una pull request del repository upstream è diventata privata. GitHub ha tentato una transizione di stato: chiudere la PR, aggiornare lo stato del branch, azzerare lo stato di merge. La transizione non è stata completata in modo atomico. Lo stato della PR mostrava contemporaneamente 'branch-forced-closed' e 'Merge status cannot be loaded'. Un job Sidekiq in background è stato eseguito alcuni minuti dopo e ha completato la riconciliazione. Gli osservatori hanno visto uno stato inconsistente per tutta la durata della finestra.
The Metered Heart: il job Sidekiq veniva eseguito secondo una pianificazione. Non veniva eseguito perché GitHub rilevava uno stato inconsistente; veniva eseguito perché il timer scattava. Un utente che osservava la PR in tempo reale vedeva una PR che si contraddiceva fino alla successiva esecuzione del job.
Form 2: Report Sprecone
Un report o un'aggregazione viene ricalcolato da zero a intervalli fissi. Nessun controllo della cache. Nessuna guardia di idempotenza. Nessun aggiornamento incrementale. Ogni esecuzione: una scansione completa.
Esempi: un job cron notturno che ricalcola l'importo totale degli acquisti di ogni utente scansionando tutti gli ordini dall'inizio. Un job di analytics giornaliero che rigenera una dashboard dai log grezzi degli eventi. Una email di riepilogo settimanale che interroga ogni riga della tabella delle attività.
Ognuno si avvia indipendentemente dal fatto che i dati siano cambiati dall'ultima esecuzione. Ognuno scansiona la storia completa anche quando solo le ultime 24 ore contengono nuovi dati. Ognuno sostituisce la ripetizione programmata con un design incrementale.
La Radice Comune
Un Metered Heart non può dire la verità sul proprio stato. Conosce solo l'orologio. Form 1: il job di riparazione dello stato viene eseguito a T+5 minuti indipendentemente dal fatto che lo stato sia inconsistente a T+0. Form 2: il job del report viene eseguito alle 2 AM indipendentemente dal fatto che i dati siano cambiati dal giorno precedente.
L'orologio non trasporta informazioni su cosa deve essere fatto. Un evento trasporta quell'informazione: 'una transizione di stato è appena fallita', 'nuovi ordini sono appena arrivati'. Un Metered Heart scarta quell'informazione e la sostituisce con una pianificazione.
Capital Drain
Un Metered Heart prosciuga il capitale vivo: ingegneri reperibili per incidenti di stato rotto. Erode la fiducia sociale: gli utenti vedono dati incoerenti e segnalano difetti che si risolvono da soli. Amplifica altri MOAD: un job di riparazione dello stato che scansiona tutti i record per trovare lo stato rotto spesso contiene MOAD-0001 (scansione O(N²)). Un job di report che ricalcola dati freddi può innescare MOAD-0005 (cache stampede). MOAD-0009 aggrava altri difetti.
The Shared Root
Form 1 e Form 2 appaiono diverse in superficie: una ripara lo stato, l'altra ricalcola i dati. La causa radice le collega.
Attiva al Cambiamento, Non all'Orologio
La progettazione event-driven si attiva quando qualcosa cambia. Il cambiamento di stato è l'evento. L'evento è il trigger.
Forma 1: la transizione atomica sostituisce il job di riparazione.
Se una transizione di stato può lasciare il sistema in uno stato intermedio rotto, il difetto risiede nella transizione, non nell'assenza di un job di riparazione. Correggi la transizione affinché si completi in modo atomico (o transazionale). Quando la transizione si completa in modo atomico, lo stato rotto non esiste mai. Il job di riparazione non ha nulla da riparare.
# DIFETTO: la transizione non atomica lascia uno stato rotto
def close_pr_on_repo_private(pr_id):
pr = PR.get(pr_id)
pr.status = 'branch-forced-closed' # step 1: partial state
pr.save() # visible to users NOW
# ... other steps may fail ...
pr.merge_status = 'not_applicable'
pr.save() # step 2: now consistent
# Sidekiq job reconciles if step 2 fails
# FIX: transizione atomica; nessuno stato intermedio visibile
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() # entrambi i campi vengono salvati atomicamente; mai a metà scrittura
Form 2: l’aggiornamento incrementale sostituisce il ricalcolo completo.
Un report che ricalcola da zero viene eseguito perché vecchi dati + nuovi dati = nuovo risultato. Ma vecchio risultato + delta = stesso nuovo risultato, calcolato in modo incrementale. L’evento: sono arrivati nuovi dati. Il trigger: aggiorna l’aggregato solo per i nuovi dati.
# DIFETTO: ricalcolo completo su pianificazione
def nightly_totals_job():
for user in all_users():
total = sum(o.amount for o in user.orders) # scansiona tutto il tempo
user.total_purchases = total
user.save()
# FIX: aggiornamento incrementale guidato dagli eventi
def on_order_placed(order):
order.user.total_purchases += order.amount # solo delta
order.user.save()
L’aggiornamento incrementale si attiva quando arriva un ordine, non alle 2 AM. Aggiorna solo l’utente interessato. Legge solo il nuovo ordine, non tutti gli ordini di sempre. Il job notturno scompare.
Perché Form 1 rivela una transizione interrotta
Un Form 1 Metered Heart rivela che una transizione di stato è stata lasciata incompleta. Il job di riparazione esiste perché un ingegnere ha notato uno stato rotto e ha aggiunto un meccanismo di riconciliazione invece di correggere la transizione. Il job di riparazione: una pezza su una decisione architetturale sbagliata.
MOAD-0009 come amplificatore
MOAD-0009 amplifica altri MOAD. Un job di riparazione dello stato che scansiona tutti i record per trovare stato rotto: MOAD-0001 (scansione O(N) o O(N²) per esecuzione del job). Un job di report che ricalcola tutto da zero: MOAD-0005 (cache stampede quando il job parte e colpisce un upstream caldo). MOAD-0009 non fa solo danno da solo; consegna altri MOAD su una pianificazione.
Diagnostica e ridisegna
Un team esegue un cron job notturno alle 2 AM. Il job scansiona tutti gli ordini di tutti gli utenti e ricalcola da zero il totale degli acquisti di ciascun utente. Il job impiega 4 ore. Alle 6 AM la dashboard mostra i totali aggiornati. Tra le 2 AM e le 6 AM la dashboard mostra i totali del giorno precedente.