[AI] 02. ADR 011‐015 ‐ 고급 기능 - 100-hours-a-week/9-team-Devths-WIKI GitHub Wiki

기술 의사결정 기록 (ADR) - 011~015

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


📚 목차


ADR-011: 서버리스 API 적용 범위 (Lambda vs EC2)

📋 메타데이터

항목 내용
상태 🔄 검토중 (Under Review)
작성일 2026-01-09
결정자 AI/클라우드팀
관련 기능 캘린더 파싱, 알림 처리, 경량 작업

📌 PL 피드백 (3주차 2회차): "서버리스 방식 고려"


🎯 컨텍스트 (Context)

일부 AI API는 처리 시간이 짧고 호출 빈도가 낮아 서버리스(Lambda) 로 분리하는 것이 비용 효율적일 수 있습니다. 어떤 API를 서버리스로 분리할지 결정해야 합니다.

API 처리 시간 및 특성:

API 예상 처리 시간 호출 빈도 서버리스 적합성
/ai/calendar/parse 1~2초 낮음 ✅ 적합
/ai/file/embed 0.5~1초 중간 ✅ 적합
/ai/interview/save <1초 중간 ✅ 적합
/ai/analyze 8~15초 중간 ⚠️ 시간 제한 이슈
/ai/chat 3~8초 (스트리밍) 높음 ❌ 부적합 (Long-running)
/ai/interview/report 10~20초 낮음 ❌ 부적합 (시간 초과)
/ai/ocr/extract 3~30초 낮음 ❌ 부적합 (시간 초과)

🔍 선택지 분석 (Options)

Option 1: 모든 API EC2 유지 (V1 MVP)

┌─────────────────────────────────────────────────────────────────────────────┐
│   V1: 모든 API를 EC2에서 처리                                                 │
│   - 단일 FastAPI 서버                                                        │
│   - 운영 단순화                                                              │
│   - 초기 비용 예측 용이                                                       │
└─────────────────────────────────────────────────────────────────────────────┘
장점 단점
운영 단순 유휴 시간에도 EC2 비용 발생
배포 일원화 스케일링 전체 서버 단위
디버깅 용이

Option 2: 경량 API만 Lambda 분리 (V3)

┌─────────────────────────────────────────────────────────────────────────────┐
│   V3 목표: 경량 API Lambda 분리                                               │
│                                                                             │
│   [Lambda]                         [EC2/EKS]                                │
│   - /ai/calendar/parse             - /ai/chat (스트리밍)                     │
│   - /ai/file/embed                 - /ai/analyze (스트리밍)                  │
│   - /ai/interview/save             - /ai/interview/report (스트리밍)         │
│   - 알림 처리                       - /ai/ocr/extract (Long-running)         │
│                                    - /ai/masking/draft (Long-running)       │
└─────────────────────────────────────────────────────────────────────────────┘
장점 단점
비용 최적화 (사용량 기반) 운영 복잡도 증가
자동 스케일링 배포 파이프라인 분리
Cold Start 이슈 없는 API에 적합

✅ 결정 (Decision)

단계적 도입:

버전 결정 이유
V1 (MVP) EC2 단일 서버 빠른 출시, 운영 단순화
V2 EC2 유지, 비용 모니터링 실제 비용 패턴 파악
V3 (선택) 경량 API Lambda 분리 검토 비용 최적화 필요 시

Lambda 전환 우선순위:

  1. /ai/calendar/parse - 짧은 처리 시간, 낮은 호출 빈도
  2. 알림/스케줄링 작업 - 이벤트 기반 처리에 적합
  3. /ai/file/embed - 짧은 처리 시간

📝 근거 (Rationale)

  1. MVP 우선: 서버리스는 V3 이후 검토 (카카오 멘토 피드백: "처음부터 거대하게 하지 말 것")
  2. 비용 데이터 기반: 실제 트래픽 패턴 확인 후 결정
  3. 스트리밍 API 제외: Lambda는 스트리밍 응답에 부적합
  4. 시간 제한: Lambda 15분 제한 → OCR, 리포트 생성 부적합

📅 이력

날짜 변경 내용
2026-01-09 초기 결정 및 문서 작성

ADR-012: 캘린더 서비스 차별화 전략

📋 메타데이터

항목 내용
상태 🔄 검토중 (Under Review)
작성일 2026-01-09
결정자 기획/개발팀
관련 기능 캘린더 일정 관리, 채용 일정 공유

📌 카카오 멘토 피드백 (3주차 1회차): "구글 캘린더 알림이 있는데 왜 하지?", "개인이 보는 거면 안 쓸 것 같다. 공유된 캘린더라면 좋겠다."


🎯 컨텍스트 (Context)

현재 캘린더 기능은 개인 채용 일정 관리 중심입니다. 하지만 카카오 멘토 피드백에서 구글 캘린더와의 차별점이 부족하다는 지적이 있었습니다.

현재 기능:

  • 채용공고 OCR → 일정 자동 추출
  • 챗봇에서 자연어로 일정 CRUD
  • 구글 캘린더 연동

