# skill-embedding 서비스 구축 및 배포 **날짜**: 2025-08-05 **작업자**: happybell80 & Claude **관련 서버**: 51124 (skill-embedding 서비스) ## 오전 10시 30분 ### 임베딩 서비스 분리 결정 **배경**: - rb10508_micro가 987.9MB 메모리 사용 (ONNX 임베딩 모델 포함) - 각 로빙마다 동일한 임베딩 모델 중복 로드 - 향후 로빙 추가 시 메모리 부담 가중 **해결 방안**: - 중앙 임베딩 서비스 구축 (skill-embedding) - 모든 로빙이 HTTP API로 임베딩 요청 - 메모리 절약: 로빙당 ~500MB 절감 예상 ## 오전 10시 45분 ### 서비스 설계 및 개발 **주요 결정사항**: 1. **포트 번호**: 8600 → 8015 (스킬 서비스 포트 범위) 2. **서비스 이름**: embedding_service → skill-embedding 3. **접근 방식**: 내부 전용 (nginx 프록시 불필요) **구현 내용**: ```python # FastAPI 엔드포인트 POST /embed - 텍스트 → 임베딩 변환 GET /health - 헬스체크 ``` **기술 스택**: - FastAPI + Uvicorn - ONNX Runtime - multilingual-MiniLM-L12-v2 모델 - 384차원 임베딩 ## 오전 11시 00분 ### 51124 서버 사전 준비 **서버팀 작업**: ```bash # 디렉토리 생성 mkdir -p /home/admin/ivada_project/skill-embedding/logs chmod 777 logs # ONNX 모델 권한 설정 chown -R 999:999 /home/admin/ivada_project/onnx_models # 환경변수 설정 cat > .env << EOF PORT=8015 SERVICE_NAME=skill-embedding LOG_LEVEL=INFO MODEL_PATH=/models/onnx/multilingual-MiniLM-L12-v2 EOF # Docker 공간 정리 (16.16GB 확보) docker system prune -a -f ``` ## 오전 11시 15분 ### 배포 및 검증 **Gitea Actions 배포**: - SSH 키 기반 51124 서버 배포 - Docker Compose로 컨테이너 실행 - 헬스체크 통과 **검증 결과**: ```bash # 컨테이너 상태 CONTAINER ID IMAGE STATUS PORTS NAMES abc123def skill-embedding Up 25 seconds 8015/tcp skill-embedding # 메모리 사용량 skill-embedding: 874.4MB (예상 범위 내) # API 테스트 curl http://localhost:8015/health {"status":"healthy","service":"skill-embedding","model":"multilingual-MiniLM-L12-v2","uptime":25.31} # 임베딩 테스트 curl -X POST http://localhost:8015/embed \ -H "Content-Type: application/json" \ -d '{"texts":["테스트"]}' # 384차원 벡터 정상 반환 ``` ## 오전 11시 20분 ### ChromaDB 통합 준비 **HTTPEmbeddingFunction 구현**: ```python class HTTPEmbeddingFunction(EmbeddingFunction): def __init__(self, embedding_service_url="http://localhost:8015"): self.url = f"{embedding_service_url}/embed" def __call__(self, texts): response = requests.post(self.url, json={"texts": texts}) return response.json()["embeddings"] ``` **적용 대상**: - rb10508_micro: 988.1MB → ~400MB (예상) - rb8001: 추후 적용 - rb10408: 추후 적용 ## 교훈 1. **서비스 분리의 이점** - 중복 제거로 메모리 효율성 향상 - 중앙 관리로 유지보수 용이 - 확장성 확보 (새 로빙 추가 시 임베딩 서비스 재사용) 2. **내부 서비스 설계** - nginx 프록시 불필요한 내부 API는 복잡도 감소 - localhost 통신으로 충분한 성능 - 보안상 외부 노출 불필요 3. **사전 준비의 중요성** - 서버팀과 긴밀한 협업으로 원활한 배포 - 권한 설정 (logs 777, ONNX 모델 999:999) 필수 - Docker 공간 확보로 빌드 실패 방지 4. **단계적 적용 전략** - 새 서비스 먼저 안정화 - 하나의 로빙(rb10508_micro)에 시범 적용 - 성공 후 다른 로빙들에 확산 5. **모니터링 지표** - 메모리 사용량: 874.4MB (ONNX 모델 포함) - 응답 시간: 단일 텍스트 ~50ms - 헬스체크: 30초 간격 ## 현재 상태 **skill-embedding 서비스**: - ✅ 정상 가동 중 (포트 8015) - ✅ 메모리 사용량 안정적 (874.4MB) - ✅ API 응답 정상 - ✅ 384차원 임베딩 생성 확인 **다음 작업**: - rb10508_micro의 memory.py 수정 - ONNXEmbeddingFunction → HTTPEmbeddingFunction 교체 - 메모리 절감 효과 측정 ## 오후 1시 40분 ### rb10508_micro HTTP 임베딩 전환 대성공 **목표**: rb10508_micro의 ONNX 임베딩을 HTTP 방식으로 전환 **구현 방식**: ```python # memory.py에 간단한 HTTPEmbeddingFunction 추가 class HTTPEmbeddingFunction(EmbeddingFunction): def __init__(self): self.url = f"{os.getenv('SKILL_EMBEDDING_URL', 'http://localhost:8015')}/embed" def __call__(self, input: List[str]) -> List[List[float]]: if not input: return [] response = requests.post(self.url, json={"texts": input}, timeout=30) response.raise_for_status() return response.json()["embeddings"] ``` **변경사항**: 1. memory.py: HTTPEmbeddingFunction 직접 구현 (파일 복사 없이) 2. requirements.txt: onnxruntime, transformers 제거 3. docker-compose.yml: ONNX 볼륨 제거, SKILL_EMBEDDING_URL 추가 **배포 결과 - 극적인 메모리 절감**: ``` 배포 전: 988.1 MiB 배포 후: 118.4 MiB 절약량: 870 MiB (88% 감소!) ``` **성능 검증**: - 헬스체크: 정상 (Up 50초, healthy) - API 응답: 정상 작동 - HTTP 임베딩: 7ms 처리 시간 - skill-embedding 연동: 완벽 ## 교훈 (추가) 6. **예상보다 좋은 결과** - 목표 400MB → 실제 118MB (예상의 30%) - ONNX 제거만으로 870MB 절감 - PyTorch 의존성이 생각보다 무거웠음 7. **간단한 구현의 힘** - 파일 복사 대신 직접 구현 (12줄) - 불필요한 추상화 제거 - 예외처리는 서비스 레벨에서 충분 8. **HTTP 임베딩의 장점** - 극적인 메모리 절감 (88%) - 7ms 레이턴시는 무시할 수준 - 중앙 관리로 업데이트 용이 9. **아키텍처 검증** - 임베딩 서비스 분리 전략 성공 - 다른 로빙들도 같은 방식 적용 가능 - 100개 로빙 = 87GB 메모리 절약 가능 ## 오후 2시 16분 ### rb10508_micro ChromaDB 경로 표준화 **문제상황**: - rb10508_micro가 `./chroma_db_micro` 사용 (비표준) - 다른 모든 로빙은 `./chroma_db` 표준 경로 사용 - 백업 크론잡과 인프라 관리의 일관성 필요 **해결과정**: 1. **서버팀 사전 작업** (51124 서버) ```bash # 컨테이너 중지 docker stop rb10508_micro # 데이터 마이그레이션 mv chroma_db_micro/* chroma_db/ # 권한 설정 chown -R 999:999 chroma_db/ ``` 2. **로컬 개발자 작업** - docker-compose.yml 수정: `./chroma_db_micro` → `./chroma_db` - git commit & push로 배포 3. **검증 결과** - ChromaDB 데이터: 17MB → 172KB (압축 후) - 기존 기억 완전 보존 ("User: 넌 누구야?" 검색 가능) - HTTP 임베딩 정상 작동 (11ms 응답) - 메모리 사용량 유지 (120.4MB) **교훈**: 10. **표준화의 중요성** - 모든 로빙이 동일한 디렉토리 구조 사용 - 백업, 모니터링, 마이그레이션 자동화 가능 - 인프라 복잡도 감소 11. **안전한 마이그레이션** - 서버팀과 협업으로 데이터 손실 방지 - 컨테이너 중지 → 데이터 이동 → 권한 설정 순서 - 배포 후 기능 검증 필수 ## 오후 2시 47분 ### rb10508_micro 최종 최적화 - 패키지 정리 **목표**: 미사용 패키지 제거로 추가 메모리 절약 **제거 대상 패키지**: 1. pytest, pytest-asyncio (테스트 도구) 2. neo4j (그래프 DB 미사용) 3. Pillow (이미지 처리 미사용) 4. passlib[bcrypt] (JWT로 충분) 5. email-validator (이메일 검증 미구현) 6. PyMuPDF (이전에 제거) **실제 최적화 결과**: ``` 메모리: 120MB → 112.7MB (7.3MB 절약) 이미지: 1.09GB → 1.06GB (30MB 절약) ``` **예상 vs 실제 비교**: | 항목 | 예상 | 실제 | 차이 | |------|------|------|------| | 메모리 절약 | 40MB | 7.3MB | -32.7MB | | 이미지 절약 | 40MB | 30MB | -10MB | **전체 최적화 과정 요약**: ``` 1. 초기상태: 987.9MB RAM, 6.19GB 이미지 2. HTTP전환: 120.0MB RAM, 1.14GB 이미지 (88% 메모리 감소) ← 핵심! 3. PyMuPDF제거: 변화 없음, 이미지 55MB 감소 4. 패키지정리: 112.7MB RAM, 1.06GB 이미지 (7MB 추가 절약) ``` **총 최적화 효과**: - 메모리: 987.9MB → 112.7MB (88.6% 감소) - 이미지: 6.19GB → 1.06GB (82.9% 감소) - 목표 200MB 대비: 56% 달성 **교훈**: 12. **예상과 실제의 차이** - 패키지 크기와 실제 메모리 사용량은 다름 - 대부분의 절약은 HTTP 임베딩 전환에서 발생 - 추가 최적화는 수익 감소 효과 13. **효율적인 최적화 우선순위** - 1순위: 임베딩 모델 제거 (870MB 절약) - 2순위: 이미지 크기 최적화 (5GB 절약) - 3순위: 미사용 패키지 정리 (7MB 절약) 14. **벤치마크 비교** - rb10408_test: 55.7MB (최소 기능) - rb10508_micro: 112.7MB (풀 기능) - 2배 차이는 추가 기능 대비 효율적 **결론**: rb10508_micro 최적화 완료! HTTP 임베딩 전환이 핵심이었으며, 112.7MB로 목표 초과 달성. 추가 최적화는 비용 대비 효과가 낮음. ## 오후 3시 34분 ### rb10508_micro Gemini 모델 404 오류 해결 **문제상황**: - AI 응답이 "응답 생성 중 오류가 발생했습니다: 404 Resource not found" 반환 - ChromaDB에는 오류 메시지만 저장 - 정상적인 대화 기능 완전 상실 **원인 분석**: ```python # app/core/brain.py:38 self.gemini_model = genai.GenerativeModel('gemini-pro') # ❌ 존재하지 않는 모델 ``` **해결**: ```python # 1줄 수정 self.gemini_model = genai.GenerativeModel('gemini-2.5-flash-lite') # ✅ 정상 모델 ``` **검증 결과**: | 항목 | 이전 | 현재 | 개선도 | |------|------|------|--------| | API 연결 | ❌ 404 오류 | ✅ 정상 | 100% | | 응답 생성 | ❌ 오류 메시지 | ✅ AI 응답 | 100% | | 응답 속도 | N/A | 0.087초 | 완전 복구 | | 메모리 저장 | ❌ 오류만 저장 | ✅ 정상 대화 | 100% | **최종 상태**: - 메모리: 117.2MB (미세 증가는 정상 작동으로 인한 것) - 기능: 완전한 AI 대화 서비스 복구 - ChromaDB: 실제 대화 내용 정상 저장 **교훈**: 15. **정확한 모델명의 중요성** - LLM API 모델명은 정확해야 함 - 존재하지 않는 모델은 404 오류 발생 - 공식 문서 확인 필수 16. **1줄 수정의 파급력** - 핵심 기능이 1줄 오류로 완전 정지 - 빠른 진단과 수정이 중요 - 테스트 코드로 사전 검증 필요 **총 최적화 프로젝트 성과**: - 메모리: 987.9MB → 117.2MB (88.1% 감소) - 기능: 100% 정상 작동 - 목표 달성: 200MB 목표 대비 58.6% 수준