English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

게스트
1 / ?
수업 목록으로

형태 1: 상태 복구. 형태 2: 낭비적인 리포트.

박동하는 심장은 필요에 따라 뛰는 것이 아니라, 변화에 따라 뛰는 것도 아니라, 타이머에 따라 규칙적으로 뛴다.

두 가지 형태는 하나의 근본 원인을 공유한다: 올바른 설계를 대신하는 예약 작업.

형태 1: 상태 복구

상태 전환이 원자적으로 완료되지 못한다. 전환을 수정하는 대신, 백그라운드 작업이 지연 후 실행되어 상태를 조정한다. 사용자는 조정 기간 동안 깨진 상태를 보게 된다.

GitHub 예시 (2026-04-08): 풀 리퀘스트의 업스트림 저장소가 비공개로 전환되었습니다. GitHub는 상태 전환을 시도했습니다: PR을 닫고, 브랜치 상태를 업데이트하며, 병합 상태를 초기화했습니다. 이 전환은 원자적으로 완료되지 않았습니다. PR 상태는 'branch-forced-closed'와 'Merge status cannot be loaded'가 동시에 표시되었습니다. Sidekiq 백그라운드 작업이 몇 분 후 실행되어 조정을 완료했습니다. 관찰자들은 해당 기간 동안 깨진 상태를 보았습니다.

The Metered Heart: Sidekiq 작업은 일정에 따라 실행되었습니다. 깨진 상태를 감지했기 때문이 아니라 타이머가 울렸기 때문입니다. PR을 실시간으로 보고 있던 사용자는 다음 작업 실행 전까지 모순된 PR을 보았습니다.

Form 2: 낭비되는 보고서

보고서 또는 집계가 고정된 간격으로 처음부터 다시 계산됩니다. 캐시 확인 없음. 멱등성 가드 없음. 증분 업데이트 없음. 모든 실행: 전체 스캔.

예시: 매일 밤 실행되는 cron 작업이 모든 주문 내역을 처음부터 스캔하여 각 사용자의 총 구매 금액을 다시 계산합니다. 매일 실행되는 분석 작업이 원시 이벤트 로그에서 대시보드를 다시 생성합니다. 매주 실행되는 요약 이메일이 활동 테이블의 모든 행을 조회합니다.

각 작업은 마지막 실행 이후 데이터가 변경되었는지와 관계없이 실행됩니다. 지난 24시간 동안의 새로운 데이터만 존재하더라도 전체 기록을 스캔합니다. 각 작업은 증분 설계 대신 예약된 반복으로 대체합니다.

The Shared Root

Metered Heart는 자신의 상태에 대해 진실을 말할 수 없습니다. 오직 시계만을 압니다. Form 1: 상태 복구 작업은 T+0 시점에 상태가 깨졌는지와 관계없이 T+5분에 실행됩니다. Form 2: 보고서 작업은 어제 이후 데이터가 변경되었는지와 관계없이 새벽 2시에 실행됩니다.

시계는 해야 할 일에 대한 정보를 가지고 있지 않습니다. 이벤트가 그 정보를 전달합니다: '상태 전환이 실패했습니다', '새로운 주문이 도착했습니다.' Metered Heart는 이 정보를 버리고 대신 일정으로 대체합니다.

자본 유출

Metered Heart는 살아 있는 자본을 소모합니다: 깨진 상태 사고에 대비해 대기하는 엔지니어들. 사회적 신뢰를 약화시킵니다: 사용자는 일관되지 않은 데이터를 보고, 스스로 해결되는 결함을 신고합니다. 다른 MOAD를 증폭시킵니다: 깨진 상태를 찾기 위해 모든 레코드를 스캔하는 상태 복구 작업은 종종 MOAD-0001(O(N²) 스캔)을 포함합니다. 콜드 데이터를 재계산하는 보고서 작업은 MOAD-0005(캐시 스탬피드)를 유발할 수 있습니다. MOAD-0009는 다른 결함을 복합적으로 만듭니다.

공유된 근본 원인