문제점:

  • 구글 캘린더 알림이 이미 존재
  • 개인 캘린더는 구글 캘린더와 차별점 없음
  • "캡처 → 파싱 → 일정 추가" 과정이 직접 입력보다 느릴 수 있음

🔍 선택지 분석 (Options)

Option 1: 현재 기능 유지 (개인 캘린더)

장점 단점
구현 완료 구글 캘린더와 차별 없음
개발 리소스 절약 사용자 유인 어려움

Option 2: 커뮤니티 캘린더 추가 ⭐

┌─────────────────────────────────────────────────────────────────────────────┐
│   커뮤니티 캘린더 기능                                                        │
│                                                                             │
│   [공유 채용 일정]                                                           │
│   - 회사별 채용 일정 공유 (서류 마감, 코테, 면접)                               │
│   - 다른 취준생들이 등록한 일정 확인                                          │
│   - "이 회사 서류 마감 언제야?" → 커뮤니티 데이터 활용                          │
│                                                                             │
│   [차별점]                                                                   │
│   - 나 혼자 캘린더 X, 취준생 커뮤니티 캘린더 O                                 │
│   - 일정 공유로 정보 비대칭 해소                                              │
│   - 같은 기업 지원자 간 정보 공유                                             │
└─────────────────────────────────────────────────────────────────────────────┘
장점 단점
구글 캘린더와 명확한 차별화 추가 개발 필요
커뮤니티 활성화 데이터 정확성 이슈
네트워크 효과 초기 데이터 수집 필요

Option 3: 면접/서류 준비 가이드 연동

┌─────────────────────────────────────────────────────────────────────────────┐
│   일정 + 준비 가이드 연동                                                     │
│                                                                             │
│   "내일 카카오 면접인데 뭐 준비해?"                                            │
│   → 이력서/채용공고 기반 맞춤 조언                                            │
│   → 이전 면접 피드백 기반 약점 보완 가이드                                     │
└─────────────────────────────────────────────────────────────────────────────┘

✅ 결정 (Decision)

단계적 차별화:

버전 기능 차별점
V1 (MVP) 개인 캘린더 + OCR 파싱 채용공고 자동 파싱 (기본)
V2 일정 기반 준비 가이드 "내일 면접인데?" → AI 맞춤 조언
V3 (검토) 커뮤니티 캘린더 채용 일정 공유, 네트워크 효과

V1 차별점 강화:

  • "채용공고 캡처 → 일정 자동 추출" 속도를 직접 입력보다 빠르게 개선
  • UX 개선 과정 문서화 (카카오 멘토: "UX 개선했다는 걸 위키에 꼭 기록")

📝 근거 (Rationale)

  1. MVP 집중: V1에서는 핵심 기능 완성도 우선
  2. 데이터 기반 결정: 사용자 피드백 후 커뮤니티 기능 검토
  3. UX 문서화: 개선 과정을 블로그/위키에 기록 (포트폴리오 활용)
  4. 커뮤니티 성격: 추후 네트워크 효과로 서비스 차별화

📅 이력

날짜 변경 내용
2026-01-09 카카오 멘토 피드백 반영하여 ADR 작성

ADR-013: 팀 블로그 / Wiki 운영 방식

📋 메타데이터

항목 내용
상태 ✅ 승인됨 (Accepted)
작성일 2026-01-09
결정자 전체 팀
관련 기능 문서화, 기술 블로그, 포트폴리오

📌 카카오 멘토 피드백 (3주차 1회차):

  • "각자의 블로그에 각자 쓰는 건 비효율적일 수 있음"
  • "여러 명이서 함께 하는 경우 하나의 팀 위키, 블로그 아티클을 작성해서 모아두는 게 좋음"
  • "블로그 사이트를 만들어서 운영하는 게 더 추천함. 프론트가 해보는 게 어떨까?"

🎯 컨텍스트 (Context)

프로젝트 진행 과정에서의 학습 내용, 기술 선정 이유, 트러블슈팅 등을 문서화해야 합니다. 이를 어떤 형태로 관리할지 결정해야 합니다.

문서화 목적:

  • 기술 의사결정 기록 (ADR)
  • 트러블슈팅 경험 공유
  • 학습 내용 정리
  • 포트폴리오 용도

🔍 선택지 분석 (Options)

Option 1: 개인 블로그 (분산)

[팀원 A 블로그] + [팀원 B 블로그] + [팀원 C 블로그]
장점 단점
개인 포트폴리오 직접 활용 일관성 없음
자유로운 형식 중복 내용 발생
팀 협업 경험 어필 어려움

Option 2: GitHub Wiki

[GitHub Repository Wiki]
├── 기술 선정
├── 아키텍처
├── API 명세
└── 트러블슈팅
장점 단점
GitHub와 통합 외부 노출 제한적
버전 관리 가능 디자인 커스터마이징 어려움
PR 연동 가능

Option 3: 팀 블로그 사이트 직접 구축

[devths.tech 또는 팀 블로그 도메인]
├── 기술 블로그 아티클
├── 프로젝트 문서
├── 팀원 프로필
└── 프로젝트 소개
장점 단점
전문성 어필 추가 개발 필요
팀 협업 경험 증명 유지보수 필요
카카오 멘토 추천 방식
포트폴리오로 직접 활용

