fos-blog/study
01 / 홈02 / 카테고리03 / 시리즈
01 / 홈02 / 카테고리03 / 시리즈

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • agent 페이지로 이동
    • langgraph 페이지로 이동
    • 사람용 CLI와 AI 에이전트용 CLI는 설계가 다르다
    • agents.md
    • BMAD Method — AI 에이전트로 애자일 개발하는 방법론
    • Claude Code 메모리: CLAUDE.md와 .claude/rules를 규칙으로 쓰는 법
    • Claude Code의 Skill 시스템 - 개발자를 위한 AI 자동화의 새로운 차원
    • Claude Code를 5주 더 쓴 결과 — 스킬·CLAUDE.md를 키워가는 방식
    • Claude Code를 11일 동안 쓴 결과 — 데이터로 본 나의 사용 패턴
    • Claude Code 멀티 에이전트 — Teams
    • AI 에이전트와 디자인의 새 컨벤션 — DESIGN.md, Google Stitch, Claude Design
    • Docling — IBM Research 의 문서 파싱 toolkit 상세 정리
    • 하네스 엔지니어링 실전 — 4인 에이전트 팀으로 코딩 파이프라인 구축하기
    • 하네스 엔지니어링 — 오래 실행되는 AI 에이전트를 위한 설계
    • 멀티모달 LLM (Multimodal Large Language Model)
    • AI 에이전트와 함께 MVP 만들기 — dooray-cli 사례
    • OpenClaw는 context와 memory를 어떻게 관리하나 — 나만의 에이전트를 구성하는 법
    • OpenClaw vs Hermes Agent — 갈아탈까 고민하며 정리한 비교
    • 스킬 문서를 신경망처럼 학습시킨다 — Microsoft SkillOpt 분석
  • ai 페이지로 이동
    • agent 페이지로 이동
    • [초안] AI 제품 백엔드 안정성 — 지연·비용·권한·관측·도구 실패·폴백/재시도/사람 에스컬레이션
    • [초안] LLM 평가 프레임워크: 골든셋, 회귀 테스트, LLM-as-a-judge, 사람 피드백 루프
  • algorithm 페이지로 이동
    • live-coding 페이지로 이동
    • 분산 계산을 위한 알고리즘
  • apartment 페이지로 이동
    • 구리 럭키아파트 24평 인테리어 레퍼런스 모음
  • architecture 페이지로 이동
    • [초안] 시니어 백엔드를 위한 API 설계 실전 스터디 팩 — REST · 멱등성 · 페이지네이션 · 버전 전략
    • [초안] API Versioning과 Backward Compatibility: 시니어 백엔드 관점 정리
    • 캐시 설계 전략 총정리
    • [초안] 커머스 Spring 서비스에 Clean/Hexagonal Architecture를 실용적으로 적용하기
    • [초안] 커머스 도메인 모델링: 주문·재고·노출의 세 축을 분리해서 설계하기
    • 커머스 주문 상태와 데이터 정합성 기본기
    • [초안] 쿠폰/프로모션 동시성과 정합성 기본기 — 선착순·중복 사용 방지·발급/사용/복구
    • [초안] DDD와 도메인 모델링: 시니어 백엔드 관점의 전술/전략 패턴 실전 가이드
    • [초안] Decorator & Chain of Responsibility — 행동을 체인으로 조립하는 두 가지 방식
    • 디자인 패턴
    • [초안] 분산 아키텍처 완전 정복: Java 백엔드 시니어 인터뷰 대비 실전 가이드
    • [초안] 분산 트랜잭션과 Outbox 패턴 — 왜 2PC를 피하고 어떻게 대신할 것인가
    • 분산 트랜잭션
    • [초안] e-Commerce 주문·결제 도메인 모델링: 상태머신, 멱등성, Outbox/Saga 실전 정리
    • [초안] Event Sourcing과 CQRS — 상태가 아니라 변화를 저장한다는 발상
    • [초안] F&B 쿠폰·프로모션·멤버십·포인트 설계
    • [초안] F&B · e-Commerce 디지털 채널 도메인 한 장 정리
    • [초안] F&B 주문/매장/픽업 상태머신 설계
    • [초안] F&B 이커머스 결제·환불·정산 운영 가이드
    • [초안] Hexagonal / Clean Architecture를 Spring 백엔드에 적용하기
    • [초안] 대규모 커머스 트래픽 처리 패턴 — 대규모 회원과 메가 프로모션을 버티는 설계
    • [초안] 레거시 JSP/jQuery 화면과 신규 API가 공존하는 백엔드 운영 전략
    • [초안] MSA 서비스 간 통신: Redis [Cache-Aside](../database/redis/cache-aside.md) × Kafka 이벤트 하이브리드 설계
    • [초안] Observability 입문: 시니어 백엔드가 장애를 탐지하고 대응하는 방식
    • [초안] Outbox / Inbox Pattern 심화 — 분산 메시징의 정합성 문제를 DB 트랜잭션으로 풀어내기
    • [초안] 결제 도메인 멱등성과 트랜잭션 재시도 기본기
    • [초안] 시니어 백엔드를 위한 Resilience 패턴 실전 가이드 — Timeout, Retry, Circuit Breaker, Bulkhead, Backpressure
    • [초안] REST API 버저닝과 모바일 앱 하위 호환성 — 디지털 채널 백엔드 관점
    • [초안] Spring Batch vs Event-Driven — 같은 비동기처럼 보이지만 전혀 다른 두 패러다임
    • [초안] Strategy Pattern — 분기문을 없애는 설계, 시니어 백엔드 인터뷰 핵심 패턴
    • [초안] 시니어 백엔드를 위한 시스템 설계 입문 스터디 팩
    • [초안] 템플릿 메서드 패턴 - 백엔드 처리 골격을 강제하는 가장 오래되고 가장 위험한 패턴
    • [초안] 대규모 트래픽 중 무중단 마이그레이션 — Feature Flag + Shadow Mode 실전
  • database 페이지로 이동
    • milvus 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • qdrant 페이지로 이동
    • redis 페이지로 이동
    • vespa 페이지로 이동
    • 김영한의-실전-데이터베이스-설계 페이지로 이동
    • [초안] DB Connection Pool Saturation과 Thread Pool 격리
    • 커넥션 풀 크기는 얼마나 조정해야 할까?
    • 인덱스 - DB 성능 최적화의 핵심
    • [초안] JPA N+1과 커머스 조회 모델: 주문/메뉴/쿠폰 도메인에서 살아남기
    • [초안] MyBatis 기본기 — XML Mapper, resultMap, 동적 SQL, 운영 패턴 정리
    • [초안] MyBatis와 JPA/Hibernate 트레이드오프 — 레거시 백엔드를 다루는 시니어 관점
    • 벡터 DB 5종, 아키텍처는 어떻게 다른가
    • 벡터 DB 어떻게 고를까 — OpenSearch · Milvus · Qdrant · Vespa · pgvector 비교
    • 벡터 DB를 실제로 도입한 사례 — 빅테크 프로덕션
    • 역정규화 (Denormalization)
    • 데이터 베이스 정규화
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • k8s-in-action 페이지로 이동
    • observability 페이지로 이동
    • [초안] 커머스/F&B 채널 장애 첫 5분과 관측성 기본기
    • [초안] 운영 데이터 정합성 장애 대응 — 결제 취소 누락과 중복 적재 런북
    • Envoy Proxy
    • [초안] F&B / e-Commerce 운영 장애 대응과 모니터링 — 백엔드 관점 정리
    • Graceful Shutdown
    • [초안] 시니어 백엔드를 위한 SLO와 Error Budget 기반 장애 대응
  • http 페이지로 이동
    • HTTP Connection Pool
    • HTTPS는 어떻게 안전한가 — TLS, 인증서, 그리고 termination
  • interview 페이지로 이동
    • [초안] AI 서비스 팀 경험 기반 시니어 백엔드 면접 질문 뱅크 — Spring Batch RAG / gRPC graceful shutdown / 전략 패턴 / 12일 AI 웹툰 MVP
    • Observability — 면접 답변 프레임
    • [초안] 시니어 Java 백엔드 면접 마스터 플레이북 — 김병태
    • [초안] NSC 슬롯팀 경험 기반 질문 은행 — 도메인 모델링·동시성·성능·AI 협업
  • java 페이지로 이동
    • concurrency 페이지로 이동
    • jdbc 페이지로 이동
    • opentelemetry 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • testing 페이지로 이동
    • 더_자바_코드를_조작하는_다양한_방법 페이지로 이동
    • [초안] Java 동시성 락 정리 — 커머스 메뉴/프로모션 정책 캐시 갱신 관점
    • [초안] JVM 튜닝 실전: 메모리 구조부터 Virtual Threads, GC 튜닝, 프로파일링까지
    • Java의 로깅 환경
    • MDC (Mapped Diagnostic Context)
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node 백엔드 운영 패턴 — Streams 백프레셔, pipe/pipeline, 멱등성 vs 분산 락
    • Node.js
    • npm vs pnpm — 어떤 기준으로 선택했나
    • `setImmediate()`
  • kafka 페이지로 이동
    • [초안] Kafka 기본 개념 — 토픽, 파티션, 오프셋, 복제
    • Kafka를 사용하여 **데이터 정합성**은 어떻게 유지해야 할까?
    • [초안] Kafka 실전 설계: 파티션 전략, 컨슈머 그룹, 전달 보장, 재시도, 순서 보장 트레이드오프
    • 메시지 전송 신뢰성
    • [초안] Spring Kafka 컨슈머 오프셋 커밋과 트랜잭션 정렬: AckMode, manual ack, 멱등 처리
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • tmux — Terminal Multiplexer
  • mlops 페이지로 이동
    • Python CUDA 버전 생태계 — nvidia-smi, nvcc, pip, conda가 다 다른 버전을 말하는 이유
    • GPU 컨테이너의 CUDA 버전 호환성 — nvidia-smi부터 이미지 다이어트까지
    • Kubernetes GPU 노드에서 /run tmpfs가 꽉 차서 Pod가 안 뜰 때
    • GPU·CUDA·MPS 기초 — 자바 백엔드 개발자가 처음 만나는 그림
    • Multi-process GPU 워크로드 — 자바 ThreadPool 사용자가 만나는 모델 차이
    • ML 서비스 성능 분석 워크플로 — 자바 백엔드 트러블슈팅과 다른 점
    • 한 GPU 를 여러 프로세스가 나눠 쓰기 — Time-Slicing 과 MPS
  • network 페이지로 이동
    • Connection reset by peer는 누가 보낸 걸까 — 리버스 프록시 홉마다 TCP 연결은 따로 논다
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • python 페이지로 이동
    • Python async/await — CompletableFuture·Reactor 와 다른 점, 그리고 blocking I/O 함정
    • Python 의존성 관리 — Java Maven/Gradle 사용자가 만나는 첫 충격
    • FastAPI 기초 — Spring Boot 사용자가 빠르게 익히는 법
    • Java 개발자를 위한 Python 심화 — OOP·데코레이터·컨텍스트 매니저
    • PyTorch 기초 — 텐서, 디바이스, 그리고 모델 로딩이 무거운 이유
    • Java 개발자를 위한 Python 문법 핵심
    • ThreadLocal 에서 contextvars 로 — Python 의 요청 컨텍스트 전파
    • OCR 동작 원리 — Layout · Text · Post-process 3단계
    • Python 서버의 RSS 가 안 줄어드는 이유 — gc.collect 의 한계와 malloc_trim
  • rabbitmq 페이지로 이동
    • [초안] RabbitMQ Basics — 실전 백엔드 관점에서 정리하는 메시지 브로커 기본기
    • [초안] RabbitMQ vs Kafka — 백엔드 메시징 선택 기준과 실전 운영 관점
  • security 페이지로 이동
    • [초안] 시니어 백엔드를 위한 보안 / 인증 스터디 팩 — Spring Security, JWT, OAuth2, OWASP Top 10
    • [초안] Spring Security 6.x OAuth2 + JWT 상용 인증 설계 — Grant 선택, Resource Server, Refresh Rotation, 로그아웃
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
  • testing 페이지로 이동
    • [초안] 시니어 Java 백엔드를 위한 테스트 전략 완전 정리 — 피라미드부터 TestContainers, 마이크로벤치, Contract까지
  • travel 페이지로 이동
    • 오사카 3박 4일 일정표: 우메다 쇼핑, USJ, 난바·도톤보리, 오사카성
  • web 페이지로 이동
    • [초안] HTTP / Cookie / Session / Token 인증 기본기 — 레거시 JSP와 모바일 API가 공존하는 백엔드 관점
