ES /docs

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#

  1. 2025-02-19 00:15 KST — 최초 발생 (first_seen 2025-02-18T15:15:13Z)
  2. 2026-07-22 ~ 2026-07-31 (KST) — 최근 재발. 동일 facility key (w8opxs, x1rax0, hnxykm, gq08zn) 로 14초 내 34건씩 burst 발생
  3. 2026-08-01 08:16 KST — 최근 발생 (last_seen 2026-07-31T14:16:30Z = KST 23:16:30, 아래 로그와 일치)
  4. 2026-08-04 — RCA 수행. Representative Error 가 stale 하지 않음을 확인 (recent 로그가 동일 메시지)

Error Log#

Datadog Logs

text
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
app/controllers/api/v1/facilities_controller.rb:161-165ruby
def refresh_elements_count
  @model = repository_instance.refresh_elements_count
  show
end
  • Guard 판정 및 raise: app/repositories/facility_repository.rb:265-271
app/repositories/facility_repository.rb:265-271ruby
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
app/models/concerns/has_element.rb:7-33ruby
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
app/controllers/concerns/server_error_controller.rb:7-40ruby
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::ArgumentCupix::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 로 기록됨):

text
service:cupixworks-api "already requested"

핵심 로그 (recent, last_seen 시점과 일치 — Representative Error 가 stale 하지 않음):

json
{
  "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건):

text
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 로 14초 내 34건 burst (Datadog), guard 코드 has_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 로 이동 확인용):

text
service:cupixworks-api "already requested" "refresh_elements_count"

refresh_elements_count 엔드포인트의 500 응답 건수 추이:

text
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 코드 개선은 별도 트랙으로 권장한다.