ES /docs

RestClient::BadGateway: 502 Bad Gateway

RCA: RestClient::BadGateway 502 (pix genie 결과 기록 실패)

Overview#

Error Tracking 이슈의 Representative Error 는 502 Bad Gateway 라는 오래된(stale) 샘플이다. 그러나 last_seen(2026-07-24 16:28 KST) 근처의 실제 Datadog 로그를 조회하면 현재 발생하는 메시지는 pix genie 추론 결과를 backoffice 에 기록할 때 발생하는 422 검증 오류다. tesla 가 pixgenie backoffice 로 보내는 payload 가 API 스키마와 맞지 않아 backoffice 가 Pydantic 검증 오류를 반환하고, tesla 의 공용 error handler 가 이를 Cupix::Errors::BadGateway (502) 로 감싸 Error Tracking 에 502 로 집계된다.

What Happened#

2026-07-22 ~ 2026-07-27 사이 cupixworks-api(tesla) 에서 PixgenieInferenceOperation#record_results_from_bulk_result 가 pix genie backoffice API 로 결과를 기록하려다 반복 실패했다. backoffice 는 payload 의 seg_id 를 정수로 요구하지만 tesla 는 "seg-0001" 같은 문자열을 보냈고, review_key/level_id/record_id 등 필수 필드가 nil 이었다. backoffice 는 422 검증 오류를 반환했고, tesla 는 이를 Cupix::Errors::BadGateway → HTTP 502 로 변환했다.

Quick Facts#

Field Value
exception.class Cupix::Errors::BadGateway (Error Tracking 상 RestClient::BadGateway 로 표기, stale)
exception.message Failed to record pix genie results to backoffice: Cupix::Errors::BadGateway: [{"type"=>"int_parsing", "loc"=>["body","annotations",0,"seg_id"], "msg"=>"Input should be a valid integer, unable to parse string as an integer", "input"=>"seg-0001"}, ...]
top_frame app/operations/pixgenie_base_operation.rb:136 (handle_error else branch)
runtime Ruby on Rails (tesla)
env production

Affected Teams#

Team / Domain Error Count Impact
pix genie (console inference/review) 15 (Datadog 로그, now-14d) / 14 (Error Tracking) pix genie 추론 결과가 backoffice 에 기록되지 않음

Timeline#

  1. 2024-08-22 18:25 KST — Error Tracking 이슈 first_seen (Representative Error 502 Bad Gateway, stale)
  2. 2026-07-22 ~ 2026-07-27record_results_from_bulk_result 에서 422 검증 오류가 502 로 반복 집계
  3. 2026-07-24 16:28 KST — 이슈 last_seen (Cupix::Errors::BadGateway seg_id 파싱 오류)
  4. 2026-08-04 — RCA 수행. Representative Error 와 실제 최근 메시지 불일치 확인

Error Log#

Datadog Logs

text
502 Bad Gateway

주의: 위 Representative Error 는 first_seen 기준의 stale 샘플이다. 아래는 last_seen 근처의 실제 최근 로그다.

text
Failed to record pix genie results to backoffice: Cupix::Errors::BadGateway: [{"type"=>"string_type", "loc"=>["body", "review_key"], "msg"=>"Input should be a valid string", "input"=>nil}, {"type"=>"int_parsing", "loc"=>["body", "annotations", 0, "seg_id"], "msg"=>"Input should be a valid integer, unable to parse string as an integer", "input"=>"seg-1784867408228-1"}]

Impact#

  • Service: cupixworks-rest_client (APM adapter, 실제 앱은 cupixworks-api = tesla)
  • 발생 횟수: 14 (Error Tracking) / 15 (Datadog 로그 now-14d)
  • 최초 발생: 2024-08-22 18:25 KST
  • 최근 발생: 2026-07-24 16:28 KST

Root Cause Summary#

Error Tracking 은 여러 변형을 하나의 이슈로 묶고 first_seen 샘플(502 Bad Gateway)을 대표로 고정하기 때문에 Representative Error 가 stale 하다. 실제 root cause 는 tesla PixgenieInferenceOperation 가 pix genie backoffice API 로 결과를 기록할 때 payload 스키마가 backoffice 의 Pydantic 모델과 맞지 않는 것이다. backoffice 는 seg_id 를 정수로, review_key/position 을 문자열로, level_id/record_id/probability 를 정수·실수로 요구하지만, tesla 는 seg_id"seg-0001" 같은 문자열로 보내고 나머지 필드는 nil 로 보냈다. backoffice 는 이를 422 검증 오류로 거절한다. tesla 의 공용 error handler PixgenieBaseOperation.handle_error 는 status_code 가 400/401/404 가 아니면 모두 else 로 떨어뜨려 Cupix::Errors::BadGateway 를 raise 하므로, 클라이언트 스키마 오류(422)가 서버 게이트웨이 오류(502)로 잘못 분류되어 Error Tracking 에 502 로 집계된다.

Technical Analysis#

Code Path#

