ES /docs

NoMethodError: undefined method `save_to_s3' for #<TeamPermission:0x0000000001a97418>

RCA: NoMethodError: undefined method `save_to_s3' for #<TeamPermission>

Overview#

What Happened#

SaveToS3Workerclass_name="TeamPermission" 인자로 실행되어 model.save_to_s3 를 호출했으나, TeamPermission 모델은 save_to_s3 를 정의하는 DataWareHouse::FullJson concern 을 include 하지 않아 NoMethodError 가 발생했다. 이 메서드와 enqueue 콜백은 모두 FullJson 안에 있고 해당 concern 은 base 모듈(data_ware_house.rb)에서 주석 처리되어 있다. 즉 현재 코드로는 이 워커가 새로 enqueue 될 수 없으며, 발생 건은 과거에 쌓인 잔여/재시도 job 이 drain 되며 나온 것이다. 23개월간 174건으로 빈도가 매우 낮다.

Quick Facts#

Field Value
exception.class NoMethodError
exception.message undefined method 'save_to_s3' for #<TeamPermission:0x0000000001a97418>
top_frame app/workers/save_to_s3_worker.rb:9
runtime Ruby on Rails (Sidekiq worker, queue save_to_s3, retry 1)
deploy suspend commit cc7f41057 (TSLA-9011, 2025-03-07)
env production (cupixworks-api / cupixworks-worker, tesla repo)

Affected Teams#

Team / Domain Error Count Impact
cupixworks-worker (data warehouse S3 export) 174 (23개월 누적) 잔여 SaveToS3Worker job 실패. 사용자 대면 기능 영향 없음. data warehouse full-JSON 업로드는 TSLA-9011 로 이미 의도적으로 중단됨

Timeline#

  1. 2024-09-05 14:18 KST — 최초 발생 (first_seen). 이 시점에 이미 TeamPermissionsave_to_s3 에 응답하지 않았음
  2. 2025-03-07 09:33 KST — 커밋 cc7f41057 (TSLA-9011) 이 base 모듈에서 include ::DataWareHouse::FullJson 을 주석 처리, full-JSON S3 업로드를 명시적으로 suspend
  3. 2026-07-31 11:03 KST — 최근 발생 (last_seen). 잔여/재시도 job 이 drain 되며 산발적으로 재현
  4. 2026-08-04 — RCA 수행. Datadog 14일 로그 검색에서 매칭 로그 0건, save_to_s3 큐 items 0

Error Log#

Datadog Logs