Form 1과 Form 2는 표면적으로는 다르게 보입니다: 하나는 상태를 복구하고, 다른 하나는 데이터를 재계산합니다. 그러나 근본 원인은 이 둘을 연결합니다.

Form 1과 Form 2는 동일한 근본 원인을 공유합니다. 이를 한 문장으로 설명하세요. 그런 다음 사용해 본 소프트웨어에서 각 형태의 예시를 하나씩 제시하세요.

변경 시 실행, 시계에 의한 실행 아님

이벤트 기반 설계는 무언가가 변경될 때 실행됩니다. 상태 변경이 이벤트입니다. 이벤트가 트리거입니다.

형태 1: 원자적 전환이 복구 작업을 대체합니다.

상태 전환이 시스템을 깨진 중간 상태로 남길 수 있다면, 결함은 전환 자체에 있으며 복구 작업이 없다는 데 있지 않습니다. 전환이 원자적으로(또는 트랜잭션으로) 완료되도록 수정하세요. 전환이 원자적으로 완료되면 깨진 상태는 존재하지 않습니다. 복구 작업이 복구할 대상이 없습니다.

# 결함: 비원자적 전환이 깨진 상태를 남김
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: 원자적 전환; 중간 상태가 보이지 않음
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()   # 두 필드가 원자적으로 커밋됨; 절대 반만 기록되지 않음

형식 2: 증분 업데이트가 전체 재계산을 대체합니다.

스크래치부터 다시 계산하는 보고서는 기존 데이터 + 새 데이터 = 새 결과로 실행됩니다. 그러나 기존 결과 + 델타 = 동일한 새 결과이며, 증분 방식으로 계산됩니다. 이벤트: 새 데이터가 도착했습니다. 트리거: 새 데이터에 대해서만 집계를 업데이트합니다.

# 결함: 스케줄에 따른 전체 재계산
def nightly_totals_job():
for user in all_users():
total = sum(o.amount for o in user.orders)  # 모든 시간 스캔
user.total_purchases = total
user.save()

# FIX: 이벤트 기반 증분 업데이트
def on_order_placed(order):
order.user.total_purchases += order.amount   # 델타만
order.user.save()

증분 업데이트는 새벽 2시가 아니라 주문이 도착할 때 실행됩니다. 영향을 받는 사용자만 업데이트하며, 전체 주문이 아닌 새 주문만 읽습니다. 야간 작업은 사라집니다.

Form 1이 깨진 전환을 드러내는 이유

Form 1 Metered Heart는 상태 전환이 완료되지 않았음을 보여줍니다. 복구 작업은 엔지니어가 깨진 상태를 발견하고 전환을 수정하는 대신 조정 메커니즘을 추가했기 때문에 존재합니다. 복구 작업은 깨진 아키텍처 결정에 대한 임시방편입니다.

MOAD-0009의 증폭 효과

MOAD-0009는 다른 MOAD를 증폭시킵니다. 깨진 상태를 찾기 위해 모든 레코드를 스캔하는 상태 복구 작업은 MOAD-0001(O(N) 또는 O(N²) 스캔)을 유발합니다. 모든 것을 처음부터 다시 계산하는 보고서 작업은 MOAD-0005(작업 시작 시 따뜻한 상류 시스템에 대한 캐시 스탬피드)를 유발합니다. MOAD-0009는 그 자체로도 해롭지만, 다른 MOAD를 정기적으로 전달합니다.

진단 및 재설계

한 팀이 매일 새벽 2시에 cron 작업을 실행합니다. 이 작업은 모든 사용자의 모든 주문을 스캔하여 각 사용자의 총 구매 금액을 처음부터 다시 계산합니다. 작업은 4시간이 걸리며, 오전 6시가 되면 대시보드에 최신 합계가 표시됩니다. 새벽 2시부터 6시 사이에는 대시보드에 전날의 합계가 표시됩니다.

MOAD-0009의 어떤 형태인가요? 어떤 이벤트가 재계산을 트리거해야 하나요? 업데이트를 증분으로 만드는 중간 데이터 구조는 무엇인가요?