# Slack ID 컬럼명 표준화 작업 ## 작성일: 2025-08-26 21:30 ## 작성자: 51123 서버 관리자 ## 상태: 🔴 작업 필요 ## 영향: 데이터 일관성, JOIN 쿼리 성능, 코드 유지보수성 ## 관련: 250826_id_체계_정리_및_conversation_logs_문제_해결.md에서 분리 --- ## 1. 문제 정의 ### 1.1 핵심 문제 동일한 Slack 사용자 ID가 테이블마다 다른 컬럼명으로 저장되어 있음 ### 1.2 현재 상태 | 테이블 | 컬럼명 | 데이터 예시 | 타입 | |--------|--------|------------|------| | slack_user_mapping | **slack_user_id** | U091UNVE41M | VARCHAR(100) | | gmail_tokens | **slack_id** ❌ | U091UNVE41M | VARCHAR(100) | | conversation_logs | **slack_user_id** | U091UNVE41M | VARCHAR(100) | --- ## 2. 시스템 영향도 ### 2.1 데이터 흐름도 ``` [Slack 사용자: U091UNVE41M] │ ├─→ slack_user_mapping.slack_user_id ✅ │ ↓ (JOIN 시 컬럼명 불일치) ├─→ gmail_tokens.slack_id ❌ │ ↓ (JOIN 시 컬럼명 불일치) └─→ conversation_logs.slack_user_id ✅ ``` ### 2.2 JOIN 복잡도 증가 ```sql -- 현재: 컬럼명이 달라서 복잡 SELECT * FROM gmail_tokens g JOIN slack_user_mapping s ON g.slack_id = s.slack_user_id JOIN conversation_logs c ON s.slack_user_id = c.slack_user_id -- 목표: 모든 테이블 동일 컬럼명 SELECT * FROM gmail_tokens g JOIN slack_user_mapping s USING (slack_user_id) JOIN conversation_logs c USING (slack_user_id) ``` --- ## 3. 해결 방안 ### 3.1 작업 순서 #### Phase 1: DB 스키마 변경 (51123 서버) ```sql -- 1. 컬럼 추가 ALTER TABLE gmail_tokens ADD COLUMN slack_user_id VARCHAR(100); -- 2. 데이터 복사 UPDATE gmail_tokens SET slack_user_id = slack_id; -- 3. 제약조건 이동 ALTER TABLE gmail_tokens DROP COLUMN slack_id; -- 4. 인덱스 재생성 CREATE INDEX idx_gmail_tokens_slack_user_id ON gmail_tokens(slack_user_id); ``` #### Phase 2: 코드 수정 (로컬 개발자) | 파일 | 수정 내용 | |------|----------| | auth-server/models.py | slack_id → slack_user_id | | robeing-monitor/database.py | slack_id → slack_user_id | | skill-email/db_client.py | slack_id → slack_user_id | #### Phase 3: 배치 매핑 (선택사항) Slack ID를 가진 사용자를 UUID와 매핑하여 통합 관리 --- ## 4. 현재 데이터 현황 ### 4.1 영향받는 레코드 수 ```sql -- gmail_tokens 테이블 SELECT COUNT(*) FROM gmail_tokens WHERE slack_id IS NOT NULL; -- 결과: 3건 (U091UNVE41M, U0925SXQFDK, U092F7FQ55L) -- slack_user_mapping 테이블 SELECT COUNT(*) FROM slack_user_mapping; -- 결과: 3건 (동일 사용자) ``` ### 4.2 서비스별 영향 | 서비스 | 영향도 | 수정 필요 | |--------|--------|----------| | auth-server | 높음 | Gmail 토큰 조회 로직 | | robeing-monitor | 중간 | 토큰 관리 UI | | skill-email | 높음 | 토큰 검증 로직 | | rb8001 | 낮음 | 이미 slack_user_id 사용 | --- ## 5. 위험 요소 및 대응 ### 5.1 위험 요소 - **서비스 중단**: 컬럼명 변경 중 API 오류 가능 - **데이터 손실**: ALTER TABLE 실행 중 실수 - **캐시 문제**: 기존 ORM 캐시가 old 컬럼명 참조 ### 5.2 대응 방안 1. **백업 우선**: `pg_dump main_db > backup_20250826.sql` 2. **점진적 마이그레이션**: 새 컬럼 추가 → 이중 운영 → 구 컬럼 제거 3. **캐시 초기화**: Docker 재시작으로 ORM 캐시 클리어 --- ## 6. 검증 계획 ### 6.1 변경 전 테스트 ```sql -- 데이터 무결성 확인 SELECT COUNT(*) FROM gmail_tokens WHERE slack_id IS NOT NULL AND slack_user_id IS NULL; ``` ### 6.2 변경 후 검증 - Gmail 토큰 조회 API 테스트 - Slack 사용자 매핑 확인 - JOIN 쿼리 성능 비교 --- ## 7. 예상 효과 ### 7.1 개발 효율성 - **Before**: 테이블마다 다른 컬럼명 기억 필요 - **After**: 모든 곳에서 `slack_user_id` 통일 ### 7.2 쿼리 성능 - **Before**: 복잡한 ON 조건 - **After**: USING 절로 간단한 JOIN ### 7.3 유지보수성 - 신규 개발자 학습 곡선 감소 - 버그 발생 가능성 감소 - 코드 일관성 향상 --- ## 8. 작업 체크리스트 - [ ] PostgreSQL 백업 수행 - [ ] gmail_tokens 테이블 스키마 변경 - [ ] 로컬 개발자에게 코드 수정 지시 - [ ] 테스트 환경에서 검증 - [ ] 프로덕션 배포 - [ ] 모니터링 및 롤백 준비 --- ## 9. 관련 문서 - 원본: `/home/admin/DOCS/troubleshooting/250826_id_체계_정리_및_conversation_logs_문제_해결.md` - 이 문서는 Slack ID 컬럼명 표준화 부분만 분리하여 작성됨 --- ## 10. 뉴스 스킬 키워드 문제 분석 ### 10.1 발견된 문제 **매일 같은 AI 뉴스만 반복되는 현상** #### 원인 분석 1. **하드코딩된 키워드** - 위치: `/home/admin/ivada_project/rb8001/app/skills/dm_skill.py:309` - 코드: `["AI", "기술", "IT"]`로 고정 - 문제: 매일 동일한 키워드로만 검색 2. **환경변수 미활용** - 설정: `NEWS_KEYWORDS=keyword1,keyword2` - 실제: 사용되지 않음 3. **중복 방지 로직의 부작용** - 이미 수집된 뉴스는 건너뜀 - 새 뉴스 없으면 최근 저장된 뉴스 5개 반복 표시 - 결과: 매일 같은 뉴스 표시 ### 10.2 개선 방안 #### 방안 1: 동적 키워드 시스템 ```python # dm_skill.py 수정 예시 import random from datetime import datetime def get_daily_keywords(): """날짜별로 다른 키워드 세트 반환""" keyword_sets = [ ["AI", "인공지능", "ChatGPT"], ["반도체", "삼성", "SK하이닉스"], ["전기차", "테슬라", "현대차"], ["빅테크", "구글", "애플"], ["스타트업", "유니콘", "투자"], ["바이오", "제약", "헬스케어"], ["핀테크", "블록체인", "가상자산"] ] # 요일별로 다른 키워드 세트 사용 day_of_week = datetime.now().weekday() return keyword_sets[day_of_week] # 사용 keywords = get_daily_keywords() # ["AI", "기술", "IT"] 대신 ``` #### 방안 2: DB 기반 키워드 관리 ```sql -- 키워드 설정 테이블 생성 CREATE TABLE news_keywords ( id SERIAL PRIMARY KEY, user_id UUID REFERENCES users(id), slack_user_id VARCHAR(100), keywords TEXT[], -- PostgreSQL 배열 타입 priority INTEGER DEFAULT 0, active BOOLEAN DEFAULT true, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- 기본 키워드 설정 INSERT INTO news_keywords (slack_user_id, keywords, priority) VALUES ('U091UNVE41M', ARRAY['AI', '스타트업', '투자'], 1), ('U0925SXQFDK', ARRAY['반도체', '삼성', 'IT'], 1), ('U092F7FQ55L', ARRAY['전기차', '배터리', '에너지'], 1); ``` #### 방안 3: 사용자별 맞춤 키워드 ```python async def get_user_keywords(slack_user_id: str) -> List[str]: """사용자별 맞춤 키워드 조회""" try: db = SessionLocal() result = db.execute( text("SELECT keywords FROM news_keywords WHERE slack_user_id = :sid"), {"sid": slack_user_id} ).fetchone() if result and result[0]: return result[0] # PostgreSQL 배열 반환 else: # 기본값 또는 랜덤 키워드 return get_daily_keywords() finally: db.close() ``` ### 10.3 환경변수 DB 저장 방식 #### 설정 테이블 구조 ```sql -- 시스템 설정 테이블 CREATE TABLE system_config ( id SERIAL PRIMARY KEY, service_name VARCHAR(50) NOT NULL, key VARCHAR(100) NOT NULL, value TEXT, description TEXT, updated_at TIMESTAMP DEFAULT NOW(), updated_by VARCHAR(100), UNIQUE(service_name, key) ); -- 예시 데이터 INSERT INTO system_config (service_name, key, value, description) VALUES ('news_skill', 'default_keywords', '["AI", "기술", "IT"]', '기본 뉴스 검색 키워드'), ('news_skill', 'max_items', '5', '최대 뉴스 개수'), ('news_skill', 'search_days', '7', '검색 기간 (일)'), ('rb8001', 'morning_briefing_time', '09:00', '모닝 브리핑 시간'); ``` #### Python에서 DB 설정 읽기 ```python from typing import Optional, Any import json class ConfigManager: """DB 기반 설정 관리자""" def __init__(self, service_name: str): self.service_name = service_name self._cache = {} def get(self, key: str, default: Any = None) -> Any: """설정값 조회""" # 캐시 확인 if key in self._cache: return self._cache[key] try: db = SessionLocal() result = db.execute( text(""" SELECT value FROM system_config WHERE service_name = :service AND key = :key """), {"service": self.service_name, "key": key} ).fetchone() if result: # JSON 파싱 시도 try: value = json.loads(result[0]) except: value = result[0] self._cache[key] = value return value return default finally: db.close() def set(self, key: str, value: Any, description: str = None): """설정값 저장""" try: db = SessionLocal() # JSON 직렬화 가능한 경우 if isinstance(value, (list, dict)): value = json.dumps(value, ensure_ascii=False) else: value = str(value) db.execute( text(""" INSERT INTO system_config (service_name, key, value, description) VALUES (:service, :key, :value, :desc) ON CONFLICT (service_name, key) DO UPDATE SET value = :value, updated_at = NOW() """), { "service": self.service_name, "key": key, "value": value, "desc": description } ) db.commit() # 캐시 업데이트 self._cache[key] = value finally: db.close() # 사용 예시 config = ConfigManager("news_skill") keywords = config.get("default_keywords", ["AI", "IT"]) max_items = config.get("max_items", 5) ``` ### 10.4 구현 우선순위 1. **즉시 적용 가능**: 요일별 키워드 로테이션 2. **중기 목표**: DB 기반 키워드 관리 3. **장기 목표**: 사용자별 맞춤 키워드 + AI 추천 ### 10.5 효과 - **다양성**: 매일 다른 뉴스 제공 - **맞춤화**: 사용자별 관심사 반영 - **유연성**: DB에서 키워드 실시간 변경 가능 - **확장성**: 새로운 키워드 세트 쉽게 추가 --- *작성 완료: 2025-08-26 21:30* *뉴스 스킬 분석 추가: 2025-08-26 21:45*