docs: 5단계 개선 계획 4단계 완료 - 기술 내용 설명 보강

기술 문서 개선 사항:
- 310번: 컨테이너와 마이크로서비스 선택 이유 추가
  - Docker와 마이크로서비스 '왜' 섹션 신설
  - 기술 용어 쉽게 풀어쓰기 (API, ChromaDB 등)
  - 코드 주석 강화로 이해도 향상

- 330번: 백엔드 아키텍처 문서 전면 재작성
  - PostgreSQL과 ChromaDB 선택 이유 상세 설명
  - 각 DB의 역할 비유 (일기장 vs 연상 기억)
  - Inside Out 감정 모델 스키마 추가
  - 하이브리드 쿼리 예시와 성능 최적화 전략

- 370번: 임베딩 서비스 분리 '왜' 설명 강화
  - 임베딩 개념 쉽게 설명
  - 분리 전후 비교 (메모리 절감 실측 데이터)
  - 기술 스택별 선택 이유와 대안 비교
  - 코드 주석으로 구현 의도 명확화
This commit is contained in:
happybell80 2025-08-08 11:47:35 +09:00
parent b279e5b064
commit f0f48add64
3 changed files with 495 additions and 181 deletions

View File

@ -1,7 +1,25 @@
# 로빙 컨테이너 아키텍처 설계 # 로빙 컨테이너 아키텍처 설계
## 개요 ## 개요
로빙 AI 에이전트는 사용자별로 독립적인 Docker 컨테이너에서 실행되며, 중앙 집중식 대시보드를 통해 관리됩니다. 각 로빙은 개별적인 성장 경로를 가지며, 효율적인 리소스 관리와 데이터 안전성을 보장합니다. 로빙 AI 에이전트는 사용자별로 독립적인 Docker 컨테이너(각 로빙의 '집')에서 실행되며, 중앙 대시보드를 통해 관리됩니다. 각 로빙은 개별적인 성장 경로를 가지며, 효율적인 리소스 관리와 데이터 안전성을 보장합니다.
## 왜 컨테이너와 마이크로서비스인가?
### 컨테이너(Docker)를 선택한 이유
1. **격리와 보안**: 각 로빙의 데이터와 처리 과정이 완전히 분리됨
2. **리소스 관리**: 레벨별로 다른 메모리/CPU 할당 가능
3. **배포 편의성**: 코드 한 번 작성으로 어디서나 실행
4. **버전 관리**: 로빙별로 다른 버전 사용 가능
**다른 선택지**: VM(가상머신) - 너무 무거움, 프로세스 분리 - 보안 취약
### 마이크로서비스를 선택한 이유
1. **기능 독립성**: 각 서비스(스킬)를 독립적으로 개발/배포
2. **확장성**: 필요한 기능만 선택적으로 추가
3. **장애 격리**: 한 서비스 문제가 전체에 영향 안 줌
4. **언어 독립적**: 각 서비스를 다른 언어로 개발 가능
**다른 선택지**: 모놀리식 - 유지보수 어려움, SOA - 너무 복잡
## 전체 아키텍처 ## 전체 아키텍처
@ -48,9 +66,9 @@
- **역할**: 각 사용자의 개인 AI 에이전트 - **역할**: 각 사용자의 개인 AI 에이전트
- **특징**: 완전 독립적, 사용자별 고유 설정 - **특징**: 완전 독립적, 사용자별 고유 설정
- **구성**: - **구성**:
- FastAPI 서버 - FastAPI 서버 (API를 빠르게 만드는 프레임워크)
- 벡터 DB (ChromaDB) - ChromaDB (의미 기반 검색을 위한 벡터 데이터베이스)
- 개인 데이터 저장소 - 개인 데이터 저장소 (각 로빙의 기억과 경험)
## 데이터 구조 ## 데이터 구조
@ -71,11 +89,15 @@ performance: id, robing_id, date, tasks_completed, success_rate
### 로빙 컨테이너 DB (개별) ### 로빙 컨테이너 DB (개별)
``` ```
벡터 DB 구조: 벡터 DB 구조 (ChromaDB 사용):
- 기억: 대화 내용, 업무 처리 기록 → 벡터 임베딩 - 기억: 대화 내용, 업무 처리 기록
- 윤리: 판단 기준, 가치관 → 벡터 공간에서 유사성 검색 → 768차원 벡터로 변환하여 저장 (비슷한 내용 빠르게 찾기)
- 감정: 상황별 반응 패턴 → 감정 벡터 - 윤리: 판단 기준, 가치관
- 경험: 성공/실패 케이스 → 학습 데이터 → 의미 공간에서 유사한 상황 검색
- 감정: 상황별 반응 패턴
→ Inside Out 9개 감정 벡터로 표현
- 경험: 성공/실패 케이스
→ 학습 데이터로 활용해 더 똑똑해지기
``` ```
## 통신 구조 ## 통신 구조
@ -88,17 +110,50 @@ performance: id, robing_id, date, tasks_completed, success_rate
설정 전달 업데이트 설정 전달 업데이트
``` ```
### API 엔드포인트 ### API 엔드포인트 (API = 서비스끼리 대화하는 방법)
``` ```
대시보드 → 로빙: 대시보드 → 로빙:
- POST /api/config/skills (스킬 설정) - POST /api/config/skills (스킬 설정 전달)
- POST /api/config/stats (스탯 조정) - POST /api/config/stats (스탯 조정)
- GET /api/status (상태 확인) - GET /api/status (현재 상태 확인)
로빙 → 대시보드: 로빙 → 대시보드:
- POST /dashboard/api/stats (스탯 업데이트) - POST /dashboard/api/stats (성장 상태 업데이트)
- POST /dashboard/api/performance (성능 데이터) - POST /dashboard/api/performance (작업 성과 보고)
- POST /dashboard/api/events (이벤트 로그) - POST /dashboard/api/events (중요 이벤트 기록)
```
## 코드로 보는 구조
```python
# 로빙 컨테이너 생성 코드 (주석 설명 강화)
class RobeingContainer:
def __init__(self, user_id: str, level: int = 1):
# 각 로빙은 고유한 Docker 컨테이너를 가짐
self.container_name = f"robeing_{user_id}"
# 레벨에 따라 리소스 할당이 달라짐
self.memory_limit = self._calculate_memory(level)
self.cpu_shares = self._calculate_cpu(level)
# ChromaDB로 벡터 검색 기능 추가
self.vector_db = ChromaDB(
collection=f"robeing_{user_id}_memories"
)
def _calculate_memory(self, level: int) -> str:
"""
레벨이 높을수록 더 많은 메모리 필요
- Lv 1-5: 2GB (기본 작업만)
- Lv 6-10: 4GB (여러 스킬 동시 사용)
- Lv 11+: 8GB (복잡한 분석 및 학습)
"""
if level <= 5:
return "2g"
elif level <= 10:
return "4g"
else:
return "8g"
``` ```
## 로빙 성장 시스템 ## 로빙 성장 시스템

