Cupix::Errors::Argument: already requested
RCA: Cupix::Errors::Argument: already requested
Overview#
What Happened#
cupixworks-api (tesla) 에서 PUT /api/v1/facilities/:key/tasks/refresh_elements_count 요청 처리 중 Cupix::Errors::Argument (ARG10060, reason already requested) 가 발생했다. 이는 사용자가 동일 facility 에 대해 "elements count 재계산" 요청을 30분 debounce window 내에 다시 눌렀을 때 의도적으로 발생시키는 guard 예외다. Datadog 로그를 보면 같은 facility key 로 14초 사이에 34건의 요청이 몰리는 전형적인 중복 클릭 패턴이다. 문제는 이 예외가 client 재요청(4xx) 이 아니라 HTTP 500 server error 로 매핑되어 Error Tracking 에 잡힌다는 점이다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Cupix::Errors::Argument |
| exception.message | already requested |
| exception.code | ARG10060 |
| top_frame | app/repositories/facility_repository.rb:267 |
| entry_point | PUT /api/v1/facilities/:key/tasks/refresh_elements_count (Api::V1::FacilitiesController#refresh_elements_count) |
| http_status | 500 (server_error_controller 가 Argument 를 500 으로 rescue) |
| env | production (service cupixworks-api) |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cupixworks-api (tesla) — Facility elements count | 32 (16개월 누적) | 첫 refresh 요청은 정상 처리됨. 중복 클릭에 대해 500 응답이 반환되어 사용자에게 오류로 노출될 수 있으나, 기능적 데이터 손실은 없음 |
Timeline#
- 2025-02-19 00:15 KST — 최초 발생 (
first_seen2025-02-18T15:15:13Z) - 2026-07-22 ~ 2026-07-31 (KST) — 최근 재발. 동일 facility key (
w8opxs,x1rax0,hnxykm,gq08zn) 로 14초 내 34건씩 burst 발생 - 2026-08-01 08:16 KST — 최근 발생 (
last_seen2026-07-31T14:16:30Z = KST 23:16:30, 아래 로그와 일치) - 2026-08-04 — RCA 수행. Representative Error 가 stale 하지 않음을 확인 (recent 로그가 동일 메시지)
Error Log#
already requested
Impact#
- Service:
cupixworks-api(실제 repo: tesla) - 발생 횟수: 32 (2025-02-18 ~ 2026-07-31, 약 16개월 누적, <1/day)
- 최초 발생: 2025-02-19 00:15 KST
- 최근 발생: 2026-08-01 08:16 KST
Root Cause Summary#
FacilityRepository#refresh_elements_count (app/repositories/facility_repository.rb:267) 는 동일 facility 에 대해 이미 진행 중인 재계산 요청이 있으면 Cupix::Errors::Argument.new(code: 'ARG10060', reason: 'already requested') 를 raise 한다. 이 판정은 HasElement#refresh_elements_count_already_requested? (app/models/concerns/has_element.rb:7-11) 가 30분 TTL Rails cache key 존재 여부로 수행하는 debounce guard 다. 즉 예외 자체는 "중복 요청을 막는 정상 동작"이며 코드 결함이 아니다. 실제 defect 는 이 client-side 중복 요청 예외가 server_error_controller.rb:7-8 에서 Cupix::Errors::System 과 함께 system_500_error 로 rescue 되어 HTTP 500 으로 응답된다는 매핑 오류다. 사용자 중복 클릭(정상 UX)이 500 server error 로 집계되어 Error Tracking 노이즈를 만든다.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/api/v1/facilities_controller.rb:161-165
def refresh_elements_count
@model = repository_instance.refresh_elements_count
show
end
- Guard 판정 및 raise:
app/repositories/facility_repository.rb:265-271
def refresh_elements_count
raise Cupix::Errors::PermissionDenied.new(code: 'PERM10000', reason: 'Permission denied') unless Pundit.policy(current_user, @model).refresh_elements_count?
raise Cupix::Errors::Argument.new(code: 'ARG10060', reason: 'already requested') if @model.refresh_elements_count_already_requested?
@model.refresh_elements_count
@model
end
- Debounce 판정 로직 (30분 TTL cache):
app/models/concerns/has_element.rb:7-33
def refresh_elements_count_already_requested?
return true if _elements_count_refresh_request.present?
false
end
def refresh_elements_count
self.write_elements_count_refresh_request_cache
self.update!(refresh_elements_count_requested_at: DateTime.now, elements_count_refresh_state: 'queued')
RefreshElementsCountWorker.perform_async(self.key)
end
def _elements_count_refresh_request
Rails.cache.read(elements_count_refresh_request_cache_key)
end
def write_elements_count_refresh_request_cache
Rails.cache.write(elements_count_refresh_request_cache_key, DateTime.now, expires_in: 30.minutes)
end
- Failure point (매핑 오류):
app/controllers/concerns/server_error_controller.rb:7-8, 38-40
rescue_from Cupix::Errors::System,
Cupix::Errors::Argument, with: :system_500_error
# ...
def system_500_error(exception)
raise_error(500, exception)
end
기대 동작 vs 실제 동작: 첫 refresh 요청은 cache write + update! + RefreshElementsCountWorker.perform_async 로 정상 처리된다. 30분 window 안에서의 재요청은 guard 에 걸려 "이미 요청됨"으로 거부되어야 하며, 이는 정상 흐름이다. 그러나 Cupix::Errors::Argument 가 Cupix::Errors::System 과 동일한 system_500_error handler 로 rescue 되므로, client 의 중복 요청(4xx 성격)이 HTTP 500 으로 응답되고 Error Tracking 에 server error 로 집계된다.
Log Evidence#
Datadog query (status:error 로는 0건 — 500 request 로그는 status:info 로 기록됨):
service:cupixworks-api "already requested"
핵심 로그 (recent, last_seen 시점과 일치 — Representative Error 가 stale 하지 않음):
{
"timestamp": "2026-07-31 23:16:30",
"status": "info",
"message": "[500] PUT /api/v1/facilities/gq08zn/tasks/refresh_elements_count (Api::V1::FacilitiesController#refresh_elements_count)",
"error": {
"reason": "already requested",
"code": "ARG10060",
"message": "already requested",
"class": "Cupix::Errors::Argument"
}
}
중복 클릭 burst 패턴 (동일 facility key, 14초 내 34건):
2026-07-31 23:16:30 PUT .../facilities/gq08zn/.../refresh_elements_count [500]
2026-07-31 23:16:30 PUT .../facilities/gq08zn/.../refresh_elements_count [500]
2026-07-31 23:16:28 PUT .../facilities/gq08zn/.../refresh_elements_count [500]
2026-07-31 23:16:28 PUT .../facilities/gq08zn/.../refresh_elements_count [500]
2026-07-31 01:11:28 PUT .../facilities/gq08zn/.../refresh_elements_count [500]
2026-07-31 00:34:21 PUT .../facilities/hnxykm/.../refresh_elements_count [500]
2026-07-23 01:25:59 PUT .../facilities/x1rax0/.../refresh_elements_count [500]
2026-07-22 01:20:23 PUT .../facilities/w8opxs/.../refresh_elements_count [500]
각 burst 는 서로 다른 facility key 이지만 burst 내부는 동일 key 이며 초 단위로 몰려 있다 — 단일 요청 재진입이 아니라 사용자의 연속 클릭(concurrency-free debounce hit) 임을 뒷받침한다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 사용자 중복 클릭이 30분 debounce guard 에 걸려 ARG10060 raise, 그것이 500 으로 매핑되어 노이즈 발생 |
동일 facility key 로 1has_element.rb:7-11 + cache TTL 30분 has_element.rb:28, rescue 매핑 server_error_controller.rb:8 |
— | Confirmed |
| H2 | Representative Error "already requested" 가 stale 하고 실제 최근 에러는 다른 메시지 | — | recent 로그(2026-07-31 23:16:30)가 동일 already requested/ARG10060/Cupix::Errors::Argument — stale 아님 |
Rejected |
| H2 대안 | 동일 요청의 서버 측 재진입(단일 클릭인데 내부 retry) 으로 인한 self-conflict | — | burst 가 별개 facility key 로 분산되고 burst 내부 timestamp 가 초 단위로 어긋남 → client 연속 요청 패턴. 서버 내부 retry 근거 없음 | Rejected |
| H3 | 코드 결함 (nil reference, 잘못된 상태 판정)으로 정상 요청이 실패 | — | 첫 요청은 정상 처리(cache write + worker enqueue). guard 는 의도된 debounce. 예외 메시지는 고정 코드 상수 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
없음. 데이터 손실이나 기능 장애가 아니다. 첫 refresh 요청은 항상 정상 처리된다.
단기 개선 (1주 이내)#
Cupix::Errors::Argument의 500 매핑을 재검토 (app/controllers/concerns/server_error_controller.rb:7-8).ARG10060 already requested는 client 의 중복/조기 재요청이므로 server 5xx 가 아니라409 Conflict또는429 Too Many Requests성격의 4xx 로 응답하는 것이 적절하다.Cupix::Errors::Argument는 다른 곳에서도 광범위하게 쓰이므로(예:integration_repository.rb,parameter/*.rb) 일괄 4xx 로 옮기면 회귀 위험이 있다 — refresh_elements_count 전용으로 debounce 거부를 별도 client 예외 클래스(예:Cupix::Errors::Parameter/전용 conflict) 로 raise 하도록 좁게 변경하는 것을 권장. 프런트엔드가 이 상태 코드를 어떻게 처리하는지 담당자와 협의 필요 (breaking change 가능).- 프런트엔드 debounce/버튼 disable: refresh 버튼을 요청 in-flight 동안 disable 하여 중복 클릭 자체를 억제 (프런트 담당 조율 필요).
장기 개선 (재발 방지)#
- Error Tracking 에서 client 재요청 성격의 debounce/rate-limit 거부가 server error 로 집계되지 않도록,
Cupix::Errors계층의 HTTP status 매핑 규칙을 정리 (client 4xx vs server 5xx 명확히 분리).ARG*코드는 원칙적으로 argument/parameter 오류이므로 4xx 로 매핑되는 것이 의미상 일관적이다.
Monitoring#
500 응답 중 ARG10060 already requested 빈도 추이 (매핑 개선 후 4xx 로 이동 확인용):
service:cupixworks-api "already requested" "refresh_elements_count"
refresh_elements_count 엔드포인트의 500 응답 건수 추이:
service:cupixworks-api "refresh_elements_count" "[500]"
Risk Assessment#
- Risk level: low
- 예상 복잡도: standard (status 매핑 변경은 프런트엔드 계약에 영향 → 조율 필요)
- 기능 영향: 없음 (첫 요청 정상 처리, 데이터 손실 없음). 순수 UX/노이즈 이슈.
Noise Verdict#
noise — 30분 debounce guard 에 걸린 사용자 중복 클릭(정상 UX)이 의도적으로 raise 하는 예외이며 첫 요청은 항상 정상 처리되므로 코드 결함이 아니다. 다만 client 재요청을 500 으로 매핑하는 status 코드 개선은 별도 트랙으로 권장한다.