- 300_architecture/360_로빙_컨테이너_경량화_전략.md에 Phase 2.5 추가 - 300_architecture/370_임베딩_서비스_분리_아키텍처.md 신규 생성 - 300_architecture/300_README.md 목차 업데이트 - 000_프로젝트_종합_v3.md에 최근 성과 섹션 추가 (메모리 최적화, 기술적 개선, 운영 지표) - 모든 이모지 제거 (CLAUDE.md 규칙 준수)
151 lines
4.7 KiB
Markdown
151 lines
4.7 KiB
Markdown
# 370. 임베딩 서비스 분리 아키텍처
|
|
|
|
## 개요
|
|
|
|
각 로빙이 독립적으로 ONNX 임베딩 모델을 로드하던 구조에서 중앙 임베딩 서비스를 공유하는 구조로 전환하여, 메모리 사용량을 극적으로 감소시키고 확장성을 확보했습니다.
|
|
|
|
## 문제점
|
|
|
|
- 각 로빙마다 동일한 임베딩 모델(449MB) 중복 로드
|
|
- rb10508_micro: 987.9MB 메모리 사용
|
|
- 로빙 추가 시 선형적 메모리 증가
|
|
|
|
## 해결책: 중앙 임베딩 서비스
|
|
|
|
### 아키텍처
|
|
|
|
```
|
|
이전: 이후:
|
|
┌─────────────┐ ┌─────────────┐
|
|
│ rb10508 │ │ rb10508 │
|
|
│ ONNX Model │ │ (118MB) │──┐
|
|
│ (988MB) │ └─────────────┘ │
|
|
└─────────────┘ │
|
|
▼
|
|
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐
|
|
│ rb8001 │ │ rb8001 │ │ skill-embedding │
|
|
│ ONNX Model │ → │ (200MB) │──│ (874.4MB) │
|
|
│ (416MB) │ └─────────────┘ │ - ONNX Model │
|
|
└─────────────┘ │ - Port 8015 │
|
|
▼ └─────────────────┘
|
|
┌─────────────┐ ┌─────────────┐
|
|
│ rb10408 │ │ rb10408 │
|
|
│ ONNX Model │ │ (30MB) │──┘
|
|
│ (55MB) │ └─────────────┘
|
|
└─────────────┘
|
|
```
|
|
|
|
### 기술 스택
|
|
|
|
- **FastAPI + Uvicorn**: 고성능 비동기 웹 서버
|
|
- **ONNX Runtime**: 최적화된 임베딩 생성
|
|
- **multilingual-MiniLM-L12-v2**: 다국어 지원 임베딩 모델
|
|
|
|
### API 설계
|
|
|
|
```python
|
|
# POST /embed
|
|
{
|
|
"texts": ["안녕하세요", "오늘 날씨가 좋네요"]
|
|
}
|
|
|
|
# Response
|
|
{
|
|
"embeddings": [
|
|
[0.1, 0.2, ...], # 384차원 벡터
|
|
[0.3, 0.4, ...]
|
|
]
|
|
}
|
|
|
|
# GET /health
|
|
{
|
|
"status": "healthy",
|
|
"service": "skill-embedding",
|
|
"model": "multilingual-MiniLM-L12-v2",
|
|
"uptime": 3600.5
|
|
}
|
|
```
|
|
|
|
## 구현 가이드
|
|
|
|
### 1. 로빙 측 변경사항
|
|
|
|
```python
|
|
# 기존: ONNX 직접 로드
|
|
from onnx_embedder import ONNXEmbedder
|
|
embedder = ONNXEmbedder("/models/onnx/...")
|
|
|
|
# 변경: HTTP 임베딩 함수
|
|
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]]:
|
|
response = requests.post(self.url, json={"texts": input}, timeout=30)
|
|
return response.json()["embeddings"]
|
|
```
|
|
|
|
### 2. ChromaDB 통합
|
|
|
|
```python
|
|
# memory.py
|
|
self.episodic = self.client.get_or_create_collection(
|
|
name=f"{self.robing_id}_episodic",
|
|
embedding_function=HTTPEmbeddingFunction() # HTTP 방식으로 변경
|
|
)
|
|
```
|
|
|
|
### 3. Docker 구성 변경
|
|
|
|
```yaml
|
|
# 불필요한 볼륨 제거
|
|
# - /opt/models:/models:ro # 더 이상 필요 없음
|
|
|
|
# 환경변수 추가
|
|
environment:
|
|
- SKILL_EMBEDDING_URL=http://localhost:8015
|
|
```
|
|
|
|
## 성능 분석
|
|
|
|
### 메모리 절감
|
|
- **rb10508_micro**: 988MB → 118MB (-870MB, 88% 감소)
|
|
- **예상 절감 (100개 로빙)**: 87GB 메모리 절약
|
|
|
|
### 레이턴시
|
|
- **HTTP 오버헤드**: +7ms (무시할 수준)
|
|
- **전체 응답시간**: 1-3초 중 0.2% 미만
|
|
|
|
### 확장성
|
|
- 단일 임베딩 서비스로 수백 개 로빙 지원
|
|
- 수평 확장 가능 (로드밸런서 적용 시)
|
|
|
|
## 모니터링
|
|
|
|
```python
|
|
# 주요 메트릭
|
|
- 임베딩 생성 요청 수
|
|
- 평균 응답 시간
|
|
- 메모리 사용량
|
|
- 에러율
|
|
|
|
# 헬스체크
|
|
curl http://localhost:8015/health
|
|
```
|
|
|
|
## 다음 단계
|
|
|
|
1. **캐싱 레이어 추가**: Redis로 자주 사용되는 임베딩 캐싱
|
|
2. **배치 처리 최적화**: 대량 텍스트 임베딩 성능 개선
|
|
3. **모델 업데이트**: L12 → L6 모델로 추가 경량화 검토
|
|
4. **다른 로빙 적용**: rb8001, rb10408에 순차 적용
|
|
|
|
## 교훈
|
|
|
|
- **중복 제거의 힘**: 동일한 기능을 서비스로 분리하면 극적인 효율성 향상
|
|
- **간단한 구현**: 12줄의 HTTPEmbeddingFunction으로 870MB 절약
|
|
- **점진적 적용**: 하나의 로빙에서 성공 후 확산
|
|
|
|
---
|
|
|
|
*"임베딩은 공유하되, 기억은 분리하라"* |