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#
- 2026-06-23 23:18:28 KST —
EditingSplitWorker.perform시작 (request_id=a49c53df7c5cd7bd1c5067d9, parent1184633) - 2026-06-23 23:18:56 KST — Geo grouping 후 oversized group DB chunking, split group 처리 시작
- 2026-06-23 23:18:58 KST — child editing
1184649생성,reassign_element_traces가synthesizing EE from templatewarn 발행 - 2026-06-23 23:18:58 KST —
_check_d1_recurrence(1184633, 1184649)가 trashed EE+alive ET 페어 1건 검출, error 로그 발행 (이 클러스터) - 2026-06-23 (이후) — Phase 4 reconcile 가 같은 request 에서 후속 진행 (orphan ET 복구 코드 경로 별도 존재)
Error Log#
TSLA-12839 D1 recurrence detected
원본 attribute (Datadog raw):
{
"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.rb→Cupix::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
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?
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 가능성):
Element traces reassign: synthesizing EE from template
이는 reassign_element_traces 가 original_ees_by_pair[[task_id, record_id]] 를 못 찾고 template EE 로부터 새 EE 를 생성한 경로다.
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 쿼리:
service:cupixworks-worker "TSLA-12839 D1 recurrence detected"
service:cupixworks-worker @request_id:"a49c53df7c5cd7bd1c5067d9"
같은 request_id 의 split flow 로그 (시간순, 일부 발췌):
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건뿐(이 클러스터)이다.
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_atediting_id=1184649의 모든EditingEntity(trashed 포함) — task_id 별로 그룹editing_id=1184649의ElementTrace에서(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_pairs 는 editing_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(또는 parent1184633) 의 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 templatewarn 과_check_d1_recurrenceerror 의 상관관계를 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 처럼.untrashedscope 적용 audit 를 1회 더 실시.EditingEntity.where(editing_id:패턴을 정적으로 점검하는 rubocop/grep 가이드라인 추가.
Monitoring#
D1 recurrence detector 발화 추이:
service:cupixworks-worker status:error "TSLA-12839 D1 recurrence detected"
D3 detector 와 함께 추적 (관련 동반 결함):
service:cupixworks-worker status:error ("TSLA-12839 D1 recurrence detected" OR "TSLA-12839 D3 recurrence detected")
Detection 직전의 synthesize 경로 추이 (선행 지표):
service:cupixworks-worker status:warn "Element traces reassign: synthesizing EE from template"
PR 87666 가 도입한 dogstatsd histogram (Datadog 메트릭으로 직접 시각화):
sum:tesla.tsla12839.d1_recurrence.count{env:production}.as_count()
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 수준의 작업).