processing 'phase_metric.created' failed: wrong number of arguments (given 1, expected 0)
RCA: processing 'phase_metric.created' failed: wrong number of arguments (given 1, expected 0)
Overview#
What Happened#
2026-07-13 14:51 KST (ap-southeast-1, production) cupixworks-api 에서 PhaseMetric 레코드가 생성될 때 발행되는 phase_metric.created pub/sub 알림이 subscriber 로 전달되는 과정에서 ArgumentError 가 발생했다. Cupix::PubSub::Subscribers::ElementRecordSynchronizer#created 는 인자 0개를 받도록 정의되어 있는데, base subscriber 는 항상 ActiveSupport::Notifications::Event 를 1개 넘긴다. 두 건이 14초 간격으로 관측되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | ArgumentError |
| exception.message | wrong number of arguments (given 1, expected 0) |
| top_frame | lib/cupix/pub_sub/subscribers/element_record_synchronizer.rb:5 |
| env | production, ap-southeast-1 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cupixworks-api / SiteInsights (PhaseMetric) | 2 | PhaseMetric 생성 시 SiteInsights element record 동기화 실패 — ElementRecordMetaSyncWorker 가 enqueue 되지 않음 |
Timeline#
- 2026-07-13 14:51:50 KST — 첫 번째
phase_metric.created알림 처리 실패 (ArgumentError) - 2026-07-13 14:52:04 KST — 두 번째 실패, 이후 관측 없음
- 2026-07-13 (RCA) — code path 확인 및 원인 특정
Error Log#
processing 'phase_metric.created' failed: wrong number of arguments (given 1, expected 0)
Impact#
- Service:
cupixworks-api - 발생 횟수: 2
- 최초 발생: 2026-07-13 14:51:50 KST
- 최근 발생: 2026-07-13 14:52:04 KST
PhaseMetric 이 생성된 두 케이스에서 SiteInsights 쪽 element record 동기화 워커(ElementRecordMetaSyncWorker)가 enqueue 되지 않았다. 사용자에게 노출되는 API 응답은 실패하지 않았지만 (subscriber 는 base 에서 rescue StandardError 로 삼켜짐), 해당 Phase 의 element record 메타 데이터는 최신 상태로 재계산되지 않아 SiteInsights 화면/집계에서 stale 상태가 될 수 있다.
Root Cause Summary#
Cupix::PubSub::Subscribers::ElementRecordSynchronizer#created 메서드가 파라미터 없이 정의(def created)되어 있는데, base subscriber 의 call 은 항상 ActiveSupport::Notifications::Event 를 인자로 넘겨서 handler.send(method_name, event) 를 호출한다. 결과적으로 첫 dispatch 에서 ArgumentError: wrong number of arguments (given 1, expected 0) 가 발생하고, 메서드 본문의 event.payload[:model] 조차 실행되지 못한다. 같은 파일의 formula_changed(event) 는 정상적으로 event 파라미터를 받고 있어 created 메서드의 파라미터 누락은 명백한 오타/누락 버그이다.
Technical Analysis#
Code Path#
- Publish 지점:
PhaseMetric저장 후Cupix::PubSub::NotificationsManager#publish_notifications→Cupix::PubSub::Publisher.broadcast_event('phase_metric', 'created', { model: ... }) - Dispatch entry:
lib/cupix/pub_sub/subscribers/base.rb:20 - Failure point:
lib/cupix/pub_sub/subscribers/element_record_synchronizer.rb:5
Publisher 는 ActiveSupport::Notifications.instrument 로 이벤트를 발행한다.
def broadcast_event(namespace, event_name, payload = {}, &block)
event_name = [namespace, event_name].compact.join('.')
ActiveSupport::Notifications.instrument(event_name, payload) do
yield if block_given?
end
end
Base subscriber 는 subscription 콜백에서 반드시 ActiveSupport::Notifications::Event 객체를 만들어 handler 에 넘긴다.
def call(subscription_name, *args)
method_name = subscription_name.gsub("#{namespace}.", '')
handler = self.class.new(namespace)
Cupix::Logger.debug("triggered by: '#{subscription_name}'", class: self.class.name, function: method_name)
handler.send(method_name, ActiveSupport::Notifications::Event.new(subscription_name, *args))
rescue StandardError => e
Cupix::Logger.error("processing '#{subscription_name}' failed: #{e.message}", class: self.class.name, function: method_name, error: e)
end
Subscriber 는 initializer 에서 phase_metric namespace 로 attach 된다.
Rails.application.config.to_prepare do
Cupix::PubSub::Subscribers::ElementRecordSynchronizer.attach_to('phase_metric')
end
created 메서드 정의는 파라미터 없이 선언되어 있으며, 본문에서는 정의되지 않은 로컬 변수 event 를 참조한다. 같은 클래스의 formula_changed(event) 와 비교하면 파라미터 선언이 누락된 상태임이 명확하다.
module Cupix
module PubSub
module Subscribers
class ElementRecordSynchronizer < Subscribers::Base
def created
send("_sync_by_#{self.namespace}", event.payload[:model])
end
def formula_changed(event)
model = event.payload[:model]
user = event.payload[:user]
raise Cupix::Errors::Argument.new(code: 'ARG10300', reason: 'only PhaseMetric is allowed') unless model.is_a?(::PhaseMetric)
jid = ::ElementRecordMetaSyncWorker.perform_async('Phase', model.phase_id, user.try(:id) || model.user_id, Current.si_trace_id, Current.si_trace_origin)
Cupix::Logger.info("ElementRecordMetaSyncWorker (jid: #{jid}) requested for Phase with id: #{model.phase_id}", class: self.class.name, function: __method__, model: { name: 'Phase', id: model.phase_id }, jid: jid)
end
private
def _sync_by_phase_metric(model)
jid = ::ElementRecordMetaSyncWorker.perform_async('Phase', model.phase_id, model.user_id, Current.si_trace_id, Current.si_trace_origin)
Cupix::Logger.info("ElementRecordMetaSyncWorker (jid: #{jid}) requested for Phase with id: #{model.phase_id}", class: self.class.name, function: __method__, model: { name: 'Phase', id: model.phase_id }, jid: jid)
end
end
end
end
end
기대 동작 vs 실제 동작
- 기대:
created(event)가 이벤트를 받아event.payload[:model]로PhaseMetric인스턴스를 꺼내고,_sync_by_phase_metric(model)을 통해ElementRecordMetaSyncWorker를 enqueue. - 실제:
send(method_name, event)호출 시점에def created가 0-arity 이므로ArgumentError발생. 본문에 도달하지 못하고 base 의rescue StandardError에서 처리 → error 로그만 남고 worker 는 스케줄되지 않음.
Blame 상 이 메서드 형태는 98ec22c4 (2025-02-21 eddy.lee) 부터 존재하며, 이후 d6ebb2ec (2026-02-25, TSLA-11418) 커밋은 같은 파일에서 _sync_by_phase_metric 의 perform_async 인자에 Current.si_trace_id/origin 을 추가했을 뿐 created 시그니처는 손대지 않았다. 즉 잠재 결함이 오래 존재했지만, PhaseMetric.create_event_publishable? 이 false 로 설정되어 있어 평소에는 dispatch 되지 않다가 최근 이벤트 게이팅이 열린 케이스에서만 노출된 것으로 보인다 (app/models/concerns/eventable/siteinsights/phase_metric.rb:7-9).
def create_event_publishable?
false
end
def update_event_publishable?
false
end
def delete_event_publishable?
true
end
create_event_publishable? 이 false 임에도 실제로 phase_metric.created 알림이 발행된 정확한 코드 경로는 이 RCA 시점에 특정하지 못했다 (uncertain -- needs verification). Publisher 자체는 다른 경로(직접 broadcast_event('phase_metric', 'created', ...) 호출 또는 eventable 이 아닌 별도 pub/sub 등)로도 트리거될 수 있으므로, 조사가 필요하다.
Log Evidence#
Datadog query (재현)
service:cupixworks-api status:error @environment:production "processing 'phase_metric.created' failed: wrong number of arguments (given 1, expected 0)"
시간 범위: from_ts=1783918260000&to_ts=1783925580000 (클러스터 파일의 ## Datadog URL 그대로).
핵심 로그 항목 (원문)
{
"timestamp": "2026-07-13 14:52:04",
"status": "error",
"message": "processing 'phase_metric.created' failed: wrong number of arguments (given 1, expected 0)",
"class": "Cupix::PubSub::Subscribers::ElementRecordSynchronizer",
"function": "created",
"error": { "msg": "wrong number of arguments (given 1, expected 0)" }
}
{
"timestamp": "2026-07-13 14:51:50",
"status": "error",
"message": "processing 'phase_metric.created' failed: wrong number of arguments (given 1, expected 0)",
"class": "Cupix::PubSub::Subscribers::ElementRecordSynchronizer",
"function": "created",
"error": { "msg": "wrong number of arguments (given 1, expected 0)" }
}
두 로그 모두 class/function 태그가 base subscriber 의 Cupix::Logger.error(..., class: self.class.name, function: method_name, ...) 에서 유래하며, 실패 지점은 ElementRecordSynchronizer#created 로 정확히 지목된다 (lib/cupix/pub_sub/subscribers/base.rb:26).
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | ElementRecordSynchronizer#created 가 0-arity 로 정의되어 있어 base subscriber 가 넘기는 Event 인자와 시그니처가 어긋난다 |
element_record_synchronizer.rb:5 def created, base.rb:24 handler.send(method_name, Event.new(...)), 로그의 class/function 태그가 정확히 이 지점을 가리킴, 에러 메시지 문자열이 Ruby ArgumentError arity 미스매치 표준 문구 |
— | Confirmed |
| H2 | Payload 자체가 손상되어 Event.payload[:model] 접근에서 실패 |
본문 참조 시 payload 접근이 존재 | ArgumentError 는 메서드 진입 시점(arity check)에서 발생. event.payload 는 실행조차 되지 못함. 에러 문구도 NoMethodError/KeyError 가 아닌 arity 문구 |
Rejected |
| H3 | Publisher 가 잘못된 event_name 을 사용하거나 subscriber 이중 attach | attach_to 는 initializer 에서 1회, broadcast_event 는 namespace/event_name 을 결합 (publisher.rb:6) |
로그의 subscription_name 이 정확히 phase_metric.created 로 정상 조합. 이중 attach 였다면 로그는 arity 가 아닌 다른 에러였을 것 |
Rejected |
| H4 | formula_changed(event) 처럼 최근 리팩터링에서 파라미터가 잘못 제거됨 |
git blame 결과 def created 는 98ec22c4 (2025-02-21, eddy.lee) 부터 존재. 이후 파일 변경(TSLA-11418, d6ebb2ec)은 _sync_by_phase_metric 만 손댐 |
최근 회귀가 아닌, 최초 도입 시부터의 결함 | Rejected (원인 부분만 부분 확정: 회귀 아님) |
Fix Recommendation#
즉시 조치 (Critical)#
lib/cupix/pub_sub/subscribers/element_record_synchronizer.rb:5의def created시그니처에event파라미터를 추가하여 base subscriber 의 dispatch 계약(handler.send(method_name, ActiveSupport::Notifications::Event.new(...)))과 일치시킨다. 같은 파일formula_changed(event)가 이미 올바른 패턴을 따르고 있으므로 동일한 스타일을 적용한다.
- def created- send("_sync_by_#{self.namespace}", event.payload[:model])- end+ def created(event)+ send("_sync_by_#{self.namespace}", event.payload[:model])+ end- 관련 spec 이 있다면 (
spec/lib/cupix/pub_sub/subscribers/element_record_synchronizer_spec.rb등)phase_metric.created알림 발행 시ElementRecordMetaSyncWorker.perform_async가 호출되는지 확인하는 케이스를 추가/보강한다 (현재는 arity 결함이 spec 에도 반영되어 있었을 가능성 — uncertain, spec 파일 존재 확인 필요).
단기 개선 (1주 이내)#
Cupix::PubSub::Subscribers::Base#call이 모든 예외를rescue StandardError로 삼키면서 error 로그만 남기는 구조(base.rb:25-27) 때문에 arity 미스매치 같은 프로그래밍 오류가 배포 후에도 조용히 흘러갔다. base 에서 subscriber 메서드 정의 시점에 arity 를 검증하거나 (method(:created).arity확인), 최소한 CI 에서 각 subscriber 의 public 메서드 시그니처가(event)인지 확인하는 lint 를 추가한다.PhaseMetric의create_event_publishable?이false인데도 이번에 알림이 실제로 발행된 경로를 특정한다. eventable gating 이 우회되는 경우가 있다면 다른 subscriber 에도 동일한 잠재 결함이 있을 수 있다.
장기 개선 (재발 방지)#
- Pub/Sub subscriber 를 위한 명시적 인터페이스(예: base class 에서
abstract또는expect_event(event)시그니처 강제)를 도입해 새로 추가되는 subscriber 가 컴파일/부트 시점에 시그니처 오류를 잡히도록 한다. - Sorbet 타입 시그니처(
sig { params(event: ActiveSupport::Notifications::Event).void })를 subscriber 메서드에 강제하는 규칙을 추가하면 정적으로 감지 가능하다.
Monitoring#
- 알림/메트릭: pub/sub subscriber 실패 카운트를 별도 지표로 노출.
class:Cupix::PubSub::Subscribers::*,function:*, message prefix"processing '","' failed:"를 기반으로 그룹화. - Datadog query 예시 (dashboard timeseries widget 에 그대로 삽입 가능):
sum:datadog.estimated_usage.logs.ingested_events{service:cupixworks-api,status:error,@class:Cupix::PubSub::Subscribers::*}.as_count()
배포 후 이번 케이스 재발 여부만 확인하려면 다음 쿼리를 사용한다.
service:cupixworks-api status:error @environment:production "processing 'phase_metric.created' failed"
Risk Assessment#
- Risk level: low (사용자 API 응답에는 영향 없음, subscriber 는 rescue 로 삼켜짐. 다만 SiteInsights element record 동기화가 누락되어 데이터 stale 위험 존재)
- 예상 복잡도: trivial (한 줄 시그니처 수정 + spec 보강)