FOS-BLOG · FOOTERall systems normal·v0.1 · 2026.04.27·seoul, kr
Ffos-blog/study

개발 학습 기록을 정리하는 블로그입니다. 공부하면서 기록하고, 기록하면서 다시 배웁니다.

visitors
01site
  • Home↗
  • Posts↗
  • Categories↗
  • About↗
02policy
  • 소개/about
  • 개인정보처리방침/privacy
  • 연락처/contact
03categories
  • AI↗
  • Algorithm↗
  • DB↗
  • DevOps↗
  • Java/Spring↗
  • JS/TS↗
  • React↗
  • Next.js↗
  • System↗
04connect
  • GitHub@jon890↗
  • Source repositoryjon890/fos-study↗
  • RSS feed/rss.xml↗
  • Newsletter매주 1 회 · 한 편의 글→
© 2026 FOS Study. All posts MIT-licensed.
built with·Next.js·Tailwind v4·Geist·Pretendard·oklch
fos-blog/AI/[초안] AI 제품 백엔드 안정성 — 지연·…
ai

[초안] AI 제품 백엔드 안정성 — 지연·비용·권한·관측·도구 실패·폴백/재시도/사람 에스컬레이션

LLM을 붙인 제품의 백엔드는 일반 CRUD 백엔드와 안정성 모델이 다르다. 일반 백엔드는 "DB가 살아 있으면 같은 입력에 같은 출력"이 기본 전제다. AI 제품 백엔드는 그렇지 않다. - 응답이 비결정적이다 — 같은 입력에도 모델 출력이 매번 달라진다. 그래서 "정상 응답"의 정의 자체를 우리가 따로 만들어야 한다. - 지연이 크고 가변적이다 — 모델...

