Skip to main content

부록 D. 에러 처리와 Rate Limit 대응 체크리스트

LLM API를 운영하다 보면 언젠가는 꼭 이런 순간이 온다. 평소엔 멀쩡하던 요청이 갑자기 429를 뱉고, 어떤 응답은 20초 넘게 끌다가 끊기고, 어떤 날은 모델이 JSON 대신 장문의 사과문을 보내기도 한다. 이럴 때 중요한 것은 "왜 이런 일이 생기지?"보다 "우리 서비스는 이 상황에서 어떻게 복구하도록 설계되어 있지?"다.

핵심은 단순하다. 문제는 반드시 발생한다. 그러니 목표는 문제를 완전히 없애는 것이 아니라, 문제가 생겨도 당황하지 않고 같은 방식으로 복구하는 운영 기준을 갖는 것이다.

부록 D. 에러 처리와 Rate Limit 대응 체크리스트

먼저 기억할 다섯 가지 원칙

부록 전체를 관통하는 원칙부터 잡고 가자.

  • 같은 에러를 같은 방식으로 다루지 않는다.

  • 재시도는 많이 하는 기술이 아니라, 골라서 짧게 하는 기술이다.

  • 429 대응은 재시도보다 사전 속도 제어가 더 중요하다.

  • 사용자 메시지와 내부 에러 정보는 분리한다.

  • 복구 실패도 하나의 정상 흐름으로 설계한다.

특히 LLM API의 한도는 보통 요청 수만 보는 것이 아니다. 많은 제공자가 분당 요청 수, 분당 토큰 수, 때로는 일일 요청 수까지 함께 적용하며, 어떤 한도를 넘겨도 제한이 걸릴 수 있다.1

에러를 1분 안에 분류하는 표

문제가 생기면 제일 먼저 해야 할 일은 감정이 아니라 분류다. "에러가 났다"는 한 문장으로 묶으면 운영이 항상 엉킨다.

유형대표 증상재시도 여부첫 대응
Rate limit429, Too Many Requests예Retry-After 확인, 대기 후 재시도, 동시성 축소
일시적 장애408, timeout, 네트워크 오류, 500/502/503/504, 일부 공급자의 529 overload예지수 백오프, 지터, 필요 시 폴백
잘못된 요청400, 잘못된 모델명, 누락된 파라미터아니오입력 검증, 설정 수정
인증/권한401, 403아니오키, 권한, 프로젝트, 결제 상태 확인
컨텍스트 초과context_length_exceeded같은 요청 재시도는 아니오대화 축약, 입력 트리밍, 출력 토큰 예산 재조정
안전 정책 거부content filter, safety block같은 입력 재시도는 보통 아니오사용자 안내, 프롬프트/워크플로 수정
응답 형식 오류JSON 파싱 실패, 스키마 불일치제한적으로만검증, 1회 보정 시도, 실패 시 축소 응답
스트리밍 중간 실패응답이 나오다 끊김조건부첫 토큰 전이면 자동 재시도, 이후면 부분 결과 보존

운영에서 가장 흔한 실수는 400과 401, 403까지 습관처럼 재시도하는 것이다. 이건 복구가 아니라 비용 낭비다. 반대로 429나 5xx, 네트워크 오류는 잠깐만 숨을 고르면 살아나는 경우가 많다.2

코드보다 먼저 정해야 하는 운영 기준

좋은 에러 처리는 try/catch 한 줄에서 시작하지 않는다. 운영 기준부터 정해 둬야 한다. 아래 항목은 "나중에 하면 되겠지" 하고 미루기 쉬운데, 실제 장애가 터지면 거의 다 여기로 돌아온다.

  • SDK 버전을 고정하고, 요청 로그에 SDK 버전을 남긴다.

  • 요청 타임아웃, 연결 타임아웃, 스트리밍 idle 타임아웃을 따로 정한다.

  • 최대 재시도 횟수와 전체 시간 예산을 기능별로 정한다.

  • 쓰기 작업이나 툴 호출에는 idempotency key를 설계한다.

  • 모든 요청에 request_id 또는 trace_id를 부여하고 UI에서도 보이게 한다.

  • 주 모델, 대체 모델, 대체 공급자, 마지막 축소 응답 순서를 미리 정한다.

  • 장애 시 사용자에게 보여 줄 문구를 미리 준비한다.

  • 서킷 브레이커가 열리는 기준과 쿨다운 시간을 정한다.

  • 기능별 최대 입력 길이와 최대 출력 토큰을 정한다.

  • 운영 문서에 "이 에러는 누가, 몇 분 안에, 무엇부터 본다"를 적어 둔다.