text
undefined method `save_to_s3' for #<TeamPermission:0x0000000001a97418>

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 174
  • 최초 발생: 2024-09-05 14:18 KST
  • 최근 발생: 2026-07-31 11:03 KST

Root Cause Summary#

SaveToS3Worker#perform 은 전달받은 class_name 을 constantize 한 뒤 무조건 model.save_to_s3 를 호출한다. save_to_s3 메서드와 이 워커를 enqueue 하는 after_save :save_to_s3_in_worker 콜백은 둘 다 DataWareHouse::FullJson concern 안에만 존재한다. TeamPermissionDataWareHouse::TeamPermission -> ::DataWareHouse (base) 를 include 하는데, base 모듈에서 include ::DataWareHouse::FullJson 이 주석 처리되어 있어(data_ware_house.rb, TSLA-9011 로 suspend) TeamPermission 인스턴스는 save_to_s3 에 응답하지 않는다. enqueue 경로가 비활성화된 상태에서 과거에 쌓인 SaveToS3Worker job 이 뒤늦게 실행되면 이 NoMethodError 가 발생한다. 워커가 인자로 받은 모델이 실제로 메서드를 갖고 있는지 검증하지 않는 것이 결함이다.

Technical Analysis#

Code Path#

Entry point: app/workers/save_to_s3_worker.rb:5-10. 워커는 모델을 찾은 뒤 respond_to? 검사 없이 곧바로 save_to_s3 를 호출한다.

app/workers/save_to_s3_worker.rb:5-10ruby
def perform(class_name, id)
  model = class_name.classify.safe_constantize.find_by_id(id)
  return if model.nil?

  model.save_to_s3
end

save_to_s3 와 enqueue 콜백은 FullJson concern 에만 정의됨:

app/models/concerns/data_ware_house/full_json.rb:5-34ruby
included do
  after_save :save_to_s3_in_worker, unless: proc { %w[development].include?(Rails.env) }
  after_save :save_to_file_in_worker, unless: proc { %w[development].include?(Rails.env) }
end
# ...
def save_to_s3_in_worker
  SaveToS3Worker.perform_async(self.class.name, self.id)
end
# ...
def save_to_s3(datetime: nil)

TeamPermission 의 include 체인은 base 모듈로 이어진다:

app/models/team_permission.rb:6ruby
  include ::DataWareHouse::TeamPermission
app/models/concerns/data_ware_house/team_permission.rb:5-7ruby
included do
  include ::DataWareHouse
end

Failure point 은 base 모듈이다. FullJson include 가 주석 처리되어 있어 save_to_s3 가 믹스인되지 않는다:

app/models/concerns/data_ware_house.rb:6-11ruby
included do
  include ::DataWareHouse::Base
  include ::DataWareHouse::PartialJson
  # include ::DataWareHouse::FullJson
  # include ::DataWareHouse::Csv
end

기대 동작: SaveToS3Worker 에 넘어오는 모델은 save_to_s3 에 응답해야 한다. 실제 동작: TeamPermissionPartialJsonBase 만 include 하고 FullJson 은 include 하지 않아 save_to_s3 미정의 -> NoMethodError. 현재 코드에서 이 워커를 enqueue 하는 유일한 위치는 full_json.rb:19 뿐이며 이 경로는 TeamPermission 에서 실행되지 않으므로, 발생 건은 과거 enqueue 된 잔여/재시도 job 이다.

Log Evidence#

Datadog 로그 검색 (retention 14일). 사용한 쿼리:

text
service:cupixworks-api "save_to_s3"
service:cupixworks-worker "SaveToS3Worker"
service:cupixworks-worker status:error "undefined method"
service:cupixworks-worker "NoMethodError"
(service:cupixworks-api OR service:cupixworks-worker) "Failed to save TeamPermission"

NoMethodError / SaveToS3Worker / undefined method 관련 검색은 모두 0건이었다. 최근 14일 동안 이 에러가 재현되지 않았음을 의미한다. et: Error Tracking issue 는 Datadog 로그 retention(14일) 을 넘어 occurrence 를 보관하므로, last_seen (2026-07-31) 의 원본 로그는 이미 rotate out 되었거나 APM error span 으로만 집계되었을 가능성이 높다. representative message 는 특정 코드 경로에 고정된 예외 문자열이라 변형 모호성이 없고 신뢰 가능하다.

save_to_s3 큐가 현재 비어 있음을 보여주는 cron 로그:

text
{
  "timestamp": "2026-08-04 18:15:47",
  "status": "info",
  "message": "[Cron][Sidekiq] save_to_s3 has items: 0"
}

git 증거로 root cause 시점 확인:

text
cc7f41057 TSLA-9011 set fulljson & csv upload suspended
-    include ::DataWareHouse::Csv
+    # include ::DataWareHouse::FullJson
commit date: 2025-03-07 09:33:38 +0900

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 TeamPermissionFullJson 을 include 하지 않아 save_to_s3 미정의, 잔여 SaveToS3Worker job 이 실행되며 NoMethodError 발생 base 모듈 data_ware_house.rb:9 에서 # include ::DataWareHouse::FullJson 주석 처리 확인, save_to_s3full_json.rb:34 에만 정의, 워커 save_to_s3_worker.rb:9respond_to? 없이 호출, git commit cc7f41057 로 suspend 시점 확인 Confirmed
H2 현재 코드에 TeamPermission 저장 시 SaveToS3Worker 를 새로 enqueue 하는 라이브 경로가 있다 grep 결과 enqueue 지점은 full_json.rb:19 단 하나이며 FullJson 미include 인 TeamPermission 에서는 실행 불가. save_to_s3 큐 items 0 Rejected
H3 S3 업로드 실패(권한/네트워크) 로 인한 에러 save_to_s3! 는 S3 put 을 수행 예외 메시지가 NoMethodError undefined method 'save_to_s3' 로, S3 접근 이전 단계에서 메서드 자체가 없어 발생. S3 에러가 아님 Rejected
H4 representative message 가 stale 하며 실제 최근 message 는 다른 컬럼/원인 et: issue 는 stale 가능성 있음 이 예외는 고정된 코드 경로의 상수 문자열이라 변형이 없음. Datadog 14일 검색 0건이라 최근 대체 message 도 확인 불가하지만 코드 구조로 root cause 확정 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

app/workers/save_to_s3_worker.rb:9 에 방어 로직 추가를 권장한다. 워커가 인자로 받은 모델이 save_to_s3 에 응답하지 않을 수 있으므로, 호출 전에 respond_to?(:save_to_s3) 를 확인하고, 미응답 시 warn 로그 후 조용히 return 하는 방향이 적절하다. 이렇게 하면 잔여/재시도 job 이 NoMethodError 로 실패해 alarm noise 를 만드는 대신 안전하게 소진된다. NoMethodError 는 raise 되면 안 되는 상황(모델이 concern 을 include 하지 않도록 의도적으로 suspend 됨)이므로 error 가 아니라 방어적으로 처리한다.

단기 개선 (1주 이내)#

Sidekiq 의 save_to_s3 큐와 retry set 에 남아 있는 잔여 job 을 확인하고 clear 하는 방향을 검토한다. TSLA-9011 로 full-JSON 업로드가 suspend 된 이후 이 큐에 유효한 신규 job 은 들어오지 않으므로, 잔여 job 은 모두 실패할 운명이다. 큐/retry set drain 여부를 운영팀과 확인한다. (운영 작업이므로 코드 변경 아님)

장기 개선 (재발 방지)#

SaveToS3Worker 는 TSLA-9011 로 enqueue 경로가 사라진 dead worker 다. save_to_s3_in_worker 콜백과 함께 워커 자체를 제거할지, 아니면 full-JSON 업로드를 재개할지 data warehouse export 정책을 결정한다. 재개하지 않을 것이라면 SaveToS3Worker, save_to_s3 관련 concern 코드, config/sidekiq.ymlsave_to_s3 큐 정의를 함께 정리하는 방향을 권장한다.

Monitoring#

save_to_s3 큐 job 실패 추이 (widget timeseries):

text
sum:sidekiq.jobs.failure{queue:save_to_s3}.as_count()

SaveToS3Worker 관련 에러 로그 발생 여부:

text
service:cupixworks-worker "SaveToS3Worker"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial

Noise Verdict#

bug — SaveToS3Workerrespond_to? 검사 없이 save_to_s3 를 호출해, FullJson concern 을 include 하지 않는 TeamPermission 잔여 job 이 실행될 때 NoMethodError 가 발생하는 방어 로직 부재의 코드 결함이다.