Vorm 1: State-Repair. Vorm 2: Verspillend Rapport.
Een Gemeten Hart klopt op een klok. Niet op noodzaak. Niet op verandering. Op een timer.
Twee vormen, één oorzaak: een geplande taak die correct ontwerp vervangt.
Vorm 1: State-Repair
Een statustransitie slaagt er niet in atomair te voltooien. In plaats van de transitie te herstellen, draait een achtergrondtaak na een vertraging & reconcilieert. Gebruikers zien gebroken state tijdens het reconciliatievenster.
GitHub-voorbeeld (2026-04-08): Een pull request’s upstream-repository werd privé. GitHub probeerde een statusovergang: de PR sluiten, de branch-status bijwerken en de merge-status wissen. De overgang werd niet atomair voltooid. De PR-status toonde tegelijkertijd ‘branch-forced-closed’ en ‘Merge status cannot be loaded’. Een Sidekiq-achtergrondtaak liep enkele minuten later en voltooide de reconciliatie. Observers zagen een inconsistente status gedurende het venster.
The Metered Heart: de Sidekiq-taak liep volgens een schema. Hij liep niet omdat GitHub een inconsistente status detecteerde; hij liep omdat de timer afging. Een gebruiker die de PR in realtime bekeek, zag een PR die zichzelf tegensprak tot de volgende uitvoering van de taak.
Form 2: Wasteful Report
Een rapport of aggregatie wordt vanaf nul herberekend op een vast interval. Geen cache-controle. Geen idempotentie-bescherming. Geen incrementele update. Elke uitvoering: een volledige scan.
Voorbeelden: een nachtelijke cron-taak die het totale aankoopbedrag van elke gebruiker herberekent door alle orders vanaf het begin te scannen. Een dagelijkse analytics-taak die een dashboard opnieuw genereert uit ruwe event-logs. Een wekelijkse samenvattingsmail die elke rij in de activity-tabel opvraagt.
Elke uitvoering vindt plaats ongeacht of de data sinds de vorige uitvoering is gewijzigd. Elke taak scant de volledige geschiedenis, ook als alleen de laatste 24 uur nieuwe data bevatten. Elke taak vervangt incrementeel ontwerp door geplande herhaling.
The Shared Root
Een Metered Heart kan niet de waarheid vertellen over zijn eigen status. Hij kent alleen de klok. Form 1: de status-reparatietaak loopt op T+5 minuten, ongeacht of de status op T+0 al gebroken was. Form 2: de rapporttaak loopt om 2 uur ’s nachts, ongeacht of er sinds gisteren data is gewijzigd.
De klok draagt geen informatie over wat er gedaan moet worden. Een gebeurtenis draagt die informatie: 'een statusovergang is zojuist mislukt', 'nieuwe orders zijn zojuist binnengekomen.' Een Metered Heart gooit die informatie weg en vervangt die door een schema.
Kapitaalverlies
Een Metered Heart put levend kapitaal uit: engineers die standby staan voor incidenten met inconsistente status. Het ondermijnt sociaal vertrouwen: gebruikers zien inconsistente data en melden defecten die zichzelf oplossen. Het versterkt andere MOADs: een statusherstel-taak die alle records scant om inconsistente status te vinden bevat vaak MOAD-0001 (O(N²)-scan). Een rapporttaak die koude data herrekent kan MOAD-0005 veroorzaken (cache stampede). MOAD-0009 versterkt andere defecten.
De gedeelde wortel
Form 1 en Form 2 lijken aan de oppervlakte verschillend: de ene herstelt status, de andere herberekent data. De oorzaak verbindt ze.
Fire on Change, Not on Clock
Event-driven design vuurt wanneer er iets verandert. De toestandswijziging is het event. Het event is de trigger.
Form 1: de atomische transitie vervangt de reparatietaak.
Als een toestandsovergang het systeem in een gebroken tussenliggende toestand kan achterlaten, zit het defect in de transitie, niet in het ontbreken van een reparatietaak. Repareer de transitie zodat deze atomisch (of transactioneel) voltooid wordt. Wanneer de transitie atomisch voltooid is, bestaat de gebroken toestand nooit. De reparatietaak heeft dan niets meer te repareren.
# DEFECT: non-atomic transition leaves broken state
def close_pr_on_repo_private(pr_id):
pr = PR.get(pr_id)
pr.status = 'branch-forced-closed' # stap 1: gedeeltelijke status
pr.save() # nu zichtbaar voor gebruikers
# ... andere stappen kunnen mislukken ...
pr.merge_status = 'not_applicable'
pr.save() # stap 2: nu consistent
# Sidekiq-job herstelt als stap 2 mislukt
# FIX: atomische overgang; geen zichtbare tussenliggende staat
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() # beide velden worden atomisch opgeslagen; nooit half geschreven
Vorm 2: de incrementele update vervangt de volledige herberekening.
Een rapport dat vanaf nul herberekent, wordt geactiveerd omdat oude data + nieuwe data = nieuw resultaat. Maar het oude resultaat + delta = hetzelfde nieuwe resultaat, incrementeel berekend. De gebeurtenis: nieuwe data is binnengekomen. De trigger: update de aggregatie alleen voor de nieuwe data.
# DEFECT: volledige herberekening op schema
def nightly_totals_job():
for user in all_users():
total = sum(o.amount for o in user.orders) # scan all time
user.total_purchases = total
user.save()
# FIX: event-driven incrementele update
def on_order_placed(order):
order.user.total_purchases += order.amount # alleen delta
order.user.save()
De incrementele update wordt geactiveerd wanneer een order binnenkomt, niet om 2 uur ’s nachts. Hij werkt alleen de betreffende gebruiker bij. Hij leest alleen de nieuwe order, niet alle orders uit de hele geschiedenis. De nachtelijke job verdwijnt.
Waarom Form 1 een gebroken transitie onthult
Een Form 1 Metered Heart onthult dat een statustransitie onvolledig is achtergelaten. De reparatie-job bestaat omdat een engineer gebroken status opmerkte en een reconciliatiemechanisme toevoegde in plaats van de transitie te repareren. De reparatie-job: een pleister op een gebroken architectuurkeuze.
MOAD-0009 als versterker
MOAD-0009 versterkt andere MOADs. Een status-reparatie-job die alle records scant om gebroken status te vinden: MOAD-0001 (O(N) of O(N²) scan per job-run). Een rapport-job die alles koud herberekent: MOAD-0005 (cache-stampedes wanneer de job start en een warme upstream raakt). MOAD-0009 schaadt niet alleen op zichzelf; het levert andere MOADs op een vast schema.
Diagnose & Redesign
Een team draait een nachtelijke cron-job om 2 uur ’s nachts. De job scant alle orders van alle gebruikers en berekent het totale aankoopbedrag van elke gebruiker opnieuw vanaf nul. De job duurt 4 uur. Om 6 uur ’s ochtends toont het dashboard verse totalen. Tussen 2 uur en 6 uur ’s ochtends toont het dashboard de totalen van gisteren.