ES /docs

TSLA-12839 D1 recurrence detected

RCA: TSLA-12839 D1 recurrence detected

Overview#

TSLA-12839 (split EE race) 의 후속 monitoring detector 가 production us-west-2 에서 1건 트리거되었다. Editing split 후 자식 editing(1184649)에 (task_id, record_id) 가 동일한 trashed EditingEntity 와 alive ElementTrace 가 공존하는 모순 페어가 1건 검출되었다. 동일한 결함 패턴이 다시 발생했음을 의미한다.

What Happened#

2026-06-23 23:18:28 KST 에 시작된 EditingSplitWorker (request_id a49c53df7c5cd7bd1c5067d9) 가 parent editing 1184633 을 분할하는 도중, 자식 editing 1184649 생성 직후 실행된 D1 recurrence detector 가 orphan EE 1건(EditingEntity.id=1759496)을 발견하고 23:18:58 KST 에 error 로그를 발행했다. Datadog 14일 retention 내 다른 발생은 없으며 region us-west-2 단일 발생.

Quick Facts#

Field Value
exception.class Cupix::EditingSplitService (의도적 detection 로그, exception 아님)
exception.message TSLA-12839 D1 recurrence detected
top_frame app/services/cupix/editing_split_service.rb:1111
deploy production-us-west-2-20260623T0129Z0-37f28217-cupixworks
env production / us-west-2
parent_editing_id 1184633
child_editing_id 1184649
orphan_ee_ids [1759496]
orphan_count 1
tag tsla12839_d1_recurrence_check

Affected Teams#

Team / Domain Error Count Impact
Editing / split pipeline (TSLA-12839 owner) 1 단일 editing pair 의 ES ↔ RDS 동기화가 다시 어긋날 가능성. 사용자 대면 즉시 영향은 미확정 (ES partial-update 결과 미검증)

Timeline#

  1. 2026-06-23 23:18:28 KSTEditingSplitWorker.perform 시작 (request_id=a49c53df7c5cd7bd1c5067d9, parent 1184633)
  2. 2026-06-23 23:18:56 KST — Geo grouping 후 oversized group DB chunking, split group 처리 시작
  3. 2026-06-23 23:18:58 KST — child editing 1184649 생성, reassign_element_tracessynthesizing EE from template warn 발행
  4. 2026-06-23 23:18:58 KST_check_d1_recurrence(1184633, 1184649) 가 trashed EE+alive ET 페어 1건 검출, error 로그 발행 (이 클러스터)
  5. 2026-06-23 (이후) — Phase 4 reconcile 가 같은 request 에서 후속 진행 (orphan ET 복구 코드 경로 별도 존재)

Error Log#

Datadog Logs

text
TSLA-12839 D1 recurrence detected

원본 attribute (Datadog raw):

json
{
  "message": "TSLA-12839 D1 recurrence detected",
  "status": "error",
  "class": "Cupix::EditingSplitService",
  "function": "_check_d1_recurrence",
  "tag": "tsla12839_d1_recurrence_check",
  "parent_editing_id": 1184633,
  "child_editing_id": 1184649,
  "orphan_count": 1,
  "orphan_ee_ids": [1759496],
  "request_id": "a49c53df7c5cd7bd1c5067d9",
  "@timestamp": "2026-06-23T14:18:58.180Z",
  "version": "production-us-west-2-20260623T0129Z0-37f28217-cupixworks"
}

Impact#

  • Service: cupixworks-worker
  • 발생 횟수: 1
  • 최초 발생: 2026-06-23 23:18:58 KST
  • 최근 발생: 2026-06-23 23:18:58 KST
  • Region: us-west-2
  • Tenant: cupix

D1 detector 자체는 main split flow 를 깨지 않는 observability 코드 (rescue StandardError 로 감쌈). 사용자 대면 실패는 발생하지 않았다. 단, 이 신호는 PR 87666 (TSLA-12839) 가 도입한 안전 장치가 fix 한 결함과 동일한 inconsistency 가 RDS 에 다시 생성되었음을 의미한다 — Phase 4 reconcile 이 후속으로 복구하지 못하면 ES partial-update 가 silent 하게 어긋날 위험이 있다.

Root Cause Summary#