2026.06.07·10 min read·138 views

왜 이 주제가 중요한가

LLM을 붙인 제품의 백엔드는 일반 CRUD 백엔드와 안정성 모델이 다르다. 일반 백엔드는 "DB가 살아 있으면 같은 입력에 같은 출력"이 기본 전제다. AI 제품 백엔드는 그렇지 않다.

  • 응답이 비결정적이다 — 같은 입력에도 모델 출력이 매번 달라진다. 그래서 "정상 응답"의 정의 자체를 우리가 따로 만들어야 한다.
  • 지연이 크고 가변적이다 — 모델 한 번 호출이 수백 ms~수십 초까지 튄다. p99가 평균의 5배를 넘는 경우가 흔하다.
  • 호출당 비용이 실시간으로 발생한다 — 토큰 단위로 돈이 나간다. 루프가 한 번 폭주하면 장애가 아니라 청구서로 먼저 드러난다.
  • 외부 모델 제공자에 강하게 의존한다 — 우리 코드가 멀쩡해도 제공자 쪽 429/5xx/지연이 그대로 우리 SLA를 깬다.

그래서 AI 제품 백엔드의 안정성은 "장애가 안 나게 한다"가 아니라 비결정성·가변 지연·실시간 비용·외부 의존을 전제로 깔고, 나빠질 때 어디서 끊고 어떻게 우아하게 내려갈지를 설계한다 가 핵심이다. 이 문서는 그 설계를 지연·비용·권한·관측·실패 처리 다섯 축으로 정리한다.

