DOCS/journey/troubleshooting/250826_happybell80_rb8001_이중저장구현.md
happybell80 0252dd1a7f fix: 51123 서버 IP 주소 업데이트 (성수 이전)
192.168.219.45 → 192.168.0.100 일괄 변경

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 11:52:26 +09:00

7.5 KiB

rb8001 대화 이중 저장 시스템 구현

작성일: 2025-08-26

작성자: happybell80

관련 서비스: rb8001, PostgreSQL, ChromaDB


문제 상황

기존 문제점

  1. 외부 서비스 의존성 제거

    • rb8001이 직접 PostgreSQL 접근하도록 변경
    • 중간 API 계층 제거로 안정성 향상
    • 대화가 ChromaDB와 PostgreSQL 양쪽에 저장
  2. 데이터 일관성 확보

    • ChromaDB: 대화 저장 성공
    • PostgreSQL: 직접 저장으로 안정성 향상
    • 이중 저장으로 데이터 안정성 확보
  3. 성능 개선

    • 불필요한 HTTP API 호출 제거
    • 직접 DB 연결로 응답 속도 향상

조사 과정

1. 로컬 코드 분석

# rb8001 대화 저장 로직 확인
grep -r "save_conversation\|store_memory" rb8001/

# 결과:
# - router.py:309: _save_conversation() 메서드
# - ChromaDB 직접 저장 + PostgreSQL 직접 저장

2. 서버 상태 확인 (51124)

# PostgreSQL 연결 테스트
PGPASSWORD=robeings psql -h 192.168.0.100 -U robeings -d main_db -c "\dt"

# 결과:
# - conversation_log 테이블 존재 ✅
# - robeing_stats 테이블 존재 ✅
# - 직접 연결 가능

3. 데이터베이스 연결 설정

# 직접 연결 설정
192.168.0.100:5432/main_db ✅

해결 방안

1. DATABASE_URL 수정

# rb8001/.env
- DATABASE_URL=postgresql://robeings:<비밀번호>@localhost/rb8001_db
+ DATABASE_URL=postgresql://robeings:<비밀번호>@192.168.0.100:5432/main_db

2. 직접 DB 저장 구현

# rb8001/app/router/router.py:309
async def _save_conversation(self, message: str, response: str, user_id: str, 
                            channel: str, intent: str = None, confidence: float = None):
    """대화를 PostgreSQL과 ChromaDB에 직접 저장"""
    chroma_saved = False
    postgres_saved = False
    
    # 1. ChromaDB에 저장 시도
    try:
        await self.memory_manager.store_memory(...)
        chroma_saved = True
        logger.info(f"ChromaDB: Conversation saved for user {user_id}")
    except Exception as e:
        logger.error(f"ChromaDB save failed: {e}")
    
    # 2. PostgreSQL에 직접 저장 시도
    try:
        from app.state.database import SessionLocal
        db = SessionLocal()
        try:
            conversation_log = ConversationLog(
                robeing_id=settings.ROBEING_ID,
                user_id=user_id,
                channel_id=channel,
                message=message,
                response=response,
                intent=intent,
                confidence=confidence
            )
            db.add(conversation_log)
            db.commit()
            postgres_saved = True
            logger.info(f"PostgreSQL: Conversation saved for user {user_id}")
        finally:
            db.close()
    except Exception as e:
        logger.error(f"PostgreSQL save failed: {e}")
    
    # 최소 하나는 성공해야 함
    if not chroma_saved and not postgres_saved:
        logger.error("Failed to save conversation to both storages")
    elif chroma_saved and postgres_saved:
        logger.info(f"Conversation saved to both storages for user {user_id}")

3. 외부 의존성 제거

  • HTTP API 호출 코드 제거
  • 직접 SQLAlchemy 사용
  • 트랜잭션 처리 추가

구현 결과

장점

  1. 안정성 향상

    • 한쪽 저장소 실패해도 다른 쪽 저장 보장
    • 트랜잭션 단위 에러 처리
  2. 성능 개선

    • 외부 API 호출 제거
    • 네트워크 지연 감소
    • 직접 DB 연결로 빠른 저장
  3. 관리 단순화

    • 중간 서비스 계층 불필요
    • 단일 서비스 내 모든 로직 포함

