오류와 타임아웃을 우아하게 처리하기
모델 호출은 실패하고, 멈추고, 속도 제한에 걸립니다. AI 기능을 안정적으로 유지하는 재시도·타임아웃·폴백·페일세이프 동작의 실용 가이드.
데모는 모든 것이 작동한다고 가정합니다. 제품은 무언가가 실패할 것이라고 가정하고도 여전히 유용함을 유지합니다. 네트워크 너머로 언어 모델을 호출할 때, 여러분은 원격 서비스의 모든 실패 유형을 물려받습니다. 타임아웃, 속도 제한, 일시적 오류, 느린 응답에 더해, 필요한 형태와 맞지 않는 출력 같은 모델 특유의 몇 가지까지요. 취약한 기능과 안정적인 기능의 차이는 거의 전적으로 불행한 경로를 어떻게 처리하느냐에 있습니다. 이 가이드는 여러분이 대비해야 할 실패들과 각각을 우아하게 처리하는 법을 짚어 갑니다.
무엇에 대비하는지 알아 두세요
모델 호출은 알아볼 만한 몇 가지 방식으로 실패하며, 이를 잘 처리하는 일은 그것들을 구분하는 데서 시작합니다. 일시적 오류는 일시적인 딸꾹질입니다. 잠깐의 네트워크 끊김이나, 그냥 다시 시도하면 성공하는 서버 측 오류죠. 속도 제한은 요청을 너무 빨리 보내니 속도를 늦추라고 제공사가 알려 주는 것입니다. 타임아웃은 응답이 여러분이 기다릴 의향보다 오래 걸릴 때 발생하는데, 모델의 경우 생성 시간이 출력 길이에 따라 달라지기 때문에 흔합니다. 잘못된 입력 오류는 요청 자체가 잘못 구성됐다는 뜻이며, 그대로 재시도하면 똑같이 실패합니다. 그리고 내용 수준의 실패가 있습니다. 호출은 성공했지만 출력이 틀렸거나, 비었거나, 요구한 형식이 아닌 경우죠.
이 범주들이 중요한 건 각각에 옳은 대응이 다르기 때문입니다. 일시적 오류를 재시도하는 건 옳지만, 잘못 구성된 요청을 재시도하는 건 무의미합니다. 속도 제한에 물러서는 건 도움이 되지만, 더 세게 두드리면 더 나빠집니다. 처리 코드를 쓰기 전에, 지금 보고 있는 오류를 분류하세요. 분류가 처방을 결정하니까요.
일시적인 것은 재시도하되, 정중히 물러서세요
일시적 오류와 속도 제한에 대해서는 재시도가 답입니다. 다만 어떻게 재시도하느냐가 중요합니다. 즉시 반복해서 재시도하면, 특히 속도 제한에서는 더 나빠질 수 있습니다. 즉각적인 재시도가 쇄도하면 계속 제한에 묶일 뿐이죠. 표준적이고 점잖은 접근은 지터를 곁들인 지수 백오프입니다. 첫 재시도 전에 잠깐 기다리고, 이후 시도마다 대기 시간을 대략 두 배로 늘리며, 작은 무작위 오프셋을 더해 많은 클라이언트가 일제히 발맞춰 재시도하지 않게 합니다.
delay = base
for attempt in range(max_attempts):
try:
return call_model(request)
except Transient or RateLimited:
wait(delay + random_jitter())
delay = delay * 2
raise GiveUp
시도 횟수와 최대 대기 시간에 상한을 두어, 실패하는 호출이 영원히 재시도하지 않게 하세요. 그리고 재시도로 고칠 수 있는 오류만 재시도하세요. 일시적·속도 제한 실패는 재시도로 감싸되, 잘못 구성된 요청 오류는 빠르게 실패시키세요. 성공할 수 없는 요청을 재시도하는 건 시간과 돈을 낭비하니까요.
항상 타임아웃을 설정하세요
타임아웃 없는 모델 호출은 함정입니다. 생성 시간은 가변적이고, 가끔 요청이 평소보다 훨씬 오래 멈춰 있습니다. 타임아웃이 없으면 느린 호출 하나가 요청 핸들러를 붙잡고, 연결 풀을 고갈시키고, 일부 호출만 오작동했는데도 전체 장애처럼 보이는 연쇄 중단으로 번질 수 있습니다. 모든 모델 호출에 명시적 타임아웃을 항상 설정하되, 주변 맥락이 실제로 기다릴 수 있는 시간에 맞춰 고르세요.
타임아웃을 의도적으로 고르세요. 사람이 기다리는 대화형 기능은, 느긋함을 감당할 수 있는 백그라운드 배치 작업보다 더 빡빡한 한도가 필요합니다. 호출이 타임아웃을 넘기면 일시적 실패처럼 취급하세요. 취소하고, 재시도하거나 폴백하세요. 요점은 한없는 호출이 결정하게 두는 대신 여러분이 얼마나 기다릴지 결정한다는 것입니다. 여기서도 스트리밍이 도움이 됩니다. 응답을 스트리밍하면 첫 토큰까지의 시간이 호출이 살아 있다는 이른 신호를 주고, 그 첫 청크에 별도의 더 빡빡한 타임아웃을 적용할 수 있습니다.
재시도가 바닥나면 쓸 폴백을 두세요
재시도는 일시적 문제에 대한 회복력을 사주지만, 때로는 문제가 일시적이지 않습니다. 제공사에 지속적 장애가 있거나, 모든 시도가 타임아웃이 나죠. 이런 경우를 위해, 모델이 그저 사용 불가일 때 여러분의 기능이 무엇을 할지 미리 정하세요. 잘못된 답은 처리되지 않은 예외가 깨진 화면으로 솟구치는 것입니다.
폴백은 작업에 따라 여러 형태로 옵니다. 사용 가능할 가능성이 더 높은 더 작거나 대체적인 모델로 폴백할 수 있습니다. 맞는 것이 있다면 캐시되거나 기본 응답을 제공할 수 있습니다. AI가 아닌 경로로 우아하게 강등할 수도 있죠. 간단한 규칙, 수동 옵션, 또는 사용자의 입력을 보존해 아무것도 잃지 않게 하는 "잠시 후 다시 시도" 메시지처럼요. 올바른 폴백은 기능에 달려 있지만, 모든 AI 기능에는 하나가 있어야 합니다. 출시 전에 답해야 할 질문은 이것입니다. 모델이 전혀 응답할 수 없을 때, 사용자는 무엇을 보는가? 중요한 무엇에 대해서든 "오류 페이지"는 받아들일 만한 답이 아닙니다.
호출만이 아니라 출력을 검증하세요
성공한 호출이 성공한 결과는 아닙니다. 모델은 비었거나, 주제에서 벗어났거나, 가장 흔히 고통스럽게도 코드가 기대하는 구조가 아닌 텍스트를 반환할 수 있습니다. JSON을 요청해 놓고 응답이 유효하다고 가정한 채 파싱하면, 잘못 구성된 출력이 네트워크 오류만큼이나 확실하게 코드를 무너뜨립니다. 모델의 출력을 신뢰할 수 없는 입력으로 취급하고, 의존하기 전에 검증하세요.
호출 후에는 출력이 요구사항을 충족하는지 점검하세요. 파싱되는가, 필수 필드가 있는가, 예상 범위 안에 있는가. 검증이 실패하면 선택지가 있습니다. 호출을 재시도하되, 때로는 무엇이 잘못됐는지 짚어 주는 명확화 지시를 곁들일 수 있습니다. 사소한 문제는 관대한 복구를 시도할 수 있습니다. 또는 폴백할 수 있죠. 하지 말아야 할 것은, 검증되지 않은 모델 출력을 잘 구성됐다고 가정하는 코드에 곧바로 넘기는 것입니다. 모델은 확률적 구성 요소입니다. 방어적 검증이 바로 확률적 구성 요소를 그 위에 안전하게 쌓을 수 있게 만드는 방법입니다.
실패를 보이고 관측 가능하게 만드세요
본 적 없는 실패는 고칠 수 없습니다. 오류를 이해하기에 충분한 맥락과 함께 로깅하세요. 실패의 유형, 그것을 유발한 입력, 재시도가 몇 번 들었는지, 폴백이 발동했는지를요. 타임아웃·속도 제한·검증 실패의 비율을 지켜보면, 사용자가 불평하기 전에 무언가가 나빠지고 있음을 알 수 있습니다. 갑작스러운 타임아웃 급증은 제공사 문제를 뜻할 수 있고, 꾸준한 검증 실패의 흐름은 프롬프트가 표류했거나 입력이 바뀌었음을 뜻할 수 있습니다.
오류를 사용자에게 정직하되 부드럽게 드러내세요. 무언가 잘못됐고 입력은 안전하다는 명확하고 차분한 메시지가, 알 수 없는 스택 트레이스나 조용한 빈 응답보다 낫습니다. 내부적으로는, 사용자에게 조용하더라도 모니터링에서는 실패가 시끄럽게 드러나게 하세요. 사용자에게는 우아하고 여러분에게는 보이는 이 조합이, 불평을 통해 약점을 발견하는 대신 시간이 지나도 기능을 안정적으로 유지하게 해 줍니다.
정리
안정성은 불행한 경로 위에 세워집니다. 마주한 실패를 분류하세요. 일시적, 속도 제한, 타임아웃, 잘못 구성됨, 나쁜 출력 중 무엇인지요. 각각 다른 대응이 필요하니까요. 재시도 가능한 것은 지터를 곁들인 지수 백오프로 재시도하고, 나머지는 빠르게 실패시키며, 모든 호출에 명시적 타임아웃을 두어 느린 요청 하나가 시스템을 멈추지 못하게 하세요. 모델이 사용 불가일 때 무슨 일이 일어날지 미리 정하고, 모든 기능에 진짜 폴백을 주세요. 출력을 신뢰할 수 없는 입력으로 보고 의존하기 전에 검증하며, 실패를 모니터링에서 보이게 유지하세요. 이것들을 잘 처리하면, 모델이 협조하지 않는 날에도 여러분의 기능은 유용함을 유지합니다.