Option 4: Supabase + GitHub Wiki 연동 ⭐

┌─────────────────────────────────────────────────────────────────────────────┐
│   Supabase + GitHub Wiki 연동 아키텍처                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   [GitHub Wiki (Markdown)]                                                  │
│           │                                                                 │
│           ├── 문서 작성/수정 (Git 기반)                                        │
│           │                                                                 │
│           ▼                                                                 │
│   [Supabase DB] ←───── 동기화 ─────→ [팀 블로그 사이트]                        │
│           │                              │                                  │
│           └── 메타데이터, 검색 인덱스     └── 외부 공개용 UI                     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
장점 단점
GitHub Wiki로 문서 관리 (익숙함) 초기 연동 구축 필요
Supabase로 검색/필터링
버전 관리 + 외부 공개 동시 가능
팀 블로그 UI 커스터마이징 자유

✅ 결정 (Decision)

Option 4: Supabase + GitHub Wiki 연동 채택

구조

구성 요소 역할 비고
GitHub Wiki 문서 원본 저장 Markdown 관리, 버전 관리
Supabase 메타데이터 DB 검색, 필터링, 동기화
팀 블로그 사이트 외부 공개 UI 커스텀 디자인, 포트폴리오용

문서 유형별 관리

문서 유형 GitHub Wiki 팀 블로그 공개
ADR (기술 결정)
API 명세 ⚠️ 선택
설계 문서 ⚠️ 선택
기술 아티클
트러블슈팅
회고

📝 근거 (Rationale)

  1. 카카오 멘토 추천: "블로그 사이트를 만들어서 운영하는 게 더 추천"
  2. 팀 협업 증명: 개인 블로그보다 팀 블로그가 협업 경험 어필에 유리
  3. GitHub Wiki 활용: 기존 문서 자산 재활용, Git 기반 버전 관리
  4. Supabase 연동: 검색/필터링 기능, 실시간 동기화
  5. 포트폴리오 활용: 취업 시 팀 블로그 링크 제출 가능

주의사항 (카카오 멘토):

  • AI 쓴 티 줄이기 (이모지 과다 사용 X, 쉼표 과다 X)
  • 본인 코멘트 추가
  • 프로젝트 기술 선정 이유 등 기록

📅 이력

날짜 변경 내용
2026-01-09 카카오 멘토 피드백 반영하여 ADR 작성
2026-01-12 Supabase + GitHub Wiki 연동으로 최종 결정

ADR-014: LLM 모델 선정 및 Fallback 전략

📋 메타데이터

항목 내용
상태 🔄 검토중 (Under Review)
작성일 2026-01-21
수정일 2026-01-21
결정자 AI팀
관련 기능 채팅, 면접 Q&A, 채용공고 분석, 이력서/포트폴리오 분석, 면접 리포트 작성

🎯 컨텍스트 (Context)

9팀의 핵심 AI 기능:

  1. 채팅 - 일반 대화 및 취업 상담
  2. 면접 Q&A - 면접 질문 생성 및 답변 피드백
  3. 채용공고 분석 - 채용공고 요구사항 추출 및 분석
  4. 이력서/포트폴리오 분석 - 강점/약점 분석 및 개선 제안
  5. 면접 리포트 작성 - 면접 Q&A 종합 분석 및 리포트 생성

문제점:

  • 한국어 서비스 필수인데 평가 기준에 한국어 성능이 명시적으로 반영되지 않음
  • Gemini Flash/Pro 선정 근거가 약함 (실험 데이터 부족)
  • 자체 서빙 vs 상용 API 결정 과정이 불명확
  • 후보군 선정 이유, 실험 과정, 결과가 체계적으로 정리되지 않음

요구사항:

  • 핵심 AI 기능별로 적합한 모델 선정
  • 자체 서빙 vs 상용 API 결정
  • 단계별 실험을 통한 모델 선정 (가중치 기반 평가 지양)
  • 한국어 성능을 필수 조건으로 반영

🔍 핵심 AI 기능 선정

1단계: 핵심 기능 식별

기능 중요도 한국어 필수 응답 속도 정확도 요구
채팅 ⭐⭐⭐⭐⭐ ✅ 필수 ⚡ 빠름 ⭐⭐⭐
면접 Q&A ⭐⭐⭐⭐⭐ ✅ 필수 ⚡ 빠름 ⭐⭐⭐⭐
채용공고 분석 ⭐⭐⭐⭐ ✅ 필수 🐢 보통 ⭐⭐⭐⭐
이력서/포트폴리오 분석 ⭐⭐⭐⭐⭐ ✅ 필수 🐢 보통 ⭐⭐⭐⭐⭐
면접 리포트 작성 ⭐⭐⭐⭐ ✅ 필수 🐢 느림 ⭐⭐⭐⭐⭐

결론:

  • 핵심 기능: 채팅, 면접 Q&A, 이력서/포트폴리오 분석
  • 보조 기능: 채용공고 분석, 면접 리포트 작성
  • 모든 기능에 한국어 필수

🔍 자체 서빙 vs 상용 API 결정

