서비스 인프라 확장성과 모니터링 설계 - 100-hours-a-week/KTB4-4th-wiki GitHub Wiki

서비스 인프라 확장성과 모니터링 설계

단계 7 제출 문서 · 2026-09-14 기준
대상: v2·v3 MAU 약 15,000명, DAU 약 3,750명
강제 조건: 3개월 프로젝트 예산 중 상용 LLM API, DB, Redis 등 다른 클라우드 비용을 제외한 GPU 모델 서빙 비용만 360,000원 이하. 비용·부하 산정 대상인 실제 GPU 운영·검증 기간은 45일

2단계 초안은 더 큰 MoE 모델을 후보로 두었지만, 로컬 대화 하네스에서 kakaocorp/kanana-1.5-8b-instruct-2505의 응답 품질이 서비스 기준에 부족하지 않음을 확인했다. 따라서 7단계 기준 모델은 응답·요약·검색문 재작성용 8B BF16, 턴별 구조화 추출용 1.3B BF16으로 낮춘다. 3단계는 API·worker·batch·model-runtime의 배포 경계를, 4단계는 대화 한 사이클에서 응답과 추출 모델을 각각 한 번 호출하는 흐름을 정했다. 이 문서는 두 모델을 RunPod A40/A6000 48GB GPU 한 장의 두 vLLM 프로세스로 통합 배치하고, 응답을 우선하며 추출을 비동기로 이어서 처리하는 구조와 확장·관측 기준을 정한다.

이 문서에서 1개 대화 사이클은 사용자 입력 1회와 AI 응답 1회의 왕복이다. 상한 20개 대화 사이클은 사용자 20회 + AI 20회인 총 40개 발화이며, 대화 구간의 자체 서빙 모델 호출은 응답 20회 + 추출 20회인 40회다. 발화 수와 모델 호출 수가 우연히 같지만 같은 개념은 아니다.

관련 문서는 모델 추론 성능 최적화, 서비스 아키텍처 모듈화, 멀티스텝 AI/파이프라인 구현 검토다.


1. 결론

가능한 것과 불가능한 것

판단 결론
두 Kanana 모델을 vLLM으로 배포하고 서버리스 GPU 경험을 얻는 것 가능
응답 8B와 추출 1.3B BF16을 48GB GPU 한 장에 함께 올리는 것 가능. 기본 배치안으로 채택
두 모델을 24GB GPU 한 장에 함께 올리는 것 가중치 합계는 들어가지만 두 vLLM 프로세스의 KV cache·CUDA 여유가 없어 채택하지 않음
RunPod 48GB Flex worker를 24시간 상시 운영하는 것 36만 원으로 불가능
45일 내내 DAU 3,750명이 모두 평균 8개 대화 사이클을 만드는 것 낮은 요청이 하루 종일 분산되면 예산상 No-Go. 트래픽 밀도와 부하 테스트를 함께 통과해야 함
동시 접속자 50명 중 생성 요청 8~16개를 한 worker가 처리하는 것 실측 통과 시 가능
생성 요청 50개를 한 worker가 모두 즉시 처리하는 것 불가능. queue 또는 replica 필요
추천 화면 조회를 DB 조회로 끝내고 신규 대화만 GPU를 호출하는 것 가능성 있음. 실측 게이트를 통과해야 함
RDS·ElastiCache를 AWS에 두고 Modal/RunPod에서 모델만 추론하는 것 가능
AWS DB를 외부 GPU 워커에 직접 공개하는 것 금지
현재 규모에서 Kubernetes를 직접 운영하는 것 도입하지 않음

핵심은 DAU와 GPU 호출자를 같은 수로 보지 않는 것이다. 메인·친구·상품 화면은 미리 저장된 추천 리스트를 AWS 백엔드에서 조회하므로 GPU를 호출하지 않는다. GPU 용량과 비용은 DAU가 아니라 하루 신규 AI 대화 세션 수, 평균 대화 사이클 수, 입력·출력 토큰 수로 산정한다.

채택안

  1. 비용 우선 운영안은 RunPod Serverless A40/A6000 48GB 통합 worker로 둔다.
    • 8B 응답 모델과 1.3B 추출 모델을 한 container의 서로 다른 vLLM 프로세스로 올린다.
    • 외부에는 load-balancing endpoint 하나를 두고 내부 router가 /response와 /extract를 각각의 모델로 보낸다.
    • 응답은 SSE 동기 우선 경로로, 추출은 AWS SQS에서 순서를 보장하는 비동기 경로로 같은 worker를 호출한다.
    • endpoint는 min workers=0, max workers=2로 scale-to-zero한다.
  2. Modal은 비교 실험 및 대체안으로 유지한다.
    • 운영 편의, OpenTelemetry 연동, Workspace spend limit은 Modal이 유리하다.
    • Modal L40S 48GB의 시간당 단가와 실제 tokens/s를 RunPod A40/A6000 48GB와 비교하고, 무료 크레딧 적용 여부까지 확인한 뒤 선택한다.
  3. AWS는 사용자 트래픽, 영속 데이터, 세션, 비동기 잡을 담당하고 GPU 사업자는 최소 프롬프트를 받아 추론 결과만 반환한다.
  4. 45일 총 36만 원을 전부 compute에 쓰지 않고 환율·스토리지·실수 여유를 둔 compute 324,000원 상한 + 예비비 36,000원으로 관리한다.

2. 모델과 GPU 메모리 산정

응답 모델

kakaocorp/kanana-1.5-8b-instruct-2505는 8B dense 모델이며 공식 BF16 체크포인트는 약 16.1GB다. 기본 컨텍스트는 32,768토큰이지만 서비스 계약은 8,192토큰으로 제한한다. 통합 worker에서는 48GB의 약 65~70%를 8B vLLM 프로세스에 배정하고, 가중치 적재 후 남는 메모리를 CUDA graph, activation, KV cache에 사용한다.

형식 체크포인트 런타임 여유 포함 판단 GPU
공식 BF16 약 16.1GB 1.3B BF16과 두 vLLM 런타임을 함께 유지하므로 24GB는 제외. 48GB에서 8K와 목표 동시성 검증 RunPod A40/A6000 48GB

이 선택은 양자화 품질 회귀와 커뮤니티 체크포인트 의존성을 없앤다. 로컬 품질 확인을 출발점으로 삼되, 운영 확정 전에는 같은 모델 revision으로 골드셋과 대상 GPU 부하 테스트를 다시 수행한다.

이 모델의 BF16 KV cache는 토큰당 약 128KiB다. 48GB가 두 모델 가중치만 넣기 위한 선택은 아니다. 8B의 대화 KV cache와 1.3B의 배치 여유를 함께 남기기 위한 선택이다. 초기 max_num_seqs는 8B 16, 1.3B 16으로 두고, 8B 동시 1/2/4/8/16과 추출 백그라운드 부하를 함께 재생해 처리속도 SLO와 OOM을 검증한다.

추출 모델

kakaocorp/kanana-2-1.3b-instruct는 BF16 체크포인트가 약 2.59GB다. 고정 JSON Schema와 서버 검증을 사용하고, 전체 대화가 아니라 [STATE] + 직전 AI 발화 + 사용자 발화만 받으므로 1.3B를 추출기의 운영 기준으로 채택한다. 32K 컨텍스트를 할당할 이유가 없어 4K로 제한하며, 공식 모델 카드 요구에 따라 --trust-remote-code를 사용한다.