도구 호출 자체의 설계(레지스트리, 스키마 검증, dispatcher)는 LLM Tool Calling과 Agent Workflow 설계에서, 일반 분산 시스템 resilience 스택(Timeout/Retry/Circuit Breaker/Bulkhead/Backpressure)은 Resilience 패턴 가이드에서 다룬다. 이 문서는 그 둘을 AI 제품 운영 관점에서 묶는 허브 역할을 한다.

핵심 개념 — 안정성 5개 축

AI 제품 백엔드의 안정성은 다음 다섯 가지를 각각 "예산"과 "실패 정책"으로 설계하는 일이다.

축핵심 질문대표 통제 수단
지연사용자가 얼마나 기다리게 둘 것인가시간 예산, 스트리밍, 계층 timeout
비용한 요청에 얼마까지 쓸 것인가토큰/호출/누적 비용 가드, 캐싱, 모델 폴백
권한모델이 무엇을 건드리게 둘 것인가사용자 컨텍스트 권한 검증, 도구 노출 제한
관측무엇이 왜 실패했는지 사후에 알 수 있는가correlation id, audit JSONL, LLM 특화 메트릭
실패 처리나빠질 때 어떻게 내려갈 것인가폴백 사다리, 재시도, 사람 에스컬레이션

다섯 축은 독립적이지 않다. 지연 예산이 비용 예산을 제약하고, 실패 처리는 관측이 없으면 설계할 수 없다. 그래서 한 곳(보통 요청 단위 budget 객체)에 모아두는 것이 실무에서 가장 덜 깨진다.

지연 예산 (Latency Budget)

모델 호출은 느리다. 그러므로 "언제까지 기다리고, 어디서 포기할지"를 먼저 정한다.

계층 timeout

모델/도구 호출은 최소 세 계층의 timeout을 분리한다.

  • 단일 모델 호출 timeout — 한 번의 LLM 호출에 대한 상한. 스트리밍이면 "첫 토큰까지" 와 "전체 완료까지" 를 따로 둔다.
  • 단일 도구 호출 timeout — 외부 API/DB 호출 상한. 도구마다 평균/최대 응답 시간이 다르므로 도구 메타데이터에 SLO로 박는다.
  • 요청 전체 timeout(wall-clock budget) — 사용자 발화 하나를 처리하는 전체 상한. 루프가 여러 step을 돌아도 이 예산을 넘기면 끊는다.

스트리밍으로 체감 지연을 낮춘다

