# 백엔드: PostgreSQL, ChromaDB, Vector Memory 설계

## 개요

로빙의 백엔드는 전통적인 관계형 데이터베이스(PostgreSQL)와 벡터 데이터베이스(ChromaDB)를 조합하여, 구조화된 데이터와 의미 기반 검색을 모두 지원합니다. 이를 통해 로빙은 정확한 기록과 유연한 기억을 동시에 가질 수 있습니다.

## 왜 이 기술들을 선택했나?

### PostgreSQL을 선택한 이유

**PostgreSQL = 로빙의 일기장 (정확한 기록)**

1. **신뢰성**: 30년 이상 검증된 데이터베이스, 데이터 손실 거의 없음
2. **ACID 보장**: 모든 거래가 완벽하게 기록되거나 안 되거나 (중간 상태 없음)
3. **복잡한 쿼리**: 여러 테이블을 조합한 복잡한 질문 가능
4. **확장성**: 수백만 건의 데이터도 문제없이 처리

**다른 선택지와 비교**:
- MySQL: 간단하지만 고급 기능 부족
- MongoDB: NoSQL이라 관계 표현 어려움
- SQLite: 동시 접속 제한, 대용량 처리 어려움

### ChromaDB를 선택한 이유

**ChromaDB = 로빙의 연상 기억 (의미 기반 검색)**

1. **벡터 검색**: "비슷한 의미"를 찾아낼 수 있음
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 컬렉션 (의미 기반 데이터)

```python
# 기억 컬렉션 구조
memories_collection = {
    "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"
            }
        }
    ]
}

# 감정 패턴 컬렉션
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
# 매일 자동 백업 (cron)
pg_dump robeing_db > /backup/postgres/robeing_$(date +%Y%m%d).sql

# 특정 사용자만 백업
pg_dump -t users -t robeings --where="user_id='uuid'" > user_backup.sql
```

### 2. ChromaDB 백업

```python
# ChromaDB 컬렉션 백업
def backup_chromadb():
    collections = client.list_collections()
    for collection in collections:
        data = collection.get(include=['embeddings', 'metadatas', 'documents'])
        with open(f'/backup/chroma/{collection.name}.json', 'w') as f:
            json.dump(data, f)
```

## 성능 지표

### 목표 성능

| 작업 | PostgreSQL | ChromaDB |
|-----|-----------|----------|
| 단순 조회 | < 10ms | < 50ms |
| 복잡한 쿼리 | < 100ms | < 200ms |
| 대량 삽입 (1000건) | < 1s | < 2s |
| 동시 접속 | 100+ | 50+ |

### 모니터링

```python
# 성능 모니터링 코드
class PerformanceMonitor:
    def __init__(self):
        self.postgres_latency = []
        self.chroma_latency = []
    
    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
```

## 마이그레이션 전략

### 스키마 버전 관리

```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
async def migrate_to_new_schema():
    """
    구 스키마 → 신 스키마 마이그레이션
    """
    # 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()
```

## 결론

PostgreSQL과 ChromaDB의 조합은 로빙에게 두 가지 중요한 능력을 제공합니다:

1. **PostgreSQL**: 정확한 사실과 기록을 저장하는 "좌뇌"
2. **ChromaDB**: 의미와 맥락을 이해하는 "우뇌"

이 두 시스템이 협력하여 로빙은 단순한 데이터 저장을 넘어, 진정한 "기억"과 "이해"를 가진 디지털 동료가 됩니다.

*참고: 상세한 임베딩 서비스 분리 전략은 [370번 문서](./370_임베딩_서비스_분리_아키텍처.md) 참조*