역할 모델 정밀도 컨텍스트 상한 최소 실험 운영 권장
대화 응답·요약·검색문 재작성·이유 Kanana-1.5-8B-Instruct-2505 BF16 8,192 A40/A6000 48GB 통합 worker 같은 GPU의 8B vLLM 프로세스
턴 델타 추출 Kanana-2-1.3B-Instruct BF16 4,096 A40/A6000 48GB 통합 worker 같은 GPU의 1.3B vLLM 프로세스

입력 길이 계약

모델이 32K를 지원해도 서버는 그만큼 KV cache를 예약하지 않는다.

경로 입력 상한 출력 상한 초과 시
대화 응답 6,144 tokens 384 tokens 불필요 메타데이터를 먼저 제거하고, 그래도 초과하면 413 반환·기록
프로필 요약 2,048 256 항목을 신뢰도순으로 제한
추천 쿼리·이유 2,048 512 상품 사실·근거 개수 제한
델타 추출 3,072 512 STATE 항목 상한 12개 적용

대화 원문 20개 대화 사이클(40개 발화)을 유지한다는 계약과 8K 상한은 양립할 수 있지만, 실제 토큰 수를 매 사이클 계산해 6,144 이내임을 보장해야 한다. 이 게이트 없이 48GB 통합 worker의 동시성을 확정할 수 없다.

48GB 통합 worker의 운영 범위

48GB에서 가능한 범위는 두 모델의 공동 적재, 메모리상 동시성, 응답 우선 스케줄링, 지연시간 SLO, 비용으로 나눠 판단한다.

구분 1차 운영 범위 해석
모델 적재 8B BF16 + 1.3B BF16 양자화 없이 두 모델을 서로 다른 vLLM 프로세스로 실행
일반 대화 길이 입력 2K4K, 출력 160384 토큰 동시 처리와 응답 지연을 함께 확보할 기본 구간
최대 대화 길이 입력 6,144 + 출력 384, 서버 상한 8,192 긴 요청은 동시성을 낮춰야 하며 8K 부하 테스트 통과가 필요
worker당 목표 동시 생성 8B 8개부터 시작 16개는 설정 상한일 뿐 SLO 통과 전 보장값이 아님
서비스 동시 접속 50명 AWS 연결·조회는 처리 가능하나 실제 동시 생성은 worker당 8개를 기준으로 admission 제어
추출 같은 GPU의 1.3B, 동시 8~16개부터 시작 SSE 응답 완료 후 SQS에서 세션별 순서를 보장해 비동기 처리
스케줄링 8B 대화 > 1.3B 추출 > 요약·재작성·이유 사용자 대기 경로의 TTFT를 먼저 보호

안전 compute 예산 $216을 RunPod A40/A6000 Flex의 시간당 $1.224에 적용하면 통합 worker는 45일 동안 최대 약 176.5 worker-hour, 하루 평균 약 235.3분의 청구시간을 사용할 수 있다. 이는 하루에 모델을 그 시간만 연속으로 켜 두거나 운영자가 직접 켜고 끄라는 뜻이 아니다. endpoint는 min workers=0으로 배포해 요청이 없으면 자동 scale-to-zero하고, 요청이 들어오면 8B와 1.3B를 함께 로드한 worker가 기동한다. 모델 cache와 FlashBoot로 다운로드를 줄여도 scale-to-zero 후에는 디스크의 가중치를 GPU VRAM으로 옮기고 CUDA·vLLM을 초기화하는 시간이 남는다. 예산에 잡히는 것은 두 모델 로딩·요청 실행·idle timeout을 합친 통합 worker 누적 청구시간이다. 반대로 낮은 RPS가 하루 종일 이어져 worker가 scale down하지 못하면 계산 성능이 남아도 예산을 초과하며, 별도 컨테이너 디스크 비용은 scale-to-zero 중에도 남을 수 있다.


3. 사업자 비교와 비용

가격은 2026-09-14 공개 가격이며 세금·환율·CPU·메모리·스토리지는 변할 수 있다. 계획 환율은 실제 환율 주장이 아니라 예산 방어를 위한 1 USD = 1,500원으로 고정한다.

공개 GPU 단가

사업자·GPU VRAM 공개 초당 단가 시간당 환산 360,000원 전액으로 가능한 시간
Modal L4 24GB $0.000222 $0.7992 300.3h
Modal L40S 48GB $0.000542 $1.9512 123.0h
Modal A100 80GB 80GB $0.000694 $2.4984 96.1h
RunPod A4000/A4500/RTX 4000 Flex 16GB $0.000160 $0.5760 416.7h
RunPod L4/A5000/3090/MIG 24GB Flex 24GB $0.000190 $0.6840 350.9h
RunPod 4090 PRO Flex 24GB $0.000310 $1.1160 215.1h
RunPod A40/A6000 Flex 48GB $0.000340 $1.2240 196.1h
RunPod L40S Flex 48GB $0.000530 $1.9080 125.8h
RunPod A100 80GB Flex 80GB $0.000760 $2.7360 87.7h

360,000원은 계획 환율로 $240이다. 실제 운영 계산은 10%를 예비비로 남긴 $216만 사용한다.

이 문서의 비용·부하 projection은 45일 운영·검증 기간으로 계산한다. endpoint는 45일 동안 배포 상태를 유지하되 min workers=0이므로 무요청 시간에는 GPU worker가 자동으로 0대가 된다. 아래 시간은 가동 예약 시간이 아니라, 45일 동안 허용되는 전체 청구초를 하루 평균으로 나눈 예산 경계다.

45일 안전 예산 배분

항목 45일 총액 일평균 경계 목적
8B + 1.3B 통합 worker $216 / 324,000원 7,200원 A40/A6000 48GB Flex 약 176.5 worker-hour
환율·컨테이너 디스크·실수 예비비 $24 / 36,000원 800원 총액 초과 방지
합계 $240 / 360,000원 8,000원

RunPod는 시작 시간, 실행 시간, idle timeout 동안 모두 과금한다. 컨테이너 디스크는 별도 과금이므로 위 worker-hour는 이론상 최대치다. 실제 모델 로딩·콜드스타트·idle 시간이 포함된 청구 시간을 측정해야 한다.

Modal Starter의 월 무료 compute가 계정과 45일 기간에 실제 적용된다면 추가 실험 여력이 생긴다. 그러나 크레딧 정책 변경·적용 제외 가능성이 있으므로 기본 예산표에는 넣지 않는다.

왜 RunPod 48GB 통합안을 비용 우선안으로 고르는가

8B와 1.3B는 한 대화 사이클에서 8B 응답 → 1.3B 추출로 연속 호출된다. 서로 다른 serverless GPU로 나누면 모델별 scale-to-zero는 가능하지만, 짧은 1.3B 추출을 위해 두 번째 worker를 따로 깨우고 인증·라우팅·모니터링을 이중으로 운영해야 한다. 반면 RunPod A40/A6000 48GB Flex는 시간당 $1.224로, 24GB 응답 worker $0.684와 16GB 추출 worker $0.576이 동시 가동할 때의 $1.260보다 낮다. 호출이 연속된다는 서비스 패턴을 이용해 이미 8B를 위해 기동한 worker에서 1.3B 추출을 이어서 처리한다.

통합안이 자동으로 더 싸다는 뜻은 아니다. 두 모델의 청구시간이 거의 겹치지 않는다면 분리안이 더 저렴할 수 있다. 이 설계는 대화 응답 직후 추출을 실행하고, 다음 턴이 오기 전에 State를 갱신하는 호출 패턴을 전제로 한다. 따라서 최종 판단은 모델 다운로드가 아닌 VRAM 적재를 포함한 cold start, 실제 통합 worker 청구초, 추출 동시 실행 시 8B TTFT로 검증한다.