Entry point 는 pix genie 추론 결과를 backoffice 에 기록하는 PixgenieInferenceOperation#record_results_from_bulk_result 다. 이 메서드는 production 에 배포되어 있으나 로컬 develop 체크아웃(HEAD 790e093bb)에는 아직 존재하지 않는다. Datadog 로그의 @class:PixgenieInferenceOperation @function:record_results_from_bulk_result 태그가 호출 지점을 확정한다. 실제 502 변환은 공용 base operation 에서 일어난다.

모든 pix genie HTTP 호출은 PixgenieBaseOperationpost/get 를 거치며, 응답 오류는 handle_error 로 모인다.

app/operations/pixgenie_base_operation.rb:28-39ruby
def post(path, query, body, timeout: nil)
  response = RestClient::Request.execute(
    method: :post,
    url: build_url(path, query),
    payload: body&.to_json,
    headers: json_headers,
    timeout: timeout
  )
  parse_json(response.body)
rescue RestClient::Exception, StandardError => e
  handle_error(e, caller_locations(1, 1)[0].label)
end

Failure point 는 handle_error 의 status_code 분기다. 400/401/404 만 각각 InvalidState/Unauthorized/NotFound 로 매핑하고, 그 외(여기서는 422)는 모두 else 로 떨어져 BadGateway 로 raise 된다.

app/operations/pixgenie_base_operation.rb:125-137ruby
parsed_body = JSON.parse(error_body) rescue {}
error_message = parsed_body['detail'] || parsed_body['message'] || error.message

case status_code
when 400
  raise Cupix::Errors::InvalidState.new(code: status_code, reason: error_message, message: error_message)
when 401
  raise Cupix::Errors::Unauthorized.new(code: status_code, reason: error_message, message: error_message)
when 404
  raise Cupix::Errors::NotFound.new(code: status_code, reason: error_message, message: error_message)
else
  raise Cupix::Errors::BadGateway.new(code: status_code, reason: error_message, message: error_message)
end

error_messageparsed_body['detail'] 를 우선 사용한다. FastAPI 의 422 응답 body 는 {"detail": [{"type": ..., "loc": [...], "msg": ..., "input": ...}]} 형태이므로, 로그와 예외 메시지에 그대로 Pydantic 검증 배열이 실린다.

마지막으로 Cupix::Errors::BadGateway 는 컨트롤러 레벨에서 HTTP 502 로 변환된다.

app/controllers/concerns/server_error_controller.rb:22-64ruby
rescue_from Cupix::Errors::BadGateway, with: :badgateway_on_cupix_502_error
# ...
def badgateway_on_cupix_502_error(exception)
  raise_error(502, exception, code: exception.try(:code) || 'BG10001', type: Cupix::Errors::BadGateway, reason: exception.try(:reason) || 'BadGateway', message: exception.message)
end

기대 동작: pix genie 추론 결과가 스키마에 맞게 backoffice 에 기록되어야 한다. 실제 동작: payload 의 seg_id 가 문자열("seg-0001")이고 review_key/level_id/record_idnil 이라 backoffice 가 422 로 거절하고, tesla 는 이를 502 로 잘못 분류한다.

Log Evidence#

사용한 Datadog 쿼리:

text
(service:cupixworks-api OR service:cupixworks-worker) "BadGateway"
text
(service:cupixworks-api OR service:cupixworks-worker) "Failed to record pix genie results to backoffice"

service:cupixworks-rest_client status:error"RestClient::BadGateway" 는 now-14d 범위에서 0건 (adapter service 이름이며 실제 앱 로그는 tesla 서비스에 있음).

핵심 로그 (최근, class/function 태그 포함):

json
{
  "timestamp": "2026-07-24 13:30:10",
  "status": "error",
  "message": "Failed to record pix genie results to backoffice: Cupix::Errors::BadGateway: [{\"type\"=>\"string_type\", \"loc\"=>[\"body\", \"review_key\"], \"msg\"=>\"Input should be a valid string\", \"input\"=>nil}, {\"type\"=>\"int_parsing\", \"loc\"=>[\"body\", \"annotations\", 0, \"seg_id\"], \"msg\"=>\"Input should be a valid integer, unable to parse string as an integer\", \"input\"=>\"seg-1784867408228-1\"}]",
  "class": "PixgenieInferenceOperation",
  "function": "record_results_from_bulk_result"
}

더 많은 필드가 nil 인 변형도 관측됨 (level_id, record_id, position, probability 모두 nil):

json
{
  "timestamp": "2026-07-27 15:24:22",
  "status": "error",
  "message": "Failed to record pix genie results to backoffice: Cupix::Errors::BadGateway: [{\"type\"=>\"string_type\", \"loc\"=>[\"body\", \"review_key\"], ...}, {\"type\"=>\"int_type\", \"loc\"=>[\"body\", \"level_id\"], ...}, {\"type\"=>\"int_type\", \"loc\"=>[\"body\", \"record_id\"], ...}, {\"type\"=>\"int_parsing\", \"loc\"=>[\"body\", \"annotations\", 0, \"seg_id\"], \"input\"=>\"seg-0001\"}, {\"type\"=>\"string_type\", \"loc\"=>[\"body\", \"annotations\", 0, \"position\"], ...}, {\"type\"=>\"float_type\", \"loc\"=>[\"body\", \"annotations\", 0, \"probability\"], ...}]",
  "class": "PixgenieInferenceOperation",
  "function": "record_results_from_bulk_result"
}