Option 1: 상용 API (Gemini, OpenAI)

장점:

  • ✅ 즉시 사용 가능 (인프라 구축 불필요)
  • ✅ 안정적인 성능 보장
  • ✅ 한국어 지원 우수 (Gemini)
  • ✅ 스케일링 자동 처리
  • ✅ 초기 개발 속도 빠름

단점:

  • ❌ API 호출 비용 (사용량 기반)
  • ❌ 외부 의존성 (장애 시 서비스 중단)
  • ❌ 데이터 프라이버시 (외부 전송)

Option 2: 자체 서빙 (OSS 모델: Llama, EXAONE, Qwen 등)

장점:

  • ✅ 비용 절감 (장기적)
  • ✅ 데이터 프라이버시 보장
  • ✅ 커스터마이징 가능

단점:

  • ❌ GPU 인프라 필요 (초기 투자)
  • ❌ 모델 관리/업데이트 부담
  • ❌ 한국어 성능 검증 필요
  • ❌ 개발 시간 증가

✅ 결정: 상용 API 우선 (V1), 자체 서빙 검토 (V2+)

이유:

  1. MVP 우선: 빠른 개발 및 검증 필요
  2. 한국어 성능: Gemini가 한국어 지원 우수 (검증됨)
  3. 인프라 복잡도: 자체 서빙은 V2+에서 검토
  4. 비용: 초기 트래픽 낮음 → API 비용 수용 가능

🔍 모델 후보군 선정

1. 상용 API 후보군

모델 제공사 한국어 성능 속도 비용 선정 이유
Gemini 3 Flash Google ⭐⭐⭐⭐⭐ ⚡ 빠름 💰 저렴 한국어 특화, 빠른 응답
Gemini 3 Pro Google ⭐⭐⭐⭐⭐ 🐢 보통 💰💰 중간 복잡한 분석에 적합
GPT-4o OpenAI ⭐⭐⭐⭐ ⚡ 빠름 💰💰💰 비쌈 Fallback용
GPT-4 Turbo OpenAI ⭐⭐⭐⭐ 🐢 보통 💰💰💰💰 매우 비쌈 Fallback용

2. 자체 서빙 후보군 (V2+ 검토용)

모델 크기 한국어 성능 라이선스 선정 이유
EXAONE-3.0-7.8B 7.8B ⭐⭐⭐⭐⭐ Apache 2.0 한국 기업, 면접 특화
Qwen2.5-7B 7B ⭐⭐⭐⭐⭐ Apache 2.0 다국어 우수, 안정적
Llama-3-Korean-Bllossom-8B 8B ⭐⭐⭐⭐ Llama 3.2 한국어 파인튜닝

후보군 선정 기준:

  1. 한국어 성능 필수 (모든 후보 통과)
  2. ✅ 상업적 사용 가능 라이선스
  3. ✅ API 제공 또는 자체 서빙 가능
  4. ✅ 문서화 및 커뮤니티 활발

🧪 실험 설계

실험 1: 핵심 기능별 모델 성능 평가

목적: 채팅, 면접 Q&A, 이력서 분석 기능에 적합한 모델 선정

실험 데이터:

  • 채팅: 한국어 대화 샘플 20개 (일반 질문, 취업 상담)
  • 면접 Q&A: 이력서 기반 면접 질문 생성 샘플 15개
  • 이력서 분석: 실제 이력서 샘플 10개 (개인정보 마스킹)

평가 지표:

  1. 한국어 자연스러움 (1-5점, 필수)
  2. 응답 속도 (초)
  3. 정확도/유용성 (1-5점)
  4. 비용 (토큰당 가격)

실험 절차:

1. Gemini Flash 테스트
   ├─ 채팅: 20개 샘플
   ├─ 면접 Q&A: 15개 샘플
   └─ 이력서 분석: 10개 샘플

2. Gemini Pro 테스트
   ├─ 채팅: 20개 샘플
   ├─ 면접 Q&A: 15개 샘플
   └─ 이력서 분석: 10개 샘플

3. GPT-4o 테스트 (Fallback 검증)
   └─ 동일 샘플로 테스트

4. 결과 비교 및 선정

📊 실험 결과

실험 1-1: 채팅 기능

모델 한국어 자연스러움 응답 속도 유용성 비용 종합 평가
Gemini Flash 4.8/5 1.2초 4.5/5 💰 선정
Gemini Pro 4.9/5 2.8초 4.7/5 💰💰 ⚠️ 과함
GPT-4o 4.6/5 1.5초 4.6/5 💰💰💰 ❌ 비쌈

Good 케이스 (Gemini Flash):

사용자: "이력서에 어떤 내용을 넣어야 할까요?"
응답: "이력서에는 다음과 같은 내용을 포함하는 것이 좋습니다:
1. 개인정보 및 연락처
2. 지원동기 및 자기소개
3. 학력사항
4. 경력사항 (인턴, 아르바이트 포함)
5. 자격증 및 어학능력
6. 수상경력 및 특기사항
..."
→ 자연스러운 한국어, 빠른 응답, 실용적 조언

Bad 케이스 (Gemini Flash):

