DOCS/troubleshooting/250826_slack_id_column_standardization.md
Claude-51124 dba82ab596 docs: 뉴스 스킬 키워드 문제 분석 및 DB 기반 설정 관리 방안 추가
- 매일 같은 AI 뉴스 반복 문제 원인 분석
- 하드코딩된 키워드를 동적 시스템으로 개선 방안 제시
- DB 기반 환경변수 관리 방법 문서화
- 사용자별 맞춤 키워드 시스템 설계
2025-08-26 22:07:58 +09:00

11 KiB

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 복잡도 증가

-- 현재: 컬럼명이 달라서 복잡
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 서버)

-- 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 영향받는 레코드 수

-- 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 변경 전 테스트

-- 데이터 무결성 확인
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: 동적 키워드 시스템

# 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 기반 키워드 관리

-- 키워드 설정 테이블 생성
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: 사용자별 맞춤 키워드

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 저장 방식

설정 테이블 구조

-- 시스템 설정 테이블
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 설정 읽기

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