전체 응답이 8초 걸려도 첫 토큰이 400ms에 나오면 사용자 체감은 전혀 다르다. 스트리밍(SSE 또는 WebSocket)은 안정성 설계의 일부다. 단, 스트리밍 중간에 실패하면 "이미 일부를 보여준 상태"라는 새 실패 모드가 생긴다. 부분 출력 후 끊겼을 때 어떻게 마감할지(에러 토큰 주입, 재생성 버튼, 마지막 문장 절단 표시)를 미리 정한다.

text
요청 시작
 ├─ 첫 토큰까지 timeout: 5s   (안 오면 폴백)
 ├─ 전체 완료 timeout: 30s
 └─ 요청 wall-clock budget: 40s (도구 호출 포함 전체)

비용 예산 (Cost Budget)

AI 백엔드에서 비용 폭주는 "느린 장애"다. 모니터링 그래프보다 다음 달 청구서에서 먼저 보인다. 그래서 비용을 런타임 가드로 다룬다.

요청 단위 가드

한 사용자 발화당 다음을 모두 건다.

  • 최대 step 수 — 에이전트 루프가 도구를 부를 수 있는 횟수 상한 (예: 6회)
  • 최대 누적 토큰 — 입력+출력 토큰 합 상한 (예: 30k)
  • 최대 누적 비용 — 모델별 단가를 곱한 추정 비용 상한

가드에 걸리면 예외가 아니라 정해진 폴백 응답 + 사람 에스컬레이션으로 내려간다. "예산 초과로 죽었습니다"가 사용자에게 가서는 안 된다.

모델 폴백 사다리와 캐싱

비용과 안정성을 동시에 잡는 두 수단.

  • 모델 폴백 사다리 — 평소에는 작고 싼 모델로 처리하고, 신뢰도/복잡도가 임계 이상일 때만 큰 모델로 올린다. 반대로 큰 모델이 429를 던지면 같은 모델을 기다리지 말고 바로 다음 모델로 내려가는 전략도 있다. "싸 보이는 모델이 재생성을 반복해 오히려 더 비싸지는" 경우가 있으므로, 폴백 방향은 비용만이 아니라 재시도 총비용으로 판단한다.
  • 컨텍스트 캐싱 / 결과 캐싱 — 동일 시스템 프롬프트나 동일 입력에 대한 반복 호출은 제공자 컨텍스트 캐시 또는 자체 결과 캐시로 토큰을 줄인다. 캐시 키에 모델 버전과 프롬프트 버전을 포함하지 않으면 모델 교체 후 낡은 응답이 새는 함정이 있다.

권한 경계

모델은 "무엇을 할 수 있는지"를 알 필요가 없고, 알게 해서도 안 된다. 권한은 항상 현재 인증된 사용자 컨텍스트에서 검증한다. 자세한 dispatcher 구조는 Tool Calling 문서에 있으므로 여기서는 안정성 관점의 원칙만 짚는다.

  • 모델이 "시켰으니까" 권한을 잠깐 올려서 실행하는 우회는 절대 만들지 않는다. 도구 실행은 언제나 사용자 컨텍스트에서 일어난다.
  • 노출하는 도구 집합을 의도 분류 결과 + 사용자 권한으로 동적으로 좁힌다. 잡담 컨텍스트에 민감 도구를 노출하면 prompt injection 표면이 넓어진다.
  • 거절 메시지는 내부 사유를 흘리지 않는 일반화된 형태로 모델에 돌려준다. 모델은 그것을 사용자에게 그대로 풀어 쓸 수 있다.

권한이 안정성 주제인 이유는, 권한 사고가 데이터 유출/오작동이라는 가장 비싼 장애로 직결되기 때문이다.

관측 (Observability)

AI 백엔드는 "왜 이 답이 나왔는가"를 사후에 재구성할 수 없으면 개선도 사고 대응도 못 한다. 일반 관측성 기본기와 SLO·에러 예산·인시던트 대응에 더해, AI 특화 항목을 남긴다.

무엇을 남기는가

한 사용자 발화를 하나의 correlation id로 묶어 audit JSONL에 적재한다.

  • 의도 분류 결과와 confidence
  • 모델에 노출한 도구 목록과 system prompt 버전
  • 모델이 제안한 각 tool call의 이름·인자, 검증 결과(통과/거절 사유)
  • 실제 실행된 도구의 입력·출력 요약, 응답 시간, 결과 코드
  • 사용한 모델, 입력/출력 토큰, 추정 비용, step 수
  • 최종 사용자 노출 텍스트

LLM 특화 메트릭

