[AI] 01. ADR 006‐010 ‐ 인프라 및 통합 - 100-hours-a-week/9-team-Devths-WIKI GitHub Wiki

기술 의사결정 기록 (ADR) - 006~010

Architecture Decision Records - 프로젝트에서 기술 선택의 근거를 기록하는 문서


📚 목차


ADR-006: 인프라 진화 전략 (EC2 → Docker → Kubernetes)

📋 메타데이터

항목 내용
상태 ✅ 채택됨 (Accepted)
작성일 2026-01-08
결정자 AI/클라우드팀
관련 기능 AI 서버 배포, 인프라

🎯 컨텍스트 (Context)

AI 서버를 운영 환경에 배포해야 합니다. 프로젝트 일정과 팀 역량을 고려하여 점진적으로 인프라를 고도화하는 전략이 필요합니다.

고려 사항:

  • MVP 출시까지 시간이 제한적 (7주차 1차 배포)
  • 팀의 Kubernetes 경험이 제한적
  • 트래픽 증가에 대응할 수 있어야 함
  • 비용 효율적이어야 함

🔍 선택지 분석 (Options)

Option 1: 처음부터 Kubernetes (Big Bang)

┌─────────────────────────────────────────┐
│   처음부터 K8s 클러스터 구축             │
│   - EKS/GKE 설정                        │
│   - Helm 차트 작성                       │
│   - 모니터링/로깅 구성                   │
└─────────────────────────────────────────┘
장점 단점
나중에 마이그레이션 불필요 초기 학습 곡선 높음
처음부터 스케일 가능 MVP 출시 지연 위험
초기 비용 높음

Option 2: 점진적 진화 (EC2 → Docker → K8s) ⭐ 선택

┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│   V1 (MVP)          V1.5              V2                V3                  │
│   ┌───────┐      ┌───────────┐    ┌─────────┐      ┌──────────────┐        │
│   │ EC2   │  →   │ EC2 + ALB │  → │ Docker  │  →   │ Kubernetes   │        │
│   │ 빅뱅  │      │ 모듈화     │    │ + ECS   │      │ + Serverless │        │
│   └───────┘      └───────────┘    └─────────┘      └──────────────┘        │
│   (7주차)         (8주차)          (10주차)          (12주차+)              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
장점 단점
MVP 빠르게 출시 가능 마이그레이션 비용 발생
단계별 학습 가능 일부 재작업 필요
리스크 분산
실제 트래픽 보고 결정

Option 3: 서버리스 Only (Lambda/Cloud Functions)

장점 단점
관리 부담 없음 Cold Start 지연
자동 스케일링 실행 시간 제한 (15분)
사용량 기반 과금 LLM 긴 응답에 부적합

✅ 결정 (Decision)

Option 2: 점진적 진화 전략 채택

┌─────────────────────────────────────────────────────────────────────────────┐
│                        채택된 인프라 로드맵                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   [V1: EC2 빅뱅] - 7주차 MVP                                                 │
│   ├── 단일 EC2 인스턴스 (t3.medium 또는 g4dn.xlarge)                         │
│   ├── 모든 서비스 단일 서버에 배포                                            │
│   ├── PM2 또는 systemd로 프로세스 관리                                        │
│   └── 목표: 빠른 출시, 기능 검증                                              │
│                                                                             │
│   [V1.5: EC2 모듈화] - 8~9주차                                               │
│   ├── Frontend / Backend / AI 서버 분리                                      │
│   ├── ALB (Application Load Balancer) 도입                                  │
│   ├── Auto Scaling Group 설정                                               │
│   └── 목표: 장애 격리, 수평 확장 기반 마련                                     │
│                                                                             │
│   [V2: Docker 컨테이너화] - 10~11주차                                         │
│   ├── Dockerfile 작성, 이미지 빌드                                           │
│   ├── ECR (Elastic Container Registry) 사용                                 │
│   ├── ECS Fargate 또는 Docker Compose                                       │
│   └── 목표: 환경 일관성, 배포 자동화                                          │
│                                                                             │
│   [V3: Kubernetes + 서버리스] - 12주차 이후 (선택)                            │
│   ├── EKS 클러스터 구축                                                      │
│   ├── Helm 차트로 배포 관리                                                  │
│   ├── 일부 기능 Lambda로 오프로드 (예: 이미지 처리)                            │
│   └── 목표: 자동 스케일링, 고가용성                                           │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