이런 기준이 중요한 이유는 간단하다. 같은 장애가 두 번 났을 때, 두 번 다 사람마다 다르게 대응하면 시스템은 절대 안정화되지 않는다. 타임아웃, 재시도, idempotency key, 요청 ID 같은 기본 위생이 갖춰져 있어야 장애가 루틴처럼 처리된다.3

재시도 정책 체크리스트

재시도는 만능 버튼이 아니다. 잘못 쓰면 장애를 줄이기는커녕 증폭시킨다. 특히 여러 서버가 동시에 같은 시점에 다시 덤비면, 이미 힘든 공급자에게 단체로 문을 두드리는 셈이 된다.

그래서 기본 규칙은 이렇다.

  • 재시도 대상은 429, timeout, connection error, 5xx, overload 계열로 제한한다.

  • 400, 401, 403, content filter는 자동 재시도하지 않는다.

  • 공급자가 Retry-After를 주면 그 값을 우선 사용한다.

  • 그렇지 않으면 지수 백오프를 쓰고, 반드시 지터를 넣는다.

  • 재시도는 한 계층에서만 한다.

  • 동기 사용자 요청은 보통 1~2회, 배치/비동기 작업은 더 길게 가져간다.

  • 총 대기 시간은 기능의 응답 목표 안에 들어오게 제한한다.

  • 마지막 실패 원인과 시도 횟수를 로그에 남긴다.

실전 감각으로는 1초 → 2초 → 4초처럼 늘리되, 거기에 약간의 랜덤 시간을 섞는 방식이 무난하다. 이 작은 랜덤성이 재시도 폭주를 꽤 잘 막아 준다.24

그리고 꼭 기억하자. 재시도는 한 군데에서만 해야 한다. 클라이언트가 재시도하고, 서버가 또 재시도하고, 워커가 다시 재처리하면 원래 요청 하나가 순식간에 여러 배로 불어난다. LLM에서는 이게 곧 비용 폭증이다.

Rate Limit 대응 체크리스트

429는 서버가 화를 내는 방식이 아니라, 서버가 아주 예의 바르게 "지금은 너무 많이 오고 있습니다"라고 말하는 방식이다. 문제는 여기서 "그럼 바로 한 번 더!"라고 반응하는 쪽이 대개 우리라는 점이다.

Rate limit 대응은 재시도 이전에 보내는 양을 제어하는 것이 먼저다.

  • 현재 한도가 RPM인지, TPM인지, RPD인지 먼저 확인한다.

  • 한도가 API 키 기준인지, 프로젝트 기준인지 확인한다.

  • 응답 헤더나 콘솔에서 제공하는 rate limit 필드를 기록한다.

  • 요청 전 예상 입력 토큰과 최대 출력 토큰을 계산한다.

  • 요청 수 제한과 토큰 제한을 둘 다 관리한다.

  • 동시성 제한을 둔다. 특히 배치 작업은 큐로 흡수한다.

  • 실시간 요청과 백그라운드 작업의 우선순위를 분리한다.

  • 긴 프롬프트, 긴 대화, 과도한 max_tokens를 줄인다.

  • 반복되는 요청은 캐시한다.

  • 단순 작업은 더 가벼운 모델로 보낸다.

많은 팀이 RPM만 본다. 그런데 실제로는 TPM, 즉 토큰 예산이 먼저 바닥나는 경우가 더 많다. 짧은 질문 20개보다, 거대한 문서 1개를 붙인 요청 2개가 더 빨리 한도를 태울 수 있다. 그래서 rate limit는 "호출 횟수"가 아니라 호출 크기와 동시성의 문제로 봐야 한다.15

자주 터지는 오류별 점검표

이제 실전에서 바로 보는 체크리스트로 들어가 보자.

429 Too Many Requests

429가 보이면 아래 순서로 점검한다.

  • 정말 rate limit인지, 쿼터 소진인지, 결제 문제인지 콘솔에서 확인했다.

  • Retry-After 또는 유사 헤더를 읽고 대기 시간을 반영했다.

  • 실패한 요청 수뿐 아니라 동시 실행 수를 줄였다.

  • 같은 요청을 여러 레이어에서 중복 재시도하지 않는지 확인했다.

  • 필요하면 더 작은 모델이나 다른 공급자로 우회한다.

  • 장기적으로는 큐, 캐시, 토큰 절감으로 원인을 줄인다.

429를 코드 한 줄로만 해결하려 하면 늘 다시 돌아온다. 진짜 해결은 트래픽 모양을 바꾸는 것이다.

Timeout, 네트워크 오류, 5xx