기준 RunPod Modal
48GB 공개 단가 A40/A6000 Flex $1.224/h L40S $1.9512/h
scale-to-zero 지원 지원
실시간 SSE 통합 load-balancing endpoint web endpoint/server
비동기 추출 AWS SQS worker가 같은 endpoint의 /extract 호출 Function spawn/Queue 또는 AWS SQS
오토스케일 통합 endpoint request count target/max inputs와 container pool
비용 강제 차단 선불 크레딧·worker max·애플리케이션 quota 조합 Workspace spend limit이 추가로 제공됨
플랫폼 관측 요청·실행·지연·콜드스타트·worker 지표 실시간 지표·로그·OTel 전달
선택 비용 우선 1차안 운영 편의·크레딧 포함 비교안

4. 배포 아키텍처

현재 예산에서의 하이브리드 구조

flowchart TB
    U["Web / Mobile"] --> EDGE["AWS CloudFront + WAF"]
    EDGE --> ALB["ALB / API Gateway"]

    subgraph AWS["AWS · 서비스 및 데이터 계층"]
        ALB --> BE["Spring Backend<br/>ECS Fargate"]
        BE --> AIGW["AI Gateway<br/>FastAPI · 인증 · quota · circuit breaker"]
        BE --> RDS[("RDS PostgreSQL + pgvector<br/>사용자 · 프로필 · 카탈로그 · 추천")]
        AIGW --> REDIS[("ElastiCache Serverless for Valkey<br/>대화 세션 · idempotency")]
        AIGW --> SQS["SQS<br/>비동기 추출·리스트 잡"]
        SQS --> WORKER["AI Worker<br/>ECS Fargate"]
        AIGW --> EGRESS["NAT Gateway / egress proxy<br/>외부 GPU HTTPS만 허용"]
        WORKER --> EGRESS
        CW["CloudWatch<br/>AWS logs · metrics"]
    end

    subgraph RP["RunPod Serverless · 48GB 통합 GPU"]
        LB["Load-balancing Endpoint<br/>request-count autoscaler · worker 0..2"]
        subgraph G1["A40/A6000 48GB worker #1"]
            ROUTER1["FastAPI / Nginx Router<br/>응답 우선 admission"]
            M8A["vLLM 8B BF16<br/>/response · SSE"]
            M13A["vLLM 1.3B BF16<br/>/extract · JSON"]
            ROUTER1 --> M8A
            ROUTER1 --> M13A
        end
        G2["A40/A6000 48GB worker #2<br/>확장 replica · 두 모델 동일 적재"]
        LB --> ROUTER1
        LB --> G2
    end

    EGRESS -->|"HTTPS + service token<br/>/response · SSE"| LB
    EGRESS -->|"HTTPS + idempotency key<br/>/extract · 델타 JSON"| LB

    AIGW -."OTel · 비용 이벤트".-> CW
    WORKER -."OTel".-> CW
    RP -."provider metrics · logs".-> CW

    style M8A fill:#f0f7ff
    style M13A fill:#eefbf3
    style G2 fill:#f7f7f7
Loading

경계 원칙

  • 프론트엔드는 RunPod/Modal URL과 API key를 알지 못한다.
  • GPU 워커는 RDS와 ElastiCache에 직접 연결하지 않는다. 두 서비스는 public access를 끄고 AWS VPC 내부에 둔다.
  • AWS AI Gateway가 Redis에서 상태를 읽어 필요한 최소 프롬프트만 GPU endpoint로 보낸다.
  • private subnet의 AI Gateway/Worker는 NAT Gateway 또는 허용 목적지가 제한된 egress proxy를 통해 GPU endpoint에 접속한다. NAT 비용도 GPU 예산 밖이다.
  • 모델 응답이 돌아오면 AWS가 검증·병합·저장한다. GPU 컨테이너는 무상태다.
  • 대화 원문, 사용자 ID, 이메일을 GPU 로그에 남기지 않는다. request_id, 모델 버전, 토큰 수, 시간만 기록한다.

이 구조는 AWS와 외부 GPU 사업자 사이에 private link가 없다는 문제를 데이터 경계로 해결한다. DB를 인터넷에 노출해 연결하는 방식은 채택하지 않는다.

왜 두 모델을 한 48GB worker에 통합하는가

구분 8B 응답 모델 1.3B 추출 모델
처리 시점 사용자가 기다리는 SSE 구간 응답 스트림 종료 후 실행
프로세스 vLLM #1, /response vLLM #2, /extract
우선순위 최우선, TTFT 보호 SQS에서 대기·배치, 세션 순서 보장
재시도 스트림 시작 전 연결 실패만 1회 sessionId:turnNo:modelRevision 멱등 키로 최대 2회

한 사용자의 대화 사이클은 8B 응답 → 1.3B 추출이므로 두 모델을 동시에 생성할 필요가 없다. 여러 사용자의 요청이 겹칠 때만 두 vLLM 프로세스가 같은 GPU 연산을 경쟁한다. AWS AI Gateway는 8B 대화 대기열이 있을 때 새 1.3B 배치 투입을 제한하고, 요약·재작성·이유 생성은 그보다 낮은 우선순위로 둔다.

두 모델의 BF16 체크포인트 합계는 약 18.7GB다. 24GB는 두 vLLM의 CUDA runtime·graph·KV cache 여유가 없지만, 48GB는 8B에 약 6570%, 1.3B에 약 1520%를 배정하고 10~15%를 프로세스 외 예비 메모리로 남길 수 있다. 한 프로세스가 재시작해도 다른 프로세스와 AWS의 세션·잡 상태는 남도록 supervisor와 개별 health check를 둔다. GPU 전체 재시작은 두 모델에 영향을 주지만, 현재 규모에서는 상태가 AWS에 남고 멱등 재시도가 가능하므로 통합의 단순성과 비용 이득을 우선한다.

대화 상태부터 임베딩 쿼리까지의 확정 흐름

검색 문장은 추출 모델의 한 번의 출력으로 곧바로 만드는 것이 아니다. 1.3B가 매 사용자 입력에서 반환한 델타 JSON을 서버가 검증·병합해 Redis의 누적 State를 갱신하고, 대화 종료 후 확정된 TasteProfile을 8B 검색문 재작성 모델의 입력으로 사용한다.

flowchart LR
    U["사용자 입력<br/>사이클마다"] --> R8["8B 응답 생성<br/>SSE · 최우선"]
    R8 --> Q["SSE 종료 후 추출 잡 등록<br/>SQS · sessionId 순서 보장"]
    Q --> X13["1.3B 델타 추출<br/>STATE + 직전 AI + 현재 사용자"]
    X13 --> MERGE["서버 검증·병합<br/>items ≤ 12 · axes ≤ 3"]
    MERGE --> REDIS[("Redis session<br/>원문 이력 + 누적 State")]
    REDIS -->|"20번째 사용자 입력까지 반복"| U
    REDIS --> CLOSE["종료 프로필 확정<br/>서버 규칙"]
    CLOSE --> SUM["8B 공개용 취향 요약"]
    SUM --> CONFIRM["사용자 확인·승인"]
    CLOSE --> CONFIRM
    CONFIRM --> Q8["8B 검색 문장 재작성<br/>maxQueries = 6"]
    Q8 --> RESTORE["서버가 confidence·mode·hard filter 복원"]
    RESTORE --> EMB["Upstage Embed 2<br/>문장 배치 1회"]
    EMB --> PG["PostgreSQL pgvector<br/>벡터 검색 + 하드필터"]