📝 근거 (Rationale)

  1. MVP 우선: 7주차 출시 목표를 맞추려면 단순한 구조로 시작해야 함
  2. 실제 데이터 기반 결정: 트래픽 패턴을 보고 K8s 필요성 판단
  3. 팀 역량 성장: 단계별로 학습하며 진행
  4. 비용 효율: 초기에는 단일 EC2로 비용 절감
  5. 롤백 가능: 문제 발생 시 이전 단계로 복귀 용이

ADR-007: Kubernetes vs Kubeflow 도입 시기

📋 메타데이터

항목 내용
상태 ✅ 채택됨 (Accepted)
작성일 2026-01-08
결정자 AI팀
관련 기능 MLOps, 모델 학습/배포

🎯 컨텍스트 (Context)

MLOps 도구 도입을 검토하고 있습니다. Kubernetes와 Kubeflow의 차이점을 이해하고, 언제 각각을 도입할지 결정해야 합니다.

현재 상황:

  • 자체 모델 학습 없음 (Gemini API 사용)
  • 향후 오픈소스 LLM 서빙 예정 (Phase 2)
  • 향후 모델 파인튜닝 가능성 있음 (Phase 3)

🔍 선택지 분석 (Options)

Kubernetes vs Kubeflow 비교

┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│   ┌───────────────────────────────────────────────────────────────────┐    │
│   │                         Kubeflow                                  │    │
│   │   ┌─────────────┐  ┌─────────────┐  ┌─────────────┐              │    │
│   │   │ Pipelines   │  │ Katib      │  │ KServe     │              │    │
│   │   │ (워크플로우) │  │(하이퍼튜닝) │  │(모델서빙)   │              │    │
│   │   └─────────────┘  └─────────────┘  └─────────────┘              │    │
│   └───────────────────────────────────────────────────────────────────┘    │
│                                    ▲                                       │
│                                    │ 위에서 실행                            │
│   ┌───────────────────────────────────────────────────────────────────┐    │
│   │                       Kubernetes                                  │    │
│   │   Pod, Service, Deployment, Ingress, ConfigMap, Secret ...       │    │
│   └───────────────────────────────────────────────────────────────────┘    │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
항목 Kubernetes Kubeflow
정체 컨테이너 오케스트레이션 ML 워크플로우 플랫폼
역할 컨테이너 배포/관리 ML 파이프라인 자동화
필수 조건 단독 실행 가능 Kubernetes 필수
학습 곡선 🔴 높음 🔴🔴 매우 높음
우리 서비스 V3에서 도입 자체 학습 시 도입

도입 시기 분석

시나리오 Kubernetes Kubeflow
API 사용 (Gemini) ❌ 불필요 ❌ 불필요
오픈소스 LLM 서빙 ✅ 도입 ❌ 불필요
1회성 파인튜닝 ✅ 필요 ❌ 불필요 (스크립트로 충분)
정기 재학습 ✅ 필요 🟡 선택적 (Airflow 대안)
하이퍼파라미터 튜닝 ✅ 필요 ✅ 도입 (Katib 활용)
여러 모델 동시 학습 ✅ 필요 ✅ 도입