PR 87666 (TSLA-12839) 가 도입한 D1 detector 가 의도대로 트리거된 이벤트다. 자식 editing 1184649 에 대해 EditingEntity(cycle_state='deleted')ElementTrace 가 동일한 (task_id, record_id) 페어로 동시에 존재하는 inconsistency 1건이 감지되었다. detector 직전 synthesizing EE from template warn 이 동일 child editing 에서 1건 발행된 점, 그리고 PR 87666 의 untrashed 패치가 들어간 5개 query 외부 경로에서 trashed EE 가 child editing 으로 흘러들어왔을 가능성이 높다는 점이 단서. 단일 발생이고 root cause 의 실제 원인 코드 경로는 현재 로그/코드만으로는 단정할 수 없으며, 추가 DB 상태 확인이 필요하다 — uncertain, needs verification.

Technical Analysis#

Code Path#

  • Entry point: app/workers/editing_split_worker.rbCupix::EditingSplitService#split!
  • 분할 그룹 루프 진입: editing_split_service.rb:156-193 (do_split! 안의 final_groups.each)
  • 자식 editing 생성 + ET reassign: editing_split_service.rb:172-180
  • D1 detection 호출: editing_split_service.rb:192
  • Detector 본체 (Failure point — detection only): editing_split_service.rb:1098-1129
app/services/cupix/editing_split_service.rb:172-192ruby
new_editing = nil
ActiveRecord::Base.transaction do
  new_editing = create_split_editing(group)
  if group[:element_ids].present?
    reassign_element_traces(group[:element_ids], new_editing, deferred_jobs)
  else
    reassign_editing_entities(group[:task_ids], new_editing, deferred_jobs)
  end
end
result_editings << new_editing if new_editing&.persisted?

# ...

_check_d1_recurrence(editing.id, new_editing) if new_editing&.persisted?
app/services/cupix/editing_split_service.rb:1098-1117ruby
def _check_d1_recurrence(parent_editing_id, child_editing)
  trashed_ees = ::EditingEntity.unscoped
                               .where(editing_id: child_editing.id, entity_type: 'Task', cycle_state: 'deleted')
                               .pluck(:id, :entity_id, :record_id)
  return if trashed_ees.empty?

  task_ids = trashed_ees.map { |row| row[1] }.uniq
  et_pairs = ::ElementTrace.where(editing_id: child_editing.id, task_id: task_ids)
                           .pluck(:task_id, :record_id).to_set

  orphans = trashed_ees.select { |_id, task_id, record_id| et_pairs.include?([task_id, record_id]) }
  return if orphans.empty?

  Cupix::Logger.error('TSLA-12839 D1 recurrence detected',
                      class: self.class.name, function: __method__,
                      tag: 'tsla12839_d1_recurrence_check',
                      parent_editing_id: parent_editing_id,
                      child_editing_id: child_editing.id,
                      orphan_count: orphans.size,
                      orphan_ee_ids: orphans.map(&:first))

기대 동작: PR 87666 이후 reassign_editing_entities 와 4개 EE 조회에 .untrashed scope 가 추가되어 자식 editing 에 trashed EE 가 옮겨가지 않아야 한다. 결과적으로 trashed_ees 가 비어 detector 가 silent.

실제 동작: EditingEntity.id=1759496 (cycle_state deleted) 가 editing_id=1184649 로 존재했고, 같은 (task_id, record_id) 의 alive ElementTrace 도 존재. detector 가 orphan 1건 검출 후 error 로그 발행.

직전 warn 로그도 수집됨 (같은 request, 같은 child editing 가능성):

text
Element traces reassign: synthesizing EE from template

이는 reassign_element_tracesoriginal_ees_by_pair[[task_id, record_id]] 를 못 찾고 template EE 로부터 새 EE 를 생성한 경로다.

app/services/cupix/editing_split_service.rb:881-917ruby
task_record_et_counts.each do |(task_id, record_id), et_count|
  original_ee = original_ees_by_pair[[task_id, record_id]]
  synthesized = original_ee.nil?

  template_ee = nil
  if synthesized
    template_ee = siblings_by_task[task_id] || global_templates_by_task[task_id]
    # ...
    Cupix::Logger.warn('Element traces reassign: synthesizing EE from template', ...)
    original_ee = template_ee
  end

  new_ee = ::EditingEntity.new(
    editing_id: new_editing.id,
    # ...
    cycle_state: original_ee.cycle_state
  )
  new_ee.entity_count = et_count
  new_ee.save!

template path 의 original_ees_by_pair (line 853) 와 siblings_by_task/global_templates_by_task (line 862) 는 모두 .untrashed scope 를 사용한다. 따라서 합성 경로에서 cycle_state: 'deleted' 인 new_ee 가 만들어졌다고 단정하기는 어렵다 — uncertain. 이 경로 외부에서 (예: reassign_editing_entities task-id path, Phase 4 reconcile, 또는 다른 동시 split job) editing_id=1184649 + cycle_state='deleted' 인 EE 가 만들어졌을 가능성도 있다.