Loading

Redis에는 두 종류가 함께 들어간다.

Redis 세션 값 역할 종료 후 처리
사용자·AI 원문 이력 다음 응답의 문맥, 사용자 정정 시 대화 재개 사용자가 프로필을 승인해 리스트 잡을 등록하면 비동기 삭제; 미승인 세션은 TTL 삭제
누적 구조화 State items, axes, turn, 목표 통계, 직전 응답 등 추출기의 작업 메모리 TasteProfile로 투영해 AWS DB에 영속 저장한 뒤 세션과 함께 삭제

따라서 추출 모델 자체가 사용자 상태를 기억하는 것은 아니다. 모델은 무상태이며, Redis의 현재 State를 읽고 이번 발화의 변경분만 제안한다. 필드 추가·정정·삭제, 항목 상한과 방출 우선순위는 서버 로직이 결정한다. 장기 취향 프로필은 Redis가 아니라 RDS가 소유한다.

추출은 사용자에게 응답을 보여주는 경로에서는 비동기지만, 세션 상태에서는 턴 순서를 지킨다. SQS FIFO의 MessageGroupId=sessionId와 sessionId:turnNo:modelRevision 멱등 키를 사용한다. 다음 사용자 입력이 이전 턴 추출보다 먼저 오면 AI Gateway가 짧게 대기한다. 제한 시간 안에 완료되지 않으면 직전 State로 진행하며 previousTurnExtractionPending=true를 남긴다. 이 제어가 없으면 이미 들은 취향을 다시 묻는 오류가 생긴다.

20번째 사용자 입력에서도 8B 응답 → 1.3B 추출 → 서버 병합까지 끝낸다. 그 다음 UI 입력을 잠그고 분석이 완료됐어요를 표시하며, 현재 State로 프로필·요약을 만든다. 필드가 덜 채워졌어도 종료를 거절하거나 21번째 질문을 만들지 않는다. 종료 메타데이터에는 completionReason=MAX_CYCLES, profileCompleteness=partial|sufficient, missingSignals를 남겨 “완료”와 “모든 정보가 충분함”을 구분한다. 마지막 추출이 실패하면 직전 State로 확정하고 lastTurnExtractionFailed=true를 기록한다.

8B 검색문 재작성은 사용자가 승인한 최종 TasteProfile의 query 역할 값만 받아 상품 검색에 적합한 문장으로 바꾼다. 원문 대화는 넘기지 않으며, 모델은 queryText, viewType, tasteNodeIds 후보만 제안한다. confidence, mode, 알레르기·기피·보유 같은 하드필터는 서버가 원본 프로필에서 다시 채우므로 재작성 모델이 안전 조건을 완화할 수 없다. 이 구조에서 “추출 모델 결과값에 따라 임베딩 쿼리를 작성한다”는 설명은 맞지만, 정확히는 “추출 델타가 누적·검증된 최종 프로필을 8B가 검색문으로 재작성하고, 그 문장을 임베딩 API가 벡터화한다”가 맞다.


5. 인프라 기술 및 서비스 명세

AWS 서비스 계층

영역 기술·서비스 설정
외부 진입 CloudFront + WAF + ALB 또는 API Gateway TLS, rate limit, body size 제한
애플리케이션 Java/Spring Backend on ECS Fargate 최소 2 task는 별도 서비스 예산에서 운영
AI Gateway/Worker Python 3.12, FastAPI, OpenAI 호환 client GPU endpoint 인증·timeout·circuit breaker
외부 egress NAT Gateway 또는 egress proxy RunPod/Modal HTTPS 목적지만 허용
비동기 잡 SQS FIFO + DLQ 추출은 MessageGroupId=sessionId로 턴 순서 보장, visibility timeout은 최대 추론시간의 2배, DLQ 3회
관계형·벡터 DB RDS PostgreSQL 17 + pgvector private subnet, 암호화, 자동 backup
세션·멱등성 ElastiCache Serverless for Valkey TLS, 유휴 TTL 30분, 최대 수명 2시간
secret AWS Secrets Manager RunPod/Modal token, 90일 rotation
로그·지표 CloudWatch Logs/Metrics + Grafana Cloud Free 팀이 직접 대시보드 구성. OTel/Prometheus remote-write와 CloudWatch data source 사용
이미지 저장 ECR 또는 Docker Hub private repository tag와 digest 동시 고정

RDS PostgreSQL은 pgvector를 지원한다. ElastiCache Serverless for Valkey는 최소 과금 저장량이 Redis OSS보다 작고 AWS 공개 가격도 월 $6부터라 개발·초기 운영에 적합하다. 단, 이 비용과 Fargate·ALB·NAT·데이터 전송 비용은 GPU 36만 원 예산 밖의 클라우드 비용이다.

AWS 신규 계정이면 RDS Free Tier 또는 크레딧을 쓸 수 있지만, 무료 여부를 전제로 인프라 용량을 확정하지 않는다. 엄격한 36만 원이 AWS까지 포함하는 총예산이라면 RDS·ElastiCache·ALB를 함께 운영하는 안은 성립하지 않는다.

RunPod 48GB 통합 endpoint 초기 설정

항목 값
Endpoint type Load balancing
GPU A40/A6000 48GB Flex, 1 GPU/worker
Workers min 0, max 2
Autoscaler REQUEST_COUNT, 초기 scalerValue=8
응답 동시성 제한 8B worker당 8로 시작, 부하 테스트 통과 시 최대 16
추출 동시성 제한 1.3B 8~16으로 시작, 8B 대기 요청이 있으면 새 배치 투입 제한
Idle timeout 30초부터 시작, 콜드스타트/idle 비용 실측 후 15~60초 비교
FlashBoot / model cache 두 모델 모두 활성화, Hugging Face revision SHA 고정
Health /ping, /health, /health/response, /health/extract
API /response/v1/chat/completions SSE, /extract/v1/chat/completions JSON
Timeout gateway connect 10초, response first-token 20초/total 60초, extract job 30초

AWS 리전은 ap-northeast-2(서울)를 기본으로 하고, RunPod는 A40/A6000 48GB가 제공되는 아시아권 리전을 우선한다. 아시아권 재고가 없으면 미국 서부를 후보로 두되, 네트워크 RTT가 TTFT SLO에 포함된 상태로 다시 측정한다. 같은 48GB 그룹이어도 A40과 A6000의 처리량은 같지 않으므로 실제 배정된 gpu_type을 모든 부하 테스트와 비용 지표에 기록한다.

scalerValue=8은 확정 성능값이 아니라 첫 부하 테스트 시작값이다. 한 worker에서 8B 동시 8요청과 1.3B 백그라운드 추출을 함께 재생했을 때 TTFT p95가 목표를 넘으면 8B 동시성을 4로 낮추거나 추출 batch 투입량을 줄인다. 여유가 있을 때만 8B를 12~16으로 올린다. workersMax=2일 때 순간 요청 50개가 들어오면 SLO를 만족하는 범위만 실행한다. 초과 요청은 AWS AI Gateway의 크기가 제한된 대기열에서 짧게 기다리게 하거나, 대기 한도를 넘으면 429로 거절해 무한 적체를 막는다.