이 오류들은 비교적 "재시도할 가치가 있는 실패"다.

  • 요청 타임아웃과 연결 타임아웃을 분리해 뒀다.

  • 너무 긴 출력이 타임아웃을 부르는지 max_tokens를 점검했다.

  • 동기 호출이면 짧게 재시도하고, 길면 비동기 작업으로 넘긴다.

  • 일정 시간 이상 실패율이 높으면 서킷 브레이커를 연다.

  • 공급자 장애가 길어지면 폴백 모델이나 폴백 공급자를 사용한다.

  • 스트리밍이라면 첫 토큰 전후의 실패를 다르게 처리한다.

짧은 장애에는 재시도가 통하지만, 공급자 자체가 흔들리는 동안에는 폴백과 서킷 브레이커가 더 중요하다.2

Context length exceeded

이 오류는 흔히 400 계열이지만, 일반적인 400과 다르게 프로그램적으로 다룰 수 있다.

  • 시스템 메시지는 유지하고, 오래된 대화부터 줄인다.

  • 긴 히스토리는 요약본으로 압축한다.

  • 검색 문서 개수와 문서 길이를 줄인다.

  • 출력 토큰 예산을 미리 남겨 둔다.

  • 실패 후 같은 입력을 그대로 재시도하지 않는다.

  • 모델 변경이 아니라 입력 축소가 우선인지 점검한다.

이 유형은 "에러"라기보다 "입력 관리 실패"에 가깝다. 그러니 재시도보다 트리밍과 요약이 정답이다.56

400 Invalid Request, model not found, parameter error

이건 시스템이 "다시 보내지 말고 먼저 고쳐"라고 말하는 상황이다.

  • 모델명 오타가 없는지 확인한다.

  • 필수 파라미터 누락 여부를 확인한다.

  • 구조화 출력 스키마나 툴 정의 형식이 맞는지 검증한다.

  • 배포된 환경 변수와 프로젝트 설정이 맞는지 본다.

  • 자동 재시도는 끈다.

이 에러는 빨리 실패할수록 좋다. 오래 붙잡고 있으면 사용자만 더 기다리게 된다.4

401, 403 인증·권한 오류

이건 대개 코드보다 운영 설정이 문제다.

  • 올바른 API 키를 쓰고 있는지 확인한다.

  • 프로젝트, 조직, 워크스페이스가 맞는지 확인한다.

  • 권한 범위와 허용 모델이 맞는지 확인한다.

  • 키 만료, 회전, 비활성화 여부를 본다.

  • 결제 또는 사용 제한 상태를 확인한다.

  • 재시도는 하지 않고 즉시 알림을 올린다.

인증 오류에 재시도를 붙이는 것은 잠긴 문을 더 세게 두드리는 것과 비슷하다. 문은 안 열리고, 사람만 초조해진다.

Content filter, safety block

같은 입력으로 똑같이 다시 보내는 것은 보통 의미가 없다.

  • 어떤 입력이 필터를 유발했는지 확인한다.

  • 사용자에게 수정 가능한 안내를 제공한다.

  • 내부 워크플로에서 안전한 대체 경로를 준비한다.

  • 고위험 기능은 자동 생성 대신 사람 검토로 넘긴다.

  • 무한 보정 루프가 돌지 않게 재작성 횟수를 제한한다.

이 상황은 "재시도 실패"가 아니라 "다른 처리 경로가 필요한 상태"로 보는 편이 맞다.6

구조화 출력 파싱 실패

API 호출은 성공했는데 우리 서비스 입장에서는 실패인 경우다.

  • 원문 응답은 저장하되, 사용자에게 그대로 노출하지 않는다.

  • 스키마 검증기를 통과했는지 확인한다.

  • 가능하면 1회만 보정 프롬프트로 재시도한다.

  • 그래도 안 되면 자유 텍스트 모드나 축소 응답으로 내린다.

  • 파싱 실패율을 프롬프트 버전별로 본다.

이 오류는 생각보다 자주 난다. 그래서 "응답이 왔다"가 아니라 "쓸 수 있는 응답이 왔다"를 성공으로 정의해야 한다.

스트리밍 응답이 중간에 끊김

스트리밍은 일반 응답보다 까다롭다. 이미 사용자가 일부 텍스트를 본 뒤에는 뒤에서 몰래 다른 모델로 갈아끼우기 어렵다.

  • 첫 토큰 전 실패라면 자동 재시도나 폴백을 허용한다.

  • 첫 토큰 후 실패라면 부분 결과를 보존한다.

  • "이어서 다시 생성" 또는 "처음부터 다시 생성" 버튼을 제공한다.

  • 다른 모델 결과를 기존 텍스트 뒤에 조용히 이어 붙이지 않는다.

  • 중간 실패율과 끊긴 시점을 기록한다.

스트리밍 장애는 기술 문제이면서 동시에 UX 문제다. 사용자는 실패 자체보다, 이미 보던 답이 갑자기 사라질 때 더 화가 난다.2