✅ 결정 (Decision)

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Kubernetes / Kubeflow 도입 로드맵                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   [현재 ~ V2: Kubernetes 미도입]                                             │
│   ├── EC2 + Docker로 충분                                                   │
│   └── Gemini API 사용, 복잡한 오케스트레이션 불필요                            │
│                                                                             │
│   [V3: Kubernetes 도입] ⭐ 트리거 조건                                        │
│   ├── 트래픽 증가로 수동 스케일링 한계                                        │
│   ├── 오픈소스 LLM 서빙 시작                                                 │
│   ├── 여러 AI 모듈 동시 운영                                                 │
│   └── 예상 시기: 12주차 이후                                                 │
│                                                                             │
│   [V4 이후: Kubeflow 도입 검토] ⭐ 트리거 조건                                 │
│   ├── 자체 데이터로 모델 학습 시작                                            │
│   ├── 하이퍼파라미터 튜닝 자동화 필요                                         │
│   ├── 여러 모델 버전 동시 학습/비교                                           │
│   └── 대안: SageMaker, Vertex AI (관리형 서비스)                              │
│                                                                             │
│   [Kubeflow 대신 가능한 대안]                                                 │
│   ├── 정기 배치 학습 → Airflow + 학습 스크립트                                │
│   ├── 실험 추적 → MLflow 또는 Weights & Biases                               │
│   ├── 모델 서빙 → vLLM + Kubernetes (KServe 없이)                            │
│   └── 간단한 파인튜닝 → RunPod + 수동 실행                                    │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

📝 근거 (Rationale)

  1. 현재 불필요: 외부 API 사용 중이라 복잡한 ML 파이프라인 불필요
  2. 오버엔지니어링 방지: Kubeflow는 학습 곡선이 매우 높음
  3. 단계적 도입: 필요해지는 시점에 도입하여 리소스 낭비 방지
  4. 대안 존재: Airflow, MLflow로 대부분의 요구사항 충족 가능
  5. 팀 역량 고려: K8s 먼저 익숙해진 후 Kubeflow 검토

📌 실무 가이드

질문 답변
모델 서빙만 하면? Kubernetes + vLLM
가끔 학습하면? RunPod + 학습 스크립트
매일/매주 학습하면? Airflow + 학습 스크립트
하이퍼파라미터 튜닝? Kubeflow Katib 또는 Optuna
복잡한 ML 파이프라인? Kubeflow Pipelines

ADR-008: OCR + 임베딩 내부 통합 처리

📋 메타데이터

항목 내용
상태 ✅ 채택됨 (Accepted)
작성일 2026-01-08
결정자 AI/백엔드팀
관련 기능 이력서/채용공고 업로드, OCR, VectorDB 임베딩

🎯 컨텍스트 (Context)

이력서/채용공고 파일 업로드 시 OCR 텍스트 추출 → 임베딩 저장 과정이 필요합니다.

기존 설계:

Backend → AI Server(OCR) → Backend → AI Server(Embed) → Backend

문제점:

  • Backend가 OCR 완료를 기다리기 위해 폴링(Polling) 필요
  • 폴링은 주기적으로 상태를 확인 → 실질적으로 동기 처리와 다름없음
  • 네트워크 왕복 2회 발생 → 레이턴시 증가
  • Backend에 OCR 상태 관리 로직 필요 → 복잡도 증가

🔍 선택지 분석 (Options)

Option 1: Backend에서 폴링 후 임베딩 호출 (기존 설계)

┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│   Backend                        AI Server                                  │
│      │                               │                                      │
│      │── POST /ai/ocr/extract ──────▶│                                      │
│      │◀── task_id ───────────────────│                                      │
│      │                               │                                      │
│      │── GET /ai/task/{id} ─────────▶│  ┐                                   │
│      │◀── processing ────────────────│  │ 폴링 반복                          │
│      │── GET /ai/task/{id} ─────────▶│  │ (실질적으로 동기 대기)              │
│      │◀── completed ─────────────────│  ┘                                   │
│      │                               │                                      │
│      │── POST /ai/file/embed ───────▶│                                      │
│      │◀── 완료 ─────────────────────│                                      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
장점 단점
API 역할이 분리됨 폴링 = 실질적 동기 처리
단계별 디버깅 가능 네트워크 왕복 증가
Backend 복잡도 증가
레이턴시 증가

Option 2: AI Server 내부에서 OCR + 임베딩 통합 처리 ⭐ 선택

┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│   Backend                        AI Server                                  │
│      │                               │                                      │
│      │── POST /ai/ocr/extract ──────▶│                                      │
│      │   (type, user_id, doc_id)     │                                      │
│      │                               │                                      │
│      │◀── task_id ───────────────────│                                      │
│      │                               │                                      │
│      │                          ┌────┴────┐                                 │
│      │                          │ 내부 처리 │                                │
│      │                          │ 1. OCR   │                                │
│      │                          │ 2. 청킹  │                                │
│      │                          │ 3. 임베딩 │                                │
│      │                          │ 4. 저장  │                                │
│      │                          └────┬────┘                                 │
│      │                               │                                      │
│      │── GET /ai/task/{id} ─────────▶│                                      │
│      │◀── completed ─────────────────│                                      │
│      │   (text + vector_id)          │                                      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
장점 단점
네트워크 왕복 감소 API 역할이 확장됨
레이턴시 감소 내부 로직 복잡도 증가
Backend 단순화
진정한 비동기 처리 가능

Option 3: 콜백(Callback) 방식

Backend → AI Server(OCR + Embed) → Callback to Backend
장점 단점
진정한 비동기 Backend에 콜백 엔드포인트 필요
폴링 불필요 네트워크 신뢰성 이슈
재시도 로직 복잡

✅ 결정 (Decision)

Option 2: AI Server 내부에서 OCR + 임베딩 통합 처리

┌─────────────────────────────────────────────────────────────────────────────┐
│                        채택된 흐름                                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   [Backend]                                                                 │
│       │                                                                     │
│       │── POST /ai/ocr/extract ──────────────────────────────────────▶      │
│       │   {                                                                 │
│       │     file_url: "s3://...",                                          │
│       │     file_type: "pdf",                                              │
│       │     type: "resume",           // 어떤 컬렉션에 저장할지              │
│       │     user_id: "user_456",                                           │
│       │     document_id: "resume_123"                                      │
│       │   }                                                                 │
│       │                                                                     │
│       │◀── task_id 반환 ─────────────────────────────────────────────       │
│       │                                                                     │
│       │                           [AI Server 내부]                          │
│       │                           ┌─────────────────────────────────────┐   │
│       │                           │ 1. OCR/VLM으로 텍스트 추출           │   │
│       │                           │ 2. 텍스트 청킹 (500 tokens)         │   │
│       │                           │ 3. Gemini Embedding 생성            │   │
│       │                           │ 4. VectorDB에 저장                  │   │
│       │                           │ 5. 결과 저장 (Redis/Memory)         │   │
│       │                           └─────────────────────────────────────┘   │
│       │                                                                     │
│       │── GET /ai/task/{id} (폴링) ─────────────────────────────────▶       │
│       │                                                                     │
│       │◀── completed ──────────────────────────────────────────────         │
│       │   {                                                                 │
│       │     extracted_text: "...",                                         │
│       │     vector_id: "vec_abc123",                                       │
│       │     collection: "resumes"                                          │
│       │   }                                                                 │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

API 역할 변경:

API 변경 전 변경 후
/ai/ocr/extract OCR만 OCR + 임베딩 (내부 처리)
/ai/file/embed 항상 호출 텍스트 직접 입력 시에만 호출

📝 근거 (Rationale)

  1. 폴링의 본질적 한계

    • 폴링은 주기적으로 완료 여부를 확인 → 그 동안 Backend는 대기
    • 결국 동기 처리와 다를 바 없음 (단지 연결을 끊었다 다시 할 뿐)
    • 네트워크 왕복만 늘어나고 실질적 이득 없음
  2. 성능 최적화

    • 네트워크 왕복 2회 → 1회로 감소
    • Backend-AI 간 통신 오버헤드 감소
    • 전체 레이턴시 감소
  3. Backend 단순화

    • OCR 상태 관리 로직 불필요
    • 임베딩 호출 로직 불필요
    • 에러 핸들링 포인트 감소
  4. 응집도 향상

    • "파일 업로드 → VectorDB 저장"이라는 하나의 기능이 하나의 API로 완결
    • AI Server가 자신의 도메인(OCR, 임베딩, VectorDB)을 책임짐
  5. 유연성 유지

    • 텍스트 직접 입력 시에는 여전히 /ai/file/embed 사용 가능
    • 필요시 OCR 결과만 필요한 경우 별도 옵션 추가 가능