일반 RED(Rate/Error/Duration) 메트릭에 더해 본다.

  • 모델 호출 실패율 (제공자 429/5xx 비율) — 우리 코드가 아니라 외부 의존의 건강 상태
  • 폴백 발동율 — 어떤 폴백 경로가 얼마나 자주 도는지. 폴백이 "가끔"이 아니라 "기본"이 되면 설계가 무너진 신호다.
  • 요청당 평균/누적 토큰·비용 — 비용 회귀를 조기에 잡는다
  • 검증 거절율 — 모델이 스키마/의미 검증에서 떨어지는 비율. hallucination 추세 지표.

audit는 운영뿐 아니라 모델 평가의 입력이다. 이 데이터로 어떤 분류가 자주 틀리고 어떤 도구의 인자 환각이 잦은지를 정량으로 본다. 평가 루프 설계는 LLM 평가 프레임워크와 연결된다.

도구·모델 실패 처리 — 폴백 사다리

실패는 예외가 아니라 일정 확률로 항상 일어나는 사건이다. 핵심은 "전파를 어디서 끊고, 어떻게 한 단계씩 우아하게 내려갈지"다.

폴백 사다리 (degradation ladder)

가장 좋은 응답에서 최소한의 응답까지 단계를 미리 정의한다.

text
1. 큰 모델 + 전체 도구       (정상)
2. 작은 모델 + 전체 도구       (큰 모델 429/지연 시)
3. 작은 모델 + 캐시된 결과만    (외부 도구 장애 시)
4. 템플릿/규칙 기반 응답        (모델 전체 장애 시)
5. 사람 상담사로 에스컬레이션    (위 전부 실패 또는 민감 케이스)

각 단계 전환 조건(어떤 신호에서 한 칸 내려가는가)을 코드로 명시한다. "되는 데까지 해보다가 터지면 500"이 가장 나쁜 패턴이다.

재시도 — 부작용 여부로 갈라야 한다

  • 읽기/안전한 도구 (조회류): 지수 백오프 + jitter로 2~3회 재시도. 결과는 "성공" 또는 "최종 실패"로 정리해 모델에 준다.
  • 부작용 있는 도구 (발송/결제/취소류): idempotency key가 있을 때만 재시도한다. 없으면 즉시 실패로 보고한다. 모델이 같은 도구를 반복 호출해 부작용이 누적되는 사고를 막는 것이 재시도 정책보다 우선이다.
  • 모델 호출 자체의 429: 같은 모델을 무한정 기다리기보다 폴백 사다리의 다음 모델로 내려가는 편이 지연·비용 모두에 낫다.

부분 성공을 정직하게 전달한다

여러 외부 호출 중 일부만 성공한 경우, observation 페이로드에 부분 성공을 명시해 모델이 "모두 완료"라고 단정하지 않게 한다.

json
{ "sms": "sent", "push": "queue_failed" }

모델 답변은 "문자로는 보냈고 앱 알림은 잠시 후 다시 시도하겠다"가 되어야 한다.

사람 에스컬레이션은 기능이다

AI 제품에서 "사람에게 넘긴다"는 장애 처리의 마지막 수단이 아니라 설계된 정상 경로 중 하나다. 다음을 미리 정한다.

  • 언제 넘기는가 — 예산 초과, 권한 부족, 낮은 confidence, 민감 카테고리, 연속 실패.
  • 무엇을 함께 넘기는가 — 지금까지의 대화 맥락 요약, 시도한 도구와 결과, 실패 사유. 사람이 처음부터 다시 묻지 않게 한다.
  • 사용자에게 어떻게 보이는가 — "처리할 수 없습니다"가 아니라 "확인이 더 필요해 상담사에게 연결합니다" 같은 우아한 마감.

에스컬레이션 경로가 없는 AI 백엔드는 나쁜 케이스에서 무조건 사용자에게 실패를 떠넘긴다.

나쁜 예 vs 개선된 예

나쁜 예 1 — 예산 없는 에이전트 루프

java
// 안티패턴: 종료 조건이 "모델이 끝났다고 할 때"뿐
while (!response.isFinal()) {
    response = llm.next();
    dispatch(response.toolCall());
}

모델이 같은 도구를 반복 호출하면 토큰·비용·지연이 무한히 늘어난다. 장애가 청구서로 드러난다.

개선

java
for (int step = 0; step < ctx.maxSteps(); step++) {
    LlmResponse r = session.next(ctx.budget()); // 토큰·시간·비용 예산 주입
    if (r.isFinal()) return AgentReply.text(r.text());
    dispatcher.dispatch(r.toolCall(), ctx.toToolContext());
    if (ctx.budget().exhausted()) break;        // 예산 가드
}
return AgentReply.escalate("budget_exceeded"); // 우아한 마감