발생 타임라인 (now-14d, 총 15건):

text
2026-07-27 15:24:22, 2026-07-27 15:03:41
2026-07-24 13:30:10, 13:08:31, 12:28:10, 12:00:54, 11:26:59, 11:03:34
2026-07-23 22:10:37, 21:48:35, 19:13:23, 18:50:16, 18:03:00, 17:43:36
2026-07-22 17:52:21

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 Representative Error(502 Bad Gateway)가 실제 현재 원인이다 (게이트웨이/업스트림 다운) Error Tracking 이 502 로 집계 last_seen 근처 로그는 순수 게이트웨이 502 가 아니라 Pydantic 422 검증 배열을 담고 있음. Error Tracking first_seen 샘플이 stale Rejected
H2 pix genie 결과 기록 payload 가 backoffice 스키마와 불일치 (seg_id 타입/필수 필드 nil) → 422 → 502 오분류 record_results_from_bulk_result 로그의 int_parsing seg_id="seg-0001", string_type review_key=nil 등 Pydantic 422 body. handle_error else 분기가 422 를 BadGateway 로 변환 (pixgenie_base_operation.rb:135-137) Confirmed
H3 외부 의존성 outage (dep:*) status board svc:cupixworks-rest_client::unknown active=null, recent=[] Rejected
H4 pixgenie backoffice 서버가 실제로 5xx 를 반환한다 502 로 집계됨 로그의 detail 배열은 FastAPI RequestValidationError(422)의 고정 포맷(loc/type/msg/input). 5xx 는 이런 body 를 만들지 않음 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • PixgenieInferenceOperation#record_results_from_bulk_result (production 배포 버전; 로컬 develop 체크아웃에는 아직 없으므로 워크트리 생성 후 심볼로 재탐색 필요) 에서 backoffice 로 보내는 payload 를 backoffice API 스키마에 맞게 구성해야 한다. 로그상 두 종류의 불일치가 확인됨:
    • seg_id 를 정수로 보내야 하는데 "seg-0001" / "seg-1784867408228-1" 같은 문자열로 보냄 (int_parsing 오류). backoffice 가 문자열 seg_id 를 받도록 스키마를 바꿀지, tesla 가 정수 seg_id 를 추출/변환할지 backoffice 담당자와 계약(contract)을 먼저 합의해야 한다.
    • review_key(string), level_id(int), record_id(int), annotations[].position(string), annotations[].probability(float) 가 nil 로 전송됨. 이 값들이 실제로 존재하는데 매핑 누락인지, 아니면 optional 이어야 하는데 backoffice 가 required 로 선언했는지 확인 필요.
  • 프런트/backoffice 계약 조율이 필요한 항목이므로 자동 code-fix 로 일괄 반영하지 말 것. 순수 tesla 측 payload 매핑 버그로 확정되는 부분만 코드 수정 대상.

단기 개선 (1주 이내)#

  • PixgenieBaseOperation.handle_error (pixgenie_base_operation.rb:128-137) 의 status_code 분기에 422 케이스를 추가해 클라이언트 검증 오류를 502 가 아닌 4xx(예: Cupix::Errors::InvalidState/422)로 매핑한다. 현재는 422 가 else 로 떨어져 502 BadGateway 로 오분류되며, 이 때문에 Error Tracking 이 클라이언트 스키마 버그를 게이트웨이 오류로 집계해 알람이 오도된다.

장기 개선 (재발 방지)#

  • tesla ↔ pix genie backoffice 간 payload 스키마를 공유 계약(OpenAPI/JSON Schema)으로 고정하고 CI 에서 검증. seg_id 타입 같은 계약 드리프트를 배포 전에 잡는다.
  • Error Tracking 의 Representative Error 가 stale 해지는 문제를 감안해, 502 로 묶인 이슈는 @function 태그 기준으로 세분화하는 것을 검토.

Monitoring#

pix genie 결과 기록 실패 추이:

text
(service:cupixworks-api OR service:cupixworks-worker) "Failed to record pix genie results to backoffice"

handle_error 가 발생시키는 pix genie 계열 502 전체:

text
(service:cupixworks-api OR service:cupixworks-worker) @class:PixgenieInferenceOperation status:error

Risk Assessment#

  • Risk level: medium (기능 영향: pix genie 결과가 backoffice 에 기록되지 않음. 시스템 다운은 아님)
  • 예상 복잡도: standard (payload 매핑 수정 + 422 매핑 추가. 단, backoffice 계약 조율이 선행 필요)

Noise Verdict#

bug — pix genie 결과를 backoffice 에 기록할 때 tesla payload 의 seg_id 타입과 필수 필드 매핑이 backoffice 스키마와 어긋나 422 가 발생하고, 이것이 502 로 오분류되는 실제 코드 결함이다.