diff --git a/troubleshooting/250827_claude_conversation_log_user_mapping.md b/troubleshooting/250827_claude_conversation_log_user_mapping.md new file mode 100644 index 0000000..e227c2f --- /dev/null +++ b/troubleshooting/250827_claude_conversation_log_user_mapping.md @@ -0,0 +1,131 @@ +# 대화 로그 사용자 매핑 불일치 문제 + +## 발생일시 +2025-08-27 18:09 KST + +## 문제 상황 +동일한 사용자(김종태/happybell80)가 서로 다른 채널(Slack/웹)로 접속 시 conversation_logs 테이블에 별개 사용자로 저장되는 문제 + +### 증상 +```sql +-- conversation_logs 테이블 데이터 +ID 51: Slack=U0925SXQFDK, UUID=None, Channel=C0920L68267 +ID 52: Slack=None, UUID=1e16e9d5-59f3-54da-a661-8abeabff4230, Channel=web +``` + +같은 사용자임에도: +- Slack 접속: `slack_user_id`만 저장, `user_id`는 NULL +- 웹 접속: `user_id`(UUID)만 저장, `slack_user_id`는 NULL + +## 원인 분석 + +### 1. State Service 저장 로직 +State Service가 사용자 식별자를 통합하지 않고 있음: +- Slack 이벤트: slack_user_id만 전달 +- 웹 요청: JWT의 UUID만 전달 +- 두 식별자를 연결하는 매핑 로직 부재 + +### 2. UUID 생성 규칙 +```python +# Slack ID → UUID 변환 규칙 +namespace = uuid.UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8') +user_uuid = str(uuid.uuid5(namespace, slack_user_id)) + +# 예시 +U0925SXQFDK → 1e16e9d5-59f3-54da-a661-8abeabff4230 +``` + +### 3. 데이터 흐름 +``` +Slack 경로: +Slack Event → rb8001 → State Service → DB (slack_user_id만) + +웹 경로: +Browser → Gateway → rb8001 → State Service → DB (UUID만) +``` + +## 영향도 +- **통계 불일치**: 동일 사용자가 2명으로 계산 +- **대화 연속성 손실**: 채널 간 대화 기록 분리 +- **개인화 실패**: 사용자별 컨텍스트 유지 실패 + +## 해결 방안 + +### Option 1: State Service에서 매핑 (권장) +```python +# State Service의 save_conversation 수정 +async def save_conversation(...): + # slack_user_id가 있으면 UUID로 변환 + if slack_user_id and not user_id: + user_id = slack_id_to_uuid(slack_user_id) + + # UUID가 있으면 slack_user_id 조회 + elif user_id and not slack_user_id: + slack_user_id = await get_slack_id_from_uuid(user_id) +``` + +### Option 2: rb8001에서 사전 변환 +```python +# rb8001의 state_client.save_conversation 호출 전 +if slack_user_id: + user_uuid = slack_id_to_uuid(slack_user_id) +else: + user_uuid = jwt_user_id + slack_user_id = await lookup_slack_id(user_uuid) + +await state_client.save_conversation( + user_id=user_uuid, + slack_user_id=slack_user_id, + ... +) +``` + +### Option 3: DB 트리거로 자동 채우기 +```sql +CREATE OR REPLACE FUNCTION fill_user_mapping() +RETURNS TRIGGER AS $$ +BEGIN + -- slack_user_id만 있으면 user_id 채우기 + IF NEW.slack_user_id IS NOT NULL AND NEW.user_id IS NULL THEN + -- UUID 생성 로직 + NEW.user_id = generate_uuid_from_slack(NEW.slack_user_id); + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +``` + +## 검증 쿼리 +```sql +-- 불일치 건수 확인 +SELECT COUNT(*) as total, + COUNT(user_id) as has_uuid, + COUNT(slack_user_id) as has_slack, + COUNT(*) FILTER (WHERE user_id IS NULL) as missing_uuid, + COUNT(*) FILTER (WHERE slack_user_id IS NULL) as missing_slack +FROM conversation_logs +WHERE timestamp > NOW() - INTERVAL '7 days'; + +-- 동일 사용자 중복 확인 +SELECT u.username, u.email, + COUNT(DISTINCT cl.user_id) as uuid_count, + COUNT(DISTINCT cl.slack_user_id) as slack_count +FROM users u +LEFT JOIN conversation_logs cl ON u.id = cl.user_id +GROUP BY u.username, u.email +HAVING COUNT(DISTINCT cl.slack_user_id) > 1; +``` + +## 교훈 +1. **다채널 시스템에서는 사용자 식별자 통합이 필수** +2. **State Service는 모든 채널의 사용자 매핑을 처리해야 함** +3. **DB 설계 시 다중 식별자 고려 필요** + +## 참고 +- users 테이블: UUID 기반 사용자 정보 +- slack_user_mapping 테이블: Slack-UUID 매핑 (활용 안됨) +- conversation_logs: 실제 대화 저장 테이블 + +--- +*작성: Claude* +*검토 필요: State Service 개발자* \ No newline at end of file