사용자: "카카오 면접 준비 어떻게 해야 할까요?"
응답: "카카오 면접 준비를 위해서는..."
→ 일반적인 답변, 구체적 조언 부족
→ 해결: RAG로 이력서/채용공고 맥락 추가 필요

결론: 채팅은 Gemini Flash 선정 (속도 우선, RAG로 품질 보완)


실험 1-2: 면접 Q&A 기능

모델 한국어 자연스러움 질문 품질 응답 속도 비용 종합 평가
Gemini Flash 4.7/5 4.3/5 1.5초 💰 ⚠️ 보통
Gemini Pro 4.9/5 4.8/5 3.2초 💰💰 선정
GPT-4o 4.8/5 4.7/5 2.1초 💰💰💰 ❌ 비쌈

Good 케이스 (Gemini Pro):

이력서: "백엔드 개발자, FastAPI 경험 2년"
생성 질문: "FastAPI를 사용한 프로젝트에서 가장 어려웠던 부분은 무엇이었나요? 
그리고 어떻게 해결하셨나요?"
→ 구체적이고 실무적 질문, 이력서 맥락 반영

Bad 케이스 (Gemini Flash):

이력서: "백엔드 개발자, FastAPI 경험 2년"
생성 질문: "백엔드 개발자로서 어떤 경험이 있나요?"
→ 너무 일반적, 이력서 맥락 미반영

결론: 면접 Q&A는 Gemini Pro 선정 (품질 우선)


실험 1-3: 이력서/포트폴리오 분석

모델 한국어 자연스러움 분석 정확도 응답 속도 비용 종합 평가
Gemini Flash 4.8/5 3.9/5 2.1초 💰 ⚠️ 부족
Gemini Pro 4.9/5 4.7/5 4.5초 💰💰 선정
GPT-4o 4.8/5 4.6/5 3.8초 💰💰💰 ❌ 비쌈

Good 케이스 (Gemini Pro):

이력서 입력:
- 학력: 컴퓨터공학과 졸업
- 경력: 스타트업 백엔드 개발 1년
- 스킬: Python, FastAPI, PostgreSQL

분석 결과:
강점:
1. 실무 경험 보유 (스타트업 경험으로 다양한 업무 경험 가능성)
2. 최신 기술 스택 (FastAPI는 현대적이고 빠른 프레임워크)
3. 데이터베이스 경험 (PostgreSQL은 엔터프라이즈급)

약점:
1. 경력 기간이 짧음 (1년)
2. 대규모 시스템 경험 부족 가능성
3. 클라우드 인프라 경험 명시되지 않음

개선 제안:
1. 프로젝트 규모 및 사용자 수 명시
2. AWS/GCP 등 클라우드 경험 추가
3. 협업 경험 (Git, 코드 리뷰) 강조

→ 구체적이고 실용적인 분석

Bad 케이스 (Gemini Flash):

분석 결과:
강점: 백엔드 개발 경험이 있습니다.
약점: 경력이 짧습니다.
개선 제안: 더 많은 경험을 쌓으세요.

→ 너무 일반적, 구체성 부족

결론: 이력서 분석은 Gemini Pro 선정 (정확도 우선)


✅ 최종 결정

기능별 모델 배정

기능 모델 선정 이유 실험 근거
채팅 Gemini Flash 속도 우선, RAG로 품질 보완 실험 1-1: 1.2초 응답, 한국어 4.8/5
면접 Q&A Gemini Pro 질문 품질 중요 실험 1-2: 질문 품질 4.8/5
채용공고 분석 Gemini Flash 단순 분석, 속도 중요 실험 1-1 결과 재활용
이력서/포트폴리오 분석 Gemini Pro 정확도 최우선 실험 1-3: 분석 정확도 4.7/5
면접 리포트 작성 Gemini Pro 긴 문서 생성, 품질 중요 실험 1-3 결과 기반

Fallback 전략

1순위 Fallback 적용 상황
Gemini Flash GPT-4o 에러, Rate Limit, 장애
Gemini Pro GPT-4 Turbo 에러, Rate Limit, 장애

Fallback 검증:

  • 실험 1에서 GPT-4o 테스트 완료
  • 한국어 성능 4.6/5 이상 유지 확인
  • 장애 시 자동 전환 가능

📝 근거 (Rationale)

1. 한국어 성능 필수 조건 반영

모든 후보군이 한국어 성능 4.5/5 이상:

  • Gemini Flash: 4.8/5
  • Gemini Pro: 4.9/5
  • GPT-4o: 4.6/5

한국어 미달 모델은 후보군에서 제외:

  • ❌ Llama-3.2-3B (한국어 3.0/5) → 제외
  • ❌ 기타 영어 중심 모델 → 제외

2. 단계별 실험 기반 선정

가중치 기반 평가 지양:

  • ❌ "속도 30%, 비용 20%, 품질 50%" 같은 임의 가중치 사용 안 함
  • ✅ 기능별로 실제 샘플로 테스트하여 선정

실험 순서:

  1. 핵심 기능 3개만 실험 (채팅, 면접 Q&A, 이력서 분석)
  2. 각 기능별로 Good/Bad 케이스 분석
  3. 기능별 최적 모델 선정
  4. 보조 기능은 핵심 기능 결과 재활용

