NoMethodError: undefined method `save_to_s3' for #<TeamPermission:0x0000000001a97418>
RCA: NoMethodError: undefined method `save_to_s3' for #<TeamPermission>
Overview#
What Happened#
SaveToS3Worker 가 class_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#
- 2024-09-05 14:18 KST — 최초 발생 (
first_seen). 이 시점에 이미TeamPermission은save_to_s3에 응답하지 않았음 - 2025-03-07 09:33 KST — 커밋
cc7f41057(TSLA-9011) 이 base 모듈에서include ::DataWareHouse::FullJson을 주석 처리, full-JSON S3 업로드를 명시적으로 suspend - 2026-07-31 11:03 KST — 최근 발생 (
last_seen). 잔여/재시도 job 이 drain 되며 산발적으로 재현 - 2026-08-04 — RCA 수행. Datadog 14일 로그 검색에서 매칭 로그 0건,
save_to_s3큐 items 0
Error Log#
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 안에만 존재한다. TeamPermission 은 DataWareHouse::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 를 호출한다.
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 에만 정의됨:
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 모듈로 이어진다:
include ::DataWareHouse::TeamPermission
included do
include ::DataWareHouse
end
Failure point 은 base 모듈이다. FullJson include 가 주석 처리되어 있어 save_to_s3 가 믹스인되지 않는다:
included do
include ::DataWareHouse::Base
include ::DataWareHouse::PartialJson
# include ::DataWareHouse::FullJson
# include ::DataWareHouse::Csv
end
기대 동작: SaveToS3Worker 에 넘어오는 모델은 save_to_s3 에 응답해야 한다. 실제 동작: TeamPermission 은 PartialJson 과 Base 만 include 하고 FullJson 은 include 하지 않아 save_to_s3 미정의 -> NoMethodError. 현재 코드에서 이 워커를 enqueue 하는 유일한 위치는 full_json.rb:19 뿐이며 이 경로는 TeamPermission 에서 실행되지 않으므로, 발생 건은 과거 enqueue 된 잔여/재시도 job 이다.
Log Evidence#
Datadog 로그 검색 (retention 14일). 사용한 쿼리:
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 로그:
{
"timestamp": "2026-08-04 18:15:47",
"status": "info",
"message": "[Cron][Sidekiq] save_to_s3 has items: 0"
}
git 증거로 root cause 시점 확인:
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 | TeamPermission 이 FullJson 을 include 하지 않아 save_to_s3 미정의, 잔여 SaveToS3Worker job 이 실행되며 NoMethodError 발생 |
base 모듈 data_ware_house.rb:9 에서 # include ::DataWareHouse::FullJson 주석 처리 확인, save_to_s3 는 full_json.rb:34 에만 정의, 워커 save_to_s3_worker.rb:9 가 respond_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.yml 의 save_to_s3 큐 정의를 함께 정리하는 방향을 권장한다.
Monitoring#
save_to_s3 큐 job 실패 추이 (widget timeseries):
sum:sidekiq.jobs.failure{queue:save_to_s3}.as_count()
SaveToS3Worker 관련 에러 로그 발생 여부:
service:cupixworks-worker "SaveToS3Worker"
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial
Noise Verdict#
bug — SaveToS3Worker 가 respond_to? 검사 없이 save_to_s3 를 호출해, FullJson concern 을 include 하지 않는 TeamPermission 잔여 job 이 실행될 때 NoMethodError 가 발생하는 방어 로직 부재의 코드 결함이다.