[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)
- ADR-012: 캘린더 서비스 차별화 전략
- ADR-013: 팀 블로그 / Wiki 운영 방식
- ADR-014: LLM 모델 선정 및 Fallback 전략
- ADR-015: 배치 처리 시스템 선정 (Celery Beat vs Airflow vs Kubeflow)
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 전환 우선순위:
/ai/calendar/parse- 짧은 처리 시간, 낮은 호출 빈도- 알림/스케줄링 작업 - 이벤트 기반 처리에 적합
/ai/file/embed- 짧은 처리 시간
📝 근거 (Rationale)
- MVP 우선: 서버리스는 V3 이후 검토 (카카오 멘토 피드백: "처음부터 거대하게 하지 말 것")
- 비용 데이터 기반: 실제 트래픽 패턴 확인 후 결정
- 스트리밍 API 제외: Lambda는 스트리밍 응답에 부적합
- 시간 제한: 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)
- MVP 집중: V1에서는 핵심 기능 완성도 우선
- 데이터 기반 결정: 사용자 피드백 후 커뮤니티 기능 검토
- UX 문서화: 개선 과정을 블로그/위키에 기록 (포트폴리오 활용)
- 커뮤니티 성격: 추후 네트워크 효과로 서비스 차별화
📅 이력
| 날짜 | 변경 내용 |
|---|---|
| 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)
- 카카오 멘토 추천: "블로그 사이트를 만들어서 운영하는 게 더 추천"
- 팀 협업 증명: 개인 블로그보다 팀 블로그가 협업 경험 어필에 유리
- GitHub Wiki 활용: 기존 문서 자산 재활용, Git 기반 버전 관리
- Supabase 연동: 검색/필터링 기능, 실시간 동기화
- 포트폴리오 활용: 취업 시 팀 블로그 링크 제출 가능
주의사항 (카카오 멘토):
- 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 기능:
- 채팅 - 일반 대화 및 취업 상담
- 면접 Q&A - 면접 질문 생성 및 답변 피드백
- 채용공고 분석 - 채용공고 요구사항 추출 및 분석
- 이력서/포트폴리오 분석 - 강점/약점 분석 및 개선 제안
- 면접 리포트 작성 - 면접 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+)
이유:
- MVP 우선: 빠른 개발 및 검증 필요
- 한국어 성능: Gemini가 한국어 지원 우수 (검증됨)
- 인프라 복잡도: 자체 서빙은 V2+에서 검토
- 비용: 초기 트래픽 낮음 → API 비용 수용 가능
🔍 모델 후보군 선정
1. 상용 API 후보군
| 모델 | 제공사 | 한국어 성능 | 속도 | 비용 | 선정 이유 |
|---|---|---|---|---|---|
| Gemini 3 Flash | ⭐⭐⭐⭐⭐ | ⚡ 빠름 | 💰 저렴 | 한국어 특화, 빠른 응답 | |
| Gemini 3 Pro | ⭐⭐⭐⭐⭐ | 🐢 보통 | 💰💰 중간 | 복잡한 분석에 적합 | |
| 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 | 한국어 파인튜닝 |
후보군 선정 기준:
- ✅ 한국어 성능 필수 (모든 후보 통과)
- ✅ 상업적 사용 가능 라이선스
- ✅ API 제공 또는 자체 서빙 가능
- ✅ 문서화 및 커뮤니티 활발
🧪 실험 설계
실험 1: 핵심 기능별 모델 성능 평가
목적: 채팅, 면접 Q&A, 이력서 분석 기능에 적합한 모델 선정
실험 데이터:
- 채팅: 한국어 대화 샘플 20개 (일반 질문, 취업 상담)
- 면접 Q&A: 이력서 기반 면접 질문 생성 샘플 15개
- 이력서 분석: 실제 이력서 샘플 10개 (개인정보 마스킹)
평가 지표:
- 한국어 자연스러움 (1-5점, 필수)
- 응답 속도 (초)
- 정확도/유용성 (1-5점)
- 비용 (토큰당 가격)
실험 절차:
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%" 같은 임의 가중치 사용 안 함
- ✅ 기능별로 실제 샘플로 테스트하여 선정
실험 순서:
- 핵심 기능 3개만 실험 (채팅, 면접 Q&A, 이력서 분석)
- 각 기능별로 Good/Bad 케이스 분석
- 기능별 최적 모델 선정
- 보조 기능은 핵심 기능 결과 재활용
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에서 배치 처리가 필요합니다:
- RDB → VectorDB 동기화 (ADR-025)
- 이력서/채용공고 패턴 분석 → 추천 생성
- 면접 질문 패턴 분석 → 질문 풀 생성 (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 마이그레이션 경로 정의) |