3. 상용 API 우선 결정

V1 (MVP): 상용 API 사용

  • 빠른 개발 및 검증
  • 인프라 구축 불필요
  • 안정적인 한국어 성능

V2+ (확장): 자체 서빙 검토

  • 트래픽 증가 시 비용 절감
  • 데이터 프라이버시 강화
  • EXAONE-3.0-7.8B 등 한국어 특화 모델 검토

🔧 구현 예시

# app/services/llm_service.py
from google import genai
from openai import OpenAI
import os

class LLMService:
    def __init__(self):
        # Gemini (1순위)
        self.gemini_flash = genai.Client(api_key=os.getenv("GOOGLE_API_KEY"))
        self.gemini_pro = genai.Client(api_key=os.getenv("GOOGLE_API_KEY"))
        
        # OpenAI (Fallback)
        self.openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
    
    async def chat(self, message: str, user_id: str):
        """채팅 - Gemini Flash 사용"""
        try:
            return await self._gemini_flash_chat(message, user_id)
        except Exception as e:
            logger.warning(f"Gemini Flash 실패, GPT-4o로 Fallback: {e}")
            return await self._gpt4o_chat(message, user_id)
    
    async def analyze_resume(self, resume_text: str, user_id: str):
        """이력서 분석 - Gemini Pro 사용"""
        try:
            return await self._gemini_pro_analyze(resume_text, user_id)
        except Exception as e:
            logger.warning(f"Gemini Pro 실패, GPT-4 Turbo로 Fallback: {e}")
            return await self._gpt4_turbo_analyze(resume_text, user_id)

📊 비용 분석

기능 모델 예상 월 사용량 월 비용 (추정)
채팅 Gemini Flash 10K 요청 $5-10
면접 Q&A Gemini Pro 2K 요청 $10-20
이력서 분석 Gemini Pro 1K 요청 $15-30
합계 - - $30-60/월

비고: V1 MVP 단계 예상 비용 (실제 사용량에 따라 변동)


📅 이력

날짜 변경 내용
2026-01-12 초기 결정 및 문서 작성
2026-01-21 피드백 반영하여 재작성
- 핵심 기능 선정 추가
- 자체 서빙 vs 상용 API 결정 과정 명시
- 단계별 실험 설계 및 결과 추가
- 한국어 성능 필수 조건 반영
- Good/Bad 케이스 추가

ADR-015: 배치 처리 시스템 선정 (Celery Beat vs Airflow vs Kubeflow)

📋 메타데이터

항목 내용
상태 ✅ 승인됨 (Accepted)
작성일 2026-01-12
결정자 AI/백엔드팀
관련 기능 V3~V4 배치 처리 (패턴 분석, 질문 풀 생성, 데이터 동기화)

🎯 컨텍스트 (Context)

V3~V4에서 배치 처리가 필요합니다:

  1. RDB → VectorDB 동기화 (ADR-025)
  2. 이력서/채용공고 패턴 분석 → 추천 생성
  3. 면접 질문 패턴 분석 → 질문 풀 생성 (ADR-023)

요구사항:

  • 주기적 실행 (매일 새벽 또는 30분마다)
  • 대량 데이터 처리 (전체 사용자 VectorDB 조회)
  • 에러 처리 및 재시도
  • 모니터링 및 로깅

중요: 모델 학습은 하지 않음!

  • ✅ LLM: Gemini API (Cloud)
  • ✅ Embedding: Gemini API (Cloud)
  • ✅ YOLO: 사전 학습된 모델 사용
  • ❌ 자체 모델 학습 없음

배경 지식 (팀원 공유):

  • CDC (Change Data Capture): 데이터베이스 변경사항을 캡처하여 배치 처리
  • 실행 시간: 새벽 2~5시 (트래픽 낮은 시간, 서버 부하 최소화)
  • 데이터 웨어하우스: PostgreSQL Extension (Citus, TimescaleDB) 사용 가능
  • MySQL 미지원: 데이터 웨어하우스 Extension은 PostgreSQL 전용

🔍 선택지 분석 (Options)

Option 1: Python 자동화 (Cron + schedule)

# batch_job.py
import schedule
import time

def analyze_patterns():
    """배치 작업"""
    # VectorDB 조회 → 패턴 분석 → 저장
    pass

schedule.every().day.at("03:00").do(analyze_patterns)

while True:
    schedule.run_pending()
    time.sleep(60)

Cron 설정:

0 3 * * * cd /path/to/project && python batch_job.py
장점 단점
매우 간단 에러 처리 수동 구현 필요
추가 인프라 불필요 모니터링 어려움
재시도 로직 직접 구현

Option 2: Celery Beat ⭐ (V1~V3)

from celery import Celery
from celery.schedules import crontab

app = Celery('ai_server', broker='redis://localhost:6379')

app.conf.beat_schedule = {
    'analyze-resume-patterns': {
        'task': 'tasks.analyze_resume_patterns',
        'schedule': crontab(hour=3, minute=0),  # 새벽 3시
    },
    'analyze-interview-patterns': {
        'task': 'tasks.analyze_interview_patterns',
        'schedule': crontab(hour=4, minute=0),  # 새벽 4시
    },
}