데이터 흐름

graph LR
    A[Slack 메시지] --> B[rb8001]
    B --> C{저장 로직}
    C --> D[ChromaDB<br/>벡터 저장]
    C --> E[PostgreSQL<br/>구조화 저장]
    D --> F[저장 결과 로깅]
    E --> F

추가 문제 해결 (user_id UUID 타입 불일치)

발견된 문제

  1. PostgreSQL 저장 실패

    PostgreSQL save failed: invalid input syntax for type uuid: "system"
    PostgreSQL save failed: invalid input syntax for type uuid: "U0925SXQFDK"
    
  2. 원인 분석

    • conversation_log 테이블의 user_id가 UUID 타입
    • rb8001은 Slack user ID (문자열) 전송
    • UUID 변환 불가능한 값으로 저장 실패

DB 스키마 수정 (51123 서버)

-- slack_user_id 컬럼 추가
ALTER TABLE conversation_log 
ADD COLUMN slack_user_id VARCHAR(255);

-- user_id를 nullable로 변경
ALTER TABLE conversation_log 
ALTER COLUMN user_id DROP NOT NULL;

rb8001 코드 수정 필요

# router.py:338 수정
conversation_log = ConversationLog(
    robeing_id=settings.ROBEING_ID,
    slack_user_id=user_id,  # user_id 대신 slack_user_id 사용
    channel_id=channel,
    message=message,
    response=response,
    intent=intent,
    confidence=confidence
)

database.py 모델 수정

class ConversationLog(Base):
    """대화 로그 테이블"""
    __tablename__ = "conversation_log"
    
    id = Column(Integer, primary_key=True, index=True)
    robeing_id = Column(String, index=True)
    user_id = Column(String, nullable=True)  # UUID 타입, nullable
    slack_user_id = Column(String)  # Slack ID 저장용
    channel_id = Column(String)
    message = Column(String)
    response = Column(String)
    intent = Column(String)
    confidence = Column(Float)
    timestamp = Column(DateTime, default=datetime.utcnow)

모니터링

로그 확인

# 51124 서버에서
docker logs rb8001 --tail 100 | grep -E "ChromaDB|PostgreSQL|saved"

# 성공 로그 예시:
# ChromaDB: Conversation saved for user U092HLS6BKL
# PostgreSQL: Conversation saved for user U092HLS6BKL
# Conversation saved to both storages for user U092HLS6BKL

DB 확인

-- PostgreSQL 저장 확인
SELECT COUNT(*) FROM conversation_log 
WHERE robeing_id = 'rb8001' 
AND timestamp > NOW() - INTERVAL '1 hour';

-- 최근 대화 확인
SELECT user_id, message, response, timestamp 
FROM conversation_log 
WHERE robeing_id = 'rb8001' 
ORDER BY timestamp DESC 
LIMIT 5;

교훈

1. 아키텍처 단순화

  • 문제: 불필요한 중간 서비스로 인한 복잡도
  • 해결: 직접 연결로 단순화
  • 교훈: KISS 원칙 - Keep It Simple, Stupid

2. 이중 저장의 중요성

  • ChromaDB: 벡터 검색용 (의미 기반 검색)
  • PostgreSQL: 구조화 데이터 (통계, 분석)
  • 교훈: 각 저장소의 장점을 활용한 하이브리드 접근

3. 에러 처리 전략

  • 부분 실패 허용: 한쪽만 성공해도 서비스 계속
  • 명확한 로깅: 어느 저장소가 실패했는지 구분
  • 교훈: Graceful degradation 구현

4. SSH 터널 관리

  • 문제: 잘못된 터널 설정으로 auth_db 연결 시도
  • 해결: main_db로 터널 재설정
  • 교훈: 인프라 설정 문서화 필수

관련 파일

  • /home/happybell/projects/ivada/rb8001/.env - DATABASE_URL 설정
  • /home/happybell/projects/ivada/rb8001/app/router/router.py - 저장 로직
  • /home/happybell/projects/ivada/rb8001/app/state/database.py - DB 모델
  • /home/happybell/projects/ivada/rb8001/app/memory/manager.py - ChromaDB 관리

참고 문서


문서 끝