통합 worker 내부 라우팅과 우선순위

  • 외부 load-balancing endpoint가 연 포트는 하나다. FastAPI 또는 Nginx가 경로별로 내부 vLLM 포트를 나눈다.
  • /response는 스트리밍을 유지하고, /extract는 response_format=json_schema strict JSON을 반환한다.
  • AWS AI Gateway는 실시간 대화 요청을 최우선으로 admission한다. 응답 대기열이 있을 때는 새 추출 batch와 추천 후처리 투입량을 줄인다.
  • 추출은 SSE 종료 후 SQS FIFO에 넣고 MessageGroupId=sessionId로 순서를 유지한다. 다음 턴이 오기 전에 처리하는 것을 정상 경로로 둔다.
  • supervisor는 8B·1.3B 프로세스를 개별로 감시하고, 프로세스 하나가 종료되면 컨테이너 전체대신 해당 프로세스를 먼저 재시작한다.

vLLM 컨테이너

  • 기반 이미지: vllm/vllm-openai:v0.29.0을 1차 호환성 후보로 검증한다.
  • 운영 배포 시 tag만 쓰지 않고 통과한 이미지의 digest를 고정한다.
  • 1.3B 모델 카드 요구사항인 transformers>=4.57.0과 --trust-remote-code를 만족시킨다.
  • 응답 모델 공식 BF16 체크포인트를 그대로 사용해 양자화 loader 의존성을 제거한다.
  • 모델 파일도 Hugging Face revision SHA로 고정한다.

통합 worker의 supervisor가 다음 두 프로세스를 같은 CUDA_VISIBLE_DEVICES=0에서 시작한다. 응답 모델 초기 설정은 다음과 같다.

vllm serve kakaocorp/kanana-1.5-8b-instruct-2505 \
  --port 8000 \
  --dtype bfloat16 \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.68 \
  --max-num-seqs 16 \
  --max-num-batched-tokens 8192 \
  --enable-prefix-caching \
  --enable-chunked-prefill \
  --served-model-name kanana-response

추출 모델 시작 설정:

vllm serve kakaocorp/kanana-2-1.3b-instruct \
  --port 8001 \
  --dtype bfloat16 \
  --max-model-len 4096 \
  --gpu-memory-utilization 0.18 \
  --max-num-seqs 16 \
  --max-num-batched-tokens 4096 \
  --enable-prefix-caching \
  --enable-chunked-prefill \
  --trust-remote-code \
  --served-model-name kanana-extractor

위 숫자는 배포 후 검증할 초기값이지 성능 결과가 아니다. 두 gpu_memory_utilization의 합은 0.86으로 제한해 CUDA context·router·예측하지 못한 피크를 위한 여유를 남긴다. max_num_seqs, max_num_batched_tokens, gpu_memory_utilization은 혼합 부하의 OOM·KV cache 사용률·8B TTFT를 보며 한 번에 하나씩 조정한다.

KV cache와 prefill/decode 전략

  • vLLM의 PagedAttention KV cache와 continuous batching은 초기 배포부터 사용한다.
  • --enable-prefix-caching은 공통 system prompt와 같은 세션의 반복 prefix가 있을 때 prefill 계산을 줄인다. 생성(decode) 자체를 빠르게 하지는 않으며, 다른 replica의 cache는 공유되지 않는다.
  • --enable-chunked-prefill은 긴 입력의 prefill을 작은 작업으로 나눠 짧은 요청의 decode가 오래 막히는 현상을 줄인다.
  • prefill worker와 decode worker를 물리적으로 나누는 disaggregated prefill/decode는 현재 단일 48GB 통합 worker 예산에서는 사용하지 않는다. 지속적으로 여러 GPU가 켜지고 긴 입력이 병목이라는 실측이 나온 뒤, 고속 interconnect와 별도 worker pool 비용을 포함해 검토한다.

Modal 대체 설정

역할 GPU Container Input concurrency Autoscale
8B + 1.3B 통합 L40S 48GB min 0, max 2 응답 target 8, 통합 max 16부터 측정 scaledown_window=30 시작

Modal은 @modal.concurrent(target_inputs=..., max_inputs=...)로 통합 container의 입력을 제어하고, 컨테이너 안에서는 RunPod와 동일하게 두 vLLM 프로세스와 응답 우선 router를 사용한다. min_containers, max_containers, buffer_containers, scaledown_window로 pool을 제어한다. 모델 가중치는 Modal Volume에 저장하고 container enter 단계에서 두 vLLM을 시작한다. 현재 예산에서는 min_containers=0, buffer_containers=0이 강제된다.


6. 로드밸런싱·오토스케일링·장애 처리

오토스케일 정책

상태 48GB 통합 endpoint 스케줄링·비용 정책
평상시 0 worker 요청 없으면 scale-to-zero
요청 유입 request count 기준 1 worker 기동 두 모델을 cache에서 VRAM으로 적재한 후 ready
단기 burst 8B 동시 8개를 시작점으로 continuous batching 8B 응답 우선, 1.3B는 SQS에서 흡수·배치
상한 2 workers replica마다 두 모델을 같이 적재
유휴 30초 후 scale down 시작 응답 후 추출이 이어질 수 있는 warm window 확보
예산 80% max worker를 1로 고정 추천 재생성 등 저우선순위 잡 지연
예산 95% self-host 신규 대화 admission 제한 필수 델타만 처리
예산 100% GPU 호출 차단 또는 승인된 API fallback 신규 비동기 잡 등록 중단

workersMax는 확장성 값인 동시에 비용 차단기다. 초기에는 48GB 통합 worker 2대를 넘기지 않는다. 이 때문에 급격한 트래픽에서 무한 확장하는 대신 429/503 또는 queue 대기가 생길 수 있다. 36만 원 강제 조건에서는 비용 상한이 가용성보다 우선한다.

콜드스타트와 SLO의 충돌

scale-to-zero는 비용을 지키지만 첫 요청의 TTFT를 악화시킨다. 따라서 다음을 동시에 약속하지 않는다.

  • 24시간 min worker=1
  • 모든 요청 TTFT p95 1초
  • 45일 GPU 비용 36만 원

셋을 동시에 만족시키는 것은 현재 단가에서 불가능하다. 대응은 다음 순서다.

  1. 두 모델의 cache/FlashBoot, 작은 컨테이너 이미지, 고정된 모델 revision으로 다운로드 콜드스타트를 줄인다. 단, scale-to-zero 후 VRAM 적재·CUDA 초기화는 남으므로 두 모델 ready 시간을 별도로 측정한다.
  2. 사용자에게는 첫 토큰 전 “연결 중” 상태를 표시하고 cold/warm SLO를 분리한다.
  3. 데모·발표 시간처럼 트래픽 시각이 확정된 경우에만 EventBridge가 10분 전에 warm-up 요청을 보낸다.
  4. 운영 데이터에서 트래픽이 충분히 연속적이면 통합 endpoint의 idle timeout을 15→30→60초로 늘려 cold start 횟수와 idle 비용의 합을 비교한다.

세션 고정과 prefix cache

RunPod/Modal의 관리형 endpoint는 worker 선택을 플랫폼에 맡기므로 sessionId 기반 고정 라우팅을 보장하기 어렵다. 초기 max worker=1 구간에서는 문제가 없지만 replica가 2개가 되면 같은 세션이 다른 worker로 가 prefix cache hit가 떨어질 수 있다.

따라서 확장 시 다음 지표로 결정한다.

  • replica 2개에서 prefix cache hit ratio가 30%p 이상 하락하거나 TTFT p95가 2배가 되면 관리형 LB 한계를 기록한다.
  • 그때 예산을 늘려 RunPod Pod/EKS에 vLLM Router 또는 prefix-aware router를 두고 세션 affinity를 적용한다.
  • 현재 예산에서 자체 router를 위해 상시 CPU/GPU를 추가하지 않는다.

