개발

[백엔드] 성공이라 말하는 실패, 상태코드가 거짓말할 때

jykim23 2026. 7. 16. 13:11
반응형

상태코드 거짓 성공 202 전부 거부 실패 본문에 묻힘 전수조사 분류 계약 변경

부제: 전부 거부됐는데 202를 돌려주던 API, 그리고 끝내 못 고친 이유

이미지 업로드가 조용히 실패했다.
파일이 전부 거부됐는데 서버는 202를 반환했다.

앞단(nginx·백엔드)은 그 상태코드를 보고 "성공"으로 처리했다.
정작 실패 목록은 응답 본문의 errors[] 안에 얌전히 담겨 있었다.
상태코드만 보는 호출자는 알 길이 없었다.

상태코드가 결과를 속인다

문제를 이렇게 정의했다.
2xx를 반환하지만 실제로는 실패해서, 상태코드만 보는 호출자가 성공으로 오인하는 응답.

핵심은 상태코드가 결과를 정직하게 반영하지 않는다는 것이다.
배치 처리, 비동기 접수, 예외를 삼키는 핸들러 — 이런 자리에서 잘 생긴다.

한 곳에서 났으면 다른 곳에도 있을 거라 봤다.
그래서 라우터를 전수조사하기로 했다.

무엇이 거짓말이고 무엇이 정상인가

여기가 제일 중요했다.
2xx인데 실패가 섞였다고 전부 잘못은 아니다. 먼저 갈라야 했다.

  • 전부 실패인데 2xx — 생성된 게 0건인데 성공처럼 보인다. 진짜 거짓말.
  • 예외를 잡아 2xx로 — 핸들러가 에러를 삼키고 성공 코드를 준다. 진짜 거짓말.
  • 비동기 접수 202 후 백그라운드 실패 — 이건 정상이다. 접수는 진짜로 됐으니까. 다만 이후 조회(폴링)가 "실패했음"을 분명히 보여주는지는 확인해야 한다.
  • 스트림 200 시작 후 중간 실패 — 이것도 정상이다. 응답은 이미 시작됐고 에러는 스트림 이벤트로 간다. 클라이언트가 그 이벤트를 처리하는지가 관건.
  • 일부만 실패인데 2xx — 논쟁적이다. 부분 성공을 4xx로 만들면 성공한 것까지 실패로 취급돼 유실된다. 그래서 상태코드보다는 "호출자가 errors[]를 반드시 보게 만드는" 가시성 문제로 다뤘다.

표적은 앞의 둘로 좁혔다.
나머지는 정상 패턴이니 "실패가 보이긴 하는가"만 검증했다.
정상 비동기 응답을 실패로 오해해 동기 4xx를 강제하는 건, 고치는 게 아니라 망가뜨리는 것이다.

고치려니 계약이었다

그래서 전부 실패면 4xx를 주면 되겠네 — 라고 하기엔 걸리는 게 있었다.

상태코드를 바꾸는 건 API 계약 변경이다.
백엔드는 이미 2xx를 성공으로 처리하도록 짜여 있다.
우리가 맘대로 4xx로 바꾸면 그쪽이 깨진다. 협의 없이 배포할 수 없다.

그래서 레버를 둘로 나눴다.

  • ① 로깅 — 거짓 성공이 나는 순간 서버에 경고를 남긴다. 생성 0건인데 성공으로 나가는 것, 예외를 삼킨 사유. 계약 변경이 아니라 즉시 가능.
  • ② 상태코드 정정 — 실제 실패면 4xx. 계약 변경이라 백엔드 협의 후.

가시성은 로깅으로 먼저 덮고, 계약은 협상 테이블로 넘겼다.
못 고치는 동안에도 최소한 우리는 알 수 있게 만든 것이다.

정리

  • 업로드가 전부 거부돼도 202 → 상태코드만 보는 앞단은 성공으로 오인, 실패는 응답 본문에 묻힘
  • 한 곳에서 났으면 다른 곳도 → 라우터 전수조사
  • 2xx+실패가 전부 잘못은 아니다 — 정상 async 202·스트림 200과 "진짜 거짓 성공"을 가르는 분류가 먼저
  • 부분 성공을 4xx로 만들면 성공분이 유실 → 상태코드가 아니라 가시성 문제로 처리
  • 정정이 계약 변경이라 못 고침 → 로깅(즉시)상태코드(협의)를 분리
  • 원칙: 상태코드는 결과를 속이면 안 된다. 다만 당장 못 고치면, 최소한 보이게 만든다

숫자 하나가 거짓말을 하면 그 위의 모든 판단이 틀어진다.
그런데 그 거짓말을 고치는 게 남과의 계약이라면, 고치는 것과 드러내는 것을 분리해야 한다.
드러내는 건 오늘 할 수 있고, 고치는 건 협상이 필요하니까.

반응형