ADR-009: AI 채팅 컨텍스트 관리 (Redis 사용)

📋 메타데이터

항목 내용
상태 ✅ 채택됨 (Accepted)
작성일 2026-01-08
결정자 AI/백엔드팀
관련 기능 AI 채팅, 대화 히스토리, 컨텍스트 유지

🎯 컨텍스트 (Context)

AI 채팅 서비스에서 대화 히스토리와 컨텍스트를 유지해야 합니다.

요구사항:

  • 사용자와의 대화 맥락 유지
  • LLM에 전달할 최근 대화 히스토리 관리
  • 빠른 읽기/쓰기 성능
  • 다중 사용자/채팅방 지원
  • 세션 만료 및 자동 정리

현재 데이터 저장소:

저장소 용도
PostgreSQL 사용자 정보, 채팅방 메타데이터, 면접 세션
MongoDB 분석 결과, 채팅 메시지 영구 저장
ChromaDB 임베딩 벡터 저장 (VectorDB)
Redis 실시간 대화 컨텍스트 관리

🔍 선택지 분석 (Options)

Option 1: 요청마다 DB에서 히스토리 조회

┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│   매 요청마다 DB 조회                                                        │
│                                                                             │
│   User → Backend → MongoDB (최근 N개 조회) → AI Server → LLM               │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
장점 단점
추가 인프라 불필요 매 요청마다 DB I/O 발생
데이터 영속성 보장 레이턴시 증가
동시 요청 시 DB 부하

Option 2: 인메모리 캐시 (Python 딕셔너리)

# 단일 서버 인메모리
chat_sessions = {
    "room_001": ["message1", "message2", ...]
}
장점 단점
매우 빠름 서버 재시작 시 데이터 손실
구현 간단 다중 서버 환경에서 공유 불가
메모리 관리 어려움

Option 3: Redis 캐시 레이어 ⭐ 선택

┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│   Redis 캐시 레이어                                                          │
│                                                                             │
│   ┌─────────────────────────────────────────────────────────────────────┐   │
│   │                         Redis                                       │   │
│   │                                                                     │   │
│   │   chat:room_001 → ["msg1", "msg2", ...]  (TTL: 30분)               │   │
│   │   chat:room_002 → ["msg1", "msg2", ...]  (TTL: 30분)               │   │
│   │   context:user_456 → {preferences, summary, ...}                   │   │
│   │                                                                     │   │
│   └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│   User → Backend → Redis (캐시) → AI Server → LLM                          │
│                        ↓                                                    │
│                    MongoDB (영구 저장, 비동기)                               │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
장점 단점
빠른 읽기/쓰기 (1ms 이하) 추가 인프라 필요
TTL 자동 만료 지원 관리 포인트 증가
다중 서버 공유 가능
Pub/Sub 지원
LangChain 공식 지원

Option 4: LangChain Memory (ChatMessageHistory)

from langchain_community.chat_message_histories import RedisChatMessageHistory

history = RedisChatMessageHistory(
    session_id="room_001",
    url="redis://localhost:6379"
)
장점 단점
LangChain 네이티브 통합 Redis 의존성
코드 간결 커스터마이징 제한적
검증된 패턴

✅ 결정 (Decision)

Option 3 + Option 4: Redis + LangChain Memory 조합

