# 370. 임베딩 서비스 분리 아키텍처

## 개요

각 로빙이 독립적으로 '언어 의미 이해 엔진(ONNX 임베딩 모델)'을 탑재하던 구조에서, 중앙의 '언어 의미 번역 센터(임베딩 서비스)'를 함께 사용하는 구조로 전환하여 메모리 사용량을 극적으로 감소시키고 관리 효율성을 확보한 과정을 설명합니다.

> "모든 로빙이 하나의 전문 번역가를 공유하면, 각자의 부담은 가벼워지고 소통의 질은 유지된다."

## 왜 임베딩 서비스를 분리했나?

### 임베딩이란?
- **정의**: **임베딩은 "단어/문장의 의미"를 컴퓨터가 이해할 수 있는 숫자 배열(벡터)로 번역하는 기술**입니다.
- **작동 방식**: "안녕하세요"라는 텍스트를 받으면, [0.1, 0.3, -0.5, ...] 와 같이 수백 개의 숫자 배열로 변환합니다.
- **핵심 역할**: 의미가 비슷한 텍스트는 이 숫자 배열의 패턴도 유사해집니다. 이를 통해 로빙은 단어의 표면적 의미를 넘어, 문맥과 뉘앙스를 파악하는 '의미 기반 검색'을 할 수 있습니다. 로빙의 기억과 이해의 핵심 기술입니다.

### 분리 전 문제점: "모두가 각자 사전을 들고 다니는 비효율"
1.  **메모리 낭비**: 모든 로빙이 동일한 '언어 의미 사전(임베딩 모델, 약 450MB)'을 각자 메모리에 올려두고 있었습니다. 로빙 10개가 동시에 작동하면, 똑같은 사전 10개를 펴놓는 것과 같아 약 4.5GB의 메모리가 낭비되었습니다.
2.  **느린 시작 시간**: 각 로빙이 부팅될 때마다 이 무거운 사전을 펼치는 데(모델 로드) 5~10초의 시간이 걸렸습니다.
3.  **어려운 업데이트**: 사전의 새 버전이 나올 때마다, 모든 로빙의 사전을 일일이 교체하고 재시작해야 하는 번거로움이 있었습니다.

### 분리 후 장점: "마을 중앙 도서관을 함께 이용하는 효율"
1.  **메모리 절약**: 이제 마을 중앙 도서관(중앙 임베딩 서비스)에 가장 좋은 사전 한 권만 비치하고, 모든 로빙이 필요할 때마다 와서 찾아봅니다. 메모리 낭비가 사라졌습니다.
2.  **빠른 시작**: 로빙은 더 이상 무거운 사전을 들고 다닐 필요 없이, 가볍게 시작하여 도서관에 질문만 하면 됩니다.
3.  **쉬운 관리**: 새 사전이 나오면, 도서관의 사전 한 권만 교체하면 모든 로빙이 즉시 최신 정보를 이용할 수 있습니다.

## 해결책: 중앙 임베딩 서비스 (skill-embedding)

로빙의 여러 기능 중, '언어 의미 이해'라는 전문적인 역할을 별도의 **마이크로서비스(Microservice)**, 즉 '기능별로 나뉜 전문 서비스'로 분리했습니다.

### 아키텍처: "각자의 집과 중앙 도서관"

```
이전: "모든 집에 두꺼운 백과사전 비치"
┌─────────────┐
│  rb10508의 집  │
│  백과사전 (988MB)   │
└─────────────┘

┌─────────────┐
│   rb8001의 집   │
│  백과사전 (416MB)   │
└─────────────┘

이후: "가벼운 집 + 중앙 도서관"
┌─────────────┐
│  rb10508의 집  │
│   (118MB)   │──┐
└─────────────┘  │
                     │ (API 통신: "이 단어 뜻이 뭐야?")
                     ▼
┌─────────────┐  ┌─────────────────┐
│   rb8001의 집   │  │ 중앙 도서관 (skill-embedding) │
│   (200MB)   │──│   (874.4MB)     │
└─────────────┘  │  - 최고의 백과사전 1권 │
                     │  - 24시간 운영 (Port 8515) │
                     ▼  └─────────────────┘
┌─────────────┐
│  rb10408의 집  │
│   (30MB)    │──┘
└─────────────┘
```