재시도와 중복 방지

  • SSE 응답은 첫 토큰 전 연결 실패만 1회 재시도한다. 스트림이 시작된 뒤 자동 재시도하면 사용자에게 문장이 중복된다.
  • 추출은 sessionId:turnNo:modelRevision을 idempotency key로 사용한다.
  • SQS 잡은 상태 전이를 조건부 갱신해 같은 잡의 결과가 두 번 저장되지 않게 한다.
  • RunPod load-balancing endpoint는 자체 queue/retry가 없으므로 AWS AI Gateway에 timeout, exponential backoff, circuit breaker를 둔다.
  • provider 장애가 5분 이상 지속되면 허용된 상용 API로 전환하거나 신규 AI 대화를 잠시 막는다. API 비용은 이 문서의 36만 원에 포함하지 않는다.

7. 모니터링 설계

수집 대상

계층 지표 목적
사용자 SLO E2E latency p50/p95/p99, TTFT, TPOT, stream 중단률 체감 응답성
트래픽 요청/s, 신규 AI 세션/일, 대화 사이클/세션, 동시 SSE 실제 부하 모델 보정
토큰 prompt/output tokens, 요청별 최대 길이, truncated count 비용·컨텍스트 제어
vLLM scheduler running/waiting requests, preemption, request success batch와 포화 상태
vLLM cache KV cache usage, prefix cache hit/query OOM·세션 분산 영향
GPU compute utilization, memory usage, power, OOM count 사양·batch 조정
serverless active/idle worker, queue/delay time, cold start time/count 오토스케일 검증
품질 JSON schema violation, 추출 empty delta, 금지 위반, fallback 비율 빠르지만 잘못된 모델 방지
장애 HTTP 4xx/5xx, timeout, retry, circuit open, DLQ depth 운영 안정성
비용 통합 worker billed seconds, 모델 경로별 요청 비중, 누적 burn rate, 45일 예상 36만 원 강제 게이트
AWS 데이터 RDS CPU/connection/IOPS/storage, Valkey memory/ECPU/eviction, SQS age/depth GPU 밖 병목 확인

vLLM은 OpenAI 호환 서버의 /metrics에서 Prometheus 지표를 노출한다. 반드시 볼 항목은 다음이다.

  • vllm:num_requests_running, vllm:num_requests_waiting
  • vllm:kv_cache_usage_perc
  • vllm:prefix_cache_queries, vllm:prefix_cache_hits
  • vllm:prompt_tokens_total, vllm:generation_tokens_total
  • vllm:time_to_first_token_seconds
  • vllm:inter_token_latency_seconds
  • vllm:e2e_request_latency_seconds
  • vllm:request_success_total

수집과 시각화

flowchart LR
    APP["Spring / FastAPI<br/>OpenTelemetry SDK"] --> ALLOY["Grafana Alloy / OTel Collector"]
    VLLM["vLLM /metrics"] --> ALLOY
    RP["RunPod worker lifecycle<br/>custom OTel metrics"] --> ALLOY
    ALLOY -->|"Prometheus remote_write"| GCM["Grafana Cloud Metrics"]
    ALLOY -->|"선택: logs / traces"| GLT["Grafana Cloud Loki / Tempo"]
    LOG["AWS 구조화 로그·서비스 지표"] --> CW["CloudWatch"]
    CW -->|"read-only IAM role<br/>CloudWatch data source"| GRAFANA["Grafana Cloud Dashboard"]
    GCM --> GRAFANA
    GLT --> GRAFANA
    GRAFANA --> ALERT["Grafana Alerting<br/>Slack / email"]
    GRAFANA --> EXPORT["일별 요약·테스트 CSV<br/>S3 장기 보관"]
Loading

RunPod UI는 requests, execution time P70/P90/P98, delay time, cold start, worker state를 제공한다. 이는 운영 교차 확인에 사용한다. 통합 Grafana에는 custom worker가 endpoint_id, worker_id, gpu_type, model_role=response|extract, model_revision label과 함께 두 vLLM·lifecycle 지표를 outbound remote-write한다. /metrics를 인터넷에 공개하지 않는다. 애플리케이션의 p95와 provider p98은 기준이 다르므로 provider_p98과 app_p95를 분리한다. Modal을 쓸 경우 공식 OpenTelemetry 연동이 제공하는 GPU 사용률, GPU 메모리, queue time, cold start, running container 지표를 같은 보드로 보낸다.

이번 45일 프로젝트에서는 Grafana Cloud Free를 채택하고 팀이 대시보드와 알람을 직접 구현한다. Amazon Managed Grafana용 별도 workspace를 운영하지 않아 고정 관리비를 줄이고, vLLM/RunPod 지표는 Grafana Cloud에 직접 remote-write한다. RDS·ECS·Valkey 같은 AWS 지표와 로그는 최소 권한 read-only IAM role을 이용한 CloudWatch data source로 조회한다. Free 플랜의 metrics·logs·traces·k6 보존 기간은 14일이므로, 45일 전체 추세를 제출할 수 있도록 매일 비용·호출 수·오류율·TTFT·throughput·최대 VRAM 요약을 S3에 CSV로 저장하고 각 부하 테스트 종료 시 원본 결과도 CSV로 내보낸다. 개인정보가 포함된 원문 프롬프트는 어느 저장소에도 내보내지 않는다.

Grafana 대시보드 구성

Row 패널
1. SLO warm/cold TTFT p50·p95·p99, E2E latency, 성공률, stream 중단률
2. Load 요청 RPS, 동시 요청, 신규 세션, 대화 사이클/세션, 입력·출력 tokens/s
3. vLLM running/waiting, KV cache %, prefix hit %, preemption, schema violation
4. GPU/Scale GPU util·VRAM, worker count, queue delay, cold start count/time, OOM
5. Cost 오늘 비용, 45일 누적, 통합 worker 청구초, 모델 경로별 요청 비중, 45일 projection, 사용자/세션당 비용
6. AWS RDS connections/CPU, pgvector query p95, Valkey eviction, SQS oldest age, DLQ

45일 최종 보고서는 Grafana의 최근 14일 화면 캡처만 사용하지 않는다. S3의 일별 CSV를 합쳐 day, response_model_revision, extract_model_revision, gpu_type, billed_seconds, cost_usd, response_request_count, extract_request_count, ttft_p95_ms, e2e_p95_ms, output_tokens_per_sec, peak_vram_bytes, error_rate 열로 전체 기간 그래프를 재생성한다.

알람 기준

심각도 조건 조치
Warning warm TTFT p95 > 1초가 10분 지속 concurrency·queue·prefix hit 확인
Critical warm TTFT p95 > 2초 또는 5xx > 2%가 5분 지속 circuit breaker, replica 상한 내 scale-out
Warning KV cache > 85% 또는 waiting > 0가 5분 지속 max seqs/컨텍스트 축소 또는 worker 추가 검토
Critical CUDA OOM 1회 해당 revision 배포 중단, 이전 digest rollback
Warning cold start 비율 > 20% idle timeout과 warm window 실험
Critical JSON schema violation > 0.5% 모델·vLLM revision rollback
Warning 45일 compute 예산의 70% 도달 또는 projection 90% 초과 max worker 고정, 비필수 잡 제한
Critical 45일 compute 예산의 90% 도달 admission control/API fallback 준비
Critical 45일 총예산 100% 도달 예상 self-host 신규 요청 차단

로그에는 프롬프트와 생성 원문을 기본 저장하지 않는다. 장애 재현이 필요하면 사용자 동의가 있는 샘플만 별도 암호화 저장하고 짧은 TTL을 적용한다.