┌─────────────────────────────────────────────────────────────────────────────┐
│                        채택된 아키텍처                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│                           ┌─────────────────┐                               │
│                           │      Redis      │                               │
│                           │                 │                               │
│                           │  ┌───────────┐  │                               │
│                           │  │ 채팅 히스토리│  │  ← LangChain Memory         │
│                           │  │ (최근 N개) │  │                               │
│                           │  └───────────┘  │                               │
│                           │                 │                               │
│                           │  ┌───────────┐  │                               │
│                           │  │ 사용자     │  │  ← 세션 컨텍스트              │
│                           │  │ 컨텍스트   │  │                               │
│                           │  └───────────┘  │                               │
│                           │                 │                               │
│                           └────────┬────────┘                               │
│                                    │                                        │
│   ┌─────────────────────────────────────────────────────────────────────┐   │
│   │                                                                     │   │
│   │   [AI Server - FastAPI]                                            │   │
│   │                                                                     │   │
│   │   from langchain_community.chat_message_histories import           │   │
│   │       RedisChatMessageHistory                                      │   │
│   │                                                                     │   │
│   │   # 채팅 히스토리                                                    │   │
│   │   history = RedisChatMessageHistory(                               │   │
│   │       session_id=f"chat:{room_id}",                                │   │
│   │       url="redis://redis:6379",                                    │   │
│   │       ttl=1800  # 30분                                             │   │
│   │   )                                                                 │   │
│   │                                                                     │   │
│   │   # ConversationBufferMemory와 연동                                  │   │
│   │   memory = ConversationBufferWindowMemory(                          │   │
│   │       chat_memory=history,                                          │   │
│   │       k=10  # 최근 10개 메시지                                       │   │
│   │   )                                                                 │   │
│   │                                                                     │   │
│   └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│   비동기로 MongoDB에도 영구 저장 (나중에 조회 가능)                            │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Redis 키 구조:

키 패턴 용도 TTL
chat:{room_id} 채팅 히스토리 (메시지 리스트) 30분
context:{user_id} 사용자 선호도, 요약 정보 1시간
session:{session_id} 면접 세션 임시 데이터 1시간

데이터 흐름:

┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│   [사용자 메시지 도착]                                                       │
│         │                                                                   │
│         ▼                                                                   │
│   ┌─────────────────┐                                                       │
│   │ Redis에서        │                                                       │
│   │ 최근 히스토리    │  ← 1ms 이하                                           │
│   │ 조회             │                                                       │
│   └────────┬────────┘                                                       │
│            │                                                                │
│            ▼                                                                │
│   ┌─────────────────┐                                                       │
│   │ LLM 호출        │                                                       │
│   │ (히스토리 포함)  │                                                       │
│   └────────┬────────┘                                                       │
│            │                                                                │
│            ▼                                                                │
│   ┌─────────────────┐    ┌─────────────────┐                                │
│   │ Redis에         │    │ MongoDB에       │                                │
│   │ 응답 추가       │    │ 비동기 저장     │  ← 영구 보관                    │
│   └─────────────────┘    └─────────────────┘                                │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