### 기술 스택 선택 이유

- **FastAPI + Uvicorn**: 
  - **왜?** Python 기반의 초고속 도로. 여러 로빙이 동시에 질문해도 막힘없이 처리(비동기)할 수 있습니다.
  
- **ONNX Runtime**: 
  - **왜?** 같은 사전이라도 더 가볍고 빠르게 읽을 수 있게 해주는 기술. 기존 방식(PyTorch)보다 3배 빠르고 메모리는 절반만 사용합니다.
  
- **Ko-SRoBERTa(multitask) SentenceTransformer** → ONNX 변환 (768차원):
  - **왜?** 한국어 대화·명령 코퍼스에 특화돼 의도 분류·콜드메일 필터 정확도가 MiniLM 대비 20pt 이상 향상되었습니다.

### API 설계: "간단한 질문과 명확한 답변"

API는 서비스끼리 대화하는 약속(규칙)입니다. 로빙과 임베딩 서비스는 다음과 같이 간단한 대화를 나눕니다.

```python
# 로빙의 질문 (POST /embed)
{
    "texts": ["안녕하세요", "오늘 날씨가 좋네요"]
}

# 임베딩 서비스의 답변 (숫자 배열)
{
    "embeddings": [
        [0.1, 0.2, ...],  # "안녕하세요"의 의미
        [0.3, 0.4, ...]   # "오늘 날씨가 좋네요"의 의미
    ],
    "model": "jhgan/ko-sroberta-multitask",
    "dimensions": 768
}
```

## 구현 가이드

### 1. 로빙 측 변경사항: "직접 찾지 않고, 물어보기"

```python
# 이전: 로빙이 직접 무거운 사전을 뒤적임 (메모리 낭비)
from onnx_embedder import ONNXEmbedder
embedder = ONNXEmbedder("/models/onnx/...") # 449MB 모델 로드

# 변경: 가볍게 도서관(임베딩 서비스)에 전화해서 물어봄
class HTTPEmbeddingFunction(EmbeddingFunction):
    def __init__(self):
        # 도서관의 전화번호(URL)는 환경에 따라 설정
        self.url = f"{os.getenv('SKILL_EMBEDDING_URL', 'http://localhost:8515')}/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 - 로빙의 기억을 저장하는 코드
# 각 로빙은 자신만의 기억 노트(Collection)를 가짐
self.episodic = self.client.get_or_create_collection(
    name=f"{self.robeing_id}_episodic",
    # 단어의 의미를 물어볼 때는 중앙 도서관(HTTPEmbeddingFunction)을 이용
    embedding_function=HTTPEmbeddingFunction()
)
# 결과: 기억은 각자 독립적으로, 의미 분석은 중앙에서 공유
```

## 성능 분석

### 메모리 절감 (실측 데이터)
```
로빙 이름    | 이전 메모리 | 이후 메모리 | 절감량
------------|------------|------------|--------
rb10508     | 988MB      | 118MB      | -870MB (88%↓)
rb8001      | 416MB      | 200MB      | -216MB (52%↓)  
rb10408     | 55MB       | 30MB       | -25MB  (45%↓)

# 예상 절감 효과
- 로빙 10개 운영 시: 8.7GB 메모리 절약
- 로빙 100개 운영 시: 87GB 메모리 절약 (서버 1대 가격)
```

### 속도 영향
- **내부 통신 시간**: MiniLM 대비 약 +30ms 증가(총 35~40ms)하지만, 의도·콜드메일 정확도 향상 효과가 더 큽니다.

## 교훈

- **전문가의 원리**: 모두가 전문가가 될 필요는 없습니다. '언어 의미 이해'라는 전문적인 작업은 전문가(서비스)에게 맡기고, 나머지는 자신의 역할에 집중하는 것이 효율적입니다.
- **공유와 독립의 균형**: 핵심 엔진(임베딩)은 공유하여 효율을 높이되, 각자의 기억과 개성은 철저히 독립적으로 유지해야 합니다.

---

*"임베딩은 공유하되, 기억은 분리하라"*