Add embedding service separation architecture design
로빙 임베딩 서비스 분리 아키텍처 설계서 추가: - 현재 메모리 사용량 분석 (rb10508_micro: 987.9MB) - 임베딩 서비스 공유 + 기억 저장소 분리 아키텍처 - 구체적 구현 방안 (HTTP API, ChromaDB 분리) - 메모리 절약 효과 (228MB + 확장성) - 단계별 구현 계획 및 위험 완화 방안 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
11a251eb64
commit
e3a75c5df8
271
ideas/250804_로빙_임베딩_서비스_분리_아키텍처.md
Normal file
271
ideas/250804_로빙_임베딩_서비스_분리_아키텍처.md
Normal file
@ -0,0 +1,271 @@
|
||||
# 로빙 임베딩 서비스 분리 아키텍처
|
||||
|
||||
작성일: 2025년 8월 4일
|
||||
작성자: Claude (51124 서버)
|
||||
|
||||
## 배경
|
||||
|
||||
현재 각 로빙 컨테이너마다 독립적으로 ONNX 임베딩 모델(449MB)을 로드하여 메모리 사용량이 높은 상황입니다. rb10508_micro의 경우 987.9MB를 사용하고 있으며, 장기 운영 시 메모리 부족 위험이 있습니다.
|
||||
|
||||
### 현재 메모리 사용 현황
|
||||
- rb10508_micro: 987.9MB (ONNX 모델 포함)
|
||||
- rb8001: 416.4MB
|
||||
- rb10408_test: 54.96MB
|
||||
- 각 컨테이너마다 독립적인 임베딩 모델 로드
|
||||
|
||||
## 제안 아키텍처: 임베딩 서비스 분리
|
||||
|
||||
### 핵심 아이디어
|
||||
- **임베딩 생성**: 공유 서비스 사용
|
||||
- **기억 저장소**: 각 로빙별 완전 분리
|
||||
- **프라이버시**: 로빙 간 기억 접근 불가
|
||||
- **효율성**: 메모리 절약 + 확장성
|
||||
|
||||
### 아키텍처 구조
|
||||
|
||||
```
|
||||
임베딩 서비스 (공유)
|
||||
├── ONNX 모델 (449MB)
|
||||
├── FastAPI HTTP API
|
||||
└── 포트: 8600
|
||||
↑
|
||||
┌──────┴──────┐
|
||||
rb8001 컨테이너 rb10508 컨테이너
|
||||
├── ChromaDB (/chroma_db) ├── ChromaDB (/chroma_db_micro)
|
||||
├── HTTP 클라이언트 ├── HTTP 클라이언트
|
||||
├── 기억: rb8001 전용 ├── 기억: rb10508 전용
|
||||
└── 메모리: ~200MB └── 메모리: ~400MB
|
||||
```
|
||||
|
||||
## 구현 방안
|
||||
|
||||
### 1. 임베딩 서비스 구성
|
||||
|
||||
#### Docker Compose 설정
|
||||
```yaml
|
||||
services:
|
||||
embedding-service:
|
||||
build: ./embedding_service
|
||||
container_name: embedding_service
|
||||
network_mode: host
|
||||
ports:
|
||||
- "8600:8600"
|
||||
volumes:
|
||||
- /home/admin/ivada_project/onnx_models:/models/onnx:ro
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8600/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
```
|
||||
|
||||
#### 임베딩 서비스 API
|
||||
```python
|
||||
# embedding_service/main.py
|
||||
from fastapi import FastAPI
|
||||
from onnx_embedder import ONNXEmbedder
|
||||
|
||||
app = FastAPI()
|
||||
embedder = ONNXEmbedder("/models/onnx/multilingual-MiniLM-L12-v2")
|
||||
|
||||
@app.post("/embed")
|
||||
async def create_embeddings(request: EmbedRequest):
|
||||
embeddings = []
|
||||
for text in request.texts:
|
||||
embedding = embedder.encode(text)
|
||||
embeddings.append(embedding.tolist())
|
||||
|
||||
return {"embeddings": embeddings}
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
return {"status": "healthy"}
|
||||
```
|
||||
|
||||
### 2. 로빙별 ChromaDB 분리
|
||||
|
||||
#### 볼륨 마운트 구조
|
||||
```
|
||||
호스트 디렉토리:
|
||||
/home/admin/ivada_project/
|
||||
├── rb8001/chroma_db/ # rb8001 전용 기억
|
||||
├── rb10508_micro/chroma_db_micro/ # rb10508 전용 기억
|
||||
├── rb10408/chroma_db/ # rb10408 전용 기억
|
||||
└── onnx_models/ # 공유 모델 (임베딩 서비스용)
|
||||
```
|
||||
|
||||
#### 로빙별 컬렉션 ID 분리
|
||||
```python
|
||||
# rb8001의 memory.py
|
||||
class Memory:
|
||||
def __init__(self):
|
||||
self.client = chromadb.PersistentClient(path="/code/chroma_db")
|
||||
|
||||
# rb8001 전용 컬렉션들
|
||||
self.episodic = self.client.get_or_create_collection(
|
||||
name="rb8001_episodic",
|
||||
embedding_function=HTTPEmbeddingFunction()
|
||||
)
|
||||
self.semantic = self.client.get_or_create_collection(
|
||||
name="rb8001_semantic",
|
||||
embedding_function=HTTPEmbeddingFunction()
|
||||
)
|
||||
|
||||
# rb10508의 memory.py
|
||||
class Memory:
|
||||
def __init__(self):
|
||||
self.client = chromadb.PersistentClient(path="/code/chroma_db")
|
||||
|
||||
# rb10508 전용 컬렉션들
|
||||
self.episodic = self.client.get_or_create_collection(
|
||||
name="rb10508_episodic",
|
||||
embedding_function=HTTPEmbeddingFunction()
|
||||
)
|
||||
```
|
||||
|
||||
### 3. HTTP 임베딩 함수 구현
|
||||
|
||||
```python
|
||||
# common/http_embedding_function.py
|
||||
import requests
|
||||
from chromadb.api.types import EmbeddingFunction
|
||||
|
||||
class HTTPEmbeddingFunction(EmbeddingFunction):
|
||||
def __init__(self, embedding_service_url="http://localhost:8600"):
|
||||
self.url = f"{embedding_service_url}/embed"
|
||||
|
||||
def __call__(self, texts):
|
||||
response = requests.post(self.url,
|
||||
json={"texts": texts},
|
||||
timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()["embeddings"]
|
||||
```
|
||||
|
||||
## 동작 흐름
|
||||
|
||||
### 메모리 저장 과정
|
||||
|
||||
#### 현재 방식
|
||||
```python
|
||||
def store_memory(self, content):
|
||||
# 로컬 ONNX 모델로 임베딩 생성 (987MB 메모리 사용)
|
||||
embedding = self.embedding_function(content)
|
||||
|
||||
# ChromaDB에 저장
|
||||
self.episodic.add(
|
||||
documents=[content],
|
||||
embeddings=[embedding],
|
||||
ids=[memory_id]
|
||||
)
|
||||
```
|
||||
|
||||
#### 임베딩 서비스 방식
|
||||
```python
|
||||
def store_memory(self, content):
|
||||
# HTTP로 임베딩 서비스 호출
|
||||
response = requests.post("http://localhost:8600/embed",
|
||||
json={"texts": [content]})
|
||||
embedding = response.json()["embeddings"][0]
|
||||
|
||||
# ChromaDB에 저장 (임베딩은 받은 값 사용)
|
||||
self.episodic.add(
|
||||
documents=[content],
|
||||
embeddings=[embedding],
|
||||
ids=[memory_id]
|
||||
)
|
||||
```
|
||||
|
||||
### 실제 대화 시나리오
|
||||
|
||||
#### rb8001과의 대화
|
||||
```
|
||||
사용자 → rb8001: "오늘 피자 먹었어"
|
||||
|
||||
1. rb8001이 임베딩 서비스 호출
|
||||
POST localhost:8600/embed {"texts": ["오늘 피자 먹었어"]}
|
||||
|
||||
2. rb8001의 ChromaDB에만 저장
|
||||
rb8001_episodic.add(
|
||||
documents=["오늘 피자 먹었어"],
|
||||
embeddings=[[0.1, 0.2, ...]],
|
||||
metadata={"user_id": "user123", "timestamp": "..."}
|
||||
)
|
||||
```
|
||||
|
||||
#### rb10508과의 대화
|
||||
```
|
||||
사용자 → rb10508: "어제 뭐 먹었지?"
|
||||
|
||||
1. rb10508이 임베딩 서비스 호출 (같은 서비스 사용)
|
||||
POST localhost:8600/embed {"texts": ["어제 뭐 먹었지"]}
|
||||
|
||||
2. rb10508의 ChromaDB에서만 검색 (rb8001 기억 접근 불가)
|
||||
rb10508_episodic.query(query_embeddings=[[0.2, 0.1, ...]])
|
||||
→ 결과 없음 (rb8001의 "피자" 기억은 모름)
|
||||
|
||||
3. rb10508: "죄송해요, 어제 뭘 드셨는지 기억이 없네요"
|
||||
```
|
||||
|
||||
## 예상 효과
|
||||
|
||||
### 메모리 절약
|
||||
- **rb8001**: 416MB → 200MB (-216MB)
|
||||
- **rb10508**: 987MB → 400MB (-587MB)
|
||||
- **rb10408**: 55MB → 30MB (-25MB)
|
||||
- **임베딩 서비스**: +600MB (새로 생성)
|
||||
- **순 절약**: 228MB + 여러 로빙 추가 시 더 큰 절약
|
||||
|
||||
### 성능 영향
|
||||
- **레이턴시 증가**: +10-50ms (HTTP 호출 오버헤드)
|
||||
- **전체 응답시간**: 1-3초 → 영향 미미
|
||||
- **처리량**: 동일 (병목은 LLM API)
|
||||
|
||||
### 운영상 이점
|
||||
- **확장성**: 새로운 로빙 추가 시 임베딩 서비스 공유
|
||||
- **장애 격리**: 임베딩 서비스 장애가 모든 로빙에 영향
|
||||
- **유지보수**: 임베딩 모델 업데이트 시 한 곳만 수정
|
||||
|
||||
## 구현 단계
|
||||
|
||||
### Phase 1: 임베딩 서비스 구축
|
||||
1. 임베딩 서비스 컨테이너 개발
|
||||
2. HTTP API 구현 및 테스트
|
||||
3. 성능 벤치마크 측정
|
||||
|
||||
### Phase 2: rb10508_micro 적용
|
||||
1. HTTPEmbeddingFunction 구현
|
||||
2. memory.py 코드 수정
|
||||
3. docker-compose.yml 업데이트
|
||||
4. 배포 및 테스트
|
||||
|
||||
### Phase 3: 다른 로빙들 순차 적용
|
||||
1. rb8001 적용
|
||||
2. rb10408 적용
|
||||
3. 전체 시스템 안정성 검증
|
||||
|
||||
## 고려사항
|
||||
|
||||
### 장점
|
||||
- **메모리 효율성**: 중복 모델 로딩 제거
|
||||
- **프라이버시 보장**: 로빙 간 기억 완전 분리
|
||||
- **확장성**: 새 로빙 추가 시 효율적
|
||||
- **유지보수성**: 중앙집중식 임베딩 관리
|
||||
|
||||
### 단점
|
||||
- **네트워크 의존성**: HTTP 호출 오버헤드
|
||||
- **단일 장애점**: 임베딩 서비스 장애 시 전체 영향
|
||||
- **복잡성 증가**: 서비스 간 의존성 관리
|
||||
|
||||
### 위험 완화 방안
|
||||
- **헬스체크**: 임베딩 서비스 상태 모니터링
|
||||
- **타임아웃**: HTTP 호출 시 적절한 타임아웃 설정
|
||||
- **폴백**: 임베딩 서비스 장애 시 로컬 모델 대체 고려
|
||||
- **캐싱**: 자주 사용되는 임베딩 캐싱으로 성능 향상
|
||||
|
||||
## 결론
|
||||
|
||||
임베딩 서비스 분리 아키텍처는 메모리 효율성과 확장성을 크게 향상시키면서도 각 로빙의 프라이버시를 완벽하게 보장합니다. 약간의 레이턴시 증가가 있지만, 전체적인 이점이 훨씬 큽니다.
|
||||
|
||||
특히 향후 더 많은 로빙 서비스가 추가될 것을 고려하면, 이 아키텍처는 필수적인 개선사항으로 판단됩니다.
|
||||
Loading…
x
Reference in New Issue
Block a user