📝 근거 (Rationale)

  1. 성능 (레이턴시)

    • Redis: < 1ms
    • MongoDB: 5-20ms
    • 매 채팅마다 DB 조회 시 사용자 체감 지연 발생
  2. 다중 사용자 지원

    • 여러 사용자가 동시에 채팅해도 세션 분리
    • 서버 스케일 아웃 시에도 Redis 공유로 일관성 유지
  3. 자동 메모리 관리

    • TTL 설정으로 자동 만료
    • 오래된 세션 자동 정리
    • 메모리 누수 방지
  4. LangChain 네이티브 지원

    • RedisChatMessageHistory 공식 지원
    • ConversationBufferWindowMemory와 쉬운 연동
    • 검증된 패턴, 안정적
  5. PL 피드백 반영

    • "Redis 홈페이지 참고해서 보는 게 좋음"
    • "Memory 아키텍처, AI Agent 문서 보기"
    • "AI랑 백이랑 같이 하는 것도 좋음"
  6. 비용 효율

    • Redis는 경량 → 작은 인스턴스로 운영 가능
    • AWS ElastiCache, Redis Cloud 등 관리형 서비스 활용 가능
  7. Stateless 아키텍처 구현

    Redis를 사용하면 서버가 Stateless가 됩니다:

    ┌─────────────────────────────────────────────────────────────────┐
    │  Stateless vs Stateful 비교                                      │
    ├─────────────────────────────────────────────────────────────────┤
    │                                                                 │
    │  [Stateful (Redis 없이)]                                        │
    │      ┌────────────┐                                             │
    │      │ 서버 A     │  ← 세션 메모리에 저장                        │
    │      │ sessions{} │     (서버 재시작 시 손실)                    │
    │      └────────────┘                                             │
    │                                                                 │
    │  [Stateless (Redis 사용)]                                       │
    │      ┌────────────┐  ┌────────────┐  ┌────────────┐            │
    │      │ 서버 A     │  │ 서버 B     │  │ 서버 C     │            │
    │      │ (메모리 X) │  │ (메모리 X) │  │ (메모리 X) │            │
    │      └─────┬──────┘  └─────┬──────┘  └─────┬──────┘            │
    │            │                │                │                  │
    │            └────────────────┼────────────────┘                  │
    │                             │                                   │
    │                             ▼                                   │
    │                      ┌──────────────┐                           │
    │                      │    Redis     │  ← 세션 저장소             │
    │                      │  (공유 저장소)│     (모든 서버가 공유)      │
    │                      └──────────────┘                           │
    │                                                                 │
    └─────────────────────────────────────────────────────────────────┘
    

    Stateless의 장점:

    항목 Stateful (메모리) Stateless (Redis)
    서버 재시작 ❌ 세션 손실 ✅ 세션 유지
    로드 밸런싱 ❌ Sticky Session 필요 ✅ 자유롭게 가능
    수평 확장 ❌ 어려움 ✅ 쉬움
    장애 복구 ❌ 세션 손실 ✅ 세션 유지
    메모리 사용 ⚠️ 서버마다 세션 저장 ✅ Redis에만 저장

    구현 예시:

    # Stateful (메모리에 세션 저장) - 사용 안 함
    sessions = {}  # 서버 메모리
    
    @app.post("/login")
    async def login(user: User):
        session_id = generate_session_id()
        sessions[session_id] = {  # ← 메모리에 저장 (Stateful)
            "user_id": user.id,
            "login_time": datetime.now()
        }
        return {"session_id": session_id}
    
    # Stateless (Redis에 세션 저장) - 채택 ✅
    import redis
    redis_client = redis.Redis(host='localhost', port=6379)
    
    @app.post("/login")
    async def login(user: User):
        session_id = generate_session_id()
        redis_client.setex(  # ← Redis에 저장 (Stateless)
            f"session:{session_id}",
            3600,  # 1시간 TTL
            json.dumps({
                "user_id": user.id,
                "login_time": datetime.now().isoformat()
            })
        )
        return {"session_id": session_id}
    

    Stateless 흐름:

    1. 사용자 로그인 → 서버 A → Redis에 세션 저장
    2. 다음 요청 → 로드 밸런서 → 서버 B로 라우팅
    3. 서버 B → Redis에서 세션 조회 → 정상 처리 ✅
    

    프로젝트 적용:

    용도 Redis 키 TTL Stateless 효과
    세션 관리 session:{session_id} 1시간 서버 재시작해도 유지
    채팅 히스토리 chat:{room_id} 30분 모든 서버가 공유
    사용자 컨텍스트 context:{user_id} 1시간 로드 밸런싱 자유
    Rate Limiting rate_limit:{user_id} 1분 서버 간 일관성

    결론:

    • ✅ Redis를 사용하면 서버는 완전히 Stateless
    • ✅ 모든 상태는 Redis에 저장
    • ✅ 서버는 언제든 교체 가능
    • 수평 확장 용이

📚 참고 자료


ADR-010: OCR 서비스 선택 (Upstage vs 클로바 vs Gemini Vision)

📋 메타데이터

항목 내용
상태 🔄 검토중 (Under Review)
작성일 2026-01-09
결정자 AI팀
관련 기능 이력서/채용공고 텍스트 추출, 마스킹

