[V1] Vertex AI 부하 상황공유 - 100-hours-a-week/6-nemo-ai GitHub Wiki

Vertex AI 호출 안정화 회고

📌 목적

우리는 FastAPI 기반 AI 서버에서 Google Vertex AI를 통해 모임 설명, 커리큘럼 등을 생성하는 기능을 개발하고 있다.

이 과정에서 Google의 QPS 제한, API 에러, 느린 응답 등으로 인해 다양한 기술적 문제를 겪었고,

그에 따라 구조적 개선을 반복해왔다.

✅ 문제 요약

번호 내용
1 Vertex AI에 다수 요청 시 QuotaExceeded, DeadlineExceeded 오류 발생
2 모델 호출 실패 시 예외가 사용자에게 노출됨
3 과도한 요청이 몰릴 경우 응답 지연 또는 일부 실패 발생
4 짧은 시간에 많은 요청이 몰릴 경우 처리가 지연되거나 튕겨나감
5 AI 응답에 필수 항목이 없으면 빈 문자열이 반환됨

🧪 시도한 해결 방법

번호 해결 시도
1 ThreadPoolExecutor를 도입하여 동기 Vertex API를 병렬 처리
2 generate_content() 내부에 try/except + fallback 문구([EMPTY], [ERROR]) 리턴
3 smart_generate()에서 RateLimitedExecutor로 요청 빈도와 병렬 처리 제한
4 QueuedExecutor를 설계해 요청을 줄 세우고 천천히 실행하도록 구조 설계 착수
5 파싱 실패 시 빈 문자열 리턴 + 로깅 처리

🧰 구현한 것들 (2025.05.14 기준)

1. smart_generate() 통합 설계

  • 하나의 함수(smart_generate)로 동기/비동기 환경 모두에서 Vertex 호출을 처리하도록 설계
  • 내부적으로 run_in_executor()를 사용해 동기 호출을 비동기화
python
CopyEdit
async def smart_generate(prompt: str):
    return await loop.run_in_executor(vertex_executor, generate_content, prompt)

2. RateLimitedExecutor 설정

  • 동시에 실행 가능한 요청 수와 초당 허용 요청 수를 제한하기 위해 설정
python
CopyEdit
vertex_executor = RateLimitedExecutor(max_workers=3, qps=1)
  • 그러나 쓰레드 기반이라 완전한 직렬 처리에는 실패

3. generate_description, generate_plan, extract_tags 전체 비동기화

  • 모든 AI 호출 관련 함수들을 async def로 바꾸고 내부에서 await smart_generate(...)를 사용하도록 일괄 리팩터링
  • 파싱 실패 시 빈값을 반환하고 에러 로그를 기록하여 서버 장애를 방지

4. build_meeting_data() 리팩터링

  • 동기 방식으로 동작하던 build_meeting_data()async def로 전환
  • 내부 모든 서비스 호출에 await 적용
  • 테스트용 실행 구조도 asyncio.run(...)으로 변경

5. FastAPI 라우터 리팩터링

  • 라우터 함수 create_meeting()에서 await build_meeting_data(...) 적용
  • 유해성 검증, 에러 핸들링 구조도 예외 누수 없이 감싸도록 정비

🔍 현재 구조의 흐름 (Mermaid 다이어그램)

flowchart TD
    A[User Request (FastAPI)] --> B[Router (async def)]
    B --> C[await build_meeting_data()]
    C --> D[generate_description / plan / tags]
    D --> E[await smart_generate(prompt)]
    E --> F[run_in_executor]
    F --> G[RateLimitedExecutor (max_workers=3, qps=1)]
    G --> H[generate_content(prompt)]
    H --> I[Call Vertex AI (sync)]
    I --> J[Vertex AI Response]
    J --> K[Parse & return result]
    K --> L[Return APIResponse to user]

❗ 여전히 발생하는 문제

  • RateLimitedExecutor는 "줄을 세우는 구조"가 아님
  • 여러 쓰레드가 동시에 Vertex AI에 요청을 보내는 구조임
  • Google은 **"실제로 동시에 도달한 요청"**만 가지고 판단함
  • 따라서 QuotaExceeded, DeadlineExceeded는 여전히 발생 가능함

🧠 쉽게 정리하면

  • 우리는 요청을 줄 세운다고 생각했지만,
  • 실제로는 3명이 동시에 Vertex AI에 요청을 보낸 것
  • Google은 한 명씩만 들어오라고 제한함 → 결과: QuotaExceeded

🚫 제외한 방법

  • GCP 콘솔에서 쿼터 상향 요청은 비용/절차/유연성 문제로 제외

✅ 다음 단계 계획

항목 설명
직렬 큐 구조 asyncio.Queue 기반으로 요청을 실제로 순차 처리
단일 워커 설계 단 하나의 워커만 초당 1건씩 Vertex 호출
응답 재시도 로직 QuotaExceeded 발생 시 지연 후 자동 재시도
모니터링 추가 현재 요청 수, 대기열 수, 실패 수 확인 API 도입

🔚 현 상태 평가

항목 결과
안정성 구조적 안정성은 확보됨 (fallback, 예외 처리)
응답 속도 모델 호출 포함 평균 14~18초
요청 성공률 제한 내에서는 안정적, 초과 시 실패
리팩터링 여지 큐 직렬화, 재시도, 캐시 전략 등 여지 많음