Log Evidence#

Datadog 쿼리:

text
service:cupixworks-worker "TSLA-12839 D1 recurrence detected"
text
service:cupixworks-worker @request_id:"a49c53df7c5cd7bd1c5067d9"

같은 request_id 의 split flow 로그 (시간순, 일부 발췌):

text
2026-06-23 23:18:28 INFO  EditingSplitWorker.perform           "Starting editing split"
2026-06-23 23:18:28 INFO  Cupix::EditingSplitService.split!    "Split entry"
2026-06-23 23:18:28 INFO  ...                                  "Split lock acquired"
2026-06-23 23:18:56 INFO  ...compute_split_groups              "Group exceeds max elements, splitting by element traces"
2026-06-23 23:18:56 INFO  ...do_split!                         "Processing split group"
2026-06-23 23:18:58 INFO  ...create_split_editing              "Created split editing"  (Editing 1184649)
2026-06-23 23:18:58 INFO  ...reassign_element_traces           "Element traces reassign: task loop start"
2026-06-23 23:18:58 INFO  ...reassign_element_traces           "Element traces reassign: task processed"
2026-06-23 23:18:58 WARN  ...reassign_element_traces           "Element traces reassign: synthesizing EE from template"
2026-06-23 23:18:58 INFO  ...reassign_element_traces           "Element traces reassigned"
2026-06-23 23:18:58 ERROR ..._check_d1_recurrence              "TSLA-12839 D1 recurrence detected"  (orphan_ee=1759496)

Phase 4 reconcile: orphan ET without template (PR 87666 가 추가한 별도 error) 는 같은 request 에서 발견되지 않았다 — 즉 Phase 4 의 orphan 복구는 정상 진행되었거나 적용 대상이 아니었음. 추가로 같은 14일 retention 내 동일 detector 발생은 1건뿐(이 클러스터)이다.

text
service:cupixworks-worker @class:"Cupix::EditingSplitService" @function:"_check_d1_recurrence"
→ Found 1 logs (this cluster only)

DB 상태 (uncertain — needs verification)#

다음 조회는 RCA 시점에 직접 실행하지 않았다. 후속 검증 필요:

  • EditingEntity.id=1759496 의 현재 editing_id, entity_id, record_id, cycle_state, created_at, updated_at
  • editing_id=1184649 의 모든 EditingEntity (trashed 포함) — task_id 별로 그룹
  • editing_id=1184649ElementTrace 에서 (task_id, record_id) = (1759496 의 entity_id, record_id) 페어
  • editing_id=1184633 의 sibling 분할 결과(1184647, 1184648) 와 EE/ET 일치 상태

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 PR 87666 fix 외부의 코드 경로(또는 동시 작업) 가 editing_id=1184649 에 trashed EE 를 생성/이동시켜 detector 가 정확하게 발화 detector 정의 그대로 trashed EE + alive ET 페어 1건 검출 (orphan_ee=1759496); 직전 synthesizing EE from template warn 1건; PR 87666 description 이 detector 가 "재발 모니터링" 용임을 명시 같은 request 에서 Phase 4 reconcile: orphan ET without template error 는 없음 (다른 인접 결함은 미발생); root cause 가 어떤 외부 경로인지 코드만으로는 특정 불가 — uncertain Confirmed (detector 는 정상 작동, RDS inconsistency 실재)
H2 Detector 자체의 false positive (예: et_pairs 또는 trashed_ees 쿼리 버그) detector 코드(line 1098-1117)는 단순한 두 쿼리 + set intersection. unscoped 로 trashed 명시 조회, et_pairsediting_id=child 한정. 로직 오류 없음 Rejected
H3 synthesize EE from template 경로가 deleted template 으로부터 deleted child EE 를 만들어 self-induced inconsistency warn 이 같은 request 에서 발행됨; new_ee.cycle_state 가 template_ee.cycle_state 를 그대로 복사 template 조회는 .untrashed scope 를 사용 (line 854, 862-868). 합성된 new_ee 의 cycle_state 는 alive 여야 함 Rejected (현재 코드 기준), 단 race 시점에 template 이 dirty 상태였다면 가능 — uncertain
H4 동일 split job 의 idempotent 재실행이 trashed EE 를 만들었음 line 45 주석에 "Phase 4 cleanup 이미 완료된 작업의 재실행" 가능성 언급 retry 흔적(중복 Split entry/Split finished) 미관측. 단일 request_id 안에서 1회 실행 Rejected
H5 외부 dependency 장애 (RDS / Redis / ES) 로 인한 부분 트랜잭션 status-board scope svc:cupixworks-worker::unknown 의 active=null, recent=[]. 같은 시간대 외부 dep 장애 신호 없음 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 수정 파일/라인 변경 없음 — detector 자체는 정상 동작. 우선은 DB 상태 검증이 즉시 조치다.
    • EditingEntity.id=1759496 행 검사: editing_id, entity_id(task_id), record_id, cycle_state, created_at, updated_at 확인
    • editing_id=1184649 의 모든 EE/ET 페어를 dump 해서 inconsistency 가 Phase 4 reconcile 이후에도 남아있는지 확인 (PR 87666 의 Phase 4 orphan ET 복구가 실행되었는지)
    • PR 87666 backfill rake (backfill_ee_revive, backfill_es_et_reindex) 의 적용 대상에 이 editing pair 가 포함되는지 확인
  • 사용자 영향 확인: editing_id=1184649 (또는 parent 1184633) 의 ES partial-update 가 silent 실패했는지 ES ↔ RDS 비교 검증.