나쁜 예 2 — 모델 장애 시 그냥 500

java
// 안티패턴
String answer = llm.complete(prompt); // 제공자 5xx면 그대로 예외 → 사용자 500
return answer;

개선

폴백 사다리를 탄다. 큰 모델 실패 → 작은 모델 → 캐시된 결과 → 템플릿 응답 → 사람 에스컬레이션 순으로 한 칸씩 내려간다. 어느 단계에서 내려갔는지 audit에 남긴다.

로컬 실습 환경

  • JDK 21, Spring Boot 3.x
  • 의존성: spring-boot-starter-web, spring-boot-starter-validation, resilience4j-retry, resilience4j-circuitbreaker, micrometer-tracing
  • LLM: 키가 없으면 fake LLM으로 대체 — 시나리오별로 미리 준비된 tool call/응답을 결정적으로 돌려준다. 안정성 가드를 검증할 때는 진짜 모델보다 결정적인 가짜가 낫다.
  • 장애 주입: fake 모델/도구 어댑터가 일정 비율로 429·5xx·timeout·부분 성공을 주입할 수 있게 둔다.
  • 관측: logs/ai-audit.jsonl에 줄 단위 JSON으로 적재하고 jq로 폴백 발동율·평균 토큰을 집계한다.

실행 가능한 시나리오

손으로 굴려보며 실패 모드를 익히는 데 좋은 시나리오.

  1. 정상 경로 — 예산 안에서 도구 2회 호출 후 최종 답변. audit에 토큰·비용 기록.
  2. 모델 429 — 큰 모델이 429를 던지면 폴백 사다리 2단계(작은 모델)로 내려가고, audit에 폴백 발동을 남긴다.
  3. 외부 도구 5xx — 부작용 없는 조회 도구는 백오프 재시도 후 성공/실패 정리, 부작용 도구는 idempotency key 없으면 재시도 금지.
  4. 부분 성공 — 두 알림 채널 중 하나만 성공. observation에 부분 성공 명시, 모델이 "모두 완료"라고 답하지 않는지 확인.
  5. 예산 초과 — 모델이 같은 도구를 반복 호출. step 가드가 끊고 사람 에스컬레이션으로 마감.
  6. 권한 부족 — 사용자 권한 밖 도구 요청. dispatcher가 일반화된 거절을 돌려주고 모델이 안전한 경로로 우회.

각 시나리오는 fake LLM 기반 통합 테스트로 묶어 회귀 보호한다. 모델 응답이 바뀌어도 안정성 가드가 동작하는지를 결정적으로 검증할 수 있다.

설계 점검 질문

시니어 백엔드 관점에서 스스로 던져볼 질문과 답의 뼈대.

"AI 제품 백엔드가 일반 백엔드와 다른 점은?"

비결정성·가변 지연·실시간 토큰 비용·외부 모델 의존을 전제로 깔아야 한다는 점. 그래서 "정상 응답의 정의"와 "나빠질 때 내려가는 단계"를 우리가 직접 설계한다.

"비용·지연 폭주를 어떻게 막는가?"

요청 단위 step/토큰/시간/비용 가드를 한 budget 객체에 모으고, 초과 시 폴백·에스컬레이션으로 내려간다. 모델 폴백 사다리와 캐싱으로 평소 비용을 낮추되, 폴백 방향은 재시도 총비용으로 판단한다.

"모델/도구 실패를 어떻게 다루는가?"

폴백 사다리로 한 칸씩 우아하게 내려간다. 재시도는 부작용 여부로 가르고, 부작용 도구는 idempotency key가 있을 때만 재시도한다. 부분 성공은 정직하게 모델에 전달한다.

"관측은 어떻게 설계하는가?"

correlation id로 한 발화를 묶어 분류·노출 도구·tool call·검증·실행·토큰·비용·최종 답변까지 audit JSONL에 남긴다. 폴백 발동율·검증 거절율·요청당 비용을 LLM 특화 메트릭으로 본다. 이 데이터는 모델 평가의 입력이기도 하다.