8. 간이 부하·비용 시뮬레이션

무엇이 실측이고 무엇이 가정인가

현재 저장소에는 Kanana 두 모델을 대상 GPU에서 실행한 결과가 없다. 따라서 아래 값은 DAU를 요청률과 예산 게이트로 변환한 산술 시뮬레이션이며 GPU 성능 측정값이 아니다. 실제 TTFT, tokens/s, GPU 메모리는 2단계 E1·E7 실험으로 채운다.

입력 가정

  • DAU: 3,750명
  • GPU endpoint 운영·검증 기간: 45일 (min workers=0, 무요청 시 자동 scale-to-zero)
  • AI를 쓰는 사용자는 하루 1개 대화 세션 생성
  • 평균 8개 대화 사이클/세션 = 사용자 8회 + AI 8회인 16개 발화
  • 매 대화 사이클마다 응답 8B 1회 + 추출 1.3B 1회
  • 세션 종료·승인 후 응답 8B가 요약 1회와 검색문 재작성 1회를 추가 수행
  • 추천 이유 생성은 쿼리 단위 최대 6회 안팎이며, 아래 대화 피크 RPS와 별도인 비동기 잡으로 처리
  • 하루 대화 사이클의 10%가 가장 바쁜 15분에 집중
  • 추천·친구·상품 화면 조회는 저장 결과를 읽어 GPU 호출 0회
  • 안전 GPU 예산 $216은 A40/A6000 48GB 통합 worker의 단일 청구시간으로 관리

f를 DAU 중 그날 새 AI 대화를 시작한 비율이라고 두면 다음과 같다.

f AI 세션/일 대화 8B 호출/일 추출 1.3B 호출/일 대화 구간 모델별 피크 RPS 통합 GPU에 허용되는 청구시간/세션
5% 188 1,500 1,500 0.167 75.1초
10% 375 3,000 3,000 0.333 37.6초
20% 750 6,000 6,000 0.667 18.8초
100% 3,750 30,000 30,000 3.333 3.8초

표의 호출 수는 피크가 생기는 대화 구간만 센다. 평균 세션 하나의 전체 GPU 호출은 8B 응답 8회 + 1.3B 추출 8회 + 8B 요약 1회 + 8B 검색문 재작성 1회 + 8B 추천 이유 약 1~6회다. 최대 20개 대화 사이클이면 대화 구간에서만 8B 20회 + 1.3B 20회, 즉 40회가 호출된다. 후처리 호출은 SQS worker에서 batch해 처리하고 실제 청구초에는 포함한다.

허용 청구시간은 순수 generation time이 아니다. 모델 load, cold start, 요청 실행, worker idle timeout을 모두 합쳐 세션 수로 나눈 값이다. 따라서 실제 통과 조건은 다음과 같다.

45일 GPU 사용 예상비용 =
  48GB 통합 worker 청구초 × $0.000340
+ 컨테이너 스토리지
≤ $216

해석

  • 45일 내내 DAU 100%가 대화하는 경우는 기본 운영안에서 No-Go다. 세션당 허용되는 통합 worker 청구시간이 약 3.8초에 불과해 두 모델 로딩·대화 8회·추출 8회·후처리를 담을 수 없다. 더 큰 문제는 대화 요청이 하루 종일 드문드문 흩어지면 idle timeout이 계속 갱신되어 통합 worker의 하루 평균 허용 청구시간 약 3.92시간을 넘길 수 있다는 점이다. 서버리스가 추론한 토큰만 과금한다는 뜻은 아니며, worker 시작·VRAM 로딩·실행·idle 구간도 측정 대상이다.
  • 5% 활성 시나리오는 검증할 가치가 있다. continuous batching으로 worker 가동시간을 여러 세션이 공유하면 가능성이 있지만, 콜드스타트가 세션마다 발생하면 실패한다.
  • 45일 동안 평균 약 500세션/일이면 f≈13.3%다. 이때 두 모델 로딩·응답·추출·idle을 모두 합친 통합 worker 청구시간이 세션당 약 28.2초 안에 들어야 하므로 실측 전에는 가능하다고 확정할 수 없다. 요약·재작성·추천 이유의 비동기 GPU 시간도 이 28.2초 안에 포함해야 한다.
  • 예산을 지키려면 AI 대화가 매 화면에서 반복되지 않고, 프로필·추천을 저장해 재사용한다는 앞 단계 설계가 반드시 유지되어야 한다.

용량 시뮬레이션

통합 worker 한 대가 1.3B 백그라운드 부하를 포함하고도 8B SLO 안에서 처리한 실측 지속 처리량을 R_slo RPS라고 하면 필요한 worker 수는 다음으로 결정한다.

required_workers = ceil(peak_rps / (R_slo × 0.7))

0.7은 장애·burst를 위한 30% 여유다. 예를 들어 실측 R_slo=1.0 RPS라면 5~20% 시나리오는 1대, 100% 시나리오는 5대가 필요하다. 현재 max workers=2는 100% 시나리오를 처리하려는 값이 아니라 비용을 제한하면서 초기 실트래픽을 측정하는 값이다.

동시 접속자 50명 해석

동시 접속자 50명은 동시 생성 요청 50개와 같지 않다. WebSocket/SSE 연결이나 화면 조회는 AWS 계층이 처리하고, 사용자가 실제 메시지를 전송한 순간에만 GPU 요청이 생긴다. 한 48GB 통합 worker의 8B 초기 실행 상한은 16시퀀스로 두되, 1.3B 백그라운드 부하와 함께 SLO를 만족하는 목표 동시성은 8로 시작한다.

상황 예상 처리
50명 접속, 동시 생성 8개 이하 worker 1대로 검증 가능한 기본 구간
동시 생성 9~16개 continuous batching으로 처리하되 TTFT p95 실측 필요
동시 생성 17~32개 두 번째 worker 기동; 기동 전에는 제한된 대기열 또는 429 적용
동시 생성 33~50개 workersMax=2에서는 즉시 처리를 보장하지 않고 제한된 대기열·429로 admission 제어

따라서 문서가 보장하는 범위는 동시 접속 50명과 순간 생성 8~16개/worker다. 50개 요청을 한 GPU에서 동시에 낮은 지연으로 처리한다고 주장하지 않는다. 50개 burst를 오류 없이 흡수하는 것과 50개를 즉시 생성하는 것은 별도 합격 기준으로 측정한다.

실행할 부하 테스트

단계 입력/출력 토큰 동시성 또는 RPS 반복 합격 기준
Cold 실제 1·8·20번째 대화 사이클 프롬프트 worker 0에서 두 모델 동시 ready 10 cache hit/miss별 cold start p95 기록, 실패 0
Warm latency 8B 2K→160, 6K→384 concurrency 1/2/4/8/16 조건당 100 warm TTFT p95 ≤ 1초, OOM 0
Throughput 실제 대화 분포 0.17/0.33/0.67 RPS 각 30분 error < 1%, queue p95 ≤ 2초
Burst 1분간 기준 RPS 4배 max worker 2 5회 무한 queue 없음, 비용 상한 유지
50-user burst 2K→160 요청 50개 동시 제출 max worker 2 5회 유실 0, 5xx < 1%, queue drain 시간 기록
Extract 1K→300 JSON concurrency 1/4/8/16/32 조건당 200 schema 위반 ≤ 0.5%, p95 ≤ 3초
Mixed interference 8B concurrency 4/8 + 1.3B batch 8/16 응답 우선 router 활성 조건당 100 8B warm TTFT p95 ≤ 1초, 추출 p95 ≤ 3초, OOM 0
Turn ordering 같은 sessionId의 빠른 3개 턴 세션 100개 3회 State 적용 순서 위반 0, 중복 저장 0
Soak 5% 시나리오 축약 재생 2시간 1 메모리 증가·worker 누수 없음