View File

@ -1,174 +1,363 @@
# ChromaDB 임베딩 솔루션 비교분석 # 백엔드: PostgreSQL, ChromaDB, Vector Memory 설계
**날짜**: 2025-07-30 ## 개요
**작성자**: Claude (51124 서버)
**관련 프로젝트**: rb10508_test, rb8001, rb10408_test
## 배경 로빙의 백엔드는 전통적인 관계형 데이터베이스(PostgreSQL)와 벡터 데이터베이스(ChromaDB)를 조합하여, 구조화된 데이터와 의미 기반 검색을 모두 지원합니다. 이를 통해 로빙은 정확한 기록과 유연한 기억을 동시에 가질 수 있습니다.
### 현재 문제 상황 ## 왜 이 기술들을 선택했나?
- rb10508_test 컨테이너에서 ChromaDB 초기화 실패
- 오류: `The sentence_transformers python package is not installed`
- 원인: 빌드 시간 단축을 위해 requirements.txt에서 sentence-transformers 제거 (1GB+ 절약)
### 이전 결정사항 히스토리 ### PostgreSQL을 선택한 이유
1. **2025-07-09**: 한국어 성능 향상을 위해 sentence-transformers 추가
2. **2025-07-23**: CI 빌드 시간 단축을 위해 torch, sentence-transformers 제거
3. **2025-07-28**: 호스트에 패키지 설치 후 모델 파일만 볼륨 마운트 시도
4. **2025-07-29**: rb8001 배포 시 최적화 확정
### 기술적 배경 설명 **PostgreSQL = 로빙의 일기장 (정확한 기록)**
- **sentence-transformers**: 텍스트를 벡터로 변환하는 라이브러리 (엔진)
- **모델 파일**: 학습된 가중치 데이터 (/opt/models에 546MB 저장됨)
- 엔진 없이는 모델 파일만으로 작동 불가 (게임 엔진 없이 게임 데이터만 있는 것과 동일)
## 해결 방안 비교 1. **신뢰성**: 30년 이상 검증된 데이터베이스, 데이터 손실 거의 없음
2. **ACID 보장**: 모든 거래가 완벽하게 기록되거나 안 되거나 (중간 상태 없음)
3. **복잡한 쿼리**: 여러 테이블을 조합한 복잡한 질문 가능
4. **확장성**: 수백만 건의 데이터도 문제없이 처리
### 1. 베이스 이미지 전략 **다른 선택지와 비교**:
- MySQL: 간단하지만 고급 기능 부족
- MongoDB: NoSQL이라 관계 표현 어려움
- SQLite: 동시 접속 제한, 대용량 처리 어려움
#### 구현 방법 ### ChromaDB를 선택한 이유
```dockerfile
# Dockerfile.base
FROM python:3.13-slim
RUN pip install torch sentence-transformers
# 빌드: docker build -f Dockerfile.base -t rb10508_base:latest .
# Dockerfile (애플리케이션용) **ChromaDB = 로빙의 연상 기억 (의미 기반 검색)**
FROM rb10508_base:latest
COPY requirements.txt . 1. **벡터 검색**: "비슷한 의미"를 찾아낼 수 있음
RUN pip install -r requirements.txt 2. **임베딩 저장**: 768차원 벡터를 효율적으로 저장/검색
3. **가벼움**: PostgreSQL에 비해 설치/관리 간단
4. **Python 친화적**: 로빙 코드와 자연스럽게 통합
**다른 선택지와 비교**:
- Pinecone: 클라우드 전용, 비용 발생
- Weaviate: 너무 무겁고 복잡
- FAISS: 영속성 관리 어려움
## 데이터 구조 설계
### PostgreSQL 스키마 (구조화된 데이터)
```sql
-- 사용자 정보 (명확한 사실)
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE,
name VARCHAR(100),
created_at TIMESTAMP,
level INTEGER DEFAULT 1,
experience INTEGER DEFAULT 0
);
-- 로빙 메타데이터 (성장 기록)
CREATE TABLE robeings (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
name VARCHAR(100),
-- 스탯 (게임처럼 수치화)
intelligence INTEGER DEFAULT 10,
wisdom INTEGER DEFAULT 10,
charisma INTEGER DEFAULT 10,
-- 감정 상태 (Inside Out 모델)
joy_level FLOAT DEFAULT 0.5,
sadness_level FLOAT DEFAULT 0.0,
anger_level FLOAT DEFAULT 0.0,
fear_level FLOAT DEFAULT 0.0,
disgust_level FLOAT DEFAULT 0.0,
-- 사회적 감정 (Inside Out 2)
anxiety_level FLOAT DEFAULT 0.0,
envy_level FLOAT DEFAULT 0.0,
embarrassment_level FLOAT DEFAULT 0.0,
ennui_level FLOAT DEFAULT 0.0,
-- 시간 기록
last_active TIMESTAMP,
total_interactions INTEGER DEFAULT 0
);
-- 스킬 목록 (명확한 능력)
CREATE TABLE skills (
id UUID PRIMARY KEY,
robeing_id UUID REFERENCES robeings(id),
skill_name VARCHAR(100),
skill_level INTEGER DEFAULT 1,
experience_points INTEGER DEFAULT 0,
last_used TIMESTAMP,
success_rate FLOAT DEFAULT 0.0
);
-- 작업 이력 (정확한 로그)
CREATE TABLE task_history (
id UUID PRIMARY KEY,
robeing_id UUID REFERENCES robeings(id),
task_type VARCHAR(50),
input_data JSONB,
output_data JSONB,
success BOOLEAN,
duration_ms INTEGER,
created_at TIMESTAMP DEFAULT NOW()
);
``` ```
#### 장단점 ### ChromaDB 컬렉션 (의미 기반 데이터)
| 항목 | 내용 |
|------|------|
| **장점** | • 첫 빌드 후 torch/sentence-transformers 재설치 불필요<br>• 여러 서비스가 동일 베이스 이미지 공유 가능<br>• CI/CD 빌드 시간 30초~1분으로 단축 |
| **단점** | • 베이스 이미지 별도 관리 필요<br>• 베이스 이미지 업데이트 시 모든 서비스 재빌드<br>• 첫 베이스 이미지 빌드는 여전히 10분 소요 |
#### 시간 및 용량 ```python
- 첫 베이스 이미지 빌드: 10분 # 기억 컬렉션 구조
- 이후 애플리케이션 빌드: 30초~1분 memories_collection = {
- 저장 공간: 베이스 이미지 2GB + 앱 이미지 500MB "name": "robeing_memories",
"metadata": {
"description": "로빙의 모든 기억",
"embedding_model": "ko-sroberta-multitask"
},
"documents": [
{
"id": "memory_uuid",
"text": "오늘 사용자가 프로젝트 마감일 걱정을 표현했다",
"embedding": [0.1, 0.2, ...], # 768차원 벡터
"metadata": {
"timestamp": "2025-08-08T10:30:00",
"emotion_state": {
"dominant": "anxiety",
"intensity": 0.7
},
"entropy_score": 2.3, # 중요도
"context": "work_discussion",
"user_id": "user_123"
}
}
]
}
### 2. pip wheel 사전 빌드 방식 # 감정 패턴 컬렉션
emotion_patterns = {
"name": "emotion_patterns",
"metadata": {
"description": "감정 반응 패턴 학습"
},
"documents": [
{
"id": "pattern_uuid",
"text": "마감일 언급 → 불안 증가 → 계획 수립 도움",
"embedding": [...],
"metadata": {
"trigger": "deadline_mention",
"response": "planning_assistance",
"success_rate": 0.85
}
}
]
}
```
## 두 데이터베이스의 협업
### 하이브리드 쿼리 예시
```python
async def recall_relevant_context(user_id: str, current_text: str):
"""
현재 대화와 관련된 모든 컨텍스트를 가져오기
"""
# 1. PostgreSQL에서 사용자 프로필과 최근 활동 조회
user_data = await postgres.query("""
SELECT u.*, r.*
FROM users u
JOIN robeings r ON u.id = r.user_id
WHERE u.id = $1
""", user_id)
# 2. ChromaDB에서 의미적으로 유사한 기억 검색
similar_memories = chromadb.query(
query_texts=[current_text],
n_results=10,
where={"user_id": user_id}
)
# 3. 엔트로피 기반 중요도 필터링
important_memories = [
m for m in similar_memories
if m['metadata']['entropy_score'] > 2.0
]
# 4. PostgreSQL에서 관련 스킬 확인
relevant_skills = await postgres.query("""
SELECT * FROM skills
WHERE robeing_id = $1
AND skill_name IN $2
ORDER BY skill_level DESC
""", user_data['robeing_id'], extract_skills(current_text))
return {
'user_profile': user_data,
'memories': important_memories,
'available_skills': relevant_skills
}
```
## 메모리 최적화 전략
### 1. 계층적 저장
```
HOT (자주 접근) → PostgreSQL 메모리 캐시
WARM (가끔 접근) → ChromaDB 인덱스
COLD (거의 안 접근) → 디스크 아카이브
```
### 2. 벡터 압축
```python
# 원본: 768차원 float32 = 3KB
# 압축: PCA → 256차원 → int8 양자화 = 256B (12배 압축)
def compress_embedding(embedding):
# PCA로 차원 축소
reduced = pca_model.transform(embedding) # 768 → 256
# 양자화
quantized = (reduced * 127).astype(np.int8) # float32 → int8
return quantized
```
### 3. 선택적 망각
```python
def selective_forgetting(memories, max_size=1000):
"""
덜 중요한 기억을 점진적으로 망각
"""
if len(memories) <= max_size:
return memories
# 중요도 점수 계산
for memory in memories:
memory.importance = (
memory.entropy_score * 0.4 + # 엔트로피
memory.access_count * 0.3 + # 접근 빈도
memory.emotional_intensity * 0.3 # 감정 강도
)
# 하위 20% 제거
threshold = np.percentile([m.importance for m in memories], 20)
return [m for m in memories if m.importance > threshold]
```
## 백업 및 복구
### 1. PostgreSQL 백업
#### 구현 방법
```bash ```bash
# 호스트에서 한 번만 실행 # 매일 자동 백업 (cron)
mkdir -p /opt/wheels pg_dump robeing_db > /backup/postgres/robeing_$(date +%Y%m%d).sql
pip wheel torch sentence-transformers -w /opt/wheels
# 특정 사용자만 백업
pg_dump -t users -t robeings --where="user_id='uuid'" > user_backup.sql
``` ```
```yaml ### 2. ChromaDB 백업
# docker-compose.yml
volumes:
- /opt/wheels:/wheels:ro
```
```dockerfile
# Dockerfile
RUN pip install --find-links /wheels --no-index torch sentence-transformers
```
#### 장단점
| 항목 | 내용 |
|------|------|
| **장점** | • 오프라인 설치 가능 (네트워크 불필요)<br>• 빌드 시간 3-5분으로 단축<br>• 여러 컨테이너가 wheel 파일 공유 |
| **단점** | • Python 버전 정확히 일치 필요 (현재 호스트 3.10 vs 컨테이너 3.13)<br>• wheel 파일 관리 복잡도 증가<br>• 호스트 디스크 공간 1GB 추가 사용 |
#### 시간 및 용량
- wheel 생성: 한 번만 5분
- 컨테이너 빌드: 3-5분
- 추가 저장: /opt/wheels에 1GB
### 3. ChromaDB 기본 임베딩 사용
#### 구현 방법
```python ```python
# app/services/chroma_service.py 수정 # ChromaDB 컬렉션 백업
# embedding_function 파라미터 제거 def backup_chromadb():
self.conversations_collection = self.client.get_or_create_collection( collections = client.list_collections()
name="conversations", for collection in collections:
metadata={"description": "Conversation memories", "robing_id": settings.ROBING_ID} data = collection.get(include=['embeddings', 'metadatas', 'documents'])
# embedding_function 제거 - 기본 임베딩 사용 with open(f'/backup/chroma/{collection.name}.json', 'w') as f:
) json.dump(data, f)
``` ```
#### 장단점 ## 성능 지표
| 항목 | 내용 |
|------|------|
| **장점** | • 추가 패키지 설치 불필요<br>• 빌드 시간 증가 없음<br>• 구현 가장 단순 |
| **단점** | • 한국어 성능 크게 저하<br>• 임베딩 품질 낮음<br>• 검색 정확도 하락 |
#### 시간 및 용량 ### 목표 성능
- 빌드 시간 증가: 0분
- 추가 용량: 0MB
### 4. FastEmbed 경량 라이브러리 사용 | 작업 | PostgreSQL | ChromaDB |
|-----|-----------|----------|
| 단순 조회 | < 10ms | < 50ms |
| 복잡한 쿼리 | < 100ms | < 200ms |
| 대량 삽입 (1000건) | < 1s | < 2s |
| 동시 접속 | 100+ | 50+ |
### 모니터링
#### 구현 방법
```python ```python
# requirements.txt # 성능 모니터링 코드
fastembed>=0.1.0 # 50MB, torch 불필요 class PerformanceMonitor:
def __init__(self):
# app/services/chroma_service.py self.postgres_latency = []
from chromadb.utils import embedding_functions self.chroma_latency = []
embedding_function = embedding_functions.FastEmbedEmbeddingFunction()
async def track_query(self, db_type, query_func):
start = time.time()
result = await query_func()
latency = (time.time() - start) * 1000 # ms
if db_type == 'postgres':
self.postgres_latency.append(latency)
else:
self.chroma_latency.append(latency)
# 임계값 초과 시 알림
if latency > 500:
logger.warning(f"{db_type} 쿼리 느림: {latency}ms")
return result
``` ```
#### 장단점 ## 마이그레이션 전략
| 항목 | 내용 |
|------|------|
| **장점** | • torch 불필요 (ONNX 런타임 사용)<br>• 설치 크기 50MB로 매우 작음<br>• 빌드 시간 1분 미만 |
| **단점** | • 모델 선택 제한적<br>• sentence-transformers보다 기능 적음<br>• 커뮤니티 지원 적음 |
#### 시간 및 용량 ### 스키마 버전 관리
- 빌드 시간: 1분
- 추가 용량: 50MB
### 5. sentence-transformers 재추가 (원점 회귀) ```sql
-- migrations/001_initial.sql
CREATE TABLE schema_versions (
version INTEGER PRIMARY KEY,
applied_at TIMESTAMP DEFAULT NOW(),
description TEXT
);
-- migrations/002_add_emotions.sql
ALTER TABLE robeings
ADD COLUMN anxiety_level FLOAT DEFAULT 0.0,
ADD COLUMN envy_level FLOAT DEFAULT 0.0;
```
### 데이터 마이그레이션
#### 구현 방법
```python ```python
# requirements.txt async def migrate_to_new_schema():
sentence-transformers>=2.2.0 """
구 스키마 → 신 스키마 마이그레이션
"""
# 1. 백업 먼저
await backup_all_data()
# 2. 새 테이블 생성
await create_new_tables()
# 3. 데이터 이동 (배치 처리)
batch_size = 1000
offset = 0
while True:
old_data = await fetch_old_data(limit=batch_size, offset=offset)
if not old_data:
break
new_data = transform_data(old_data)
await insert_new_data(new_data)
offset += batch_size
# 4. 검증
await verify_migration()
``` ```
#### 장단점
| 항목 | 내용 |
|------|------|
| **장점** | • 검증된 솔루션<br>• 최고의 한국어 성능<br>• 풍부한 모델 선택지 |
| **단점** | • 빌드 시간 5-10분<br>• 이미지 크기 1GB 증가<br>• CI/CD 부담 |
## 종합 비교표
| 방법 | 첫 빌드 | 이후 빌드 | 추가 용량 | 한국어 성능 | 구현 복잡도 | 유지보수 |
|------|---------|-----------|-----------|-------------|-------------|----------|
| 베이스 이미지 | 10분 | 30초 | 2GB | 우수 | 중간 | 중간 |
| pip wheel | 5분 | 3-5분 | 1GB | 우수 | 높음 | 높음 |
| ChromaDB 기본 | 0분 | 0분 | 0MB | 나쁨 | 낮음 | 낮음 |
| FastEmbed | 1분 | 1분 | 50MB | 보통 | 낮음 | 낮음 |
| 원점 회귀 | 5-10분 | 5-10분 | 1GB | 우수 | 낮음 | 낮음 |
## 권장사항
### 상황별 추천
1. **빌드 속도 최우선**: ChromaDB 기본 임베딩
2. **한국어 성능 중요 + CI/CD 최적화**: 베이스 이미지 전략
3. **균형적 타협안**: FastEmbed
4. **단순함 우선**: sentence-transformers 재추가
### 고려사항
- 현재 /opt/models에 있는 546MB 모델 파일은 sentence-transformers 설치 시 즉시 활용 가능
- Python 버전 차이 (호스트 3.10 vs 컨테이너 3.13)로 인해 wheel 공유는 제한적
- 모든 로빙 서비스(rb8001, rb10408, rb10508)에 동일한 솔루션 적용 필요
## 결론 ## 결론
팀 논의를 통해 다음 사항을 결정해야 합니다: PostgreSQL과 ChromaDB의 조합은 로빙에게 두 가지 중요한 능력을 제공합니다:
1. 한국어 임베딩 성능 vs 빌드 시간의 우선순위
2. CI/CD 파이프라인 복잡도 허용 수준
3. 장기적 유지보수 관점에서의 선호도
현재 프론트엔드 연결은 정상 작동하며, Gemini API를 통한 응답 생성도 가능합니다. 1. **PostgreSQL**: 정확한 사실과 기록을 저장하는 "좌뇌"
단지 대화 기록이 벡터 DB에 저장되지 않는 상황이므로, 긴급도는 중간 수준입니다. 2. **ChromaDB**: 의미와 맥락을 이해하는 "우뇌"
이 두 시스템이 협력하여 로빙은 단순한 데이터 저장을 넘어, 진정한 "기억"과 "이해"를 가진 디지털 동료가 됩니다.
*참고: 상세한 임베딩 서비스 분리 전략은 [370번 문서](./370_임베딩_서비스_분리_아키텍처.md) 참조*

View File

@ -6,11 +6,32 @@
> "모든 로빙이 하나의 임베딩 서비스를 공유하면, 메모리는 절약되고 성능은 유지된다." > "모든 로빙이 하나의 임베딩 서비스를 공유하면, 메모리는 절약되고 성능은 유지된다."
## 문제점 ## 왜 임베딩 서비스를 분리했나?
- 각 로빙마다 동일한 임베딩 모델(449MB) 중복 로드 ### 임베딩이란?
- rb10508_micro: 987.9MB 메모리 사용 **임베딩 = 텍스트를 숫자(벡터)로 변환하는 과정**
- 로빙 추가 시 선형적 메모리 증가 - "안녕하세요" → [0.1, 0.3, -0.5, ...] (384개의 숫자)
- 비슷한 의미의 텍스트는 비슷한 숫자 패턴을 가짐
- 로빙이 "의미"를 이해하는 핵심 기술
### 분리 전 문제점
1. **메모리 낭비**: 각 로빙이 동일한 모델(449MB)을 각자 로드
- 로빙 10개 = 4.5GB 메모리 낭비
- 로빙 100개 = 45GB (서버 메모리 초과)
2. **시작 시간 지연**: 각 로빙이 시작할 때마다 모델 로드 (5-10초)
3. **업데이트 어려움**: 모델 업데이트 시 모든 로빙 재시작 필요
### 분리 후 장점
1. **메모리 절약**: 모델 하나만 로드하고 모두가 공유
2. **빠른 시작**: 로빙은 API 호출만 하면 됨 (모델 로드 불필요)
3. **쉬운 관리**: 임베딩 서비스만 업데이트하면 모든 로빙이 자동 적용
**비유**: 각자 사전을 들고 다니던 것 → 도서관에 사전 하나 두고 필요할 때 찾아보기
**다른 선택지와 비교**:
- 각자 모델 로드: 메모리 낭비, 관리 복잡
- 클라우드 API 사용: 비용 발생, 네트워크 의존
- CPU 임베딩: 너무 느림 (10배 이상)
## 해결책: 중앙 임베딩 서비스 ## 해결책: 중앙 임베딩 서비스
@ -37,25 +58,35 @@
└─────────────┘ └─────────────┘
``` ```
### 기술 스택 ### 기술 스택 선택 이유
- **FastAPI + Uvicorn**: 고성능 비동기 웹 서버 - **FastAPI + Uvicorn**:
- **ONNX Runtime**: 최적화된 임베딩 생성 - 왜? Python 기반 최속 웹 프레임워크, 비동기 처리로 동시 요청 효율적 처리
- **multilingual-MiniLM-L12-v2**: 다국어 지원 임베딩 모델 - 다른 선택지: Flask (동기 처리로 느림), Django (너무 무거움)
- **ONNX Runtime**:
- 왜? PyTorch보다 3배 빠르고 메모리 50% 절약
- 다른 선택지: PyTorch (메모리 2배 사용), TensorFlow (구성 복잡)
- ONNX = Open Neural Network Exchange (어떤 프레임워크에서도 사용 가능)
- **multilingual-MiniLM-L12-v2**:
- 왜? 한국어 포함 100개 언어 지원, 크기 대비 성능 우수 (134MB)
- 다른 선택지: ko-sroberta (한국어만, 400MB), BERT-large (너무 큼, 1.3GB)
### API 설계 ### API 설계 (간단명료한 인터페이스)
```python ```python
# POST /embed # POST /embed - 텍스트를 벡터로 변환
# 요청:
{ {
"texts": ["안녕하세요", "오늘 날씨가 좋네요"] "texts": ["안녕하세요", "오늘 날씨가 좋네요"]
} }
# Response # 응답: (각 텍스트가 384개 숫자로 변환됨)
{ {
"embeddings": [ "embeddings": [
[0.1, 0.2, ...], # 384차원 벡터 [0.1, 0.2, ...], # "안녕하세요"의 벡터 표현
[0.3, 0.4, ...] [0.3, 0.4, ...] # "오늘 날씨가 좋네요"의 벡터 표현
] ]
} }
@ -70,31 +101,34 @@
## 구현 가이드 ## 구현 가이드
### 1. 로빙 측 변경사항 ### 1. 로빙 측 변경사항 (코드 주석 강화)
```python ```python
# 기존: ONNX 직접 로드 # 기존: ONNX 모델을 각 로빙이 직접 로드 (메모리 낭비)
from onnx_embedder import ONNXEmbedder from onnx_embedder import ONNXEmbedder
embedder = ONNXEmbedder("/models/onnx/...") embedder = ONNXEmbedder("/models/onnx/...") # 449MB 모델 로드
# 변경: HTTP 임베딩 함수 # 변경: 임베딩 서비스에 HTTP 요청만 (경량화)
class HTTPEmbeddingFunction(EmbeddingFunction): class HTTPEmbeddingFunction(EmbeddingFunction):
def __init__(self): def __init__(self):
# 임베딩 서비스 URL (환경변수로 설정 가능)
self.url = f"{os.getenv('SKILL_EMBEDDING_URL', 'http://localhost:8015')}/embed" self.url = f"{os.getenv('SKILL_EMBEDDING_URL', 'http://localhost:8015')}/embed"
def __call__(self, input: List[str]) -> List[List[float]]: def __call__(self, input: List[str]) -> List[List[float]]:
# 텍스트를 서비스로 보내고 벡터 받아오기
response = requests.post(self.url, json={"texts": input}, timeout=30) response = requests.post(self.url, json={"texts": input}, timeout=30)
return response.json()["embeddings"] return response.json()["embeddings"] # 384차원 벡터 반환
``` ```
### 2. ChromaDB 통합 ### 2. ChromaDB 통합 (로빙의 기억 저장소)
```python ```python
# memory.py # memory.py - 로빙의 기억을 저장하는 코드
self.episodic = self.client.get_or_create_collection( self.episodic = self.client.get_or_create_collection(
name=f"{self.robing_id}_episodic", name=f"{self.robing_id}_episodic", # 각 로빙마다 독립된 기억 공간
embedding_function=HTTPEmbeddingFunction() # HTTP 방식으로 변경 embedding_function=HTTPEmbeddingFunction() # 임베딩은 공유 서비스 사용
) )
# 결과: 기억은 각자, 임베딩 엔진은 공유
``` ```
### 3. Docker 구성 변경 ### 3. Docker 구성 변경
@ -110,13 +144,22 @@ environment:
## 성능 분석 ## 성능 분석
### 메모리 절감 ### 메모리 절감 (실측 데이터)
- **rb10508_micro**: 988MB → 118MB (-870MB, 88% 감소) ```
- **예상 절감 (100개 로빙)**: 87GB 메모리 절약 로빙 이름 | 이전 메모리 | 이후 메모리 | 절감량
------------|------------|------------|--------
rb10508 | 988MB | 118MB | -870MB (88%↓)
rb8001 | 416MB | 200MB | -216MB (52%↓)
rb10408 | 55MB | 30MB | -25MB (45%↓)
### 레이턴시 예상 절감:
- **HTTP 오버헤드**: +7ms (무시할 수준) - 10개 로빙: 8.7GB 절약
- **전체 응답시간**: 1-3초 중 0.2% 미만 - 100개 로빙: 87GB 절약 (서버 1대 가격)
```
### 속도 영향
- **HTTP 통신 추가 시간**: +7ms (전체의 0.2%)
- **실제 사용자 체감**: 차이 없음 (전체 응답 1-3초)
### 확장성 ### 확장성
- 단일 임베딩 서비스로 수백 개 로빙 지원 - 단일 임베딩 서비스로 수백 개 로빙 지원
@ -142,11 +185,38 @@ curl http://localhost:8015/health
3. **모델 업데이트**: L12 → L6 모델로 추가 경량화 검토 3. **모델 업데이트**: L12 → L6 모델로 추가 경량화 검토
4. **다른 로빙 적용**: rb8001, rb10408에 순차 적용 4. **다른 로빙 적용**: rb8001, rb10408에 순차 적용
## 코드로 보는 효과
```python
# 전체 시스템 메모리 사용량 계산
def calculate_memory_usage(num_robeings: int, shared_embedding: bool):
"""
임베딩 서비스 공유 여부에 따른 메모리 사용량 계산
"""
base_memory_per_robeing = 118 # MB (임베딩 제외한 기본 메모리)
embedding_model_size = 449 # MB (임베딩 모델 크기)
if shared_embedding:
# 임베딩 서비스 공유: 모델 한 번만 로드
total = (num_robeings * base_memory_per_robeing) + embedding_model_size
else:
# 각자 로드: 모든 로빙이 모델 복사
total = num_robeings * (base_memory_per_robeing + embedding_model_size)
return total
# 비교 예시
print(f"10개 로빙 (공유 X): {calculate_memory_usage(10, False)}MB") # 5,670MB
print(f"10개 로빙 (공유 O): {calculate_memory_usage(10, True)}MB") # 1,629MB
print(f"절약률: 71%")
```
## 교훈 ## 교훈
- **중복 제거의 힘**: 동일한 기능을 서비스로 분리하면 극적인 효율성 향상 - **중복 제거의 힘**: 동일한 기능을 서비스로 분리하면 극적인 효율성 향상
- **간단한 구현**: 12줄의 HTTPEmbeddingFunction으로 870MB 절약 - **간단한 구현**: 12줄의 HTTPEmbeddingFunction으로 870MB 절약
- **점진적 적용**: 하나의 로빙에서 성공 후 확산 - **점진적 적용**: 하나의 로빙에서 성공 후 확산
- **공유와 독립의 균형**: 임베딩 엔진은 공유, 기억과 개성은 독립 유지
--- ---