체크리스트

  • 요청 단위로 step·토큰·시간·비용 예산이 한 곳에 모여 있는가
  • 단일 모델 호출 / 단일 도구 호출 / 요청 전체 timeout이 계층으로 분리돼 있는가
  • 스트리밍 중간 실패 시 마감 방식이 정의돼 있는가
  • 모델 폴백 사다리와 각 단계 전환 조건이 코드로 명시돼 있는가
  • 캐시 키에 모델 버전과 프롬프트 버전이 포함되는가
  • 도구 노출이 의도 분류 + 사용자 권한으로 동적으로 좁혀지는가
  • 부작용 도구의 재시도는 idempotency key가 있을 때만 일어나는가
  • 부분 성공이 모델에게 부분 성공으로 정확히 전달되는가
  • 폴백·에스컬레이션 발동이 audit에 남고 메트릭으로 집계되는가
  • 사람 에스컬레이션이 맥락 요약과 함께 설계된 정상 경로로 존재하는가
  • 모든 단계가 correlation id 하나로 묶여 audit JSONL에 적재되는가
  • fake LLM 기반 통합 테스트로 429·외부 실패·부분 성공·예산 초과·권한 부족이 회귀 보호되는가
on this page
  • 01왜 이 주제가 중요한가
  • 02핵심 개념 — 안정성 5개 축
  • 03지연 예산 (Latency Budget)
  • 계층 timeout
  • 스트리밍으로 체감 지연을 낮춘다
  • 04비용 예산 (Cost Budget)
  • 요청 단위 가드
  • 모델 폴백 사다리와 캐싱
  • 05권한 경계
  • 06관측 (Observability)
  • 무엇을 남기는가
  • LLM 특화 메트릭
  • 07도구·모델 실패 처리 — 폴백 사다리
  • 폴백 사다리 (degradation ladder)
  • 재시도 — 부작용 여부로 갈라야 한다
  • 부분 성공을 정직하게 전달한다
  • 08사람 에스컬레이션은 기능이다
  • 09나쁜 예 vs 개선된 예
  • 나쁜 예 1 — 예산 없는 에이전트 루프
  • 개선
  • 나쁜 예 2 — 모델 장애 시 그냥 500
  • 개선
  • 10로컬 실습 환경
  • 11실행 가능한 시나리오
  • 12설계 점검 질문
  • "AI 제품 백엔드가 일반 백엔드와 다른 점은?"
  • "비용·지연 폭주를 어떻게 막는가?"
  • "모델/도구 실패를 어떻게 다루는가?"
  • "관측은 어떻게 설계하는가?"
  • 13체크리스트

이런 글도

  • HNSW 심화 — 파라미터 튜닝과 구현체별 성능 차이
    벡터 검색 알고리즘 입문에서 HNSW가 kNN을 실시간에 쓰게 만든 표준 알고리즘이라는 걸 봤다. 이 글은 그 다음 단계다 — 파라미터를 어떻게 튜닝하고, 왜 같은 설정인데 제품마다 성능이 다른가. - HNSW 튜닝은 파라미터 3개로 끝난다 — M(그래프 밀도), efconstruction(빌드 품질), efsearch(검색 품질). 앞 둘은 인덱스에 고정...
    🤖 ai
    ai
    2026.07.01
  • 엔터프라이즈 AI Agent 설계 — reasoning, tool, memory, cost를 운영 시스템으로 묶기
    AI Agent를 엔터프라이즈 환경에 올린다는 건 "LLM이 알아서 일하게 한다"가 아니다. 모델의 reasoning 능력, 도구 호출, 메모리, 비용 예산, 권한, 감사 로그를 하나의 운영 시스템으로 묶는 일이다. 이 글은 Chain of Thought부터 MCP, LangGraph, Agent SDK, memory, cost control, risk g...
    🤖 ai
    ai
    2026.06.25
  • 사람용 CLI와 AI 에이전트용 CLI는 설계가 다르다
    예전에는 CLI를 사람만 썼다. 지금은 Claude Code 같은 AI 에이전트가 CLI를 호출해 업무를 자동화한다. 같은 도구라도 "에이전트가 쓰기 좋게" 설계하면 자동화가 훨씬 매끄럽고, 그러지 않으면 자동화가 자주 깨진다. 여러 업무 자동화 CLI를 직접 만들어 에이전트로 호출해 보면서 정리한 설계 원칙을 공유한다. (예시는 공개한 개인 도구 door...
    🤖 ai
    ai
    2026.06.17
  • 벡터 검색 알고리즘 — kNN에서 HNSW까지
    임베딩으로 텍스트를 벡터로 바꾸고 나면, "질문 벡터와 가장 가까운 문서 벡터"를 찾아야 한다. 이 글은 그 검색을 담당하는 알고리즘을 kNN(개념) → 왜 느린가 → ANN → HNSW(실전 표준) 순서로 정리한다. - kNN(k-Nearest Neighbors) = 어떤 벡터(쿼리)와 가장 가까운 k개의 이웃 벡터를 찾는 알고리즘 - 예를 들어 - 문서...
    🤖 ai
    ai
    2026.06.16

댓글 (0)