조용히 작동을 멈추는 폴백 체인
저는 폴백 체인이 고장 난 것을 청구서를 보고 알게 되었습니다. 경고나 대시보드의 빨간 줄 같은 것이 아니었습니다. 그 주간의 지출액이 평소보다 적어서 뭔가 잘못되었다는 느낌이 들었고, 찾아보니 모든 요청에 대해 체인의 마지막 모델이 답변하고 있었습니다. 앱은 괜찮았습니다. 지연 시간(Latency)도 감당할 만했고, 아무도 불평하지 않았습니다. 저는 오랫동안 비상 경로로 작동하며 그것을 프로덕션이라고 부르고 있었습니다.
체인의 첫 번째 경로는 이미 폐기된 상태였습니다. 그 후에 저에게 남은 것은 그것이 얼마나 크게 실패했어야 하는가에 대한 것이었습니다. 폐기된 OpenAI 모델은 HTTP 404와 not_found_error / model_not_found로 답변합니다. 이것은 흔들리는 타임아웃(timeout)이 아니라 명확한 거부입니다. 제 체인은 그 거부를 포착하고 다음 경로로 넘어가 계속 진행했습니다. — 그것이 바로 제가 구축하도록 의도했던 바입니다. 오류가 저에게 도달했을 때는 이미 삼켜져서 정상적인 트래픽으로 재분류된 후였습니다.
체인은 생존하기 위해 만들어진 실패를 숨긴다
폴백 체인(fallback chain)은 실패하는 경로가 일시적인 사건일 것이라는 가정에 기반합니다. 그 가정이 맞을 때는 짧은 오작동과 약간 다른 답변을 얻습니다. 하지만 경로가 영구적으로 사라졌을 때, 체인은 여전히 올바른 것처럼 보이는 일을 합니다. 남아 있는 무엇이든 곳에서 요청을 처리해 줍니다. 생존하는 멤버가 하나인 체인은 녹색 시스템(green system)입니다. 하지만 그것은 또한 당신이 더 이상 이해하지 못하는 시스템이기도 한데, 왜냐하면 고장 난 것이 바로 당신을 고장으로부터 보호해야 했던 것이기 때문입니다.
불편한 점은 좋은 이유로 작동하는 폴백과 주 경로가 삭제되었기 때문에 작동하는 폴백이 외부적으로는 똑같아 보인다는 것입니다. 둘 다 성공적인 응답을 생성합니다. 유일한 차이점은 어떤 모델이 그것을 작성했는지이며, 만약 당신이 그것을 로깅(log)하지 않는다면 아무런 신호도 얻을 수 없습니다. 저는 그것을 로깅하지 않았습니다. 그래서 한동안 저의 유일한 도구는 청구서였습니다.
폐기는 그 사실을 담고 있는 필드를 읽으면 공지된다
폐기(Retirement)는 미스터리가 아닙니다. 데이터를 가져가야 하는 문제입니다. OpenAI의 모델 목록은 일정을 공개합니다: GET /v1/models와 단일 모델인 "모델 검색(Retrieve a model)" 호출에는 선택적 필드인 shutdown_date가 포함되어 있으며, 이는 YYYY-MM-DD 또는 null입니다. 공지된 기간은 서류상으로는 관대합니다. 일반적으로 사용 가능한 모델은 6개월 이상, 특화 변형 모델은 최소 3개월, 미리 보기 모델은 때때로 약 2주 정도이며, 파인튜닝(fine-tune) 모델은 학습된 기본 모델의 폐기 일정을 따릅니다. 마지막 조항이 문제입니다. 왜냐하면 사용자의 파인튜닝 모델은 자체 이름을 유지하면서 다른 사람의 마감 기한을 물려받기 때문입니다.
Anthropic은 이와 동일한 것을 라이프사이클(lifecycle)로 설명합니다: Active, Legacy, Deprecated, Retired이며, 폐기된 모델에 대한 요청은 실패할 것이라는 명확한 진술이 있습니다. 여기서 보장은 활성 배포(active deployments)를 가진 고객에게 적용되는 최소 공지 기간입니다. 이 한정 조건을 주의 깊게 읽으십시오. 만약 특정 모델로의 트래픽이 조용해졌다면 — 예를 들어, 폴백(fallback)이 조용히 그 역할을 대신했다면 — 사용자는 생각하는 것보다 활성 배포가 아닐 수 있으며, 기대했던 공지는 도착하지 않을 수도 있습니다.
더 조용한 장애: 매개변수, 크레딧, 제한(caps)
폐기는 경로가 죽는 가장 큰 소리를 내는 방식일 뿐입니다. 다른 두 가지 방식은 "사용 중단(deprecated)"이라는 단어를 사용하지 않고도 경로를 끊습니다. 첫 번째는 매개변수 표면(parameter surface) 문제입니다. Claude 4.7 이후 버전에서는 temperature, top_p 또는 top_k에 기본값이 아닌 값을 전달하면 HTTP 400 오류가 발생합니다. 제 요청 빌더는 예전에 출력을 더 좋게 만들었기 때문에 수년 동안 temperature를 보내왔는데, 새로운 모델에서는 같은 습관이 호출을 완전히 중단시키고, 만약 그것이 폴백 뒤에 놓여 있다면, 그 경로는 단순히 사라집니다.
두 번째는 크레딧 및 한도 계정(credit and cap accounting)입니다. Anthropic의 사용 크레딧은 발급된 날짜로부터 1년 후에 만료되며, 환불되지 않고, 만료일을 연장할 수 없습니다. 계정을 폐쇄하면 남아 있는 잔액 전체가 사라집니다. 트래픽 측면에서는 오류 형태(error shapes)가 메시지만큼 중요합니다. 429 상태 코드의 rate_limit_error는 티어 지출 한도에 도달했을 때 발생하는 것과 같습니다. 이때는 retry-after 헤더가 없으며, 접근이 복구될 때까지 계속 그 상태를 유지합니다. 자체 설정한 지출 한도는 400으로 표시됩니다. 결제 문제는 402 billing_error로 나타납니다. 이들 중 어느 것도 '모델이 사라졌다'는 의미가 아니며, 모두 폐기된 모델보다 더 오래 서비스 불가(out of service) 상태에 있을 수 있습니다.
max_tokens=1로 모든 경로를 개별적으로 테스트하기
제가 겪었던 장애를 잡아냈을 테스트는 거의 놀라울 정도로 작습니다. 각 경로를 직접, 한 번에 하나씩, max_tokens=1로 호출하고 절대로 체인(chain)을 통해서는 안 되는 것입니다. 토큰 하나만으로도 HTTP 상태 코드와 오류 본문(error body)을 얻기에 충분하며, 생각할 필요 없이 스케줄링하여 실행하기에 비용도 저렴합니다.
curl -sS -o /tmp/route.json -w '%{http_code}
' \
https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $KEY" \
...
핵심은 답변을 어떻게 처리하느냐입니다. 만약 본문에 not_found_error / model_not_found가 포함되어 있다면, 해당 경로는 일시적인 오류(flaky)가 아니라 완전히 사라진 것이므로, 아무리 재시도 로직(retry logic)을 적용해도 복구할 수 없습니다. 만약 사소하다고 생각했던 매개변수에서 400 에러가 발생했다면, 이는 인시던트(incident)가 아니라 코드 수정이 필요한 문제입니다. 429 또는 402라면, 경로는 정상이며 문제가 있는 것은 사용자의 지갑입니다. 세 가지 다른 질병, 세 가지 다른 대응 방식 — 이 모든 것을 동일하게 처리하는 체인은 모니터링 전략이 아닙니다.
모니터를 신뢰하지 말고 라이브 맵과 비교하라
제 가동 상태 확인(uptime check)은 체인에 연결되어 있었습니다. 그것이 실수였습니다. 단 하나의 활성 경로를 가진 체인을 통과하는 건강 검진(health check)은 구조적으로 영원히 녹색(green)으로 표시되므로, 이는 그 안에 있는 어떤 경로의 건전성을 측정하기보다 체인이 응답하려는 의지 자체를 측정합니다. 저는 결코 실패할 수 없는 모니터를 가지고 있었고 매일 아침 그것을 확인했습니다.
그것을 대체한 것은 지루합니다. 체인은 중첩된 if 분기문이 아니라 데이터 파일에 존재합니다. 작업(job)은 제공업체의 모델 목록을 가져와 shutdown_date를 읽고, 그 목록을 체인과 비교(diffs)합니다. 이름이 더 이상 나타나지 않거나 종료 날짜가 지난 체인 항목은 요청이 그것에 닿기 전에 플래그가 지정됩니다. Anthropic에서 사용 중단(deprecated)으로 표시한 트래픽을 실행하는 모든 것은 공급업체가 찾으라고 제안하는 방식과 동일하게 찾아낼 수 있습니다: 콘솔 → 사용량(Usage) → 내보내기(Export). 여기서는 API 키와 모델별로 분리된 CSV를 통해 어떤 사용 중단 모델이 여전히 호출되고 있으며 어느 키에 의해 호출되는지 보여줍니다. 또한 저는 실제로 각 요청에 응답한 경로가 무엇인지도 기록합니다. 왜냐하면 '주요(primary)'에서 '최후의 수단(last resort)'으로 전환되는 것은 증상일 뿐이며, 성공 횟수만 계산하는 모든 대시보드에서는 보이지 않기 때문입니다.
마침내 저에게 무언가 잘못되었다고 알려준 숫자는 청구서였습니다. 다음번에는 차이점(diff)이었으면 좋겠습니다.