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#
- 2024-08-22 18:25 KST — Error Tracking 이슈 first_seen (Representative Error
502 Bad Gateway, stale) - 2026-07-22 ~ 2026-07-27 —
record_results_from_bulk_result에서 422 검증 오류가 502 로 반복 집계 - 2026-07-24 16:28 KST — 이슈 last_seen (
Cupix::Errors::BadGatewayseg_id 파싱 오류) - 2026-08-04 — RCA 수행. Representative Error 와 실제 최근 메시지 불일치 확인
Error Log#
502 Bad Gateway
주의: 위 Representative Error 는 first_seen 기준의 stale 샘플이다. 아래는 last_seen 근처의 실제 최근 로그다.
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 호출은 PixgenieBaseOperation 의 post/get 를 거치며, 응답 오류는 handle_error 로 모인다.
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 된다.
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_message 는 parsed_body['detail'] 를 우선 사용한다. FastAPI 의 422 응답 body 는 {"detail": [{"type": ..., "loc": [...], "msg": ..., "input": ...}]} 형태이므로, 로그와 예외 메시지에 그대로 Pydantic 검증 배열이 실린다.
마지막으로 Cupix::Errors::BadGateway 는 컨트롤러 레벨에서 HTTP 502 로 변환된다.
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_id 가 nil 이라 backoffice 가 422 로 거절하고, tesla 는 이를 502 로 잘못 분류한다.
Log Evidence#
사용한 Datadog 쿼리:
(service:cupixworks-api OR service:cupixworks-worker) "BadGateway"
(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 태그 포함):
{
"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):
{
"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건):
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 결과 기록 실패 추이:
(service:cupixworks-api OR service:cupixworks-worker) "Failed to record pix genie results to backoffice"
handle_error 가 발생시키는 pix genie 계열 502 전체:
(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 로 오분류되는 실제 코드 결함이다.