사용자 경험 체크리스트

에러 처리는 서버 안에서 끝나지 않는다. 사용자 화면까지 포함해야 진짜 운영 기준이다.

  • 자동 재시도 중에는 "재시도 중" 상태를 보여 준다.

  • 원시 에러 메시지를 그대로 노출하지 않는다.

  • 부분 성공이면 부분 결과를 살린다.

  • 완전 실패면 다음 행동을 안내한다.

  • 요청 ID를 사용자나 지원팀이 확인할 수 있게 한다.

  • 고가치 작업은 실패 후 수동 재시도 버튼을 제공한다.

  • 축소 응답일 때는 그 사실을 분명히 알린다.

좋은 실패 메시지는 기술적으로 길 필요가 없다. 대신 사용자가 "지금 기다리면 되는지, 다시 눌러야 하는지, 지원팀에 알려야 하는지"를 알 수 있어야 한다.

최소 로그 항목 체크리스트

23장에서 로그와 트레이스를 자세히 다뤘다면, 이 부록에서는 에러 대응용 최소 필드만 짚고 가자. 아래 항목이 없으면 rate limit나 재시도 장애를 나중에 거의 재구성할 수 없다.

  • timestamp

  • request_id, trace_id

  • feature, tenant_id, user_id_hash

  • provider, model, region

  • sdk_version, prompt_version

  • estimated_input_tokens, max_output_tokens

  • 실제 사용 토큰과 비용 추정치

  • HTTP status, provider error code, error type

  • retry_count, retry_delay, retry_after

  • fallback_used, degraded_used, circuit_state

  • queue wait, total latency, stream interrupted 여부

중요한 것은 로그를 많이 남기는 것이 아니라, 질문에 답할 수 있게 남기는 것이다. "왜 429가 급증했지?"라고 물었을 때 요청 수만 볼 게 아니라 토큰 크기, 모델, 동시성, 폴백 비율까지 함께 보여야 한다.35

한 페이지 최종 체크리스트

마지막으로, 현장에서 바로 붙여 두기 좋은 형태로 압축해 보자.

요청 보내기 전

  • 입력 검증을 통과했는가

  • 예상 토큰 수가 한도 안에 들어오는가

  • max_tokens가 과도하지 않은가

  • 캐시 가능한 요청은 아닌가

  • 동시성 제한과 큐가 적용되는가

  • request_id와 trace_id가 생성되는가

요청 실패 시

  • 에러를 retryable / non-retryable / degraded로 분류했는가

  • Retry-After를 확인했는가

  • 재시도는 한 레이어에서만 일어나는가

  • 지수 백오프와 지터가 적용되는가

  • 총 재시도 시간이 사용자 SLO를 넘지 않는가

  • 마지막 실패는 로그와 알림으로 남는가

계속 실패할 때

  • 같은 공급자의 대체 모델로 내릴 수 있는가

  • 다른 공급자로 폴백 가능한가

  • 서킷 브레이커가 열린 상태인가

  • 축소 응답 또는 사람 검토 경로가 준비되어 있는가

  • 동일 요청의 중복 실행을 막는 장치가 있는가

운영 점검

  • 429, timeout, 5xx 비율을 기능별로 본다

  • fallback 비율이 급증하지 않았는지 본다

  • 파싱 실패율을 프롬프트 버전별로 본다

  • TTFT와 전체 지연 시간을 분리해서 본다

  • 비용은 요청 수가 아니라 토큰 수와 함께 본다

  • 장애 대응 문서와 실제 코드가 같은 기준을 쓰는지 확인한다

마치며

LLM API 운영은 "에러가 안 나게 하는 일"이 아니라, 에러가 나도 서비스가 우아하게 버티게 하는 일에 가깝다. 재시도는 짧고 똑똑해야 하고, rate limit 대응은 사전 제어가 핵심이며, 복구 실패까지 포함한 사용자 경험이 설계되어 있어야 한다.

잘 만든 서비스는 장애가 없어서 좋은 것이 아니다. 장애가 생겨도 사용자가 "아, 잠깐 느렸지만 결국 처리됐네"라고 느끼게 만든다. 그 차이가 실험용 기능과 운영 가능한 서비스의 차이다.

참고

1Rate limits | Gemini API | Google AI for Developers

2LLM API Error Handling and Retry Patterns - Library

3A production checklist for your LLM API

4Retry patterns for LLM API errors in production

5LLM API 모범 사례 - AI Vibe Coding 가이드

6AI Agent Resilience in Production: Retry, Fallback, and Circuit Breaker Patterns with Python

책 목록

실전 LLM 서비스 개발: ChatGPT·Claude·Gemini API로 만드는 AI 웹 서비스의 설계부터 운영까지

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기