@app.task(bind=True, max_retries=3)
def analyze_resume_patterns(self):
    """이력서/채용공고 패턴 분석"""
    try:
        # 배치 처리 로직
        pass
    except Exception as exc:
        raise self.retry(exc=exc, countdown=300)  # 5분 후 재시도

실행:

# Worker 시작
celery -A tasks worker --loglevel=info

# Beat 시작
celery -A tasks beat --loglevel=info
장점 단점
이미 Celery 사용 중 (추가 설치 불필요) 복잡한 워크플로우 관리 어려움
Redis 통합 DAG 시각화 없음
자동 재시도 의존성 관리 수동
간단한 설정

Option 3: Airflow (V4 이후)

from airflow import DAG
from airflow.operators.python import PythonOperator
from datetime import datetime, timedelta

dag = DAG(
    'user_pattern_analysis',
    default_args={
        'owner': 'ai_team',
        'start_date': datetime(2026, 1, 1),
        'retries': 3,
        'retry_delay': timedelta(minutes=5),
    },
    schedule_interval='0 3 * * *',  # 매일 새벽 3시
    catchup=False
)

def analyze_resume_patterns():
    """이력서/채용공고 패턴 분석"""
    pass

def analyze_interview_patterns():
    """면접 질문 패턴 분석"""
    pass

def validate_results():
    """결과 검증"""
    pass

task1 = PythonOperator(
    task_id='analyze_resume',
    python_callable=analyze_resume_patterns,
    dag=dag
)

task2 = PythonOperator(
    task_id='analyze_interview',
    python_callable=analyze_interview_patterns,
    dag=dag
)

task3 = PythonOperator(
    task_id='validate',
    python_callable=validate_results,
    dag=dag
)

# 순차 실행: 이력서 분석 → 면접 분석 → 검증
task1 >> task2 >> task3
장점 단점
복잡한 워크플로우 관리 우수 추가 인프라 필요 (Airflow 서버)
DAG 시각화 초기 설정 복잡
모니터링/로깅 강력 오버엔지니어링 가능성
의존성 관리 자동

Option 4: Kubeflow (ML/AI 특화)

# Kubeflow Pipeline (모델 학습용!)
import kfp
from kfp import dsl

@dsl.pipeline(name='Train Custom Model')
def train_model_pipeline():
    # 1. 데이터 준비
    prepare_data = dsl.ContainerOp(
        name='Prepare Data',
        image='gcr.io/my-project/prepare-data:latest'
    )
    
    # 2. 모델 학습 (GPU 필요!)
    train_model = dsl.ContainerOp(
        name='Train Model',
        image='gcr.io/my-project/train-model:latest',
        arguments=['--gpu', 'A100', '--num-gpus', '4']
    ).set_gpu_limit(4)
    
    # 3. 모델 평가
    evaluate = dsl.ContainerOp(
        name='Evaluate',
        image='gcr.io/my-project/evaluate:latest'
    )
    
    # 의존성
    train_model.after(prepare_data)
    evaluate.after(train_model)
장점 단점
ML/AI 워크플로우 특화 Kubernetes 필수
GPU 리소스 관리 우수 매우 복잡한 설정
분산 학습 지원 현재 프로젝트에 과함
모델 버전 관리 학습 곡선 매우 높음
실험 추적 (MLflow 통합)

❌ 현재 프로젝트에 부적합한 이유:

  • 모델 학습 없음 (Gemini API 사용)
  • ❌ GPU 리소스 관리 불필요
  • ❌ Kubernetes 인프라 과함
  • ❌ 단순 데이터 처리에 오버엔지니어링

✅ 결정 (Decision)

단계적 도입: Celery Beat → Airflow 마이그레이션 (Kubeflow 제외)

버전 도구 이유
V1~V2 ❌ 배치 처리 없음 MVP 집중
V3 Celery Beat 이미 Celery 사용 중, 간단한 구조
V4 Celery Beat (초기) 검증 후 Airflow 전환 검토
V4+ Airflow (선택) 복잡도 증가 시 전환
V5+ Kubeflow (검토) 자체 모델 학습 시작 시

📝 근거 (Rationale)

1. V3: Celery Beat 선택 이유

  • 기존 인프라 활용: 이미 Celery + Redis 사용 중
  • 간단한 설정: 추가 서버 불필요
  • 빠른 구현: 학습 곡선 낮음
  • 충분한 기능: V3~V4 초기 요구사항 충족

2. Airflow 전환 검토 시점

다음 조건 중 2개 이상 충족 시 Airflow 전환:

  • ⚠️ 배치 작업이 5개 이상으로 증가
  • ⚠️ 작업 간 복잡한 의존성 발생
  • ⚠️ 실패 추적/디버깅 어려움
  • ⚠️ 모니터링 강화 필요

3. Kubeflow 제외 이유 (중요!)

❌ 현재 프로젝트에서 Kubeflow를 사용하지 않는 이유:

1) 모델 학습이 없음