📌 PL 피드백 (3주차 2회차): "Upstage OCR, 클로바 등 검토"


🎯 컨텍스트 (Context)

이력서, 채용공고, 포트폴리오 파일(PDF, 이미지)에서 텍스트를 추출해야 합니다. 정확도와 비용 효율 사이의 균형이 필요합니다.

요구사항:

  • 한국어 OCR 정확도 높아야 함 (이력서, 채용공고)
  • PDF 이미지 형태 문서 처리 가능
  • 비용 효율적이어야 함 (API 과금)
  • 표, 레이아웃 인식 지원

🔍 선택지 분석 (Options)

항목 Gemini Vision Upstage Document AI Clova OCR
한국어 정확도 ⭐⭐⭐⭐ (양호) ⭐⭐⭐⭐⭐ (최상) ⭐⭐⭐⭐⭐ (최상)
표/레이아웃 ⭐⭐⭐ (기본) ⭐⭐⭐⭐⭐ (우수) ⭐⭐⭐⭐ (양호)
비용 $0.00025/이미지 월 구독형 종량제
API 제한 15 RPM (Free) 무제한 (유료) 무제한 (유료)
장점 Gemini LLM과 통합 문서 특화, 한국어 강점 네이버 생태계
단점 문서 특화 아님 비용 높음 단순 OCR만

Option 1: Gemini Vision (V1 MVP)

┌─────────────────────────────────────────────────────────────────────────────┐
│   V1 선택: Gemini Vision                                                     │
│   - 이미 Gemini API 사용 중 → 추가 연동 비용 없음                               │
│   - 한국어 이력서/채용공고에는 충분한 정확도                                     │
│   - 정확도 이슈 발생 시 V2에서 전문 OCR로 전환                                  │
└─────────────────────────────────────────────────────────────────────────────┘

Option 2: Upstage Document AI (V2 검토)

┌─────────────────────────────────────────────────────────────────────────────┐
│   V2 검토: Upstage Document AI                                               │
│   - 한국어 문서 OCR 최상급 정확도                                              │
│   - 표, 레이아웃, 구조 인식 우수                                               │
│   - 비용이 높아 트래픽 증가 시 검토                                            │
└─────────────────────────────────────────────────────────────────────────────┘

Option 3: Clova OCR (대안)

┌─────────────────────────────────────────────────────────────────────────────┐
│   대안: Clova OCR                                                            │
│   - 네이버 한국어 OCR                                                        │
│   - 단순 텍스트 추출에 적합                                                   │
│   - 복잡한 레이아웃은 Upstage가 더 우수                                       │
└─────────────────────────────────────────────────────────────────────────────┘

✅ 결정 (Decision)

단계적 도입 전략:

버전 OCR 선택 이유
V1 (MVP) Gemini Vision 빠른 출시, 추가 연동 비용 없음
V2 (정확도 이슈 시) Upstage Document AI 검토 한국어 문서 특화
마스킹 (V2) PaddleOCR + YOLO (RunPod) 자체 호스팅으로 비용 절감

결정 기준:

  1. V1 배포 후 OCR 정확도 측정 (CER < 5% 기준)
  2. 정확도 미달 시 Upstage Document AI 비용 분석
  3. 트래픽 증가 시 자체 모델 (PaddleOCR) 검토

📝 근거 (Rationale)

  1. MVP 우선: 당장 전문 OCR 불필요, Gemini Vision으로 시작
  2. 비용 효율: 추가 API 연동 없이 기존 Gemini 활용
  3. 점진적 개선: 정확도 이슈 발생 시에만 전문 OCR 도입
  4. 데이터 기반 결정: 실제 사용자 데이터로 정확도 측정 후 판단
  5. PL 피드백 반영: "Upstage OCR, 클로바 등 검토" → 5주차 비용 대비 성능 평가 예정

📚 참고 자료


📅 이력

날짜 변경 내용
2026-01-08 파일 생성 (템플릿)
2026-01-09 PL 피드백 반영하여 OCR 선택 ADR 작성