vllm bench serve 또는 Locust/k6에서 실제 토큰 길이 분포를 재생한다. 테스트마다 다음 결과 표를 자동 저장한다.

GPU·모델 Context Concurrency TTFT p50/p95 E2E p95 Output tok/s Peak VRAM Queue p95 청구초 비용/1k턴
A40/A6000 48GB · 8B + 1.3B · 8B only 2K/8K 1/4/8/16 미측정 미측정 미측정 미측정 미측정 미측정 미측정
A40/A6000 48GB · 8B + 1.3B · extract only 4K 1/8/16/32 미측정 미측정 미측정 미측정 미측정 미측정 미측정
A40/A6000 48GB · 8B + 1.3B · mixed 2K/4K/8K 8B 4/8 + 1.3B 8/16 미측정 미측정 미측정 미측정 미측정 미측정 미측정
L40S 48GB · 8B + 1.3B · mixed 비교군 2K/4K/8K 8B 4/8 + 1.3B 8/16 미측정 미측정 미측정 미측정 미측정 미측정 미측정

측정하지 않은 숫자를 DAU 표에서 역산해 성능 결과처럼 쓰지 않는다.


9. 성장과 안정적 운영에 기여하는 방식

트래픽 증가에 대응하는 지점

증가 원인 현재 대응 다음 단계
화면 조회 100배 추천을 RDS에 저장해 GPU 호출 없음 read replica·cache·CDN 확장
신규 대화 증가 RunPod/Modal replica auto-scale worker max와 예산 동시 증액
추출 burst SQS가 흡수, 통합 GPU에서 8B 응답 우선 혼합 부하가 TTFT를 지속 위반할 때만 추출을 CPU/저사양 GPU로 분리
긴 프롬프트 증가 token admission과 max context 요약 정책·컨텍스트 계층 재설계
GPU 사업자 장애 circuit breaker와 API fallback 이중 provider canary
모델 회귀 revision/digest 고정, golden set, rollback blue/green·shadow traffic
카탈로그 증가 RDS pgvector와 offline batch 분리 인덱스·파티션·전용 vector DB 검토

이 구조는 8B와 1.3B를 같이 스케일하는 대신, 대화 사이클마다 이어지는 두 호출이 같은 warm worker를 사용한다. 비동기 잡은 AWS queue에서 순서·멱등성을 유지하고 GPU 내부에서는 응답 우선 admission을 적용한다. 모델은 무상태라 통합 replica를 추가할 때 DB migration이 필요 없다.

다만 사용자 100배 증가를 같은 36만 원으로 처리한다는 뜻은 아니다. 구조적으로는 worker 상한을 높여 중단 없이 확장할 수 있지만, 실제 확장은 처리량에 비례한 GPU 예산 승인을 전제로 한다. 현재 hard cap에서는 admission control과 queue로 비용을 지키는 쪽을 선택한다.

성장 시 전환 순서

  1. max workers 2→4와 예산 증액만으로 서버리스 유지
  2. GPU가 하루 8시간 이상 연속 가동되면 Flex와 dedicated Pod/Active worker의 손익분기 비교
  3. replica가 여러 개이고 prefix cache 손실이 커지면 vLLM Router 기반 prefix-aware routing 도입
  4. 상시 여러 GPU가 필요하고 배포 빈도가 높아질 때만 EKS/Kubernetes + KServe/Ray Serve 검토
  5. 한 provider 장애가 매출에 직접 영향을 줄 때 Modal/RunPod 이중화와 1% canary traffic 운영

Kubernetes는 4단계 이후의 문제다. 현재는 관리할 노드가 없고 serverless control plane이 load balancing·autoscaling을 대신하므로, 직접 Kubernetes를 도입하면 가용성보다 운영 복잡도와 고정비가 먼저 늘어난다.


10. 배포·운영 체크리스트

배포 전

  • 응답 8B의 Apache 2.0과 추출 1.3B의 Kanana Open License를 제품·법무가 각각 확인했다.
  • NIJU_RESPONSE_MODEL=kakaocorp/kanana-1.5-8b-instruct-2505, NIJU_EXTRACT_MODEL=kakaocorp/kanana-2-1.3b-instruct를 명시하고 /models 응답으로 확인했다.
  • 검색문 재작성 호출이 model=RESPONSE_MODEL을 명시하는지 확인했다. 현재 로컬 chat/finalize.py의 _rewrite()는 모델 인자를 생략해 json_chat() 기본값인 추출 모델을 사용할 수 있으므로 배포 전에 수정해야 한다.
  • 외부에 모델 API 자체를 재판매하지 않는 서비스 형태인지 확인했다.
  • 로컬에서 확인한 8B 응답 품질을 고정 골드셋 수치로 남겼다.
  • 이미지 digest, 모델 revision SHA, prompt version을 기록했다.
  • 통합 container에서 /response와 /extract 개별 health check가 모두 통과한다.
  • 두 모델의 파일 cache와 revision SHA 고정이 적용되었고, cache hit/miss별 VRAM ready 시간을 측정했다.
  • 8K 응답과 4K 추출 혼합 부하에서 OOM이 없다.
  • SQS FIFO의 세션별 턴 순서와 멱등 중복 방지를 검증했다.
  • DB와 Redis public access가 꺼져 있다.
  • GPU endpoint key가 프론트 번들·로그에 없다.
  • RunPod 선불 잔액·자동결제 정책 또는 Modal spend limit을 확인했다.

배포 중

  • 새 revision에 1% canary 또는 내부 계정만 보낸다.
  • cold/warm에서 두 모델 health check를 각각 통과한다.
  • schema violation, 8B TTFT, 추출 혼합 부하, GPU memory, 비용 projection을 30분 관찰한다.
  • 이상이 없을 때 10%→50%→100%로 전환한다.

매일/매주

  • 일별 GPU 비용과 45일 projection을 확인한다.
  • cold start 비율과 idle billed seconds를 비교한다.
  • prefix cache hit와 replica 수의 상관을 확인한다.
  • DLQ, timeout, schema violation을 확인한다.
  • 주 1회 골드셋과 대표 프롬프트 성능 회귀를 실행한다.

11. 최종 Go/No-Go 기준

다음 다섯 조건을 모두 만족할 때만 v2 실서비스 기본 경로로 self-hosted Kanana를 사용한다.

기준 Go
품질 필수재현율 ≥ BF16/API 기준선 - 5%p, 금지 위반 증가 0
안정성 최대 컨텍스트·목표 동시성 CUDA OOM 0
지연 warm TTFT p95 ≤ 1초, 추출 p95 ≤ 3초
비용 실제 활성률을 넣은 45일 projection ≤ $216
운영 budget alert·hard stop·rollback·DLQ 검증 완료

하나라도 실패하면 순서대로 ① context/concurrency·추출 batch 조정, ② A40/A6000 대신 L40S 48GB 비교, ③ 추출 1.3B를 CPU 또는 저사양 GPU로 물리 분리, ④ GPU를 호출하는 비필수 기능 축소, ⑤ 상용 API fallback을 적용한다. DAU 숫자만으로 GPU를 먼저 상시 켜지 않는다.


12. 근거 문서

모델·vLLM

Modal

RunPod

AWS

Grafana

⚠️ **GitHub.com Fallback** ⚠️