단기 개선 (1주 이내)#

  • _check_d1_recurrence 가 발화한 케이스에 대해 자동 복구를 붙일지 판단. 현재는 detection-only 이므로, RDS 의 inconsistency 가 그대로 남으면 ES sync 가 어긋난다. Phase 4 reconcile 이 동일 패턴(orphan ET → synthesize EE)을 처리하지만, D1 발화 케이스(trashed EE + alive ET 의 동일 pair)는 별개다 — orphan_ee_ids 를 받아 ET 측을 trash 하거나 EE 의 cycle_state 를 복원하는 수습 경로 추가 검토.
  • synthesize EE from template warn 과 _check_d1_recurrence error 의 상관관계를 dashboard 에서 같은 child_editing_id 로 join 해 확인할 수 있도록 두 로그 모두 child_editing_id 를 기록하도록 일관화 (현재 둘 다 new_editing_id/child_editing_id 로 기록 중 — naming 통일).
  • D1 detector 실행을 do_split! 의 자식 생성 직후뿐 아니라 Phase 4 reconcile 종료 후에도 한 번 더 호출해, 어떤 단계에서 inconsistency 가 발생/잔존했는지 분리 관측.

장기 개선 (재발 방지)#

  • editing split 의 EE/ET 정합성을 단일 트랜잭션 경계 안에서 invariant 로 강제 (예: child editing 커밋 직전 검증). detector 가 검출하는 상태는 application code 가 만들 수 없는 invariant 위반이므로, write 시점에 reject 하는 방향 검토.
  • 아직 식별되지 않은 trashed EE 발생 경로(reassign_editing_entities 의 task-id path 또는 동시 split job 등) 에 대해 PR 87666 처럼 .untrashed scope 적용 audit 를 1회 더 실시. EditingEntity.where(editing_id: 패턴을 정적으로 점검하는 rubocop/grep 가이드라인 추가.

Monitoring#

D1 recurrence detector 발화 추이:

text
service:cupixworks-worker status:error "TSLA-12839 D1 recurrence detected"

D3 detector 와 함께 추적 (관련 동반 결함):

text
service:cupixworks-worker status:error ("TSLA-12839 D1 recurrence detected" OR "TSLA-12839 D3 recurrence detected")

Detection 직전의 synthesize 경로 추이 (선행 지표):

text
service:cupixworks-worker status:warn "Element traces reassign: synthesizing EE from template"

PR 87666 가 도입한 dogstatsd histogram (Datadog 메트릭으로 직접 시각화):

text
sum:tesla.tsla12839.d1_recurrence.count{env:production}.as_count()
text
sum:tesla.tsla12839.d3_recurrence.count{env:production}.as_count()

Alert 권장: 14일 동안 0 이었던 tesla.tsla12839.d1_recurrence.count 가 향후 24h 내 1 이상이면 즉시 Slack 알림 (현재 detector 가 error level 이지만 cluster 임계 미달로 status-board 에 잡히지 않음).

Risk Assessment#

  • Risk level: medium — detector 가 의도대로 동작했고 main flow 는 영향 없음. 그러나 RDS inconsistency 가 실재해 ES ↔ RDS 동기화가 silent 하게 어긋날 수 있으며, 14일 만에 처음 재발이라 빈도는 낮지만 root cause 경로가 미특정.
  • 예상 복잡도: standard — 즉시 조치는 DB 상태 dump + backfill 확인. 단기 개선은 자동 복구 경로 1곳 추가. 장기 개선은 EE 조회 경로 audit (PR 87666 수준의 작업).