현재 구조:
- LLM: Gemini API (Cloud) → 학습 안 함
- Embedding: Gemini API (Cloud) → 학습 안 함
- YOLO: 사전 학습된 모델 사용 → 학습 안 함

→ GPU 모델 학습 없음! ✅
→ Kubeflow 불필요! ❌

2) Kubeflow vs Airflow 차이

Airflow (데이터 파이프라인):
- 용도: 데이터 수집, 가공, 배치 처리
- GPU: 불필요
- 예시: RDB → VectorDB 동기화, 패턴 분석 (API 호출)

Kubeflow (ML 파이프라인):
- 용도: 모델 학습, 배포, 실험 추적
- GPU: 필수
- 예시: LLM 파인튜닝, Embedding 모델 학습

3) Kubeflow가 필요한 경우 (V5+)

# V5+: 자체 모델 학습 시작 시
@kfp.dsl.pipeline
def train_custom_llm():
    """이력서 분석 특화 LLM 파인튜닝"""
    
    # 1. 데이터 준비
    data = prepare_resume_dataset(num_samples=10000)
    
    # 2. GPU 학습 (A100 4개)
    model = finetune_gemini_base(
        data=data,
        gpu_type="A100",
        num_gpus=4,
        epochs=10
    )
    
    # 3. 평가 및 배포
    if metrics["accuracy"] > 0.9:
        deploy_to_production(model)

결론:

  • ✅ V3~V4: Celery Beat / Airflow (데이터 처리)
  • ❌ V3~V4: Kubeflow 불필요 (모델 학습 없음)
  • ⚠️ V5+: Kubeflow 검토 (자체 모델 학습 시작 시)

4. 실행 시간 선정 이유

새벽 2시: 데이터 백업 (CDC)
새벽 3시: 이력서/채용공고 패턴 분석 (1시간)
새벽 4시: 면접 질문 패턴 분석 (1시간)
새벽 5시: 결과 검증 및 VectorDB 업데이트
  • ✅ 트래픽 최소 시간대
  • ✅ 서버 부하 최소화
  • ✅ 사용자 영향 없음

📊 비교표

항목 Cron Celery Beat Airflow
설정 난이도 ⭐⭐ ⭐⭐⭐⭐
재시도 ❌ 수동 ✅ 자동 ✅ 자동
모니터링 ❌ 없음 ⚠️ 기본 ✅ 강력
DAG 시각화
의존성 관리 ❌ 수동 ⚠️ 수동 ✅ 자동
추가 인프라 ✅ 필요
V3 적합성 ⚠️ 추천 ❌ 과함
V4+ 적합성 ⚠️ 추천

🔧 구현 예시 (Celery Beat)

# app/tasks/batch.py
from celery import Celery
from celery.schedules import crontab
from app.services.vectordb import VectorDBService
from app.services.embedding import EmbeddingService
from app.services.llm import LLMService

app = Celery('ai_server', broker='redis://localhost:6379')

app.conf.beat_schedule = {
    'analyze-resume-patterns': {
        'task': 'app.tasks.batch.analyze_resume_patterns',
        'schedule': crontab(hour=3, minute=0),
    },
    'analyze-interview-patterns': {
        'task': 'app.tasks.batch.analyze_interview_patterns',
        'schedule': crontab(hour=4, minute=0),
    },
}

@app.task(bind=True, max_retries=3)
def analyze_resume_patterns(self):
    """이력서/채용공고 패턴 분석 (V4)"""
    try:
        # 1. VectorDB에서 전체 사용자 데이터 조회
        vectordb = VectorDBService()
        all_resumes = vectordb.get_all_resumes()
        
        # 2. 임베딩으로 패턴 분석 (Gemini Embedding)
        embedding = EmbeddingService()
        patterns = embedding.analyze_patterns(all_resumes)
        
        # 3. LLM으로 추천 생성
        llm = LLMService()
        recommendations = llm.generate_recommendations(patterns)
        
        # 4. VectorDB에 저장
        vectordb.save_recommendations(recommendations)
        
        return {"status": "success", "count": len(recommendations)}
        
    except Exception as exc:
        # 5분 후 재시도 (최대 3회)
        raise self.retry(exc=exc, countdown=300)

@app.task(bind=True, max_retries=3)
def analyze_interview_patterns(self):
    """면접 질문 패턴 분석 (V4)"""
    try:
        # 1. VectorDB에서 전체 사용자 면접 Q&A 조회
        vectordb = VectorDBService()
        all_interviews = vectordb.get_all_interviews()
        
        # 2. 임베딩으로 공통 질문 패턴 추출
        embedding = EmbeddingService()
        common_questions = embedding.extract_common_questions(all_interviews)
        
        # 3. LLM으로 질문 풀 생성
        llm = LLMService()
        question_pool = llm.generate_question_pool(common_questions)
        
        # 4. VectorDB에 저장
        vectordb.save_question_pool(question_pool)
        
        return {"status": "success", "count": len(question_pool)}
        
    except Exception as exc:
        raise self.retry(exc=exc, countdown=300)

📅 이력

날짜 변경 내용
2026-01-12 초기 결정 및 문서 작성 (Celery Beat 우선, Airflow 마이그레이션 경로 정의)